661 lines
18 KiB
Vue
661 lines
18 KiB
Vue
<template>
|
|
<view class="ly-map-wrapper">
|
|
<!-- 地图展示 -->
|
|
<view :id="mapId" :config="config" :change:config="LyMap.init" :call="option" :change:call="LyMap.call"
|
|
class="ly-map" />
|
|
<!-- 中心图标 -->
|
|
<image v-if="showCenterIcon" :src="centerIcon||defCenterIcon" class="ly-center-icon" />
|
|
<!-- 定位图标 -->
|
|
<view v-if="showLocationIcon" class="ly-location-icon" @click="onLocation">
|
|
<image :src="locationIcon" class="icon" />
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import centerIcon from '../../static/ly-map/center.png';
|
|
import locationIcon from '../../static/ly-map/location.png';
|
|
|
|
export default {
|
|
// 接口参数
|
|
props: {
|
|
// 地图key
|
|
mapKey: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 经纬度
|
|
lonlat: {
|
|
type: Array,
|
|
default: () => ([111.668097, 40.825417]),
|
|
},
|
|
// 缩放
|
|
zoom: {
|
|
type: Number,
|
|
default: 16,
|
|
},
|
|
// 是否显示中心定位图标
|
|
showCenterIcon: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
// 中心点图标
|
|
centerIcon: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
// 是否显示用户定位图标
|
|
showLocationIcon: {
|
|
type: Boolean,
|
|
default: false,
|
|
}
|
|
},
|
|
// 数据定义
|
|
data() {
|
|
return {
|
|
// 地图容器id
|
|
mapId: this.genId(),
|
|
// 调用渲染层
|
|
option: {},
|
|
// 地图配置(传递到render中的数据)
|
|
config: {},
|
|
// 调用渲染层队列
|
|
event: [],
|
|
// 事件处理定时器
|
|
timer: null,
|
|
// 中心图标
|
|
defCenterIcon: centerIcon,
|
|
// 定位图标
|
|
locationIcon: locationIcon,
|
|
}
|
|
},
|
|
// 挂载完成后
|
|
mounted() {
|
|
// 设置渲染数据
|
|
this.config = {
|
|
mapId: this.mapId,
|
|
mapKey: this.mapKey,
|
|
lonlat: this.lonlat,
|
|
zoom: this.zoom,
|
|
};
|
|
},
|
|
// 通用方法
|
|
methods: {
|
|
// 生成唯一ID
|
|
genId() {
|
|
let result = '';
|
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
const charactersLength = characters.length;
|
|
for (let i = 0; i < 30; i++) {
|
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
}
|
|
return Date.now() + result;
|
|
},
|
|
// 调用
|
|
call() {
|
|
if (this.timer) return;
|
|
// 消费事件队列(生产者/消费者机制)
|
|
this.timer = setInterval(() => {
|
|
if (this.event.length) {
|
|
this.option = this.event.shift();
|
|
} else {
|
|
clearInterval(this.timer);
|
|
this.timer = null;
|
|
}
|
|
}, 10);
|
|
},
|
|
// 添加事件队列
|
|
addEvent(name, data) {
|
|
// #ifdef APP-PLUS
|
|
// tips:由于采用监听option改变来调用方法,
|
|
// 如果连续变化两次option,渲染层只会则监听到最后一次
|
|
// 导致调用丢失,所以采用事件队列形式解决,稍微延时10ms
|
|
// 等待渲染进程监听到option变化,在进行更改option
|
|
// 从性能上,几乎无感可以放心使用
|
|
const option = {
|
|
id: this.genId(),
|
|
name: `_${name}`,
|
|
data
|
|
};
|
|
this.event.push(option);
|
|
this.call();
|
|
// #endif
|
|
|
|
// #ifndef APP-PLUS
|
|
this[`_${name}`] && this[`_${name}`](data);
|
|
// #endif
|
|
},
|
|
// 设置
|
|
setOption(option) {
|
|
this.addEvent("setOption", option);
|
|
},
|
|
// 设置类型 0矢量地图 1卫星地图
|
|
geoCoder(lng,lat,uuid) {
|
|
this.addEvent("geoCoder", {
|
|
lng,lat,uuid
|
|
});
|
|
},
|
|
// 设置位置
|
|
setCenter(lon, lat, zoom) {
|
|
this.addEvent("setCenter", {
|
|
lon,
|
|
lat,
|
|
zoom
|
|
});
|
|
},
|
|
// 设置类型 0矢量地图 1卫星地图
|
|
setType(type) {
|
|
this.addEvent("setType", {
|
|
type
|
|
});
|
|
},
|
|
// 设置标注 marks:[{url, width, height, lon, lat}]
|
|
setMarkers(markers = []) {
|
|
this.addEvent("setMarkers", {
|
|
markers
|
|
});
|
|
},
|
|
// 在原有的情况下添加覆盖物
|
|
addOvers(overs) {
|
|
this.addEvent("addOvers", overs);
|
|
},
|
|
// 清除原有同类型的覆盖物,并重新创建
|
|
setOvers(overs) {
|
|
this.addEvent("setOvers", overs);
|
|
},
|
|
// 移除指定属性的覆盖物
|
|
removeOvers(prop, value) {
|
|
this.addEvent("removeOvers", {
|
|
prop,
|
|
value
|
|
});
|
|
},
|
|
// 清除指定类型的覆盖物,不指定则清除所有
|
|
clearOvers(type = "") {
|
|
this.addEvent("clearOvers", type);
|
|
},
|
|
// 设置缩放
|
|
setZoom(zoom) {
|
|
this.addEvent("setZoom", {
|
|
zoom
|
|
});
|
|
},
|
|
// 设置是使能
|
|
setEnable(option) {
|
|
this.addEvent("setEnable", option);
|
|
},
|
|
// 重置地图
|
|
resize() {
|
|
this.addEvent("resize");
|
|
},
|
|
// 开始拖拽地图
|
|
UserEvent(data) {
|
|
this.$emit('onUserEvent',data);
|
|
}
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<script module="LyMap" lang="renderjs">
|
|
// 天地图接口
|
|
const TDT_API = "https://api.tianditu.gov.cn/api?v=4.0&tk=";
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
// 初始化配置数据
|
|
_config: {},
|
|
// 地图实例
|
|
_mapInstance: {},
|
|
// 注记
|
|
_markerList: [],
|
|
// 覆盖物
|
|
_overs: {},
|
|
// 地图事件
|
|
_event: {},
|
|
};
|
|
},
|
|
methods: {
|
|
// 初始化
|
|
init(newValue, oldValue, ownerInstance, instance) {
|
|
// 防止重复渲染
|
|
if (!Object.keys(newValue).length) {
|
|
return;
|
|
}
|
|
this._config = newValue;
|
|
// 初始化地图
|
|
if (typeof window.LyMap === 'function') {
|
|
this._createMap();
|
|
} else {
|
|
const script = document.createElement('script');
|
|
script.src = TDT_API + newValue.mapKey;
|
|
script.onload = this._createMap;
|
|
document.head.appendChild(script);
|
|
}
|
|
},
|
|
// 通过监听call来调用渲染层方法
|
|
call(newValue, oldValue, ownerInstance, instance) {
|
|
if (this[newValue.name] && typeof this[newValue.name] === "function") {
|
|
this[newValue.name](newValue.data);
|
|
}
|
|
},
|
|
// 创建地图
|
|
_createMap() {
|
|
// 创建地图实例
|
|
try {
|
|
this._mapInstance = new T.Map(this._config.mapId);
|
|
this._mapInstance.addEventListener('load', () => {
|
|
this.$ownerInstance.callMethod('UserEvent',{type:'onMapLoad'});
|
|
});
|
|
|
|
setTimeout(() => {
|
|
this._setCenter({
|
|
lon: this._config.lonlat[0],
|
|
lat: this._config.lonlat[1],
|
|
zoom: this._config.zoom
|
|
});
|
|
}, 100);
|
|
this._bindEvent();
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
_geoCoder(lng,lat, uuid) {
|
|
//创建对象
|
|
const geocode = new T.Geocoder();
|
|
geocode.getLocation(new T.LngLat(lng,lat), (res)=>{
|
|
console.log(res.getStatus ());
|
|
console.log(res.getMsg ());
|
|
console.log(res.getAddress ());
|
|
console.log(res.getAddressComponent ());
|
|
console.log(res.getLocationLevel ());
|
|
});
|
|
return geocode;
|
|
},
|
|
// 绑定事件
|
|
_bindEvent() {
|
|
try {
|
|
// 绑定开始移动事件
|
|
this._event = {};
|
|
var fn = ({type})=>{
|
|
if(this._mapInstance==null)return;
|
|
const center = this._mapInstance.getCenter();
|
|
this.$ownerInstance.callMethod('UserEvent', {
|
|
type,
|
|
lng: center.getLng(),
|
|
lat: center.getLat()
|
|
});
|
|
}
|
|
//this._mapInstance.addEventListener('movestart', fn);
|
|
this._mapInstance.addEventListener('move', fn);
|
|
this._mapInstance.addEventListener('moveend', fn);
|
|
this._mapInstance.addEventListener('zoomstart', fn);
|
|
this._mapInstance.addEventListener('zoomend', fn);
|
|
this._mapInstance.addEventListener('dragstart', fn);
|
|
this._mapInstance.addEventListener('drag', fn);
|
|
this._mapInstance.addEventListener('dragend', fn);
|
|
this._mapInstance.addEventListener('touchstart', fn);
|
|
this._mapInstance.addEventListener('touchmove', fn);
|
|
this._mapInstance.addEventListener('touchend', fn);
|
|
this._mapInstance.addEventListener('longpress', fn);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 卸载事件
|
|
_unbindEvent() {
|
|
const eventsKey = Object.keys(this._event);
|
|
eventsKey.forEach(key => this._mapInstance.removeEventListener(key));
|
|
this._event = null;
|
|
},
|
|
// 重置地图尺寸
|
|
_resize() {
|
|
try {
|
|
this._mapInstance.checkResize && this._mapInstance.checkResize();
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 设置选项
|
|
_setOption(data) {
|
|
data.type && this._setType(data);
|
|
data.center && this._setCenter({
|
|
...data.center,
|
|
zoom: data.zoom
|
|
});
|
|
if (!data.center && data.zoom) this._setZoom(data);
|
|
data.markers && this._setMarkers(data);
|
|
data.overs && this.setOvers(data.overs);
|
|
},
|
|
// 设置中心点
|
|
_setCenter(data) {
|
|
try {
|
|
this._resize();
|
|
this._mapInstance.panTo(new T.LngLat(Number(data.lon), Number(data.lat)), data.zoom || this._mapInstance.getZoom());
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 设置地图显示类型
|
|
_setType(data) {
|
|
try {
|
|
this._mapInstance.setMapType(data.type === 1 ? TMAP_SATELLITE_MAP : TMAP_NORMAL_MAP);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 设置地图缩放
|
|
_setZoom(data) {
|
|
try {
|
|
this._mapInstance.setZoom(data.zoom);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 设置注记
|
|
_setMarkers(data) {
|
|
try {
|
|
// 清空当前所有注记
|
|
//if (!this._markerList) this._markerList = [];
|
|
//this._markerList.forEach(marker => this._mapInstance.removeOverLay(marker));
|
|
this._markerList = [];
|
|
this._mapInstance.clearOverLays();
|
|
// 创建新注记
|
|
data.markers && data.markers.forEach(item => {
|
|
const option = {};
|
|
if (item.icon) {
|
|
option.icon = new T.Icon({
|
|
iconUrl: item.icon,
|
|
iconSize: new T.Point(item.width || 24, item.height || 24),
|
|
iconAnchor: new T.Point(item.offsetX || item.width / 2, item.offsetY ||
|
|
item.height)
|
|
});
|
|
}
|
|
const marker = new T.Marker(new T.LngLat(item.lon, item.lat), option);
|
|
item.click && marker.addEventListener("click", () => this._setEvent('click', item));
|
|
this._markerList.push(marker);
|
|
this._mapInstance.addOverLay(marker);
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 设置使能
|
|
_setEnable(data) {
|
|
// 允许地图拖拽
|
|
if (data.hasOwnProperty("drag")) {
|
|
!!data["drag"] ? this._mapInstance.enableDrag() : this._mapInstance.disableDrag();
|
|
}
|
|
// 允许双击放大,默认启用。
|
|
if (data.hasOwnProperty("doubleClickZoom")) {
|
|
!!data["doubleClickZoom"] ? this._mapInstance.enableDoubleClickZoom() : this._mapInstance
|
|
.disableDoubleClickZoom();
|
|
}
|
|
// 允许地图惯性拖拽
|
|
if (data.hasOwnProperty("inertia")) {
|
|
!!data["inertia"] ? this._mapInstance.enableInertia() : this._mapInstance.disableInertia();
|
|
}
|
|
// 允许双指操作缩放
|
|
if (data.hasOwnProperty("pinchToZoom")) {
|
|
!!data["pinchToZoom"] ? this._mapInstance.enablePinchToZoom() : this._mapInstance.disablePinchToZoom();
|
|
}
|
|
},
|
|
// 添加覆盖物
|
|
_addOvers(data = []) {
|
|
if (!this._overs) this._overs = {};
|
|
data.forEach(item => {
|
|
switch (item.type) {
|
|
// 信息窗口
|
|
case "infowindow":
|
|
this._createOverInfoWindow(item);
|
|
break;
|
|
// 文本
|
|
case "label":
|
|
this._createOverLabel(item);
|
|
break;
|
|
// 创建圆
|
|
case "circle":
|
|
this._createOverCircle(item);
|
|
break;
|
|
// 创建折线
|
|
case "polyline":
|
|
this._createOverPolyLine(item);
|
|
break;
|
|
// 创建多边形
|
|
case "polygon":
|
|
this._createOverPolygon(item);
|
|
break;
|
|
// 创建矩形
|
|
case "rect":
|
|
this._createOverRectangle(item);
|
|
break;
|
|
// 创建带箭头的折线
|
|
case "polylineArrow":
|
|
// 创建不带箭头的三次样条曲线
|
|
case "cardinalCurve":
|
|
// 创建带箭头的三次样条曲线
|
|
case "cardinalCurveArrow":
|
|
this._createOverSymbol(item);
|
|
break;
|
|
default:
|
|
console.warn(
|
|
`'${item.type}'未知的覆盖物类型,有效值为 circle,polyline,polygon,rect,polylineArrow,cardinalCurve,cardinalCurveArrow`
|
|
);
|
|
}
|
|
});
|
|
},
|
|
// 设置覆盖物
|
|
_setOvers(data = []) {
|
|
if (!this._overs) this._overs = {};
|
|
// 移除现有同类型(时间复杂度On^2,不影响使用,暂时不优化了)
|
|
data.forEach(item => this._clearOvers(item.type));
|
|
// 添加
|
|
this._addOvers(data);
|
|
},
|
|
// 清除覆盖物
|
|
_clearOvers(type = "") {
|
|
if (!this._overs) this._overs = {};
|
|
// 清除
|
|
for (const id in this._overs) {
|
|
if (this._overs[id].options.type === type || !type) {
|
|
this._mapInstance.removeOverLay(this._overs[id]);
|
|
delete this._overs[id];
|
|
}
|
|
}
|
|
},
|
|
// 移除指定属性的覆盖物
|
|
_removeOvers(data = {}) {
|
|
if (!this._overs) this._overs = {};
|
|
for (const id in this._overs) {
|
|
if (this._overs[id].options[data.prop] === data.value) {
|
|
this._mapInstance.removeOverLay(this._overs[id]);
|
|
delete this._overs[id];
|
|
}
|
|
}
|
|
},
|
|
// 创建覆盖物
|
|
_createOver(over) {
|
|
// 添加到覆盖物层
|
|
this._mapInstance.addOverLay(over);
|
|
this._overs[this._genKey()] = over;
|
|
// 绑定点击事件
|
|
if (over.options.click) {
|
|
over.addEventListener("click", () => this._setEvent('click', over.options));
|
|
}
|
|
},
|
|
// 创建信息窗口
|
|
_createOverInfoWindow(option) {
|
|
try {
|
|
// 格式化参数
|
|
const param = {
|
|
...option
|
|
};
|
|
// 弹出窗口位置的补偿值。在同一图层中打开弹出窗口时对于控制锚点比较有用。
|
|
if (option.offsetX !== undefined || option.offsetY !== undefined) {
|
|
param.offset = new T.Point(option.offsetX || 0, option.offsetY || 0);
|
|
}
|
|
// 在地图视图自动平移产生后弹出窗口和地图视图之间的边缘。
|
|
if (option.autoPanPaddingX !== undefined || option.autoPanPaddingY !== undefined) {
|
|
param.autoPanPadding = new T.Point(option.autoPanPaddingX || 0, option.autoPanPaddingY || 0);
|
|
}
|
|
const infoWin = new T.InfoWindow(option.content, param);
|
|
// 设置或改变信息浮窗所指向的地理位置坐标
|
|
if (option.lon && option.lat) {
|
|
infoWin.setLngLat(new T.LngLat(option.lon, option.lat));
|
|
}
|
|
this._createOver(infoWin);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 创建文本覆盖物
|
|
_createOverLabel(option) {
|
|
try {
|
|
// 格式化参数
|
|
const param = {
|
|
...option
|
|
};
|
|
if (option.lon && option.lat) param.position = new T.LngLat(option.lon, option.lat);
|
|
param.offset = new T.Point(option.offsetX || 0, option.offsetY || 0);
|
|
const label = new T.Label(param);
|
|
this._createOver(label);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 创建圆覆盖物
|
|
_createOverCircle(option) {
|
|
try {
|
|
const circle = new T.Circle(new T.LngLat(option.lon, option.lat), option.rad, option);
|
|
this._createOver(circle);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 创建折线覆盖物
|
|
_createOverPolyLine(option) {
|
|
try {
|
|
const points = option.points.map(point => new T.LngLat(point[0], point[1]));
|
|
const line = new T.Polyline(points, option);
|
|
this._createOver(line);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 创建多边形覆盖物
|
|
_createOverPolygon(option) {
|
|
try {
|
|
const points = option.points.map(point => new T.LngLat(point[0], point[1]));
|
|
const polygon = new T.Polygon(points, option);
|
|
this._createOver(polygon);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 创建矩形覆盖物
|
|
_createOverRectangle(option) {
|
|
try {
|
|
const lt = option.leftTop;
|
|
const rb = option.rightBottom;
|
|
const bounds = new T.LngLatBounds(new T.LngLat(lt[0], lt[1]), new T.LngLat(rb[0], rb[1]));
|
|
const rect = new T.Rectangle(bounds, option);
|
|
this._createOver(rect);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 创建符号覆盖物
|
|
_createOverSymbol(option) {
|
|
try {
|
|
let over = null;
|
|
const points = option.points.map(point => new T.LngLat(point[0], point[1]));
|
|
switch (option.type) {
|
|
case "polylineArrow":
|
|
over = new T.PolylineArrow(points, option);
|
|
break;
|
|
case "cardinalCurve":
|
|
over = new T.CardinalCurve(points, option);
|
|
break;
|
|
case "cardinalCurveArrow":
|
|
over = new T.CardinalCurveArrow(points, option);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (over) this._createOver(over);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
},
|
|
// 设置事件
|
|
_setEvent(type, target) {
|
|
this.$ownerInstance.callMethod('UserEvent', {
|
|
type:'onMapListen',
|
|
type, // 事件类型UserEvent
|
|
target, // 事件目标
|
|
});
|
|
},
|
|
// 释放地图
|
|
_dispose() {
|
|
delete this._mapInstance;
|
|
this._mapInstance = null;
|
|
this._unbindEvent();
|
|
},
|
|
// 生成唯一ID
|
|
_genKey() {
|
|
let result = '';
|
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
const charactersLength = characters.length;
|
|
for (let i = 0; i < 30; i++) {
|
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
}
|
|
return Date.now() + result;
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.ly-map-wrapper {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: relative;
|
|
overflow: hidden;
|
|
background: #f0f0f0;
|
|
|
|
.ly-map {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.ly-center-icon {
|
|
position: absolute;
|
|
z-index: 401;
|
|
left: 50%;
|
|
top: 50%;
|
|
transform: translateX(-50%) translateY(-100%);
|
|
width: 64rpx;
|
|
height: 64rpx;
|
|
}
|
|
|
|
.ly-location-icon {
|
|
position: absolute;
|
|
z-index: 401;
|
|
right: 24rpx;
|
|
bottom: 24rpx;
|
|
width: 72rpx;
|
|
height: 72rpx;
|
|
background: #ffffff;
|
|
border-radius: 12rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 0 8rpx rgba(0, 0, 0, .15);
|
|
|
|
.icon {
|
|
width: 44rpx;
|
|
height: 44rpx;
|
|
}
|
|
}
|
|
}
|
|
</style> |