Vue3+Vite+LeafLet实现运动轨迹播放
·
前置环境
安装Leaflet
AnimatedMarker方法(参考)
import L from 'leaflet';
L.AnimatedMarker = L.Marker.extend({
options: {
speetX: 1,
speedList: [],
// meters(米)
distance: 1000,
// ms(毫秒)
interval: 100,
// animate on add?
autoStart: false,
isPlay: false,
playCall: null
},
initialize: function(latlngs, options) {
Object.assign(this.options, options);
this.isZooming = false;
L.Marker.prototype.initialize.call(this, latlngs[0], options);
this.setLine(latlngs);
// this.resetIcon();
},
resetIcon: function() {
const v = this;
var iconOption = v.options.icon.options;
var Icon = L.DivIcon.extend({
createIcon: function() {
// outerDiv.style.transform is updated by Leaflet
var outerDiv = document.createElement('div');
outerDiv.style.width = iconOption.iconSize[0] + "px";
outerDiv.style.height = iconOption.iconSize[1] + "px";
outerDiv.style.position = "absolute";
v.div = document.createElement('div');
v.div.style.width = iconOption.iconSize[0] + "px";
v.div.style.height = iconOption.iconSize[1] + "px";
v.div.style.transition = 'transform linear 100ms';
v.div.style.transformOrigin = 'center';
v.div.style.transform = 'translate3d(-' + iconOption.iconAnchor[0] + 'px, -' + iconOption.iconAnchor[0] + 'px, 0) rotate(-' + v._latlngs[0].bearing + 'deg)';
const img = document.createElement('img');
img.src = iconOption.iconUrl;
img.width = iconOption.iconSize[0];
img.height = iconOption.iconSize[1];
v.div.appendChild(img);
outerDiv.appendChild(v.div);
return outerDiv;
},
rotate(deg) {
if ((v.before && (Math.abs(v.before - deg) >= 180))) {
v.div.style.transition = 'none';
} else {
v.div.style.transition = 'transform linear 100ms';
// var _speed = 100 / v.options.speetX + 'ms'
// v.div.style.transition = 'transform linear ' + _speed
}
v.div.style.transform = 'translate3d(-19px, -13px, 0) rotate(-' + deg + 'deg)';
v.before = deg;
},
iconSize: iconOption.iconSize,
});
this.icon = new Icon();
this.setIcon(this.icon);
},
// Breaks the line up into tiny chunks (see options) ONLY if CSS3 animations
// // are not supported.
// _chunk: function (latlngs) {
// var i,
// len = latlngs.length,
// chunkedLatLngs = [];
// for (i = 1; i < len; i++) {
// var cur = latlngs[i - 1],
// next = latlngs[i],
// dist = cur.distanceTo(next),
// factor = this.options.distance / dist,
// dLat = factor * (next.lat - cur.lat),
// dLng = factor * (next.lng - cur.lng);
// if (dist > this.options.distance) {
// chunkedLatLngs.push(cur);
// while (dist > this.options.distance) {
// var bearing = cur.bearing
// var duration = cur.duration
// cur = new L.LatLng(cur.lat + dLat, cur.lng + dLng);
// cur.bearing = bearing
// cur.duration = duration
// dist = cur.distanceTo(next);
// chunkedLatLngs.push(cur);
// }
// } else {
// chunkedLatLngs.push(cur);
// }
// }
// chunkedLatLngs.push(latlngs[len - 1]);
// return chunkedLatLngs;
// },
onAdd: function(map) {
L.Marker.prototype.onAdd.call(this, map);
this.animate();
this.map = map;
const _this = this;
map.addEventListener('zoomstart', function() {
_this.isZooming = true;
});
map.addEventListener('zoomend', function() {
_this.isZooming = false;
});
// Start animating when added to the map
if (this.options.autoStart) {
this.start();
}
},
animate: function() {
var now = Date.now();
var end = this.startedAt + this.duration;
if (now < end && this.getLatLng().distanceTo(this.startLatLng) < this.nextLatLng.distanceTo(this.startLatLng)) {
if (this.isPlay) {
requestAnimationFrame(this.animate.bind(this));
}
} else if (this._i < (this._latlngs.length - 1)) {
if (this.speetChange) {
this._latlngs = this._latlngsnew;
this.speetChange = false;
}
this.startedAt = Date.now();
this.startLatLng = this._latlngs[this._i];
this.nextLatLng = this._latlngs[this._i + 1];
this.duration = this.startLatLng.duration;
this.setLatLng(this._latlngs[this._i]);
// this.icon.rotate(this.startLatLng.bearing);
this._i++;
if (this.isPlay) {
requestAnimationFrame(this.animate.bind(this));
}
}
// 解决地图缩放时图标会偏移轨迹线问题
if (!this.isZooming && this.map) {
var t = now - this.startedAt;
if (t > this.duration){ // 解决结束时小车位置超出终点问题
t = this.duration;
}
var lat = this.startLatLng.lat + ((this.nextLatLng.lat - this.startLatLng.lat) / this.startLatLng.duration * t);
var lng = this.startLatLng.lng + ((this.nextLatLng.lng - this.startLatLng.lng) / this.startLatLng.duration * t);
if ( isNaN(lat) && isNaN(lng) ) {
return;
} else {
this.setLatLng({
lat: lat,
lng: lng
});
// 用于动态轨迹线展示
if (this.options.playCall) {
this.options.playCall({
lat: lat,
lng: lng
});
}
}
}
},
setSpeetX: function(_speetX) {
var speed = _speetX / this.options.speetX;
this._latlngsnew = [];
for (var i = 0; i < this._latlngs.length; i++) {
var _latlng = L.latLng(this._latlngs[i].lat, this._latlngs[i].lng);
_latlng.duration = this._latlngs[i].duration / speed;
_latlng.bearing = this._latlngs[i].bearing;
this._latlngsnew.push(_latlng);
}
this.options.speetX = _speetX;
this.speetChange = true;
},
// Start the animation
start: function() {
this.isPlay = true;
this.startedAt = Date.now();
if (this.isPause) {
this.startedAt = this.startedAt - this.playedTime;
this.isPause = false;
}
this.animate();
},
pause: function() {
this.playedTime = Date.now() - this.startedAt;
this.isPause = true;
this.isPlay = false;
},
// Stop the animation in place
stop: function() {
// console.log('stop')
this.isPlay = false;
this.isPause = false;
this.startedAt = NaN;
this._i = 0;
this.animate();
},
setLine: function(latlngs) {
// console.log('distance' + this.options.distance)
// console.log('interval' + this.options.interval)
for (var i = 0; i < latlngs.length; i++) {
if (i === latlngs.length - 1) {
latlngs[i].duration = latlngs[i - 1].duration;
latlngs[i].bearing = latlngs[i - 1].bearing;
} else {
if (this.options.speedList.length > 0 && this.options.speedList[i] !== undefined) {
latlngs[i].duration = latlngs[i].distanceTo(latlngs[i + 1]) / this.options.distance * (this.options.interval / this.options.speedList[i]);
} else {
latlngs[i].duration = latlngs[i].distanceTo(latlngs[i + 1]) / this.options.distance * this.options.interval;
}
latlngs[i].bearing = this.getRotation(latlngs[i], latlngs[i + 1]);
}
}
this._latlngs = latlngs;
// this._latlngs = this._chunk(latlngs);
this._i = 0;
},
getRotation: function(start, end) {
var dx = end.lng - start.lng;
var dy = end.lat - start.lat;
var radian = Math.atan2(dy, dx); //弧度值
var rotation = 180 * radian / Math.PI; //转换为角度值
if (rotation > -180 && rotation < 0) {
rotation = 360 + rotation;
}
return rotation;
}
});
L.animatedMarker = function(latlngs, options) {
return new L.AnimatedMarker(latlngs, options);
};
使用方法
//先画进去巡查轨迹
let polyline = L.polyline(xuncha, {
id: 'Particle',
color: 'rgb(51,136,255)'
});
mapStore.activePolyline.addLayer(polyline);
//已走轨迹
realEchoLine = L.polyline([], {color: "#FF9900"});
mapStore.activePolyline.addLayer(realEchoLine);
//添加箭头
mapStore.activePolyline.addLayer(L.polylineDecorator(polyline, {
patterns: [
{
repeat: 50,
symbol: L.Symbol.arrowHead({
pixelSize: 5,
headAngle: 75,
polygon: false,
pathOptions: {
stroke: true,
weight: 2,
color: "#FFFFFF",
},
}),
},
],
}));
// 绘制已行走轨迹线(橙色那条)
const updateRealLine = (latlng) => {
realLineLatlngs.push(latlng);
realEchoLine.setLatLngs(realLineLatlngs);
}
//标签动画
let inspectPerson = L.icon({
iconSize: [30, 30],
iconAnchor: [19, 13],
iconUrl: new URL("@/assets/skjz/images/icon/patrolmanagement.png", import.meta.url).href,
})
animatedMarker = L.animatedMarker(polyline.getLatLngs(), {
distance: 10,
// interval: 100,
icon: inspectPerson,
autoStart: false,
playCall: updateRealLine,
});
mapStore.activePolyline.addLayer(animatedMarker);
let realLineLatlngs = [polyline.getLatLngs()[0]];
animatedMarker.start();//播放动画
animatedMarker.pause();//停止播放更多推荐


所有评论(0)