当前位置: 首页 > news >正文

四十二、openlayers官网示例Flight Animation扩展——在地图上绘制飞机航线、飞机随航线飞行效果

 

上篇在地图上绘制了动态的飞机航线,于是我想着,能不能加个飞机的图标跟着航线飞行。

在iconfont上下载一个飞机的svg图形,放在public的data/icons下面 

因为图标需要随着航线的方向飞行,需要根据航线调整角度,因此在加载数据源的时候需要计算一下角度,绑在每个feature上。

//计算角度
const rotation = calculateRotation(from, to);features.push(new Feature({geometry: line,finished: false,rotation: rotation,}));function calculateRotation(from, to) {const dx = to[0] - from[0];const dy = to[1] - from[1];return Math.atan2(dy, dx);}

在航线绘制完成之后,添加一个飞机动画开始执行的标识flight,给feature设置一个初始的index值

if (elapsedPoints >= coords.length) {feature.set("finished", true);feature.set("flight", true);feature.set("index", 0);}

animateFlights会一直执行,所以我们利用这个特点来绘制循环的动画。绘制线的思路是取坐标数组的第0个到第n个,每毫秒绘制不同的线。绘制点的思路则是直接取第n个点,每毫秒绘制不同的点,并且在n大于等于坐标数组之后又让n重新等于0,以此来实现循环的动画。

 if (feature.get("flight")) {const frameState = event.frameState;const coords = feature.getGeometry().getCoordinates();let index = feature.get("index");index += step;if (index >= coords.length - 1) {index = 0;}if (index < 0) {index = 0;}feature.set("index", index);style.getImage().setRotation(feature.get("rotation"));vectorContext.setStyle(style);const currentPoint = new Point(coords[Math.floor(index)]);// 在当前和最近相邻的包裹世界中需要动画const worldWidth = getWidth(map.getView().getProjection().getExtent());const offset = Math.floor(map.getView().getCenter()[0] / worldWidth);//直接用矢量上下文绘制线条//在平铺地图上绘制线段时,需要考虑地图的无限水平滚动特性。通过平移和多次绘制线段,确保即使用户滚动地图,线段也能正确显示在地图的两端。这个方法处理了跨越地图边界的线段,避免了图形被截断的问题。currentPoint.translate(offset * worldWidth, 0);vectorContext.drawGeometry(currentPoint);currentPoint.translate(worldWidth, 0);vectorContext.drawGeometry(currentPoint);}

完整代码:

<template><div class="box"><h1>External map</h1><div id="map"></div></div>
</template><script>
import Feature from "ol/Feature.js";
import { LineString, Point, Polygon } from "ol/geom.js";
import Map from "ol/Map.js";
import StadiaMaps from "ol/source/StadiaMaps.js";
import VectorSource from "ol/source/Vector.js";
import View from "ol/View.js";
import { Stroke, Style, Icon, Circle as CircleStyle, Fill } from "ol/style.js";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
import { getVectorContext } from "ol/render.js";
import { getWidth } from "ol/extent.js";
var arc = require("arc");
export default {name: "",components: {},data() {return {map: null,extentData: "",};},computed: {},created() {},mounted() {const tileLayer = new TileLayer({source: new StadiaMaps({layer: "outdoors",}),});const map = new Map({layers: [tileLayer],target: "map",view: new View({center: [-11000000, 4600000],zoom: 2,}),});const style = new Style({stroke: new Stroke({color: "#EAE911",width: 2,}),image: new Icon({anchor: [0.5, 0.5],src: "data/icons/flight.svg",rotation: -0.19931501061749937,}),});const flightsSource = new VectorSource({attributions:"Flight data by " +'<a href="https://openflights.org/data.html">OpenFlights</a>,',loader: function () {const url ="https://openlayers.org/en/latest/examples/data/openflights/flights.json";fetch(url).then(function (response) {return response.json();}).then(function (json) {let flightsData = json.flights;flightsData = flightsData.splice(26, 100);for (let i = 0; i < flightsData.length; i++) {const flight = flightsData[i];const from = flight[0];const to = flight[1];// 在两个位置之间创建一个圆弧const arcGenerator = new arc.GreatCircle({ x: from[1], y: from[0] },{ x: to[1], y: to[0] });//生成100个点 offset是偏移量const arcLine = arcGenerator.Arc(100, { offset: 10 });//计算角度const rotation = calculateRotation(from, to);//穿过-180°/+180°子午线的路径是分开的//分成两个部分,按顺序设置动画const features = [];arcLine.geometries.forEach(function (geometry) {const line = new LineString(geometry.coords);//将 line 对象的坐标系从 WGS84(EPSG:4326)转换为 Web Mercator 投影(EPSG:3857)line.transform("EPSG:4326", "EPSG:3857");features.push(new Feature({geometry: line,finished: false,rotation: rotation,}));});// 动画延迟:使用i * 50来设置延迟是为了确保每条路径的动画不会同时启动,这样可以产生连续动画的效果。addLater(features, i * 50);}//tileLayer 图层每次完成渲染之后调用tileLayer.on("postrender", animateFlights);});},});let flag = false;const flightsLayer = new VectorLayer({source: flightsSource,style: function (feature) {//等动画完毕再现在最终的线样式if (feature.get("finished")) {return [new Style({stroke: new Stroke({color: "#EAE911",width: 2,}),}),new Style({image: new Icon({anchor: [0.5, 0.5],src: "data/icons/flight.svg",rotation: feature.get("rotation"),}),}),];}return null;},});map.addLayer(flightsLayer);const pointsPerMs = 0.05;let duration = 2000;let step = 0.5;function animateFlights(event) {const vectorContext = getVectorContext(event);const frameState = event.frameState;vectorContext.setStyle(style);const features = flightsSource.getFeatures();for (let i = 0; i < features.length; i++) {const feature = features[i];if (!feature.get("finished")) {// 只画动画尚未完成的线const coords = feature.getGeometry().getCoordinates();const elapsedTime = frameState.time - feature.get("start");if (elapsedTime >= 0) {const elapsedPoints = elapsedTime * pointsPerMs;if (elapsedPoints >= coords.length) {feature.set("finished", true);feature.set("flight", true);feature.set("index", 0);}const maxIndex = Math.min(elapsedPoints, coords.length);const currentLine = new LineString(coords.slice(0, maxIndex));// 在当前和最近相邻的包裹世界中需要动画const worldWidth = getWidth(map.getView().getProjection().getExtent());const offset = Math.floor(map.getView().getCenter()[0] / worldWidth);//直接用矢量上下文绘制线条//在平铺地图上绘制线段时,需要考虑地图的无限水平滚动特性。通过平移和多次绘制线段,确保即使用户滚动地图,线段也能正确显示在地图的两端。这个方法处理了跨越地图边界的线段,避免了图形被截断的问题。currentLine.translate(offset * worldWidth, 0);vectorContext.drawGeometry(currentLine);currentLine.translate(worldWidth, 0);vectorContext.drawGeometry(currentLine);}}if (feature.get("flight")) {const frameState = event.frameState;const coords = feature.getGeometry().getCoordinates();let index = feature.get("index");index += step;if (index >= coords.length - 1) {index = 0;}if (index < 0) {index = 0;}feature.set("index", index);style.getImage().setRotation(feature.get("rotation"));vectorContext.setStyle(style);const currentPoint = new Point(coords[Math.floor(index)]);// 在当前和最近相邻的包裹世界中需要动画const worldWidth = getWidth(map.getView().getProjection().getExtent());const offset = Math.floor(map.getView().getCenter()[0] / worldWidth);//直接用矢量上下文绘制线条//在平铺地图上绘制线段时,需要考虑地图的无限水平滚动特性。通过平移和多次绘制线段,确保即使用户滚动地图,线段也能正确显示在地图的两端。这个方法处理了跨越地图边界的线段,避免了图形被截断的问题。currentPoint.translate(offset * worldWidth, 0);vectorContext.drawGeometry(currentPoint);currentPoint.translate(worldWidth, 0);vectorContext.drawGeometry(currentPoint);}}//告诉OpenLayers继续动画map.render();}function addLater(features, timeout) {window.setTimeout(function () {let start = Date.now();features.forEach(function (feature) {feature.set("start", start);flightsSource.addFeature(feature);const duration =(feature.getGeometry().getCoordinates().length - 1) / pointsPerMs;start += duration;});}, timeout);}function calculateRotation(from, to) {const dx = to[0] - from[0];const dy = to[1] - from[1];return Math.atan2(dy, dx);}},methods: {},
};
</script><style lang="scss" scoped>
#map {width: 100%;height: 500px;
}
.box {height: 100%;
}</style>

相关文章:

  • 【C++进阶】深入STL之list:模拟实现深入理解List与迭代器
  • 【高频】什么是索引的下推和覆盖
  • 什么是Docker ?
  • Oracle作业调度器Job Scheduler
  • 【纯血鸿蒙】——响应式布局如何实现?
  • C++多线程同步总结
  • 工具-金舟投屏软件: 手机如何投屏到电脑上 / Wi-Fi / USB
  • Linux--进程间通信(system V共享内存)
  • Sentinel与Nacos强强联合,构建微服务稳定性基石的重要实践
  • springCloud中将redis共用到common模块
  • Linux通过安装包配置环境变量(详细教程)
  • 使用Aspose技术将Excel/Word转换为PDF
  • 用Python实现奇怪的疯狂按键需求
  • 风机5G智能制造工厂工业物联数字孪生平台,推进制造业数字化转型
  • 每日一题——Python实现PAT乙级1037 在霍格沃茨找零钱(举一反三+思想解读+逐步优化)
  • Android开源项目规范总结
  • ES学习笔记(10)--ES6中的函数和数组补漏
  • FineReport中如何实现自动滚屏效果
  • JAVA SE 6 GC调优笔记
  • java第三方包学习之lombok
  • KMP算法及优化
  • Laravel 实践之路: 数据库迁移与数据填充
  • mysql中InnoDB引擎中页的概念
  • PHP那些事儿
  • sessionStorage和localStorage
  • Webpack 4 学习01(基础配置)
  • webpack4 一点通
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 批量截取pdf文件
  • 扑朔迷离的属性和特性【彻底弄清】
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 微服务核心架构梳理
  • 为什么要用IPython/Jupyter?
  • 我有几个粽子,和一个故事
  • Semaphore
  • 国内开源镜像站点
  • ​​​​​​​STM32通过SPI硬件读写W25Q64
  • # wps必须要登录激活才能使用吗?
  • # 达梦数据库知识点
  • ## 1.3.Git命令
  • #中国IT界的第一本漂流日记 传递IT正能量# 【分享得“IT漂友”勋章】
  • (1)SpringCloud 整合Python
  • (2024,RWKV-5/6,RNN,矩阵值注意力状态,数据依赖线性插值,LoRA,多语言分词器)Eagle 和 Finch
  • (4)事件处理——(2)在页面加载的时候执行任务(Performing tasks on page load)...
  • (C++17) optional的使用
  • (delphi11最新学习资料) Object Pascal 学习笔记---第2章第五节(日期和时间)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (Ruby)Ubuntu12.04安装Rails环境
  • (黑马C++)L06 重载与继承
  • (强烈推荐)移动端音视频从零到上手(下)
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • (原創) 如何將struct塞進vector? (C/C++) (STL)
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • (转)PlayerPrefs在Windows下存到哪里去了?