四十一、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.问题现象描述 …

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 …

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

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

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…

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

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

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;编译器就不会提供默认构造函数&…

[imx6ull]Linux下的SocketCAN通信

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

【知识整理】软件版本号的定义及规范

版本号简述 在软件开发项目中&#xff0c;版本号是一个非常重要的概念&#xff0c;它能够告诉用户软件的功能、质量和安全性等信息&#xff0c;同时也可以帮助开发者追踪软件的历史和进展&#xff0c;并做好版本控制工作。在本文中&#xff0c;我们将介绍版本号的定义及规范&a…

Java基础面试重点-3

41. 简述线程生命周期(状态) 其它参考《多线程重点》中的说法。三种阻塞&#xff1a; 等待阻塞&#xff1a; 运行的线程执行o.wait()方法&#xff08;该线程已经持有锁&#xff09;&#xff0c;JVM会把该线程放入等待队列中。同步阻塞&#xff1a; 运行的线程在获取对象的同步…

数据挖掘丨轻松应用RapidMiner机器学习内置数据分析案例模板详解(下篇)

RapidMiner 案例模板 RapidMiner 机器学习平台提供了一个可视化的操作界面&#xff0c;允许用户通过拖放的方式构建数据分析流程。RapidMiner目前内置了 13 种案例模板&#xff0c;这些模板是预定义的数据分析流程&#xff0c;可以帮助用户快速启动和执行常见的数据分析任务。 …

jsp 实验20

三、源代码以及执行结果截图&#xff1a; NewFile.jsp <% page import "java.io.*" %> <% page contentType"text/html" %> <% page pageEncoding "utf-8" %> <jsp:useBean id"english" class "web.Engli…

QT--DAY1

不使用图形化界面实现一个登陆界面 #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {//设置窗口标题this->setWindowTitle("登录界面");//设置窗口大小this->resize(535,410);//固定窗口大小this->setFixedSize(535,410)…

181.二叉树:验证二叉树(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…

Flink任务如何跑起来之 2.算子 StreamOperator

Flink任务如何跑起来之 2.算子 StreamOperator 前文介绍了Transformation创建过程&#xff0c;大多数情况下通过UDF完成DataStream转换中&#xff0c;生成的Transformation实例中&#xff0c;核心逻辑是封装了SimpleOperatorFactory实例。 UDF场景下&#xff0c;DataStream到…

Spring Boot整合hibernate-validator实现数据校验

文章目录 概念基本概念常用校验注解 前置内容整合Hibernate Validator快速入门优雅处理参数校验异常其余注解校验自定义校验注解 参考来源 概念 基本概念 Hibernate Validator 是一个参数校验框架&#xff0c;可以非常方便地帮助我们校验应用程序的入参&#xff0c;实现了参数…

解决:selenium运行时driver初始化失败 DevToolsActivePort file doesn‘t exist的问题

解决&#xff1a;selenium运行时driver初始化失败 DevToolsActivePort file doesn‘t exist的问题 DevToolsActivePort file doesnt exist报错信息&#xff1a;![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/b3f8acc1c47d45e3912575896e421567.png)现象&#xff1…