概述
列表和地图联动是webgis中一个非常常见的功能,本文讲一下在mapboxGL中结合vue如何实现此功能。
效果
GIF.gif实现思路
1. 获取数据
获取数据并将数据保存起来;
2. 列表展示
列表简单的用ul
、li
来实现。
3. 地图展示
数据获取之后,通过document.createElement()
的方式创建marker,将结果在地图上展示。
4. 列表地图联动
联动存在两个交互:鼠标移动和点击选中,所以需要两个变量用来记录当前鼠标经过的和点击选中的,如果鼠标经过的和点击选中发生变化的时候,去设置对应的样式即可。
实现代码
<template>
<div class="search-panel">
<div
class="search-result">
<h4 class="title">
查询结果
</h4>
<ul
class="result-list">
<li
v-for="(item, index) in searchResult"
:key="index"
:class="getClass(item)"
@mouseover="currentMarker = item"
@mouseout="currentMarker = {}"
@click="selectMarker = item">
<div class="marker">
<span>{{ index + 1 }}</span>
</div>
<div class="info">
<b>{{ item.name }}</b><br>
{{ item.addr }}
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Search',
data() {
return {
searchResult: [], //记录查询结果
markers: [],
currentMarker: {},
selectMarker: {},
markerPopup: null
};
},
methods: {
init() {
this.markerPopup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false,
className: 'marker-popup',
offset: [0, -15],
anchor: 'bottom'
});
// 造测试数据
this.searchByKeyword();
},
getRandomPos() {
const bbox = [79.740682, 20.10212, 134.0345, 44.8279];
const lon = Math.random() * (bbox[2] - bbox[0] + 1) + bbox[0];
const lat = Math.random() * (bbox[3] - bbox[1] + 1) + bbox[1];
return [lon, lat];
},
getClass(item) { // 设置列表样式
let cls = item.id === this.currentMarker.id ? 'active' : '';
cls += item.id === this.selectMarker.id ? ' select' : '';
return cls;
},
setMarkersClass() { // 设置地图marker样式
this.searchResult.forEach(item => {
const dom = document.getElementById(item.id);
if (item.id === this.currentMarker.id) {
dom.classList.add('active');
} else {
dom.classList.remove('active');
}
if (item.id === this.selectMarker.id) {
dom.classList.add('select');
} else {
dom.classList.remove('select');
}
});
},
searchByKeyword() {
const that = this;
if (this.keyword !== '') {
that.loading = true;
that.searchResult = [];
this.removeMarkers();
setTimeout(() => {
for (let i = 0; i < 10; ++i) {
that.searchResult.push({
id: 'marker' + i,
name: '大石地铁站',
addr: '广东省广州市番禺区大石地铁站',
pos: that.getRandomPos()
});
}
// 添加markers
that.addMarkers2Map();
that.loading = false;
}, 2000);
}
},
addMarkers2Map() {
const that = this;
const map = window.map;
that.searchResult.forEach((item, index) => {
const ele = document.createElement('div');
ele.setAttribute('class', 'map-marker');
ele.setAttribute('id', item.id);
ele.innerText = (index + 1).toString();
const option = {
element: ele
};
const marker = new mapboxgl.Marker(option)
.setLngLat(item.pos)
.addTo(map);
that.markers.push(marker);
ele.onmouseover = () => {
that.currentMarker = item;
};
ele.onmouseout = () => {
that.currentMarker = {};
};
ele.onclick = () => {
that.selectMarker = item;
};
});
},
removeMarkers() {
this.markers.forEach(marker => {
marker.remove();
});
},
showMarkerInfo() {
const that = this;
if (this.selectMarker.id) {
const description = `
<h5>
${this.selectMarker.name}
<span class="close el-icon-close" id="popupClose"></span>
</h5>
<ul>
<li>温度: 20℃</li>
<li>湿度: 52%</li>
<li>降水: 1mm</li>
<li>风速: 4m/s</li>
<li>风向: 无持续风向</li>
</ul>
`;
this.markerPopup
.setLngLat(this.selectMarker.pos)
.setHTML(description)
.addTo(window.map);
// 添加关闭事件
document.getElementById('popupClose').onclick = () => {
that.selectMarker = {};
};
window.map.flyTo({
center: this.selectMarker.pos
});
} else {
this.markerPopup.remove();
}
}
},
watch: {
currentMarker(val) {
this.setMarkersClass();
},
selectMarker(val) {
this.setMarkersClass();
this.showMarkerInfo();
}
}
};
</script>
<style scoped lang="scss">
@import '../../assets/css/map';
.search-panel {
position: absolute;
top: $padding;
left: $padding;
z-index: 99;
.search-result {
width: 315px;
background-color: $bg-color;
border-radius: 4px;
padding-bottom: 10px;
.title {
margin: 0;
padding: 10px;
border-bottom: 1px solid #ccc;
i {
float: right;
font-size: 14px;
&:hover,
&.active {
cursor: pointer;
color: $active-color;
text-decoration: underline;
}
}
}
}
.result-list {
margin: 10px 0;
min-height: 100px;
li {
padding: 8px 10px;
overflow: hidden;
border-bottom: 1px dashed #ddd;
&:hover,
&.active,
&.select {
cursor: pointer;
.marker span {
background-image: url('../../assets/images/marker-blue.png');
}
}
&.select {
background-color: rgba(0, 0, 0, 0.05);
}
.marker {
float: left;
width: 35px;
text-align: center;
line-height: 30px;
span {
width: 26px;
height: 26px;
line-height: 20px;
text-align: center;
display: inline-block;
background-image: url('../../assets/images/marker-red.png');
background-size: 100%;
color: white;
border-radius: 100%;
}
}
.info {
float: left;
}
}
}
}
</style>
<style lang="scss">
@import '../../assets/css/map';
.map-marker {
width: 26px;
height: 26px;
line-height: 20px;
text-align: center;
background-image: url('../../assets/images/marker-red.png');
background-size: 100%;
color: white;
border-radius: 100%;
cursor: pointer;
&:hover,
&.active,
&.select {
background-image: url('../../assets/images/marker-blue.png');
}
}
.marker-popup {
color: white;
.mapboxgl-popup-content {
background-color: $black65;
margin: 0;
padding: 8px;
white-space: nowrap;
font-size: 12px;
h5 {
margin: 0 0 3px 0;
padding: 3px 0;
font-size: 14px;
span {
float: right;
cursor: pointer;
}
}
label {
display: inline-block;
text-align: right;
width: 65px;
}
}
.mapboxgl-popup-tip {
border-top-color: $black65 !important;
}
.mapboxgl-popup-close-button {
color: white;
}
}
</style>
网友评论