四十一、openlayers官网示例Flight Animation解析——在地图上绘制飞机航线、牵引线效果、动态动画

官网demo地址:

Flight Animation 

这篇介绍了如何实现飞机航线动画。 

 首先加载一张底图,定义一个样式。

 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,}),});

使用fetch请求一组航线数据,并将数据源加到图层上

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;});},});

const flightsLayer = new VectorLayer({source: flightsSource,});map.addLayer(flightsLayer);

每个数组里装的是航线的起点和终点。

接下来需要使用arc库,在起点和终点连接成圆弧线。

npm i arcvar arc = require("arc");
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 });//穿过-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,}));});// 动画延迟:使用i * 50来设置延迟是为了确保每条路径的动画不会同时启动,这样可以产生连续动画的效果。addLater(features, i * 50);}

addLater函数中给每个feature绑定了时间,便于后续的动画效果中使用。 

function addLater(features, timeout) {window.setTimeout(function () {let start = Date.now();features.forEach(function (feature) {feature.set("start", start);flightsSource.addFeature(feature);// 计算每个特征的动画持续时间 duration,根据特征几何图形的坐标长度和 pointsPerMs 来计算const duration =(feature.getGeometry().getCoordinates().length - 1) / pointsPerMs;start += duration;});}, timeout);}

使用postrender事件来做动画效果 

   //tileLayer 图层每次完成渲染之后调用tileLayer.on("postrender", animateFlights);

 animateFlights获取当前帧对象,利用时间差截取数组中的项,来实现线慢慢变长的效果。

const pointsPerMs = 0.05;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);}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);}}}

 代码看着很简单,但为啥要这么写呢,咱们来分析一波。把 flightsData = flightsData.splice(26, 1);截取一下只留一条线,我们来打印下elapsedTime。

可以看到,elapsedTime是当前的时间减去初始时间的时间戳。 

 每一毫秒都会绘制长度不同的线。

 而绘制的线段点需要从coords坐标数组中取,每次都取0到index的坐标。elapsedPoints就是表示了当前需要取的index值,因为elapsedTime是毫秒值,会很快,所以没有直接使用elapsedTime去从数组里取值,而是乘以了一个系数const pointsPerMs = 0.02。

调整pointsPerMs 的值可以控制速度。

当elapsedPoints大于等于了数组长度时给feature添加了finished属性。

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

绘制过程中需要实时设置一下绘制的样式,而绘制结束后,也需要保持线的样式,所以在图层里需要定义一个完成后的样式。

const flightsLayer = new VectorLayer({source: flightsSource,style: function (feature) {// 等动画完毕再现在最终的线样式if (feature.get("finished")) {return style;}return null;},});

 完整代码:

<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: 5,}),});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, 1);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 });//穿过-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,}));});// 动画延迟:使用i * 50来设置延迟是为了确保每条路径的动画不会同时启动,这样可以产生连续动画的效果。console.log("features", features);addLater(features, i * 50);}//tileLayer 图层每次完成渲染之后调用tileLayer.on("postrender", animateFlights);});},});const flightsLayer = new VectorLayer({source: flightsSource,style: function (feature) {// 等动画完毕再现在最终的线样式if (feature.get("finished")) {return style;}return null;},});map.addLayer(flightsLayer);const pointsPerMs = 0.02;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);}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);}}}//告诉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);// 计算每个特征的动画持续时间 duration,根据特征几何图形的坐标长度和 pointsPerMs 来计算const duration =(feature.getGeometry().getCoordinates().length - 1) / pointsPerMs;start += duration;});}, timeout);}},methods: {},
};
</script><style lang="scss" scoped>
#map {width: 100%;height: 500px;
}
.box {height: 100%;
}</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/26496.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【实例分享】访问后端服务超时,银河麒麟服务器操作系统分析及处理建议

1.服务器环境以及配置 【机型】 处理器&#xff1a; Intel 32核 内存&#xff1a; 128G 整机类型/架构&#xff1a; x86_64虚拟机 【内核版本】 4.19.90-25.22.v2101.kylin.x86_64 【OS镜像版本】 kylin server V10 SP2 【第三方软件】 开阳k8s 2.问题现象描述 …

富格林:细心发现虚假确保安全

富格林指出&#xff0c;现货黄金市场内蕴藏着丰富的盈利机会&#xff0c;然而并非所有人都能够抓住这些机会。要想从市场中获取丰厚的利润并且保障交易的安全&#xff0c;必须要求我们掌握一些交易技巧利用此去发现虚假陷阱。当我们不断汲取技巧过后&#xff0c;才可利用此来发…

API工具--Apifox和Postman对比(区别)

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

【学习笔记】Linux

Linux 1、 介绍 1.1、概述 1.2、特点 1.3、Linux的发行版2、 基础篇 —— 文件系统 2.1、文件系统 2.2、目录结构3、 基础篇 —— VI/VIM 编辑器 3.1、概述 3.2、编辑器模式及常用命令4、 基础篇 —— 网络配置 4.1、VMware NetWork …

供应链初学者手册——第七部分:全球供应链管理

供应链初学者手册 文章目录 供应链初学者手册第七部分&#xff1a;全球供应链管理14. 全球供应链的特征14.1 全球供应链的优势和挑战14.2 跨国供应链管理策略 15. 国际物流与供应链管理15.1 国际物流管理 15. **国际物流与供应链管理**15.1 国际物流管理15.2 全球供应链风险管理…

探索重要的无监督学习方法:K-means 聚类模型

在数据科学和机器学习领域,聚类分析是一种重要的无监督学习方法,用于将数据集中的对象分成多个组(簇),使得同一簇中的对象相似度较高,而不同簇中的对象相似度较低。K-means 聚类是最广泛使用的聚类算法之一,它以其简单、快速和易于理解的特点受到了广泛关注。本文将深入…

Linux so文件无法找到及某条命令找不到的解决办法

前言 在一些定制软件中可能会自带so文件。或者自带一些二进制命令。 这时会如果运行某个程序会发生 **.so 文件无法找到的错误。 以及 * 某条命令无法找到的错误。 比如像是下面这样 解决办法&#xff1a; so文件无法找到 通过往 LD_LIBRARY_PATH 变量中追加路径来告诉程序…

Linux中FTP安装

文章目录 一、FTP介绍1.1、FTP是什么1.2、FTP的工作原理1.3、FTP的传输模式1.4、FTP用户类别1.5、FTP的优点与缺点1.6、FTP数据传输格式 二、FTP客户端与服务端2.1、服务端2.2、客户端 三、FTP服务器软件介绍3.1、WU-FTPD3.2、ProFtpD3.3、vsftpd3.4、Pure-FTP3.5、FileZilla S…

使用 zkCli 操作 ZooKeeper 节点

创建一个新节点&#xff1a; create /example "Hello, ZooKeeper!"列出根目录下的节点&#xff1a; ls /获取节点的数据&#xff1a; get /example更新节点的数据&#xff1a; set /example "Hello, Updated ZooKeeper!"删除节点&#xff1a; delete …

cdh中的zookeeper怎么配置zoo.cfg

你手动改了zoo.cfg目录是不会生效的&#xff0c;因为是cdh在管控&#xff0c;所以只能通过cdh修改。 首先打开cdh。 xxx:7180 点击zookeeper 选配置&#xff0c;然后选高级 在右边找&#xff0c;有一个就是zoo.cfg&#xff0c;可以点击右边的感叹号。然后在里面编辑的就会直…

LabVIEW RT环境中因字符串拼接导致的系统崩溃问题

在LabVIEW实时操作系统&#xff08;RT&#xff09;环境中运行的应用程序出现字符串拼接后死机的问题&#xff0c;通常涉及内存管理、内存泄漏或其他资源管理问题。以下是一些指导和步骤&#xff0c;帮助解决这个问题&#xff1a; 1. 内存泄漏检测 字符串拼接会在内存中创建新…

Android14音频进阶之CarAudioManager::getOutputDeviceForUsage流程分析(七十七)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+AOSP…

ROS 获取IMU数据(C++实现)

ROS 获取IMU数据&#xff08;C实现&#xff09; 实现思路 构建一个新的软件包&#xff0c;包名叫做imu_pkg在软件包中新建一个节点&#xff0c;节点名叫做imu_node在节点中&#xff0c;向ROS大管家NodeHandle申请订阅话题/imu/data,并设置回调函数为IMUCallback()构建回调函数…

2024 年 19 种最佳大型语言模型

大型语言模型是 2023 年生成式人工智能热潮背后的推动力。然而&#xff0c;它们已经存在了一段时间了。 LLM是黑盒 AI 系统&#xff0c;它使用深度学习对超大数据集进行处理&#xff0c;以理解和生成新文本。现代 LLM 开始成型于 2014 年&#xff0c;当时一篇题为“通过联合学…

ffmpeg把视频文件转码为MP4格式

windows系统需要下载ffmpeg软件,并在代码中指定路径 centos系统需要安装ffmepg是可执行的命令 package com.xkj.utils;import lombok.extern.slf4j.Slf4j;import java.io.*; import java.util.ArrayList; import java.util.List;@Slf4j public class ConvertVideoUtils {//需…

在 ASP.NET Core 应用程序中,Program.cs、Startup.cs 和 Module.cs区别作用

在 ASP.NET Core 应用程序中&#xff0c;Program.cs、Startup.cs 和 Module.cs&#xff08;虽然 Module.cs 并不是 ASP.NET Core 的默认部分&#xff0c;但它可能是一个自定义的类或文件&#xff09;各自扮演着不同的角色&#xff0c;并服务于不同的目的。 Program.cs Program.…

Github2024-06-12 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-06-12统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目4JavaScript项目2Lua项目1PHP项目1Blade项目1非开发语言项目1TypeScript项目1Shell项目1从零开始构建你喜爱的技术 创建周期:2156 天…

C++ 25 之 调用函数调用规则

c25调用函数调用规则.cpp #include<iostream> using namespace std;class Students04{ // 1.创建好类之后&#xff0c;编译器会默认提供三个函数&#xff1a;默认构造函数、构造函数、拷贝构造函数 // 2.自己写了有参构造函数&#xff0c;编译器就不会提供默认构造函数&…

与和或运算

1、与运算 例如&#xff1a;3&5 十进制3转为二进制的3&#xff1a;0000 0011 十进制5转为二进制的5&#xff1a;0000 0101 ------------------------结果&#xff1a;0000 0001 ->转为十进制&#xff1a;1 即&#xff1a;3&5 1 2、或运算 运算规则&#xff1a; 十…

[imx6ull]Linux下的SocketCAN通信

文章目录 一、CAN总线协议1.简介2.电气属性3.通信原理①数据帧的帧格式&#xff1a;②总线同步③总线竞争④数据保护 二、Linux下CAN的操作1.硬件连接①CAN电平转换器②扩展板使用CAN 2.查询 can 信息3.开启/关闭 can4.发送/接收 can 数据5.设置 can 参数 三、CAN的回环测试四、…