概述
室内地图的实现最大的难点在于数据的收集,常见的方式有:1.基于施工CAD图纸转换;2.基于商场室内导视图进行绘制。本文的数据是截图高德地图SDK室内地图,并在QGIS中叠加高德地图进行配准后进行,对配准后的图像进行数字化而得到的。获取到数据后将数据叠加到mapboxGL中进行展示,并根据数据添加了楼层控制控件。
效果
image.png
实现
1. 数据获取
在高德SDK示例-室内地图(https://lbs.amap.com/demo/javascript-api-v2/example/indoormap/add-indoormap)中,对各个楼层进行截图,并在QGIS中进行配准,在QGIS中配准的操作可参考教程栅格配准进行操作。
相关链接:
- 高德SDK示例-室内地图:https://lbs.amap.com/demo/javascript-api-v2/example/indoormap/add-indoormap
- 栅格配准:https://lzugis.github.io/qgis-cookbook/docs/basic/raster.html#%E4%B8%89%E3%80%81%E6%A0%85%E6%A0%BC%E7%BA%A0%E6%AD%A3
对各个楼层进行配准后对所得的图像进行数字化便可得到对应的室内地图数据。本文只为演示,所以数字化的不是很精细,数字化后的数据如下:
image.png
说明:
- layer是楼层的名称;
- floor是为了将楼层进行正确排序用的;
2. 页面实现
为方便,页面实现写了用单个vue进行实现,实现代码如下:
<template>
<div class="wrapper" id="map">
<div class="floor-switch" v-show="floors.length > 0">
<div class="floor-item" :class="{disabled: currentFloor === floors[0]}" @click="updateFloor(-1)">▲</div>
<div
v-for="floor in floors"
:key="floor"
class="floor-item"
:class="{active: currentFloor === floor}"
@click="currentFloor = floor">{{floor}}</div>
<div class="floor-item" :class="{disabled: currentFloor === floors[floors.length - 1]}" @click="updateFloor(1)">▼</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
map: 'null',
floors: [],
currentFloor: '',
}
},
mounted() {
this.initMap()
},
watch: {
currentFloor(floor) {
this.map.setFilter('indoor', ['==', ['get', 'layer'], floor])
this.map.setFilter('indoor-line', ['==', ['get', 'layer'], floor])
this.map.setFilter('indoor-label', ['==', ['get', 'layer'], floor])
}
},
methods: {
updateFloor(val) {
let index = this.floors.indexOf(this.currentFloor)
index += val
this.currentFloor = this.floors[index]
},
initMap() {
this.map = new mapboxgl.Map({
container: "map",
center: [116.432307, 39.990542],
zoom: 16,
});
this.map.on('load', () => {
this.currentFloor = ''
fetch('/indoor.geojson').then(res => res.json()).then(res =>{
const features = res.features.map((f, i) => {
f.properties.id = 'feature' + i
return f
})
this.map.addSource('indoor', {
'type': 'geojson',
'data': res
})
this.map.addLayer({
'id': 'indoor',
'type': 'fill',
'source': 'indoor',
'paint': {
'fill-color': [
'match',
['get', 'name'],
'null', '#aaa',
'停车场', '#666',
'#ffd299'
],
'fill-opacity': [
'match',
['get', 'name'],
'边框', 0.01,
0.3
]
}
})
this.map.addLayer({
'id': 'indoor-line',
'type': 'line',
'source': 'indoor',
'paint': {
// 'line-color': [
// 'match',
// ['get', 'name'],
// 'null', '#666',
// '边框', '#FF7F50',
// '#ffd299'
// ],
'line-color': '#FF7F50',
'line-width': [
'match',
['get', 'name'],
'边框', 3,
0.75
]
},
})
this.map.addLayer({
'id': 'indoor-label',
'type': 'symbol',
'source': 'indoor',
'layout': {
'text-field': ['get', 'name'],
'text-size': 12
},
paint: {
'text-color': [
'match',
['get', 'name'],
'null', 'rgba(0,0,0,0)',
'边框', 'rgba(0,0,0,0)',
'停车场', '#666',
'#FF7F50'
],
'text-halo-color': [
'match',
['get', 'name'],
'null', 'rgba(0,0,0,0)',
'边框', 'rgba(0,0,0,0)',
'#ffffff'
],
'text-halo-width': 1
}
})
let floorDict = {}
features.forEach(feature => {
const {floor, layer} = feature.properties
if (!floorDict[floor]) floorDict[floor] = layer
})
const floors = Object.keys(floorDict).map(Number).sort((a, b) => b - a)
this.floors = floors.map(f => floorDict[f])
this.currentFloor = floors.includes(1) ? floorDict[1] : floors[0]
})
})
}
}
}
</script>
<style lang="scss">
.wrapper {
width: 100%;
height: 100%;
}
.floor-switch {
position: absolute;
top: 0;
right: 2rem;
height: 100vh;
width: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 99;
.floor-item {
padding: 0.5rem 0.6rem;
background: #fff;
cursor: pointer;
border-bottom: 1px solid #eee;
&:last-child {
border-bottom: none;
}
&.disabled {
color: #aaa;
cursor: not-allowed;
pointer-events: none;
}
&:hover {
background: #efefef;
}
&.active {
color: #fff;
background: #FF7F50;
}
}
}
</style>