iovmap
Version:
a map sdk base on leaflet
230 lines (213 loc) • 7.52 kB
JavaScript
L.AnimatedMarker = L.Marker.extend({
options: {
// meters
distance: 100,
// ms
interval: 100,
// animate on add?
autoStart: false,
isPlay: false,
playCall: null
},
initialize: function (latlngs, options) {
this.options.interval = options.interval || 100;
this.isZooming = false;
this.setLine(latlngs);
L.Marker.prototype.initialize.call(this, latlngs[0], options);
this.resetIcon(options.icon.options);
},
resetIcon: function (options) {
const v = this
var Icon = L.DivIcon.extend({
createIcon: function () {
// outerDiv.style.transform is updated by Leaflet
var outerDiv = document.createElement('div');
outerDiv.style.width = options.iconSize[0] + "px"
outerDiv.style.height = options.iconSize[1] + "px"
v.div = document.createElement('div');
v.div.style.width = options.iconSize[0] + "px"
v.div.style.height = options.iconSize[1] + "px"
v.div.style.transition = 'transform linear 100ms'
v.div.style.transformOrigin = 'center'
// 初始 transform:X 用 iconAnchor[0],Y 用 iconAnchor[1],修正之前两个都用 [0] 的错误
v.div.style.transform = 'translate3d(-' + options.iconAnchor[0] + 'px, -' + options.iconAnchor[1] + 'px, 0) rotate(-' + v._latlngs[0].bearing + 'deg)';
const img = document.createElement('img');
img.src = options.iconUrl;
img.width = options.iconSize[0];
img.height = options.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'
}
v.div.style.transform = 'translate3d(-' + options.iconAnchor[0] + 'px, -' + options.iconAnchor[1] + 'px, 0) rotate(-' + deg + 'deg)';
v.before = deg
},
iconSize: options.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
// 保存函数引用,供 onRemove 时解绑,避免事件监听泄漏
this._zoomStartHandler = function () {
_this.isZooming = true;
};
this._zoomEndHandler = function () {
_this.isZooming = false;
};
map.addEventListener('zoomstart', this._zoomStartHandler);
map.addEventListener('zoomend', this._zoomEndHandler);
// Start animating when added to the map
if (this.options.autoStart) {
this.start();
}
},
onRemove: function (map) {
// 暂停动画,清理 zoom 事件监听,避免内存泄漏
this.pause();
if (this._zoomStartHandler) {
map.removeEventListener('zoomstart', this._zoomStartHandler);
this._zoomStartHandler = null;
}
if (this._zoomEndHandler) {
map.removeEventListener('zoomend', this._zoomEndHandler);
this._zoomEndHandler = null;
}
this.map = null;
L.Marker.prototype.onRemove.call(this, map);
},
animate: function () {
var now = Date.now();
var end = this.startedAt + this.duration;
if (now < end) {
if (this.isPlay) {
requestAnimationFrame(this.animate.bind(this));
}
} else if (this._i < (this._latlngs.length - 1)) {
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;
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);
this.setLatLng({
lat: lat,
lng: lng
});
// 用于动态轨迹线展示
if (this.options.playCall) {
this.options.playCall({
lat: lat,
lng: lng
}, this._i);
}
}
},
// Start the animation
start: function () {
this.isPlay = true
this.startedAt = Date.now();
this.animate()
},
pause: function () {
this.isPlay = false
},
// Stop the animation in place
stop: function () {
this.isPlay = false
this.startedAt = NaN
this._i = 0
this.animate();
},
changeSpeed: function (interval) {
this.options.interval = interval || 100;
for (let index = 0; index < this._latlngs.length; index++) {
if (index === this._latlngs.length - 1) {
this._latlngs[index].duration = this._latlngs[index - 1].duration;
} else {
this._latlngs[index].duration = this._latlngs[index].distanceTo(this._latlngs[index + 1]) / this.options.distance * this.options.interval
}
}
},
changeIndex: function (index) {
this._i = index;
},
setLine: function (latlngs) {
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 {
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);
};