高德地图轨迹播放
MapDemo
<template>
<div>
<button @click="clearMap">清除地图</button>
<button @click="initTrack">初始化轨迹</button>
<button @click="playTrack">轨迹播放</button>
<button @click="playTrack2">带状态的轨迹播放</button>
<button @click="pauseTrack">暂停轨迹播放</button>
<button @click="continueTrack">继续轨迹播放</button>
<button @click="stopTrack">结束轨迹播放</button>
<button @click="clearTrack">清除轨迹播放</button>
<el-slider
v-model="speed"
@change="setTrackSpeed"
></el-slider>
<div id="mapContainer"></div>
<div id="trackContainer"></div>
</div>
</template>
<script>
import { MapTrack } from './track';
export default {
name: 'MapDemo',
data() {
return {
map: null,
speed: 0,
mapTrack: null,
};
},
mounted() {
this.initMap();
},
beforeDestroy() {
this.destroyMap();
},
methods: {
initMap() {
this.map = new AMap.Map('mapContainer', {
zoom: 15,
center: this.$mapCenter, // 设置地图中心点
doubleClickZoom: false,
});
this.mapTrack = new MapTrack(this.map, { iconFn: this.trackIconFn, infoWindowContentFn: this.trackInfoWindowContentFn });
},
destroyMap() {
this.mapTrack?.destroy();
this.map?.destroy();
this.map = null;
},
clearMap() {
this.map.clearMap();
},
initTrack() {
const lines = [
{ lng: 114.987519, lat: 30.125872, status: 1 },
{ lng: 114.992969, lat: 30.128545, status: 1 },
{ lng: 114.992969, lat: 30.128545, status: 2 },
{ lng: 114.99563, lat: 30.127617, status: 2 },
{ status: 1 },
{ status: 1 },
{ status: 1 },
{ lng: 114.996831, lat: 30.126355, status: 2 },
{ status: 1 },
{ lng: 114.998634, lat: 30.126318, status: 1 },
{ lng: 115.001337, lat: 30.127914, status: 3 },
{ lng: 115.004427, lat: 30.127673, status: 2 },
{ lng: 115.00623, lat: 30.126838, status: 2 },
{ lng: 115.00623, lat: 30.126838, status: 1 },
];
this.mapTrack.initTrack(lines);
},
playTrack2() {
this.mapTrack.playTrack(null, 100);
},
playTrack() {
const lines = [
[114.987519, 30.125872],
[114.992969, 30.128545],
[114.99563, 30.127617],
[114.996831, 30.126355],
null,
[114.998634, 30.126318],
[115.001337, 30.127914],
[115.004427, 30.127673],
[115.00623, 30.126838],
[115.00623, 30.126838],
];
this.mapTrack.playTrack(lines, 100);
},
trackIconFn(item) {
if (item.status === 1) return { fillColor: '#00ff00' };
if (item.status === 2) return { fillColor: '#ff0000' };
return { fillColor: '#0000ff' };
},
trackInfoWindowContentFn(item) {
return `
<div id="track-infoWindow">
<span>我是自定义的信息窗口: ${item.status}</span>
</div>
`;
},
pauseTrack() {
this.mapTrack.pauseTrack();
},
continueTrack() {
this.mapTrack.continueTrack();
},
stopTrack() {
this.mapTrack.stopTrack();
},
clearTrack() {
this.mapTrack.clearTrack();
},
setTrackSpeed(speed) {
this.mapTrack.setSpeed(speed);
},
},
};
</script>
<style scoped lang="scss">
#mapContainer {
width: 100%;
height: 500px;
}
</style>
<style lang="scss">
#track-infoWindow {
padding: 10px;
}
</style>
transform.js 见!地图工具类:地图坐标系转换、获取点位经纬度信息
track.js
import IconDefault from './icon/icon_marker_default.svg';
import IconEnd from './icon/icon_marker_end.svg';
import IconStart from './icon/icon_marker_start.svg';
import { getLngLatOfPoint } from './transform';
const ICON_SIZE = new AMap.Size(36, 36);
const ICON_OFFSET = new AMap.Pixel(-18, -36);
export class MapTrack {
#mapObj = null; // 地图对象
#infoWindow = null; // 信息窗口
speed = 5000; // 轨迹播放速度
status = 'stop'; // 状态
#marker = null;
isInited = false;
pointDatas = []; // 轨迹数据
paths = []; // 路径集合
#polylines = []; //
#pointMarkers = []; //
#currentPointIndex = null;
iconFn = null;
infoWindowContentFn = null;
constructor(mapObj, { iconFn, infoWindowContentFn } = {}) {
if (!this.#mapObj) {
this.#mapObj = mapObj;
}
if (typeof iconFn === 'function') {
this.iconFn = iconFn;
}
if (typeof infoWindowContentFn === 'function') {
this.infoWindowContentFn = infoWindowContentFn;
this.#initInfoWindow();
}
this.#mapObj.plugin(['AMap.MoveAnimation'], function () {});
}
/* 销毁 */
destroy() {
this.status = 'stop';
this.clearTrack();
}
/* 初始化信息窗口 */
#initInfoWindow() {
if (this.#infoWindow) return;
this.#infoWindow = new AMap.InfoWindow({
anchor: 'top-left',
offset: new AMap.Pixel(10, 20),
});
}
/* 清空轨迹*/
clearTrack() {
if (!this.isInited) return;
this.paths = [];
this.pointDatas = [];
this.status = 'stop';
this.#mapObj.remove(this.#polylines);
this.removeMoveMarker();
this.#mapObj.remove(this.#pointMarkers);
this.#polylines = [];
this.#pointMarkers = [];
this.#currentPointIndex = null;
this.isInited = false;
}
/* 轨迹初始化*/
initTrack(path) {
if (path.length < 2) return;
this.clearTrack();
this.isInited = true;
// 过滤掉无坐标的数据
this.pointDatas = path.filter((v) => {
if (Array.isArray(v) && v.length === 2) return true;
return v?.lng && v?.lat;
});
this.paths = this.pointDatas.map((item) => getLngLatOfPoint(item));
this.#intPolylines(path);
this.#creatPoints(this.pointDatas);
}
/* 创建移动点*/
#createMarker() {
if (!this.pointDatas?.length) return;
const firstPosition = getLngLatOfPoint(this.pointDatas[0]);
this.#marker = new AMap.Marker({
map: this.#mapObj,
position: firstPosition,
icon: new AMap.Icon({
size: ICON_SIZE,
imageSize: ICON_SIZE,
image: IconDefault,
}),
offset: ICON_OFFSET,
});
this.#marker.on('moving', (e) => this.#onMarkerMove(e));
}
/* 轨迹播放*/
playTrack(path, speed) {
if (path?.length >= 2) {
this.initTrack(path);
} else if (!this.#marker) {
this.#createMarker();
}
this.status = 'play';
this.speed = speed;
this.#markerMovingAlong();
}
/* 暂停轨迹播放*/
pauseTrack() {
this.status = 'pause';
this.#marker?.pauseMove();
}
/* 继续轨迹播放*/
continueTrack() {
this.status = 'play';
this.#marker?.resumeMove();
}
/* 停止轨迹播放*/
stopTrack() {
this.status = 'stop';
this.#marker?.stopMove();
}
/* 设置轨迹播放速度*/
setSpeed(speed) {
this.speed = speed;
if (!this.#marker) return;
this.#markerMovingAlong();
}
#markerMovingAlong() {
if (!this.paths?.length) return;
this.#marker.moveAlong(this.paths, {
duration: this.speed * 10, // 每一段的时长
// JSAPI2.0 是否延道路自动设置角度在 moveAlong 里设置
autoRotation: false,
});
}
#creatPoints(pointDatas) {
this.#pointMarkers = [];
pointDatas.forEach((item, index) => {
const position = getLngLatOfPoint(item);
if (index === 0) {
const startElement = new AMap.Marker({
map: this.#mapObj,
position,
icon: new AMap.Icon({
size: ICON_SIZE,
imageSize: ICON_SIZE,
image: IconStart,
}),
offset: ICON_OFFSET,
});
this.#mapObj.add(startElement);
this.#pointMarkers.push(startElement);
} else if (index === pointDatas.length - 1) {
const endElement = new AMap.Marker({
map: this.#mapObj,
position,
icon: new AMap.Icon({
size: ICON_SIZE,
imageSize: ICON_SIZE,
image: IconEnd,
}),
offset: ICON_OFFSET,
});
this.#mapObj.add(endElement);
this.#pointMarkers.push(endElement);
}
const config = this.iconFn?.(item) || {};
let element = new AMap.CircleMarker({
center: position, // 圆心
radius: 6, // 半径
// strokeColor: '#165dff', // 轮廓线颜色
strokeWeight: 0, // 轮廓线宽度
fillColor: '#165dff', // 圆点填充颜色
fillOpacity: 1, // 圆点填充透明度
zIndex: 10, // 圆点覆盖物的叠加顺序
cursor: 'pointer', // 鼠标悬停时的鼠标样式
...config,
extData: item,
});
this.#pointMarkers.push(element);
element.setMap(this.#mapObj);
this.#elementBindEvent(element);
});
}
/* 创建线 */
#createPolylineElement(path, strokeStyle = 'solid') {
const element = new AMap.Polyline({
map: this.#mapObj,
path: path,
strokeStyle,
strokeColor: strokeStyle === 'solid' ? '#28F' : '#bfbfbf', // 线颜色
strokeWeight: 6, // 线宽
zIndex: 1,
});
this.#polylines.push(element);
this.#mapObj.add(element);
return;
}
/* 创建线集合*/
#intPolylines(paths) {
let currentPath = [];
let dashedPaths = [];
const pathsLength = paths.length;
for (let index = 0; index < pathsLength - 1; index++) {
const point = getLngLatOfPoint(paths[index]);
let nextPoint;
if (index + 1 < pathsLength - 1) {
nextPoint = getLngLatOfPoint(paths[index + 1]);
}
// 如果当前点及下一个点有数据,则推送到currentPath中
if (point && nextPoint) {
currentPath.push(point, nextPoint);
index++;
} else {
// 出现中断,绘制已有实线
if (currentPath.length > 1) {
this.#createPolylineElement(currentPath);
currentPath = [];
}
// 找到上一个及下一个有数据的点,绘制虚线
let prevIndex;
if (point) {
prevIndex = index;
} else {
prevIndex = index - 1;
while (prevIndex >= 0 && !getLngLatOfPoint(paths[prevIndex])) {
prevIndex--;
}
}
let nextIndex = index + 1;
while (nextIndex < pathsLength - 1 && !getLngLatOfPoint(paths[nextIndex])) {
nextIndex++;
}
if (prevIndex >= 0 && nextIndex < pathsLength) {
const prevPoint = getLngLatOfPoint(paths[prevIndex]);
const nextPoint = getLngLatOfPoint(paths[nextIndex]);
dashedPaths.push([prevPoint, nextPoint]);
}
index = nextIndex - 1; // 循环结束会++,此处-1
}
}
if (currentPath.length > 1) {
this.#createPolylineElement(currentPath);
}
dashedPaths.forEach((path) => {
this.#createPolylineElement(path, 'dashed');
});
}
#onMarkerMove(e) {
// this.#mapObj.setCenter(e.target.getPosition(), true);
if (e.index !== this.#currentPointIndex) {
this.#currentPointIndex = e.index;
}
if (e.progress === 1 && this.#currentPointIndex === this.pointDatas.length - 2) {
this.status = 'stop';
this.removeMoveMarker();
}
}
removeMoveMarker() {
if (!this.#marker) return;
this.stopTrack();
this.#marker?.off('moving', (e) => this.#onMarkerMove(e));
this.#mapObj.remove(this.#marker);
this.#marker = null;
}
/* 元素绑定事件*/
#elementBindEvent(element) {
element.on('mouseover', (e) => {
if (this.#infoWindow) {
const data = e.target.getExtData();
const content = this.infoWindowContentFn?.(data);
const lnglat = getLngLatOfPoint(data);
this.#infoWindow.setContent(content);
this.#infoWindow.open(this.#mapObj, lnglat);
}
});
element.on('mouseout', () => {
this.#infoWindow?.close();
});
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类