openLayers 轨迹回放图标转向问题

752 阅读1分钟

2022-4.26.gif 实现原理

使用反正切函数实现计算夹角弧度来实现图标的转向。 在开始计算前图标的默认指向角度也要提前了解,如这个 “<-” 默认为180度,正余弦为sin=0 cos=-1夹角弧度为Math.atan2(0, -1)。知道图标的默认弧度后就可实时的计算转向的弧度了,公式为Math.atan2(y2 - y1, x2 - x1) - Math.atan2(0, -1)。

www.shuxuele.com/sine-cosine… 可以使用这个网站来快速查询正弦、余弦值

源码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.14.1/css/ol.css">
    <style>
        #map {
            width: 1200px;
            height: 500px;
        }
    </style>
</head>
<body>
<div id="map"></div>
<label for="speed">
    speed:&nbsp;
    <input id="speed" type="range" min="10" max="999" step="10" value="60">
</label>
<button id="start-animation">Start Animation</button>
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.14.1/build/ol.js"></script>
<script>
    const center = [-5639523.95, -3501274.52];
    const map = new ol.Map({
        target: document.getElementById('map'),
        view: new ol.View({
            center: center,
            zoom: 10,
            minZoom: 2,
            maxZoom: 19,
        }),
        layers: [
            new ol.layer.Tile({
                name: "OSM",
                source: new ol.source.OSM(),
            }),
            new ol.layer.Tile({
                source: new ol.source.TileDebug({
                    projection: "EPSG:3857",
                    tileGrid: new ol.source.OSM().getTileGrid()
                })
            }),
        ],
        controls: ol.control.defaults().extend([
            new ol.control.MousePosition({
                projection: 'EPSG:3857',
                coordinateFormat: function (coordinate) {
                    // console.log(coordinate)
                    return coordinate
                    // return format(coordinate, '经度:{x} 纬度:{y}', 2);
                },
            })      // 实例化坐标拾取控件,并加入地图
        ])
    });

    fetch('https://openlayers.org/en/latest/examples/data/polyline/route.json').then(function (response) {
        response.json().then(function (result) {
            const polyline = result.routes[0].geometry;

            const route = new ol.format.Polyline({
                factor: 1e6,
            }).readGeometry(polyline, {
                dataProjection: 'EPSG:4326',
                featureProjection: 'EPSG:3857',
            });

            const routeFeature = new ol.Feature({
                type: 'route',
                geometry: route,
            });
            const startMarker = new ol.Feature({
                type: 'icon',
                geometry: new ol.geom.Point(route.getFirstCoordinate()),
            });
            const endMarker = new ol.Feature({
                type: 'icon',
                geometry: new ol.geom.Point(route.getLastCoordinate()),
            });

            const position = startMarker.getGeometry().clone();

            const geoMarker = new ol.Feature({
                type: 'geoMarker',
                geometry: position,
            });
            // console.log(route.getFirstCoordinate())
            const text = new ol.Feature({
                // geometry: new ol.geom.Point([-5655881.474053027, -3511517.0817902135]),
                geometry: position,
                type: 'text',
            });
            let test = position.getFirstCoordinate()

            // 初始样式设置
            const styles = {
                'route': new ol.style.Style({
                    stroke: new ol.style.Stroke({
                        width: 6,
                        color: [237, 212, 0, 0.8],
                    }),
                }),
                'icon': new ol.style.Style({
                    image: new ol.style.Icon({
                        anchor: [0.5, 1],
                        src: 'https://openlayers.org/en/latest/examples/data/icon.png',
                    }),
                }),
                text: new ol.style.Style({
                    text: new ol.style.Text({
                        text: '->',
                        // rotateWithView: true,
                        rotation: Math.atan2(test[0] - test[1], test[1] - test[0]),
                        font: 'normal 20px 微软雅黑',
                        fill: new ol.style.Fill({
                            color: 'red'
                        })
                    })
                })
            };

            const vectorLayer = new ol.layer.Vector({
                source: new ol.source.Vector({
                    features: [routeFeature, geoMarker, startMarker, endMarker, text],
                }),
                style: function (feature) {
                    return styles[feature.get('type')];
                },
            });

            map.addLayer(vectorLayer);

            const speedInput = document.getElementById('speed');
            const startButton = document.getElementById('start-animation');
            let animating = false;
            let distance = 0;
            let lastTime;
            let oldP = []
            let rotation = 0

            function moveFeature(event) {
                const speed = Number(speedInput.value);
                const time = event.frameState.time;
                const elapsedTime = time - lastTime;
                distance = (distance + (speed * elapsedTime) / 1e6) % 2;
                lastTime = time;

                const currentCoordinate = route.getCoordinateAt(
                    distance > 1 ? 2 - distance : distance
                );

                position.setCoordinates(currentCoordinate);
                const vectorContext = new ol.render.getVectorContext(event);

                /**
                 * 使用反正切函数实现计算夹角弧度来实现图标的转向。
                 * 在开始计算前图标的默认指向角度也要提前了解
                 * 如这个 “<-” 默认为180度,正余弦为sin=0 cos=-1夹角弧度为Math.atan2(0, -1)。知道图标的默认弧度后就可实时的计算转向的弧度了
                 * 公式为Math.atan2(y2 - y1, x2 - x1) - Math.atan2(0, -1)
                 **/
                if (oldP.length) {
                    rotation = Math.atan2(0, 1) - Math.atan2(currentCoordinate[1] - oldP[1], currentCoordinate[0] - oldP[0])
                }

                vectorContext.setStyle(new ol.style.Style({
                    text: new ol.style.Text({
                        text: '->',
                        // rotateWithView: true,
                        rotation: rotation,
                        font: 'normal 20px 微软雅黑',
                        fill: new ol.style.Fill({
                            color: 'red'
                        })
                    })
                }));
                vectorContext.drawGeometry(position);

                oldP = currentCoordinate

                map.render();
            }

            function startAnimation() {
                animating = true;
                lastTime = Date.now();
                startButton.textContent = 'Stop Animation';
                vectorLayer.on('postrender', moveFeature);
                text.setGeometry(null);
            }

            function stopAnimation() {
                animating = false;
                startButton.textContent = 'Start Animation';

                text.setGeometry(position);
                vectorLayer.un('postrender', moveFeature);
            }

            startButton.addEventListener('click', function () {
                if (animating) {
                    stopAnimation();
                } else {
                    startAnimation();
                }
            });
        });
    });

</script>
</body>
</html>