前置环境

安装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();//停止播放
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐