SOURCE

console 命令行工具 X clear

                    
>
console
/////////////////////////////////////////////////
// Definition of required vector math functions
// as plugin for the calculations.
// Change this to adapt to your favourite vector library.
function generateSpeedVectorBack(latitude, speed, course) {
    var northSpeed = speed * Math.cos((course) * Math.PI / 180) / 60 / 3600;
    var eastSpeed = speed * Math.sin((course) * Math.PI / 180) / 60 / 3600 * Math.abs(Math.sin(latitude * Math.PI / 180));
    return [northSpeed, eastSpeed, 0]
}

function generateSpeedVector(speed, course) {
    var northSpeed = speed / 3600 / 60 * Math.cos((course - 90) * Math.PI / 180);
    var eastSpeed = speed / 3600 / 60 * Math.sin((course - 90) * Math.PI / 180);
    return [northSpeed, eastSpeed, 0]
}

var MathFunc = {
    add: function (a, b) {
        return [
            a[0] + b[0],
            a[1] + b[1],
            a[2] + b[2]
        ];
    },
    sub: function (a, b) {
        return [
            a[0] - b[0],
            a[1] - b[1],
            a[2] - b[2]
        ];
    },
    mulScalar: function (a, s) {
        return [
            a[0] * s,
            a[1] * s,
            a[2] * s
        ];
    },
    dot: function (a, b) {
        return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
    },
    lengthSquared: function (a) {
        return a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
    }
};
/////////////////////////////////////////////

function withMathFunc(mf) {
    return {

        /**
         * calculates the new position by speed and delta-time.
         * @method getPositionByVeloAndTime
         * @param position {Vector3d} The old position.
         * @param velocity {Velocity} The velocity.
         * @param dt {Number} The delta-time.
         * @return {Vector3d} The new position.
         * @for motionpredict
         */
        getPositionByVeloAndTime: function (position, velocity, dt) {
            return mf.add(position, mf.mulScalar(velocity, dt));
        },


        /**
         * Solves the quadratic equation for p and q.
         * @method quadEquation
         * @param p {Number} The parameter p.
         * @param q {Number} The parameter q.
         * @return {Array} The result with zero, one or two solutions.
         * @for motionpredict
         */
        quadEquation: function (p, q) {
            var wurzel = Math.sqrt((p * p / 4) - q);
            var vorwurzel = (-p / 2);
            var result = [];
            if (wurzel > 0) {
                result = [vorwurzel + wurzel, vorwurzel - wurzel];
            }
            else if (wurzel === 0) {
                result = [vorwurzel];
            }
            return result;
        },


        /**
         * Calculates the time of closest point of approach (TCPA) of two tracks.
         * @method calcCPATime
         * @param position1 {Vector3d} The position of track 1.
         * @param velocity1 {Vector3d} The velocity of track 1.
         * @param position2 {Vector3d} The position of track 2.
         * @param velocity2 {Vector3d} The velocity of track 2.
         * @return {Number} The time of CPA relative to now (0s) or undefined if CPA does not exists. Negative values show a CPA in the past 
         * @for motionpredict
         */
        calcCPATime: function (position1, velocity1, position2, velocity2) {
            var posDiff = mf.sub(position2, position1);
            var veloDiff = mf.sub(velocity2, velocity1);

            var zaehler = -mf.dot(posDiff, veloDiff);
            //返回向量长度平方值
            var nenner = mf.lengthSquared(veloDiff);
            return nenner === 0.0 ? undefined : zaehler / nenner;
        },


        /**
         * Calculates the position of closest point of approach (CPA) of track 1.
         * @method calcCPAPositionTarget1
         * @param position1 {Vector3d} The position of track 1.
         * @param velocity1 {Vector3d} The velocity of track 1.
         * @param position2 {Vector3d} The position of track 2.
         * @param velocity2 {Vector3d} The velocity of track 2.
         * @return {Vector3d} The position of CPA or undefined if CPA does not exists. 
         * @for motionpredict
         */
        calcCPAPositionTarget1: function (position1, velocity1, position2, velocity2) {
            var tcpa = this.calcCPATime(position1, velocity1, position2, velocity2);
            if (tcpa === undefined)
                return undefined;
            return this.getPositionByVeloAndTime(position1, velocity1, tcpa);
        },


        /**
         * Calculates the position of closest point of approach (CPA) of track 2.
         * @method calcCPAPositionTarget2
         * @param position1 {Vector3d} The position of track 1.
         * @param velocity1 {Vector3d} The velocity of track 1.
         * @param position2 {Vector3d} The position of track 2.
         * @param velocity2 {Vector3d} The velocity of track 2.
         * @return {Vector3d} The position of CPA or undefined if CPA does not exists. 
         * @for motionpredict
         */
        calcCPAPositionTarget2: function (position1, velocity1, position2, velocity2) {
            var tcpa = this.calcCPATime(position1, velocity1, position2, velocity2);
            if (tcpa === undefined)
                return undefined;
            return this.getPositionByVeloAndTime(position2, velocity2, tcpa);
        },


        /**
         * Calculates the intercepttime to the target at a given speed.
         * @method calcInterceptTime
         * @param myPos {Vector3d} The position of the interceptor.
         * @param myVelo {Number} The velocity at which the target should be intercepted.
         * @param targetPos {Vector3d} The position of the target.
         * @param targetVelo {Vector3d} The velocity and direction in which the target is moving.
         * @return {Number} The time from now at which the target is reached.
         * @for motionpredict
         */
        calcInterceptTime: function (myPos, myVelo, targetPos, targetVelo) {
            var relTargetPos = mf.sub(targetPos, myPos);
            var a = mf.lengthSquared(targetVelo) - myVelo * myVelo;
            var b = 2.0 * mf.dot(targetVelo, relTargetPos);
            var c = mf.lengthSquared(relTargetPos);

            if (a === 0) {
                if (b !== 0) {
                    var time = -c / b;
                    if (time > 0.0)
                        return time;
                }
            }
            else {
                // P und Q berechnen...
                var p = b / a;
                var q = c / a;

                // Quadratische Gleichung lösen...
                var times = this.quadEquation(p, q);
                if (times.length === 0)
                    return [];

                if (times.length === 2) {
                    var icptTime = Math.min(times[0], times[1]);
                    if (icptTime < 0.0) {
                        icptTime = Math.max(times[0], times[1]);
                    }
                    return icptTime;
                }
                else if (times.length === 1) {
                    if (times[0] >= 0.0) {
                        return times[0];
                    }
                }
            }
            return undefined;
        },


        /**
         * Calculates the intercept-position of the target.
         * @method calcInterceptPosition
         * @param myPos {Vector3d} The position of the interceptor.
         * @param myVelo {Number} The velocity at which the target should be intercepted.
         * @param targetPos {Vector3d} The position of the target.
         * @param targetVelo {Vector3d} The velocity and direction in which the target is moving.
         * @return {Vector3d} The position at which the target is reached.
         * @for motionpredict
         */
        calcInterceptPosition: function (myPos, myVelo, targetPos, targetVelo) {
            var ticpt = this.calcInterceptTime(myPos, myVelo, targetPos, targetVelo);
            if (ticpt === undefined)
                return undefined;
            return this.getPositionByVeloAndTime(targetPos, targetVelo, ticpt);
        },


        /**
         * Calculates the arrivaltime of the target to my position.
         * @method calcArrivalTime
         * @param myPos {Vector3d} The position of me.
         * @param myVelo {Vector3d} The velocity of me.
         * @param targetPos {Vector3d} The position of the target.
         * @param targetVelo {Vector3d} The velocity and direction in which the target is moving.
         * @return {Number} The time in which the target has reached me or undefined if not reachable.
         * @for motionpredict
         */
        calcArrivalTime: function (myPos, myVelo, targetPos, targetVelo) {
            var distance = Math.sqrt(mf.lengthSquared(mf.sub(targetPos, myPos)));
            var approachSpeed = this.calcApproachSpeed(myPos, myVelo, targetPos, targetVelo);
            if (approachSpeed > 0.0) {
                return distance / approachSpeed;
            }
            else {
                return undefined;
            }
        },


        /**
         * Calculates the approachspeed of the target to my position.
         * @method calcApproachSpeed
         * @param myPos {Vector3d} The position of me.
         * @param myVelo {Vector3d} The velocity of me.
         * @param targetPos {Vector3d} The position of the target.
         * @param targetVelo {Vector3d} The velocity and direction in which the target is moving.
         * @return {Number} The speed at which the target is approaching.
         * @for motionpredict
         */
        calcApproachSpeed: function (myPos, myVelo, targetPos, targetVelo) {
            var posDiff = mf.sub(targetPos, myPos);
            var veloDiff = mf.sub(targetVelo, myVelo);
            var approachSpeed = mf.dot(posDiff, veloDiff);
            var posDiffLength = Math.sqrt(mf.lengthSquared(posDiff));
            if (posDiffLength <= 0.0)
                return 0.0;
            return -approachSpeed / posDiffLength;
        }

    };
};

//// THE EXAMPLE ////
var motionpredict = withMathFunc(MathFunc);

//https://www.bootcdn.cn/
let lat = 39.38114242464665;
let lng = 121.26219749450685;



let mapUtils = null;
const map = L.map('map').setView([lat, lng], 11);
L.tileLayer(`http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=faed44eb8716fbc8bc9978d8e44ab7b4`, {
    attribution: '&copy; <a href="https://stadiamaps.com/" target="_blank">Stadia Maps</a> &copy; <a href="https://stamen.com/" target="_blank">Stamen Design</a> &copy; <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> &copy; <a href="https://www.openstreetmap.org/about" target="_blank">OpenStreetMap</a> contributors',
    minZoom: 2,
    maxZoom: 18
}).addTo(map);
map.attributionControl.setPosition('bottomleft');
map.zoomControl.setPosition('bottomright');
// 添加点击事件监听
map.on('click', function (e) {
    // e.latlng 包含了点击位置的经纬度
    console.log("Latitude: " + e.latlng.lat + ", Longitude: " + e.latlng.lng);
});
function disToPixeldistance(distance) {
    var l2 = L.GeometryUtil.destination(map.getCenter(), 0, distance);
    var p1 = map.latLngToContainerPoint(map.getCenter())
    var p2 = map.latLngToContainerPoint(l2)
    return p1.distanceTo(p2)
}

function pixelDistance(p1, p2) {
    var l1 = mapUtils.layerPointToLatLng(p1);
    var l2 = mapUtils.layerPointToLatLng(p2);
    return l1.distanceTo(l2);
}

function calculateApolloniusCircle(x1, y1, x2, y2, k) {
    const dx = x2 - x1;
    const dy = y2 - y1;
    const d = Math.sqrt(dx * dx + dy * dy);
    const cx = x1 + dx * k / (k + 1);
    const cy = y1 + dy * k / (k + 1);

    const r = Math.abs(d * k / (k * k - 1));
    const rx = dx * k / (k * k - 1);
    const ry = dy * k / (k * k - 1);


    //计算圆心坐标
    const centerX = cx + rx
    const centerY = cy + ry
    return { centerX, centerY, radius: r, cx, cy };
}

// 定义一个函数将角度转换为弧度
function toRadians(degrees) {
    return degrees * Math.PI / 180;
}

// 定义一个函数将速度和方向转换为向量
function speedToVector(speed, direction) {
    // 如果方向是以角度给出的,先转换为弧度
    if (typeof direction === 'number' && direction > 0 && direction < 360) {
        direction = toRadians(direction);
    }

    // 计算向量的x和y分量
    let x = speed * Math.cos(direction);
    let y = speed * Math.sin(direction);

    return { x: x, y: y };
}

function shortestDistance(x1, y1, vx1, vy1, x2, y2, vx2, vy2) {
    // Coefficients of the quadratic equation (at^2 + bt + c = 0)
    let a = (vx1 - vx2) ** 2 + (vy1 - vy2) ** 2;
    let b = 2 * ((x1 - x2) * (vx1 - vx2) + (y1 - y2) * (vy1 - vy2));
    let c = (x1 - x2) ** 2 + (y1 - y2) ** 2;

    // Time t for which distance is minimized (critical point)
    let t = -b / (2 * a);

    // Position of point 1 at time t
    let px1 = x1 + t * vx1;
    let py1 = y1 + t * vy1;

    // Position of point 2 at time t
    let px2 = x2 + t * vx2;
    let py2 = y2 + t * vy2;

    // Shortest distance
    let shortestDist = Math.sqrt((px1 - px2) ** 2 + (py1 - py2) ** 2);

    return shortestDist;
}



var ships = [];
const duration = 60 * 60;//一小时,毫秒
const timeScale = 10;//
let frame = null;
let showAshiCirle = false;
let showTraceLine = true;
let firstDraw = true;
let prevZoom;
let start;
const animate = (timestamp) => {
    const zoom = mapUtils.getMap().getZoom();
    const container = mapUtils.getContainer();
    const renderer = mapUtils.getRenderer();
    const project = mapUtils.latLngToLayerPoint;
    const scale = mapUtils.getScale();
    if (start === null) start = timestamp;
    const progress = (timestamp - start) / 1000 * timeScale;
    let lambda = progress / duration;
    if (lambda > 1) lambda = 1;
    //lambda = lambda * (0.4 + lambda * (2.2 + lambda * -1.6));
    ships.forEach((ship, index) => {
        var sSpeedV = generateSpeedVector(ship.info.speed, ship.info.angle);
        let newX = ship.fromPixel.x + sSpeedV[0] * progress;
        let newY = ship.fromPixel.y + sSpeedV[1] * progress;

        let rad = Math.atan((ship.fromPixel.x - newY) / (ship.fromPixel.y - newX)) + 45 * (Math.PI / 180);
        if (rad < 0) {
            rad = Math.PI * 2 + rad;
        }
        ship.triangle.rotation = rad;
        ship.triangle.x = newX;
        ship.triangle.y = newY;
    })

    ships.forEach((aShip, aIndex) => {
        ships.forEach((bShip, bIndex) => {
            if (aIndex !== bIndex) {
                aShip.lines[bIndex].clear();
                aShip.lines[bIndex].lineStyle(1 / scale, 0x669920, 1);
                aShip.lines[bIndex].moveTo(aShip.triangle.x, aShip.triangle.y);
                aShip.lines[bIndex].lineTo(bShip.triangle.x, bShip.triangle.y);


                let dis = pixelDistance(aShip.triangle, bShip.triangle);
                dis = `${dis.toFixed(0)}m-${(progress).toFixed(0)}s`

                aShip.lineTexts[bIndex].text = dis;
                aShip.lineTexts[bIndex].x = (aShip.triangle.x + bShip.triangle.x) / 2;
                aShip.lineTexts[bIndex].y = (aShip.triangle.y + bShip.triangle.y) / 2;
                aShip.lineTexts[bIndex].zIndex = 1;
            }
        })
    })
    renderer.render(container);
    if (progress < duration) {
        frame = requestAnimationFrame(animate);
    }
};


var shipInfos = [{
    lat: 39.38114242464665,
    lng: 121.26219749450685,
    angle: -90,
    top: 40,
    left: 10,
    right: 10,
    bottom: 10,
    minDis: 100,
    maxDis: 500,
    speed: 20000 * 1.852
}, {
    lat: 39.38692224789837,
    lng: 121.24580383300783,
    angle: -120,
    top: 160,
    left: 20,
    right: 20,
    bottom: 40,
    minDis: 100,
    maxDis: 500,
    speed: 10000 * 1.852
}, {
    lat: 39.295516858108876,
    lng: 121.22039794921876,
    angle: -20,
    top: 160,
    left: 20,
    right: 20,
    bottom: 40,
    minDis: 100,
    maxDis: 200,
    speed: 11000 * 1.852
}, {
    lat: 39.27106874641232,
    lng: 121.12495422363283,
    angle: 40,
    top: 160,
    left: 20,
    right: 20,
    bottom: 40,
    minDis: 100,
    maxDis: 200,
    speed: 12000 * 1.852
}, {
    lat: 39.39428523176663,
    lng: 121.01303100585939,
    angle: 110,
    top: 160,
    left: 20,
    right: 20,
    bottom: 40,
    minDis: 100,
    maxDis: 200,
    speed: 12300 * 1.852
}, {
    lat: 39.45581202472926,
    lng: 121.06521606445314,
    angle: 150,
    top: 160,
    left: 20,
    right: 20,
    bottom: 40,
    minDis: 100,
    maxDis: 200,
    speed: 12400 * 1.852
}, {
    lat: 39.33376633431887,
    lng: 121.04496002197267,
    angle: 69, top: 160,
    left: 20,
    right: 20,
    bottom: 40,
    minDis: 100,
    maxDis: 200,
    speed: 12402 * 1.852
}];



const pixiOverlay = (() => {
    shipInfos.forEach(info => {
        var latlng = L.latLng(info.lat, info.lng);
        var top = L.GeometryUtil.destination(latlng, 0 + info.angle, info.top)
        var tmpDot1 = L.GeometryUtil.destination(top, 90 + info.angle, info.right)
        tmpDot1 = L.GeometryUtil.destination(tmpDot1, 180 + info.angle, info.right / 2)
        var tmpDot2 = L.GeometryUtil.destination(top, -90 + info.angle, info.left)
        tmpDot2 = L.GeometryUtil.destination(tmpDot2, 180 + info.angle, info.left / 2)
        var bottom = L.GeometryUtil.destination(latlng, 180 + info.angle, info.bottom)
        var tmp2 = L.GeometryUtil.destination(bottom, 90 + info.angle, info.right)
        var tmp4 = L.GeometryUtil.destination(bottom, 270 + info.angle, info.left)
        var distance = info.speed / 3600 * duration //每秒速度乘以一小时,动画是毫秒所以除1000
        var to = L.GeometryUtil.destination(latlng, info.angle, distance);

        ships.push({
            info: info,
            from: latlng,
            fromPixel: null,
            to: to,
            toPixel: null,
            centerRadius: 0,
            minDisRadius: 0,
            maxDisRadius: 0,
            polygonLatLngs: [
                [top.lat, top.lng],
                [tmpDot1.lat, tmpDot1.lng],
                [tmp2.lat, tmp2.lng],
                [bottom.lat, bottom.lng],
                [tmp4.lat, tmp4.lng],
                [tmpDot2.lat, tmpDot2.lng],
                [top.lat, top.lng]
            ],
            projectedPolygon: []

        })
    });

    const pixiContainer = new PIXI.Container();
    ships.forEach(ship => {
        ship.triangle = new PIXI.Graphics();
        ship.ftLine = new PIXI.Graphics();
        ship.lines = [];
        ship.lineTexts = [];
        ship.aShiCirles = [];
        ship.disLines = [];
        ship.disLineTexts = [];

        for (var i = 0; i < ships.length; i++) {
            var disLine = new PIXI.Graphics();
            var line = new PIXI.Graphics()
            var lineText = new PIXI.Text('', {
                fontFamily: 'Arial',
                fontSize: 10,
                fill: 0xff1010,
                align: 'center',
            });
            var disLineText = new PIXI.Text('', {
                fontFamily: 'Arial',
                fontSize: 10,
                fill: 0x11f122,
                align: 'center',
            });
            var aShiCirle = new PIXI.Graphics();
            ship.aShiCirles.push(aShiCirle);
            ship.lineTexts.push(lineText);
            ship.disLineTexts.push(disLineText);
            ship.disLines.push(disLine);
            ship.lines.push(line);
            pixiContainer.addChild(disLine, disLineText, line, lineText, aShiCirle);
        }
        ship.disPointOne = new PIXI.Graphics();
        ship.disPointTwo = new PIXI.Graphics();

        pixiContainer.addChild(ship.triangle, ship.ftLine, ship.disPointOne, ship.disPointTwo);
    })


    const doubleBuffering = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;



    return L.pixiOverlay((utils) => {
        mapUtils = utils;
        const zoom = utils.getMap().getZoom();
        const container = utils.getContainer();
        const renderer = utils.getRenderer();
        const project = utils.latLngToLayerPoint;
        const unproject = utils.layerPointToLatLng;
        const scale = utils.getScale();

        function draw(prevShip, ship, bIndex) {
            ship.lines[bIndex].clear();

            ship.lines[bIndex].lineStyle(1 / scale, 0x669920, 1);
            ship.lines[bIndex].moveTo(prevShip.triangle.x, prevShip.triangle.y);
            ship.lines[bIndex].lineTo(ship.triangle.x, ship.triangle.y);
            ship.lines[bIndex].endFill();
            ship.lineTexts[bIndex].scale.set(1 / scale);
            ship.disLineTexts[bIndex].scale.set(1 / scale);



            ship.aShiCirles[bIndex].clear();
            if (showAshiCirle) {
                const apolloniusCircle = calculateApolloniusCircle(prevShip.fromPixel.x, prevShip.fromPixel.y, ship.fromPixel.x, ship.fromPixel.y, prevShip.info.speed / ship.info.speed);
                ship.aShiCirles[bIndex].lineStyle(1 / scale, 0xff0000, 1);
                ship.aShiCirles[bIndex].x = apolloniusCircle.centerX;
                ship.aShiCirles[bIndex].y = apolloniusCircle.centerY;
                ship.aShiCirles[bIndex].drawCircle(0, 0, apolloniusCircle.radius);
            }

            var pSpeedV = generateSpeedVector(prevShip.info.speed, prevShip.info.angle);
            var sSpeedV = generateSpeedVector(ship.info.speed, ship.info.angle);
            var position1 = [prevShip.fromPixel.x, prevShip.fromPixel.y, 0];
            var velocity1 = pSpeedV;

            var position2 = [ship.fromPixel.x, ship.fromPixel.y, 0];
            var velocity2 = sSpeedV;
            var tcpa = motionpredict.calcCPATime(position1, velocity1, position2, velocity2);


            var p1 = motionpredict.getPositionByVeloAndTime(position1, velocity1, tcpa)

            var p2 = motionpredict.getPositionByVeloAndTime(position2, velocity2, tcpa)
            var p3 = unproject({ x: p1[0], y: p1[1] }).distanceTo(unproject({ x: p2[0], y: p2[1] }));

            ship.disLines[bIndex].clear();
            ship.disLines[bIndex].lineStyle(1 / scale, 0x11f122, 1);
            ship.disLines[bIndex].moveTo(p1[0], p1[1]);
            ship.disLines[bIndex].lineTo(p2[0], p2[1]);
            ship.disLines[bIndex].endFill();


            ship.disLineTexts[bIndex].text = `${p3.toFixed(0)}m-${tcpa.toFixed(2)}s`;
            ship.disLineTexts[bIndex].x = (p1[0] + p2[0]) / 2;
            ship.disLineTexts[bIndex].y = (p1[1] + p2[1]) / 2;
            ship.disLineTexts[bIndex].zIndex = 1;

        }

        if (prevZoom !== zoom) {
            const getRenderer = utils.getRenderer;
            const boundary = new PIXI.EventBoundary(container);

            ships.forEach((ship, index) => {
                ship.projectedPolygon = ship.polygonLatLngs.map((coords) => project(coords));
                ship.fromPixel = project(ship.from);
                ship.toPixel = project(ship.to);
                ship.minDisRadius = disToPixeldistance(ship.info.top * 2 + ship.info.bottom * 2 + ship.info.speed / 3600 * 10);
                ship.maxDisRadius = disToPixeldistance(ship.info.maxDis);
                ship.centerRadius = disToPixeldistance(4);
                ship.triangle.clear();
                ship.triangle.lineStyle(1 / scale, 0x3388ff, 1);
                ship.triangle.x = ship.fromPixel.x;
                ship.triangle.y = ship.fromPixel.y;


                ship.projectedPolygon.forEach((coords, index) => {
                    if (index == 0) ship.triangle.moveTo(coords.x - ship.fromPixel.x, coords.y - ship.fromPixel.y);
                    else ship.triangle.lineTo(coords.x - ship.fromPixel.x, coords.y - ship.fromPixel.y);
                });
                ship.triangle.drawCircle(0, 0, ship.minDisRadius / scale)
                //ship.triangle.drawCircle(0, 0, ship.maxDisRadius / scale)

                ship.ftLine.clear();
                if (showTraceLine) {
                    ship.ftLine.lineStyle(1 / scale, 0xfff122, 1);
                    ship.ftLine.moveTo(ship.fromPixel.x, ship.fromPixel.y);
                    ship.ftLine.lineTo(ship.toPixel.x, ship.toPixel.y);
                    ship.ftLine.endFill();
                }

                // if (index == 0) return;
                // let prevShip = ships[index - 1];
                // draw(prevShip, ship);
                // if (index === ships.length - 1) {
                //     prevShip = ships[0]
                //     draw(ship, prevShip);
                // }


            })

            ships.forEach((aShip, aIndex) => {
                ships.forEach((bShip, bIndex) => {
                    if (aIndex !== bIndex) {
                        draw(aShip, bShip, aIndex);
                    }
                })
            })

        }

        firstDraw = false;
        prevZoom = zoom;
        renderer.render(container);
    }, pixiContainer, {
            doubleBuffering: doubleBuffering,
            autoPreventDefault: false
        });
});


const $startBtn = document.querySelector("#startBtn")

$startBtn.onclick = (e) => {
    start = null;
    frame = requestAnimationFrame(animate);
}

const $stopBtn = document.querySelector("#stopBtn")

$stopBtn.onclick = (e) => {
    if (frame) {
        cancelAnimationFrame(frame);
        frame = null;
    }
}

const $showBtn = document.querySelector("#showBtn")

$showBtn.onclick = (e) => {
    showAshiCirle = !showAshiCirle;
    mapUtils.getMap().setZoom(prevZoom + 1);
}

const $showTraceBtn = document.querySelector("#showTraceBtn")

$showTraceBtn.onclick = (e) => {
    showTraceLine = !showTraceLine;
    mapUtils.getMap().setZoom(prevZoom + 1);
}

const $addBtn = document.querySelector("#addBtn")
const $delBtn = document.querySelector("#delBtn")



function getRamdom(min, max) {
    // 生成1到num之间的随机整数
    return parseFloat((Math.random() * (max - min) + min).toFixed(4));;
}

let overlay = pixiOverlay();
overlay.addTo(map);

$addBtn.onclick = (e) => {
    map.removeLayer(overlay);
    if (frame) {
        cancelAnimationFrame(frame);
        frame = null;
    }
    ships = [];
    prevZoom = 0;
    for (var i = 0; i < 1; i++) {
        let test = {
            lat: getRamdom(39.18708, 39.38708),
            lng: getRamdom(121.187, 121.387),
            angle: getRamdom(0, 360),
            top: getRamdom(30, 300),
            left: 20,
            right: 20,
            bottom: 40,
            minDis: 100,
            maxDis: 200,
            speed: getRamdom(22404, 24404) * 1.852
        }
        shipInfos.push(test);
    }
    overlay = pixiOverlay();
    overlay.addTo(map);
}

$delBtn.onclick = (e) => {
    if (frame) {
        cancelAnimationFrame(frame);
        frame = null;
    }
    map.removeLayer(overlay);
    ships = [];
    prevZoom = 0;
    shipInfos.pop();
    overlay = pixiOverlay();
    overlay.addTo(map);
}
<div id="map" style="height: 100%; width: 100%;position:absolute">
</div>

<button id="startBtn" style="position:absolute;z-index:1000;">开始动画</button>
<button id="stopBtn" style="position:absolute;z-index:1000;left:100px">停止动画</button>
<button id="showBtn" style="position:absolute;z-index:1000;left:200px">阿氏圆</button>
<button id="showTraceBtn" style="position:absolute;z-index:1000;left:300px">轨迹线</button>

<button id="addBtn" style="position:absolute;z-index:1000;left:400px">增加船</button>
<button id="delBtn" style="position:absolute;z-index:1000;left:500px">减少船</button>