当前位置 : 主页 > 网络编程 > JavaScript >

vue利用openlayers实现动态轨迹

来源:互联网 收集:自由互联 发布时间:2023-02-01
目录 实现效果 创建一个地图容器 引入地图相关对象 创建地图对象 创建一条线路 画一条线路 添加起、终点 添加小车 准备开车 完整代码 实现效果 今天介绍一个有趣的gis小功能:动态
目录
  • 实现效果
  • 创建一个地图容器
    • 引入地图相关对象
    • 创建地图对象
  • 创建一条线路
    • 画一条线路
    • 添加起、终点
    • 添加小车
  • 准备开车
    • 完整代码

      实现效果

      今天介绍一个有趣的gis小功能:动态轨迹播放!效果就像这样:

      这效果看着还很丝滑!别急,接下来教你怎么实现。代码示例基于parcel打包工具和es6语法,本文假设你已经掌握相关知识和技巧。

      gis初学者可能对openlayers(后面简称ol)不熟悉,这里暂时不介绍ol了,直接上代码,先体验下感觉。

      创建一个地图容器

      引入地图相关对象

      import Map from 'ol/Map';
      import View from 'ol/View';
      import XYZ from 'ol/source/XYZ';
      import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
      

      创建地图对象

      const center = [-5639523.95, -3501274.52];
      const map = new Map({
        target: document.getElementById('map'),
        view: new View({
          center: center,
          zoom: 10,
          minZoom: 2,
          maxZoom: 19,
        }),
        layers: [
          new TileLayer({
            source: new XYZ({
              attributions: attributions,
              url: 'https://api.maptiler.com/maps/hybrid/{z}/{x}/{y}.jpg?key=' + key,
              tileSize: 512,
            }),
          }),
        ],
      });
      

      创建一条线路

      画一条线路

      可以用这个geojson网站随意画一条线,然后把数据内容复制下来,保存为json文件格式,作为图层数据添加到地图容器中。

      你可以用异步加载的方式,也可以用require方式,这里都介绍下吧:

      // fetch
      fetch('data/route.json').then(function (response) {
        response.json().then(function (result) {
          const polyline = result.routes[0].geometry;
        }),
      };
      // require
      var roadData = require('data/route.json')
      

      后面基本一样了,就以fetch为准,现在把线路加载的剩余部分补充完整:

      fetch('data/route.json').then(function (response) {
        response.json().then(function (result) {
          const polyline = result.routes[0].geometry;
      	// 线路数据坐标系转换
          const route = new Polyline({
            factor: 1e6,
          }).readGeometry(polyline, {
            dataProjection: 'EPSG:4326',
            featureProjection: 'EPSG:3857',
          });
      	// 线路图层要素
          const routeFeature = new Feature({
            type: 'route',
            geometry: route,
          });
          // 起点要素
          const startMarker = new Feature({
            type: 'icon',
            geometry: new Point(route.getFirstCoordinate()),
          });
          // 终点要素
          const endMarker = new Feature({
            type: 'icon',
            geometry: new Point(route.getLastCoordinate()),
          });
          // 取起点值
          const position = startMarker.getGeometry().clone();
          // 游标要素
          const geoMarker = new Feature({
            type: 'geoMarker',
            geometry: position,
          });
      	// 样式组合
          const styles = {
              // 路线
            'route': new Style({
              stroke: new Stroke({
                width: 6,
                color: [237, 212, 0, 0.8],
              }),
            }),
            'icon': new Style({
              image: new Icon({
                anchor: [0.5, 1],
                src: 'data/icon.png',
              }),
            }),
            'geoMarker': new Style({
              image: new CircleStyle({
                radius: 7,
                fill: new Fill({color: 'black'}),
                stroke: new Stroke({
                  color: 'white',
                  width: 2,
                }),
              }),
            }),
          };
      	// 创建图层并添加以上要素集合
          const vectorLayer = new VectorLayer({
            source: new VectorSource({
              features: [routeFeature, geoMarker, startMarker, endMarker],
            }),
            style: function (feature) {
              return styles[feature.get('type')];
            },
          });
      	// 在地图容器中添加图层
          map.addLayer(vectorLayer);
      

      以上代码很完整,我加了注释,整体思路总结如下:

      • 先加载路线数据
      • 构造路线、起始点及游标对应图层要素对象
      • 构造图层并把要素添加进去
      • 在地图容器中添加图层

      添加起、终点

      这个上面的代码已经包括了,我这里列出来是为了让你更清晰,就是startMarkerendMarker对应的代码。

      添加小车

      同样的,这里的代码在上面也写过了,就是geoMarker所对应的代码。

      准备开车

      线路有了,车也有了,现在就到了激动人心的开车时刻了,接下来才是本文最核心的代码!

      const speedInput = document.getElementById('speed');
          const startButton = document.getElementById('start-animation');
          let animating = false;
          let distance = 0;
          let lastTime;
          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 = getVectorContext(event);
            vectorContext.setStyle(styles.geoMarker);
            vectorContext.drawGeometry(position);
            map.render();
          }
          function startAnimation() {
            animating = true;
            lastTime = Date.now();
            startButton.textContent = 'Stop Animation';
            vectorLayer.on('postrender', moveFeature);
            // 隐藏小车前一刻位置同时触发事件
            geoMarker.setGeometry(null);
          }
          function stopAnimation() {
            animating = false;
            startButton.textContent = '开车了';
            // 将小车固定在当前位置
            geoMarker.setGeometry(position);
            vectorLayer.un('postrender', moveFeature);
          }
          startButton.addEventListener('click', function () {
            if (animating) {
              stopAnimation();
            } else {
              startAnimation();
            }
          });
      

      简单说下它的原理就是利用postrender事件触发一个函数,这个事件本来是地图渲染结束事件,但是它的回调函数中,小车的坐标位置一直在变,那就会不停地触发地图渲染,当然最终也会触发postrender。这样就实现的小车沿着轨迹的动画效果了。这段代码有点难理解,最好自己尝试体验下,比较难理解部分我都加上了注释。

      好了,ol动态巡查已经介绍完了,动手试下吧!看你的车能否开起来?

      完整代码

      index.html

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8">
          <title>Marker Animation</title>
          <!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
          <script src="https://unpkg.com/elm-pep"></script>
          <style>
            .map {
              width: 100%;
              height:400px;
            }
          </style>
        </head>
        <body>
          <div id="map" class="map"></div>
          <label for="speed">
            speed: 
            <input id="speed" type="range" min="10" max="999" step="10" value="60">
          </label>
          <button id="start-animation">Start Animation</button>
          <script src="main.js"></script>
        </body>
      </html>
      

      main.js

      import 'ol/ol.css';
      import Feature from 'ol/Feature';
      import Map from 'ol/Map';
      import Point from 'ol/geom/Point';
      import Polyline from 'ol/format/Polyline';
      import VectorSource from 'ol/source/Vector';
      import View from 'ol/View';
      import XYZ from 'ol/source/XYZ';
      import {
        Circle as CircleStyle,
        Fill,
        Icon,
        Stroke,
        Style,
      } from 'ol/style';
      import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
      import {getVectorContext} from 'ol/render';
      const key = 'Get your own API key at https://www.maptiler.com/cloud/';
      const attributions =
        '<a href="https://www.maptiler.com/copyright/" rel="external nofollow"  target="_blank">&copy; MapTiler</a> ' +
        '<a href="https://www.openstreetmap.org/copyright" rel="external nofollow"  target="_blank">&copy; OpenStreetMap contributors</a>';
      const center = [-5639523.95, -3501274.52];
      const map = new Map({
        target: document.getElementById('map'),
        view: new View({
          center: center,
          zoom: 10,
          minZoom: 2,
          maxZoom: 19,
        }),
        layers: [
          new TileLayer({
            source: new XYZ({
              attributions: attributions,
              url: 'https://api.maptiler.com/maps/hybrid/{z}/{x}/{y}.jpg?key=' + key,
              tileSize: 512,
            }),
          }),
        ],
      });
      // The polyline string is read from a JSON similiar to those returned
      // by directions APIs such as Openrouteservice and Mapbox.
      fetch('data/polyline/route.json').then(function (response) {
        response.json().then(function (result) {
          const polyline = result.routes[0].geometry;
          const route = new Polyline({
            factor: 1e6,
          }).readGeometry(polyline, {
            dataProjection: 'EPSG:4326',
            featureProjection: 'EPSG:3857',
          });
          const routeFeature = new Feature({
            type: 'route',
            geometry: route,
          });
          const startMarker = new Feature({
            type: 'icon',
            geometry: new Point(route.getFirstCoordinate()),
          });
          const endMarker = new Feature({
            type: 'icon',
            geometry: new Point(route.getLastCoordinate()),
          });
          const position = startMarker.getGeometry().clone();
          const geoMarker = new Feature({
            type: 'geoMarker',
            geometry: position,
          });
          const styles = {
            'route': new Style({
              stroke: new Stroke({
                width: 6,
                color: [237, 212, 0, 0.8],
              }),
            }),
            'icon': new Style({
              image: new Icon({
                anchor: [0.5, 1],
                src: 'data/icon.png',
              }),
            }),
            'geoMarker': new Style({
              image: new CircleStyle({
                radius: 7,
                fill: new Fill({color: 'black'}),
                stroke: new Stroke({
                  color: 'white',
                  width: 2,
                }),
              }),
            }),
          };
          const vectorLayer = new VectorLayer({
            source: new VectorSource({
              features: [routeFeature, geoMarker, startMarker, endMarker],
            }),
            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;
          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 = getVectorContext(event);
            vectorContext.setStyle(styles.geoMarker);
            vectorContext.drawGeometry(position);
            // tell OpenLayers to continue the postrender animation
            map.render();
          }
          function startAnimation() {
            animating = true;
            lastTime = Date.now();
            startButton.textContent = 'Stop Animation';
            vectorLayer.on('postrender', moveFeature);
            geoMarker.setGeometry(null);
          }
          function stopAnimation() {
            animating = false;
            startButton.textContent = '开车了';
            geoMarker.setGeometry(position);
            vectorLayer.un('postrender', moveFeature);
          }
          startButton.addEventListener('click', function () {
            if (animating) {
              stopAnimation();
            } else {
              startAnimation();
            }
          });
        });
      });
      

      package.json

      {
        "name": "feature-move-animation",
        "dependencies": {
          "ol": "6.9.0"
        },
        "devDependencies": {
          "parcel": "^2.0.0-beta.1"
        },
        "scripts": {
          "start": "parcel index.html",
          "build": "parcel build --public-url . index.html"
        }
      }
      

      参考资源:

      https://openlayers.org/en/latest/examples/feature-move-animation.html

      以上就是vue利用openlayers实现动态轨迹的详细内容,更多关于vue openlayers动态轨迹的资料请关注自由互联其它相关文章!

      网友评论