目录

实现效果

今天介绍一个有趣的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方式,这里都介绍下吧:

// fetchfetch('data/route.json').then(function (response) {  response.json().then(function (result) {    const polyline = result.routes[0].geometry;  }),};// requirevar 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

          Marker Animation                  .map {        width: 100%;        height:400px;      }                          

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 =  '© MapTiler ' +  '© OpenStreetMap contributors';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动态轨迹的资料请关注脚本之家其它相关文章!