vue利用openlayers实现动态轨迹

目录
  • 实现效果
  • 创建一个地图容器
    • 引入地图相关对象
    • 创建地图对象
  • 创建一条线路
    • 画一条线路
    • 添加起、终点
    • 添加小车
  • 准备开车
  • 完整代码

实现效果

今天介绍一个有趣的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动态轨迹的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue使用天地图、openlayers实现多个底图叠加显示效果

    实现效果: 需求:根据返回的经纬度列表通过天地图.openlayers实现底图添加(航道图层.线图层.水深图层) tk:自己申请的密钥 安装opelayers cnpm i -S ol #或者 npm install ol <script> // openlayers地图 import "ol/ol.css"; import { Icon, Style, Stroke } from "ol/style"; import 'ol/ol.css' impor

  • vue+openlayers绘制省市边界线

    本文实例为大家分享了vue+openlayers绘制省市边界线的具体代码,供大家参考,具体内容如下 1.创建项目 vue init webpack ol_vue 2.安装ol依赖包 npm install ol 3.引入axios npm install axios --save 文件目录:src/main.js import Vue from 'vue' import router from './router' import App from './App' import axios fro

  • Vue+Openlayers自定义轨迹动画

    本文实例为大家分享了Vue+Openlayers实现轨迹动画的具体代码,供大家参考,具体内容如下 <template> <div class="map-warp"> <h3> <a href="https://openlayers.org/en/latest/examples/feature-move-animation.html?q=polyline" target="_bank" >Openla

  • vue-openlayers实现地图坐标弹框效果

    本文实例为大家分享了vue-openlayers实现地图坐标弹框的具体代码,供大家参考,具体内容如下 openlayers 这个效果是点击地图,弹出坐标信息. 点击地图边缘时,底图会跟着移动,使弹窗能完整显示出来. <template> <div class="vm"> <h2 class="h-title">弹窗 popup</h2> <div id="map" class="ma

  • vue利用openlayers加载天地图和高德地图

    目录 一.天地图部分 1.在vue中安装openlayers 二.高德地图部分 一.天地图部分 1.在vue中安装openlayers npm i --save ol 这里说的vue是基于脚手架构建的. 新建个页面,也就是vue文件,配置好路由.接着就是可以直接放入我的代码运行显示了. <template> <div class="wrapper"> <div>天地图</div> <div class="map"

  • vue openlayers实现台风轨迹示例详解

    目录 功能描述 创建一个地图容器 引入地图相关对象 创建地图对象 开始绘制 准备台风数据和图层 绘制台风名称 绘制台风轨迹点和轨迹线 添加台风风圈动画 让台风轨迹动起来 结尾 功能描述 台风轨迹点实时绘制,根据不同点的类型绘制不同的轨迹点颜色 轨迹线绘制,涉及实时轨迹线段与预报轨迹线,根据台风类型绘制成不同颜色 当前正在发生的台风还需增加当前台风所风圈位置 台风轨迹点点击弹框显示轨迹点信息 openlayers(简称ol)这里不做介绍,刚开始写此类文章,直接上代码 创建一个地图容器 引入地图相关

  • vue利用openlayers实现动态轨迹

    目录 实现效果 创建一个地图容器 引入地图相关对象 创建地图对象 创建一条线路 画一条线路 添加起.终点 添加小车 准备开车 完整代码 实现效果 今天介绍一个有趣的gis小功能:动态轨迹播放!效果就像这样: 这效果看着还很丝滑!别急,接下来教你怎么实现.代码示例基于parcel打包工具和es6语法,本文假设你已经掌握相关知识和技巧. gis初学者可能对openlayers(后面简称ol)不熟悉,这里暂时不介绍ol了,直接上代码,先体验下感觉. 创建一个地图容器 引入地图相关对象 import M

  • Vue利用openlayers实现点击弹窗的方法详解

    目录 解释 编写弹窗 引入 openlayer使用弹窗组件 点击事件 这个写的稍微简单一点就行了,其实呢,这个不是很难,主要是知道原理就可以了. 我想实现的内容是什么意思呢?就是说页面上有很多坐标点,点击坐标点的时候在相应的位置弹出一个框,然后框里显示出这个坐标点的相关数据. 解释 这个内容的其实就是添加一个弹窗图层,然后在点击的时候让他显示出来罢了. 编写弹窗 首先一点,我们这个弹窗需要自己写一下,具体的样式,展示的内容之类的,所以说写一个弹窗组件,然后在openlayer文件中引用加载. 比

  • vue里面v-bind和Props 利用props绑定动态数据的方法

    如下所示: <add v-bind:子组件的值="父组件的属性"></add> <div id="app"> <add v-bind:btn="h"></add> </div> <script> var vm = new Vue({ el: '#app', data: { h: "hello" }, components: { "ad

  • 在vue中使用Echarts利用watch做动态数据渲染操作

    依旧直接上代码- 首先安装引入Echarts,我是直接把Echarts挂到VUE全局变量上了 //引入echarts import Vue from 'vue'; import echarts from 'echarts'; Vue.prototype.$echarts = echarts; <template> <div class="demo-container"> <div ref="chart_wrap" class="

  • 聊聊Vue 中 title 的动态修改问题

    由于之前的 Vue 项目打包成果物一直是嵌入集成平台中,所以一直没有关注过项目的 title.直到最近,突然有个需求,要求点击按钮在集成平台外新开一个页面,此时我才发现,原来我的项目的 title 一直是万年不变的 vue-project.理所应当的,这个问题被测试爸爸提了一个大大的缺陷. 犯了错的我赶紧解决这个问题,但是经过一段时间的摸索,我却发现,这一个小小的问题,却有着很多不同的解法. 首先,毫无疑问的是,我们应该使用 document.title 方法通过 DOM 操作来修改 title

  • vue 实现拖拽动态生成组件的需求

    产品需求 开完产品需求会议,遇到了一个需求,首先页面分成两栏布局,左侧展示数据组件,支持拖拽排序,点击按钮清除组件.右侧支持将组件的缩略图拖拽至左侧生成一个新的组件. 思路 对于动态生成组件来说每一次都要是生成全新的一个组件,那么就可以把 组件放进函数当中 return.在JSX中调用函数,每次调用函数都会返回一个全新的组件.这对React来说非常简单,但是对于Vue来说,直接将组件返回是不可能的.尽管这个 return 写法不适合Vue,但是我们不可否认,思路是非常正确的,所以我们应该考虑一个

  • vue利用vue meta info设置每个页面的title与meta信息

    title: vue 使用 vue-meta-info 设置每个页面的 title 和 meta 信息 #文章页面上的显示名称,一般是中文 date: 2019-11-20 16:30:16 #文章生成时间,一般不改,当然也可以任意修改 categories: vue #分类 tags: [vue] #文章标签,可空,多标签请用格式,注意:后面有个空格 description: vue 使用 vue-meta-info 设置每个页面的 title 和 meta 信息 如需使用 vue-meta-

  • Vue结合openlayers按照经纬度坐标实现锚地标记及绘制多边形区域

    目录 前言 1.安装openlayers 2.引入模块 3.地图与弹窗html样式 4.data数据定义 5.methods方法 6.mounted数据加载 7.锚地数据获取 前言 本文介绍vue结合openlayers实现根据返回的经纬度坐标完成锚地标记.绘制多边形区域: 注意点: 1.根据返回的经纬度取第一个坐标作为锚地图标位置: 2.根据返回的经纬度坐标数据,这里的后台数据需要处理(根据返回的数据处理成需要的格式),得到坐标数组渲染绘制区域画图显示在航道图层上. 3.关于数据渲染的问题:

  • Vue利用路由钩子token过期后跳转到登录页的实例

    在Vue2.0中的路由钩子主要是用来拦截导航,让它完成跳转或前取消,可以理解为路由守卫. 分为全局导航钩子,单个路由独享的钩子,组件内钩子. 三种 类型的钩子只是用的地方不一样,都接受一个函数作为参数,函数传入三个参数,分别为to,from,next. 其中next有三个方法 (1)next(); //默认路由 (2)next(false); //阻止路由跳转 (3)next({path:'/'}); //阻止默认路由,跳转到指定路径 这里我使用了组件内钩子进行判断token过期后跳转到登录页,

随机推荐