在无人船巡航管理后台项目中,有多处需要高德地图进行定位与轨迹显示,为此对高德地图API组件进行封装。
HTML结构并不复杂,仅仅是需要一个容器MapContainer
。
对于本项目而言,地图最经常使用的功能是清除标记和视角重置的控件,这里定义一个插槽提供内部的方法。
<template>
<div class="map-view">
<slot name="header">slot>
<div id="MapContainer">div>
<slot name="footer" :clear="clear" :fit="fitView">slot>
div>
template>
绑定的:clear
和:fit
属性,提供插槽作用域让外部使用,以下是使用ZMap(组件名)的示例。
<ZMap ref="zmap" :initConfig="initConfig">
<template #footer="{ clear, fit }">
<div class="text-center m-t-10">
<el-button @click="clear('note')" size="mini" type="primary">清空标记el-button>
<el-button @click="fit" size="mini" type="info">视角恢复el-button>
div>
template>
ZMap>
高德地图生成所必须的两个key定义在组件内即可,不需要在外部配置定义。
window._AMapSecurityConfig = {
securityJsCode: "****************************", //开启地图安全密钥
};
var webKey = '****************************'; // 申请好的Web端开发者Key
项目经常需要多个标记,为此需要引入不同的icon:
data() {
var mUrl = 'assets/mapEle/';// 标记点url路径
return {
map: null,
contextMenu: null,
infoWindow: null,
markerType: {
pink: require(`@/${mUrl}marker-pink.svg`),
red: require(`@/${mUrl}marker-red.svg`),
green: require(`@/${mUrl}marker-green.svg`),
blue: require(`@/${mUrl}marker-blue.svg`),
I1: require(`@/${mUrl}indexMarker1.png`),
I2: require(`@/${mUrl}indexMarker2.png`),
I3: require(`@/${mUrl}indexMarker3.png`),
I4: require(`@/${mUrl}indexMarker4.png`),
}
}
},
使用props传入必传的外部配置参数initConfig
:
props: {
initConfig: {
type: Object,
required: true
}
}
export const initConfig = {
isAsync: true,//允许可异步加载
initMap: {
zoom: 18, //初始化地图级别
center: ["110.92330940012255", "21.67770383372478"],
showLabel: false, //不显示地图文字标记
viewMode: '2D',
pitch: 50,
},
plugins: ["AMap.Geocoder", "AMap.HeatMap", "AMap.ControlBar"],//所要用到的插件
defaultStyle: {//所要用到的样式
polylineStyle: {
//直线样式...
},
followLineStyle: {
//跟随曲线样式...
},
circleStyle: {
//圆标记样式...
}
}
}
在ZMap的created
钩子中注入data:
created() {
// 把配置的样式注入
let { initConfig: { defaultStyle } } = this;
for (let k in defaultStyle) {
this[k] = defaultStyle[k];
}
},
在ZMap组件内定义initMap()
初始化函数:
initMap() {
let { initMap, plugins = [] } = this.initConfig;
return new Promise((resolve, reject) => {
AMapLoader.load({
key: webKey, // 申请好的Web端开发者Key,首次调用 load 时必填
version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins
}).then((AMap) => {
let map = new AMap.Map("MapContainer", initMap);
// 设置地图鼠标样式
map.setDefaultCursor("pointer");
this.map = map;
// 插件初始化:
// 1.3D地图方向调节
if (plugins?.includes('AMap.Geocoder')) {
// 定义逆地址编码
this.geocoder = new AMap.Geocoder({
// city 指定进行编码查询的城市,支持传入城市名、adcode 和 citycode
city: "全国",
});
}
if (plugins?.includes("AMap.ControlBar")) {
map.addControl(new AMap.ControlBar({}));
}
// 定义菜单实例
this.contextMenu = new AMap.ContextMenu();
// 定义信息窗体
this.infoWindow = new AMap.InfoWindow({
offset: new AMap.Pixel(0, -30),
});
resolve(true);
}).catch((e) => {
// this.$Notify('地图加载失败!', e);
resolve(false);
});
})
},
如果传入了相应的插件就需要进行插件的初始化,并且用可选链判断plugins?.includes
。
值得注意的是,高德地图的地图加载和请求会带有缓存,初始加载和后续加载的时间不同,所以地图的加载时间难以确定。
如果随这个ZMap组件渲染的时候同步加载地图,一旦当前业务需要先请求一些标记数据,就很容易造成,数据回来了但地图还未加载,或者一些其他错位问题。
为了更方便地使用组件,这里选择自设一个异步加载配置:isAsync: true,//手动异步加载
。
让外部使用组件的时候可以控制组件是同步加载,还是异步加载,如果是异步加载,可以自己在外部控制地图的加载时机,为的就是把地图加载完毕的时间点牢牢掌握在自己手上。
//ZMap组件内:
mounted() {
let { isAsync = false } = this.initConfig;
if (isAsync) return;//如果isAsync:true,停止自动加载。
this.initMap();//默认自动加载
},
//使用ZMap的组件:
mounted(){
this.opm('initMap').then(res => {
if (!res) return
// 加载完地图之后需要获取无线传感网络的位置
this.toGetSensor();
// 加载水质区域状态热力图
this.initHeatData();
})
}
methods:{
//这个函数用于更便捷地操作组件内的方法
//传入方法名和参数,就可以调用组件内部的方法
opm(fn, ...args) {
return this.$refs.zmap[fn](...args)
},
}
以下是项目常用的方法
// 获取指定的标记
getA_Type(type) {
return this.map.getAllOverlays(type);
},
// 增加折线
addPolyline(config, styleName) {
const polyline = new AMap.Polyline(this[styleName]);
polyline.setOptions({
...config
});
this.map.add(polyline); //添加入地图
return polyline
},
// 增加圆形
addCircle(config, styleName) {
//......
return circle;
},
// 往地图上批量添加点标记
addMarker(markers = []) {
if (markers.length === 0) {
console.warn('未传入标记变量!')
return false;
};
let { markerType } = this;
markers.forEach(item => {
let { showInfo = false, content, icon = 'blue', options } = item;
let marker = new AMap.Marker({
anchor: "bottom-center",
zIndex: 10,
...options,
icon: markerType[icon]
});
if (showInfo) {
//设定信息窗口
marker.content = content || '空';
//添加点击出现信息窗口事件
marker.on("click", this.markerClick);
}
this.map.add(marker);
})
},
// 获取信息窗体
getInfoWindow(options) {
return new AMap.InfoWindow(options);
},
// 逆地理编码地址[longitude, latitude]
useGeocoder(lnglat) {
if (this.geocoder) {
return new Promise((res, rej) => {
this.geocoder.getAddress(lnglat, function (status, result) {
if (status === "complete" && result.regeocode) {
res(result.regeocode.formattedAddress)
} else rej(false)
});
})
} else {
return false;
}
},
// 点击锚点标记出现信息窗口
markerClick(e) {
this.infoWindow.setContent(e.target.content);
this.infoWindow.open(this.map, e.target.getPosition());
},
// 清空地图上所有标记
clear(able) {
this.map.clearMap();
if (able === 'note') {
this.$Notify('已清空所有标记!');
}
},
// 调整视角
fitView() {
this.map.setFitView();
},
// 地图销毁
destroy() {
let { map } = this;
map && map.destroy();
}
<template>
<el-card class="box-card card-header m-t-20">
<div slot="header" class="clearfix f-medium">
<i class="fa fa-map m-r-10 text-c-blue" aria-hidden="true">i>
<span>电子地图span>
div>
<div class="map-view">
<ZMap ref="zmap" :initConfig="initConfig">
<template #footer="{ clear, fit }">
<div class="text-center m-t-10">
<el-button @click="clear('note')" size="mini" type="primary">清空标记el-button>
<el-button @click="fit" size="mini" type="info">视角恢复el-button>
div>
template>
ZMap>
div>
el-card>
template>
<script>
import { initConfig } from '../config/map-config'
export default {
name: "EMap",
data() {
return {
initConfig,
};
},
mounted() {
this.$bus.$on("toAddMarker", this.addMarker);
},
beforeDestroy() {
this.$bus.$off("toAddMarker");
},
methods: {
// 对map组件的操作:传入要调用的函数名+参数
op_map(fn, ...query) {
return this.$refs.zmap[fn](...query)
},
// 添加标记点
addMarker(data) {
let { op_map } = this;
let MarkerIndex = data.index + 1;
let lnglat = [data.pos[0], data.pos[1]];
// 如果已经标记,则取消上一个标记
const allMarkers = op_map('getA_Type', "marker");
let indexItem = allMarkers.filter(
(item) => item.getExtData().id == MarkerIndex
);
if (indexItem.length) indexItem[0].setMap(null);
op_map('addMarker', [{
options: {
position: lnglat,
extData: {
id: MarkerIndex,
},
},
showInfo: true,
icon: 'blue',
content: `第${MarkerIndex}张图拍摄坐标为${lnglat}`
}])
op_map('fitView');
},
},
};
script>