前言
项目需要使用Leaflet加天地图的服务,具体实现不同类型图层的切换,地图打点点位信息编辑,点击点位显示提示框、点的缩放聚合。
使用vue2、vue-cli、leaflet;这个vue react都行这个地图库也就是js操作随便用哪个都可以地图实例能获取到就可以操作。
- leaflet 中文网址 leafletjs.cn/reference.h…
- leaflet.markercluster www.npmjs.com/package/lea…
- element-ui element.eleme.io/#/zh-CN/com…
-
默认数据 和聚合2个
-
点击点位
-
点击编辑
-
点击新增按钮 后点击地图
一、准备
1.下载 leaflet、leaflet.markercluster
npm install leaflet --save
npm install leaflet.markercluster --save
在使用的地方引入
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet.markercluster';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
2.下载 element-ui
npm i element-ui -S
main.js注册
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
3.申请天地图key
sso.tianditu.gov.cn/login?servi…
注册登录创建新的应用得到key;
二、Leaflet库的初始和方法
1.准备创建一个methods.js
引入Leaflet相关库;存放天地图服务地址和key
引入leaflet库,和一张图片用来作为marker图标
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet.markercluster';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
//! 引入一张图片 作为marker图标
import icon from '../images/xiaolong.gif';
存放天地图服务key,和地图服务地址数组;服务地址数组方便类型切换设置地图图层;
spherical
:球面墨卡托投影,
LatLong
:球面墨卡托投影,
mapLabel
:地图注记服务
//! 天地图服务Key
export const KEY = `512e6b397b64c5f690cf9cca2925da1f`;
//! 天地图图层信息数组
export const layerList = [
{
name: "矢量底图",
spherical: `http://t{s}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=${KEY}`,
LatLong: `http://t{s}.tianditu.gov.cn/DataServer?T=vec_c&x={x}&y={y}&l={z}&tk=${KEY}`,
mapLabel: {
name: "矢量注记",
spherical: `http://t{s}.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=${KEY}`,
LatLong: `http://t{s}.tianditu.gov.cn/DataServer?T=cva_c&x={x}&y={y}&l={z}&tk=${KEY}`,
}
},
{
name: "影像底图",
spherical: `http://t{s}.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${KEY}`,
LatLong: `http://t{s}.tianditu.gov.cn/DataServer?T=img_c&x={x}&y={y}&l={z}&tk=${KEY}`,
mapLabel: {
name: "影像注记",
spherical: `http://t{s}.tianditu.gov.cn/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=${KEY}`,
LatLong: `http://t{s}.tianditu.gov.cn/DataServer?T=cia_c&x={x}&y={y}&l={z}&tk=${KEY}`,
}
},
{
name: "地形晕渲",
spherical: `http://t{s}.tianditu.gov.cn/DataServer?T=ter_w&x={x}&y={y}&l={z}&tk=${KEY}`,
LatLong: `http://t{s}.tianditu.gov.cn/DataServer?T=ter_c&x={x}&y={y}&l={z}&tk=${KEY}`,
mapLabel: {
name: "影像注记",
spherical: `http://t{s}.tianditu.gov.cn/DataServer?T=cta_w&x={x}&y={y}&l={z}&tk=${KEY}`,
LatLong: `http://t{s}.tianditu.gov.cn/DataServer?T=cta_c&x={x}&y={y}&l={z}&tk=${KEY}`,
}
},
{
name: "全球境界",
spherical: `http://t{s}.tianditu.gov.cn/DataServer?T=ibo_w&x={x}&y={y}&l={z}&tk=${KEY}`,
LatLong: `http://t{s}.tianditu.gov.cn/DataServer?T=ibo_c&x={x}&y={y}&l={z}&tk=${KEY}`,
mapLabel: null
},
]
2.初始化地图方法
实现initMap方法,传入el元素返回L.Map实例对象
选项小多请查看 L.map方法
/**
* 初始化 Leaflet 地图
*
* @param {HTMLElement} el - 地图容器的元素或元素的ID
* @returns {L.Map} 返回一个初始化后的 Leaflet 地图实例
*/
export const initMap = (el) => {
return L.map(el,
{
center: [39.912565, 116.408509],
zoom: 5,
maxZoom: 18,
zoomControl: true,
zoomAnimation: true,
}
);
}
3.设置图层方法
由于需要切换图层类型,显示不同的图层需要实现两个方法:
-
删除地图中所有图层信息 clearLayers 方法
传入地图实例,通过实例方法eachLayer 遍历所有图层对象
通过判断图层对象是否含有 formState key区分是否是marker
后续使用到聚合功能使用的插件,这里单独一个图层需要清除
-
设置图层方法 setLayer
传入地图实例、天地图服务对象,通过
L.tileLayer
方法实例图层对象由该实例方法
addTo(map)
添加到地图中创建聚合图层实例,将该图层实例添加地图实例一个值上方便后面操作添加点位
/**
* 删除地图的所有 图层,用于图层类型的切换
*
* @param {L.Map} map - 地图容器的元素或元素的ID
*/
export const clearLayers = (map) => {
map.eachLayer(l => {
if (!('formState' in l)) { //! 如果有 formState 表示图标图标这里不删除
map.removeLayer(l);
}
})
// 清除聚合层中的所有marker
if (map.markers) {
map.markers.clearLayers();
}
}
/**
* 设置图层方法
*
* @param {L.Map} map - 地图容器的元素或元素的ID
* @param {Object} layerObject - layerList[n]对象
* */
//! 1. 每次先调用 clearLayers 清楚所有图层
//! 2. 完了之后在使用 titleLayer 设置图层对象, addTo添加到地图当中
export const setLayer = (map, layerObject) => {
clearLayers(map);
const {spherical, mapLabel} = layerObject;
let layer = L.tileLayer(spherical, {
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
maxZoom: 18,
minZoom: 1,
});
layer.addTo(map);
//! 有些地图图层类型,可能没有 label 标注 这里判断
if (mapLabel && Object.keys(mapLabel).length !== 0) {
const {spherical: sphericalLabel} = mapLabel;
let layerLabel = L.tileLayer(sphericalLabel, {
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
maxZoom: 18,
minZoom: 1
});
layerLabel.addTo(map);
}
// 创建marker聚合层
const markers = L.markerClusterGroup();
// 创建一个自定义的LayerGroup来控制层级
const layerGroup = L.layerGroup([markers]);
layerGroup.addTo(map);
// 设置层级,注意这个方法会将图层在地图中移到指定位置
layerGroup.setZIndex(9999999999999999999999); // 设置图层的层级
// 将markers保存到map对象上,以便后续使用
map.markers = markers;
map.layerGroup = layerGroup;
}
4.设置地图事件方法
传入地图实例,和事件名对象以回调形式使用
//! 地图事件的添加
export const MapEvents = (map, events) => {
map.on('click', events.click)
map.on('zoom', events.zoom)
}
5.添加点位方法
存放点位配置对象
L.marker方法配置信息
//! marker 配置信息
const markerOption = {
draggable: true, //是否允许拖拽点
icon: L.icon({
iconUrl: icon,
iconSize: [27, 27],
iconAnchor: [14, 14],
})
}
点位添加方法
传入表单对象,用于存放点位信息和提示窗回显内容
地图实例用于添加点位到地图中,这里就用到设置图层方法中设置的聚合图层值
传入的list 用来存放所有marker的数组 用来保存点位
传入事件名对象,操作点位的事件
/**
* 添加点位方法
*
* @param {Object} form - 点位表单信息对象
* @param {L.Map} map - 地图实例对象
* @param {Array} list - 存放所有点位对象数组,用来保存提交接口时
* @param {Object} event - 事件对象包含 click dblclick 等等;采用回调形式
* */
const addMarkerItem = (form, map, list, event) => {
let latlng = Object.values(form.latlng);
let marker = L.marker(latlng, markerOption);
marker['formState'] = form;
//! 点击打开 信息提示
marker.on('click', event.click);
//! 双击编辑
marker.on('dblclick', event.dblclick);
marker.on('dragend', event.dragend);
marker.on('move', event.move);
list.push(marker);
// 将marker添加到聚合层
marker.addTo(map.markers); //! 这里要有聚合功能就必须添加到这里
}
6.编辑点位方法对象
用来调用新增list,新增单个和编辑点位表单信息的对象
export const editMarkerMethod = {
addList(formStates, map, list, event) {
formStates.map((form) => {
addMarkerItem(form, map, list, event)
})
},
add(form, map, list, event) {
addMarkerItem(form, map, list, event)
},
edit(form, list) {
let item = list.find((v) => {
if (v.formState.timeId === form.timeId) return v;
})
item.formState = {...form};
}
}
7.添加信息窗(显示信息窗)
信息窗只能展示一个,这里用一个变量POPUP
接收实例,后面用于删除信息窗(切换图层类型时)
addPopup
显示信息窗方法,传入地图实例,和表单对象,事件名对象
- 表单对象用来回显内容
- 时间名对象,用来编辑表单信息和删除操作
removePopup
切换不同的图层类型,删除掉信息窗
//! popup 信息窗 只会展示一个,这里保存最新的;用来关闭使用
let POPUP = null;
//! 添加 信息窗;设置了编辑。删除功能 针对于点位的操作
export const addPopup = (map, formState, events) => {
let {latlng} = formState;
let dom = document.createElement("div");
dom.innerHTML = `
<a class="setLink">编辑</a>
<a class="delLink">删除</a>
<div>名字:${formState.name}</div>
<div>活动区域:${formState.region}</div>
<div>活动形式:${formState.hotType}</div>
`
dom.querySelector('.setLink').onclick = () => {
events.click && events.click(formState);
}
dom.querySelector('.delLink').onclick = () => {
events.linkDel && events.linkDel(formState);
}
let popup = L.popup()
.setLatLng(latlng)
.setContent(dom)
popup.openOn(map);
formState.popupOpen = true;
popup.on('remove', () => {
formState.popupOpen = false;
})
POPUP = popup;
}
//! 删除信息窗的方法
export const removePopup = (map) => {
map.removeLayer(POPUP);
}
三、具体使用
template
layerListSelect
用于选择图层的组件,layer
就是选中图层对象;layerListClick
切换时的事件editMarker
组件表单弹窗,editSubmit
表单确定事件,通过ref实例显示表单ref.show(form)
<template>
<div class="LMap">
<layerListSelect :list="layerList" :active.sync="layer" @click="layerListClick"></layerListSelect>
<div class="LMap-actions">
<div class="LMap-actions-add" @click="clickAddPoint">
{{ isEdit ? "点击地图添加点位" : "添加点" }}
</div>
</div>
<div class="container" id="container" ref="container">
</div>
<editMarker @editSubmit="editSubmit" ref="RefEditMarker"></editMarker>
</div>
</template>
script
-
data
变量用途map
存放地图实例layerList
天地图服务地址数组layer
选中服务地址isEdit
点击添加按钮 是否的判断markList
存放marker点位对象数组defaultMarkList
地图上默认点位的表单数组
-
mounted
初始化地图初始化地图,添加地图事件,设置地图图层,设置地图默认点位
-
layerListClick
是layerListSelect
组件的点击事件用来切换图层,每次切换吧存放marker数组清空,克隆一份重新渲染点位
-
clickAddPoint
添加点位按钮,更改isEdit
值- 在
mounted
中的事件对象判断该值是否触发事件
- 在
-
mounted
生命周期中的events.click
- 事件中的
This.$refs.RefEditMarker.show(formState)
打开表单组件 - 通过formState type 判断新增或编辑
- 事件中的
-
editSubmit
表单组件确认事件,返回formState
表单对象- 通过表单对象
type
判断新增|编辑 - 修改 则调用
editMarkerMethod.edit(markFormState,this.markList)
更改该表单对象timeId
和markList
中的每一项timeId
相同的formState
- 更改后调用
this.showPopup
方法- 该方法 有两个事件名函数,用来给信息窗中的
编辑
删除
添加事件 linkSet
点击信息窗的编辑
触发该函数linkDel
点击信息窗的删除
触发该函数- 事件添加完毕 重新调用
addPopup
更新信息窗表单内容
- 该方法 有两个事件名函数,用来给信息窗中的
- 如果是新增则调用
editMarkerMethod.add(markFormState, this.map, this.markList, event);
- 传入
formState
表单对象,map
地图实例,markList
存放marker的数组,event
事件名对象event.click
marker点击事件,显示信息窗调用this.showPopup(e.target.formState);
event.dblclick
双击可编辑点位formState
后续放弃了改为 信息窗内的按钮操作event.move
marker拖动事件,每次移动更新该点位的坐标对象event.dragend
marker拖动结束事件,拖动后显示新的位置信息窗,先判断该信息窗是否打开
- 传入
- 通过表单对象
<script>
import {initMap, setLayer, layerList, MapEvents, editMarkerMethod, addPopup,removePopup} from './tools/methods';
import layerListSelect from './components/layerList/layerList.vue';
import editMarker from './components/editMarker/editMarker.vue';
import deepClone from "./tools/deepClone";
export default {
data() {
return {
map: null,
layerList,
layer: null,
isEdit: false,
markList: [],
defaultMarkList:[
{
latlng:{
lat:41.44272637767212,
lng:104.150390625
},
name: '名字1',
region: 'region',
hotType: '啊我的娃',
timeId:1,
},
{
latlng:{
lat:41.84272637767212,
lng:124.150390625
},
name: '名字1',
region: 'region',
hotType: '啊我的娃',
timeId:2,
},
{
latlng:{
lat:42.44272637767212,
lng:104.150390625
},
name: '名字1',
region: 'region',
hotType: '啊我的娃',
timeId:3,
}
]
}
},
components: {
layerListSelect,
editMarker
},
mounted() {
this.map = initMap(this.$refs.container);
setLayer(this.map, this.layer);
let This = this;
const events = {
click(e) {
if(!This.isEdit) return;
let formState = {
type: '添加',
latlng: e.latlng,
timeId:+new Date()
}
This.$refs.RefEditMarker.show(formState);
},
zoom(e) {
console.log('zoom',e)
}
}
MapEvents(this.map, events);
this.addDefaultMarkList();
},
methods: {
layerListClick() {
setLayer(this.map, this.layer);
//! 每次切换图层后 重新回显内容
let deepArray = deepClone(this.markList);
this.markList.length = 0;
deepArray.map((item)=>{
this.editSubmit(item.formState);
})
},
clickAddPoint() {
this.isEdit = !this.isEdit;
},
showPopup(formState){
let linkSet = (formState) =>{
formState.type = "修改";
this.$refs.RefEditMarker.show(formState);
}
let linkDel = (formState) =>{
let findK = null;
let findItem = this.markList.find((item,index)=>{
if(item.formState.timeId === formState.timeId){
findK = index;
return item;
}
})
findItem && findItem.remove && findItem.remove();
this.map.markers.removeLayer(findItem);
this.markList.splice(findK,1);
removePopup(this.map);
}
addPopup(this.map,formState,{click:linkSet,linkDel});
},
editSubmit(markFormState) {
if(markFormState.type === '修改'){
editMarkerMethod.edit(markFormState,this.markList)
this.showPopup(markFormState);
}
if (markFormState.type === '添加' || !markFormState.type) {
let event = {
click: (e) => {
this.showPopup(e.target.formState);
},
dblclick:(e)=>{
let formState = e.target.formState;
formState.type = "修改";
this.$refs.RefEditMarker.show(formState);
},
move: (e) => {
if (e.latlng) {
e.target.formState.latlng = e.latlng;
}
},
dragend: (e) => {
if(e.target.formState.popupOpen){
this.showPopup(e.target.formState)
}
}
}
editMarkerMethod.add(markFormState, this.map, this.markList, event);
}
this.isEdit = false;
},
addDefaultMarkList(){
this.defaultMarkList.map((item)=>{
this.editSubmit(item);
})
}
}
}
</script>
editMarker组件
editMarke
<template>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<editMarkerForm ref="refForm"></editMarkerForm>
<span slot="footer" class="dialog-footer">
<el-button @click="clickClose">取 消</el-button>
<el-button type="primary" @click="clickSubmit">确 定</el-button>
</span>
</el-dialog>
</template>
<script>
import editMarkerForm from "./editMarker.form.vue";
export default {
name: "editMarker",
components:{
editMarkerForm
},
data() {
return {
dialogVisible: false,
title:""
};
},
methods: {
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
this.$refs.refForm.initFormState();
done();
})
.catch(_ => {
});
},
show(markForm) {
this.dialogVisible = true;
this.title = markForm.type;
this.$nextTick(()=>{
this.$refs.refForm.setFormState(markForm);
})
},
clickSubmit(){
let formData = this.$refs.refForm.toFormState();
this.$emit("editSubmit",formData);
this.dialogVisible = false;
},
clickClose(){
this.$refs.refForm.initFormState();
this.dialogVisible = false;
}
}
}
</script>
<style scoped>
</style>
editMarke.form
<template>
<el-form :label-position="labelPosition" label-width="80px" :model="formState">
<el-form-item label="名称">
<el-input v-model="formState.name"></el-input>
</el-form-item>
<el-form-item label="活动区域">
<el-input v-model="formState.region"></el-input>
</el-form-item>
<el-form-item label="活动形式">
<el-input v-model="formState.hotType"></el-input>
</el-form-item>
</el-form>
</template>
<script>
import deepClone from "../../tools/deepClone";
export default {
name: "editMarker.form",
data() {
return {
labelPosition: 'right',
formState: {
name: '',
region: '',
hotType: ''
},
};
},
methods: {
setFormState(formData) {
let form = deepClone(formData);
this.formState.name = form.name || '';
this.formState.region = form.region || '';
this.formState.hotType = form.hotType || '';
this.formState.latlng = form.latlng;
this.formState.type = form.type;
this.formState.timeId = form.timeId;
},
initFormState() {
this.formState.name = '';
this.formState.region = '';
this.formState.hotType = '';
},
toFormState() {
return deepClone(this.formState);
}
}
}
</script>
<style scoped>
</style>
layerList组件
<template>
<div class="LMap-layerList">
<div class="LMap-layerList-item" @click="handleClickItem(item)" :key="index" v-for="(item,index) in list" :class="{'LMap-layerList-active':isEqual(item,active)}">
<div class="LMap-layerList-item-name">
{{item.name}}
</div>
</div>
</div>
</template>
<script>
import isEqual from "../../tools/isEqualObject";
export default {
name: "layerList",
props:{
list:{
type:Array,
default:function(){
return []
}
},
active:{
type:Object,
default:function (){
return {}
}
},
onClick:{
type:Function,
default:function(){
return null
}
}
},
created(){
this.$emit("update:active", this.list[0])
},
methods:{
isEqual,
handleClickItem(item){
this.$emit("update:active",item)
this['onClick'](item);
this.$emit('click', item);
}
}
}
</script>
<style scoped>
.LMap-layerList{
width: 100%;
display: flex;
padding: 10px 0;
}
.LMap-layerList-item{
flex: 1;
min-height: 50px;
box-shadow: -1px -1px 3px #8ddb8c5a,1px 1px 3px #4298f95a;
border-radius: 10px;
cursor: pointer;
border: 1px solid #eee;
}
.LMap-layerList-item+.LMap-layerList-item{
margin-left: 10px;
}
.LMap-layerList-item-name{
padding: 6px 12px;
font-size: 14px;
color: #000;
}
.LMap-layerList-active{
box-shadow: -1px -1px 3px #8ddb8c,1px 1px 3px #4298f9;
background-color: #4298f90a;
}
</style>