openlayers-16-添加一组轨迹动画

news2025/1/13 17:27:58

实现一组动画,即根据一组只有起止点坐标的线段,实现点在这些线段上较为平滑的移动,移动速度和平滑程度均可控制。

下面的代码仅作为思路参考,还欠缺很多细节,比如在进行插值计算时,还需要判断经纬度坐标差,选择差值大的作为已知项计算插值,这样会避免一些bug并让计算的插值数据更平滑。还有如何把示例中的圆点改为箭头,并计算箭头的方向与线的走向一致等等一些问题。如果有时间,后期会整理一个更加具体的,可以直接移植使用的demo。

运行结果

在这里插入图片描述

代码如下

<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>添加箭头动画</title>
    <link href="ol/ol.css" rel="stylesheet" type="text/css"/>
    <script src="ol/ol.js" type="text/javascript"></script>
    <style type="text/css">
        #mapCon {
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
<!-- 地图容器 -->
<div id="mapCon"></div>
<div style="position: absolute;top: 20px;left: 50%;">
    运动速度:<input id="speed" type="range" min="1" max="10" step="1" value="5"/>
    <button id="start-animation">开始</button>
</div>
<script type="text/javascript">
    var key = "4689fc6b9bc0fdc8c48298f751ebfb41";//天地图密钥

    //ol.layer.Tile:是一个瓦片图层类,用于显示瓦片资源。
    //source是必填项,用于为图层设置来源。

    //ol.source.XYZ:
    //创建天地图矢量图层
    var TiandiMap_vec = new ol.layer.Tile({
        title: "天地图矢量图层",
        source: new ol.source.XYZ({
            url: "http://t{0-7}.tianditu.com/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=" + key,
            wrapX: false
        })
    });
    //创建天地图矢量注记图层
    var TiandiMap_cva = new ol.layer.Tile({
        title: "天地图矢量注记图层",
        source: new ol.source.XYZ({
            url: "http://t{0-7}.tianditu.com/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=" + key,
        })
    });
    //实例化Map对象加载地图
    var map = new ol.Map({
        //地图容器div的ID
        target: 'mapCon',
        //地图容器中加载的图层
        layers: [TiandiMap_vec, TiandiMap_cva],
        //地图视图设置
        view: new ol.View({
            //地图初始中心点(经纬度)
            center: [118.37, 37.14],
            //地图初始显示级别
            zoom: 12,
            projection: "EPSG:4326"
        })
    });

    /**
     * 线性插值
     * @param start 线段开始点的坐标
     * @param end 结束点坐标
     * @param step 步长
     * @returns {*[]}
     */
    function interpolate(start, end, step) {
        const x = start[0] + step * (end[0] - start[0]);
        let num = Math.ceil(Math.abs(end[0] - start[0]) / step);
        let xArr = [];
        setp = end[0] > start[0] ? step : 0 - step;
        for (let i = 1; i < num; i++) {
            xArr.push(start[0] + setp * i);
        }
        //插入起点
        let lonlat = [start];
        for (let i = 0; i < xArr.length; i++) {
            let y = start[1] + (end[1] - start[1]) / (end[0] - start[0]) * (xArr[i] - start[0]);
            lonlat.push([xArr[i], y]);
        }
        //加入终点
        lonlat.push(end);
        return lonlat;
    }

    //构建一组离散化的点(一组线段的起止点)
    var Coordinates = [
        {
            lonlat: [[118.37, 37.14], [118.48, 37.05]]
        },
        {
            lonlat: [[118.46, 37.04], [118.51, 37.06]]
        }
    ];
    //遍历线段,并根据起止点,计算插值数据,用于动画
    let routes = [];//路径对象数组
    for (let i = 0; i < Coordinates.length; i++) {
        const item = Coordinates[i];
        //获取插值数据
        let lonlatArr = interpolate(item.lonlat[0], item.lonlat[1], 0.01);
        //将插值数据构建为Line对象
        let route = new ol.geom.LineString(lonlatArr);
        //获取直线的坐标
        let routeCoords = route.getCoordinates();
        let routeLength = routeCoords.length;

        let routeFeature = new ol.Feature({
            type: 'route',
            geometry: route
        });
        let geoMarker = new ol.Feature({
            type: 'geoMarker',
            geometry: new ol.geom.Point(routeCoords[0])
        });
        routes.push(
            {
                route: route,
                routeCoords: routeCoords,
                routeLength: routeLength,
                routeFeature: routeFeature,
                geoMarker, geoMarker,
                index: 0
            }
        )
    }


    var styles = {
        'route': new ol.style.Style({
            stroke: new ol.style.Stroke({
                width: 6,
                color: [237, 212, 0, 0.8]
            })
        }),
        'icon': new ol.style.Style({
            image: new ol.style.Icon({
                anchor: [0.5, 1],
                src: "../../images/stationicon.png"
            })
        }),
        'geoMarker': new ol.style.Style({
            image: new ol.style.Circle({
                radius: 7,
                snapToPixel: false,
                fill: new ol.style.Fill({color: 'black'}),
                stroke: new ol.style.Stroke({
                    color: 'white',
                    width: 2
                })
            })
        })
    };

    var animating = false;
    var speed, now;
    var speedInput = document.getElementById('speed');
    var startButton = document.getElementById('start-animation');
    //添加用于展示动画的层
    var vectorSource = new ol.source.Vector();
    var vectorLayer = new ol.layer.Vector({
        source: vectorSource,
        style: function (feature) {
            //如果动画是激活的就隐藏geoMarker
            if (animating && feature.get('type') === 'geoMarker') {
                return null;
            }
            return styles[feature.get('type')];
        }
    });

    map.addLayer(vectorLayer);
    for (let i = 0; i < routes.length; i++) {
        let item = routes[i];
        vectorSource.addFeature(item.routeFeature);
        vectorSource.addFeature(item.geoMarker);
    }
    var moveFeature = function (event) {
        var vectorContext = event.vectorContext;
        var frameState = event.frameState;

        if (animating) {

            //通过增加速度,来获得lineString坐标
            for (let i = 0; i < routes.length; i++) {
                const item = routes[i];
                var elapsedTime = frameState.time - item.now;
                item.index = Math.round(speed * elapsedTime / 1000);
                if (item.index >= item.routeLength) {
                    //重置时间和各个线段的下标,用于循环
                    item.now = new Date().getTime();
                    item.index = 0;
                    //执行下面两行代码 自动结束动画
                    //stopAnimation(true);
                    //return;
                }

                var currentPoint = new ol.geom.Point(item.routeCoords[item.index]);
                var feature = new ol.Feature(currentPoint);
                vectorContext.drawFeature(feature, styles.geoMarker);
            }

        }
        //继续动画效果
        map.render();
    };

    function startAnimation() {
        if (animating) {
            stopAnimation(false);
        } else {
            animating = true;
            speed = speedInput.value;
            startButton.textContent = '结束运动';
            //隐藏geoMarker
            for (let i = 0; i < routes.length; i++) {
                const item = routes[i];
                item.geoMarker.setStyle(null);
                item.now = new Date().getTime();
            }

            //设置显示范围
            // map.getView().setCenter(center);
            map.on('postcompose', moveFeature);
            map.render();
        }
    }


    /**
     * @param {boolean}结束动画
     */
    function stopAnimation(ended) {
        animating = false;
        startButton.textContent = '开始运动';
        for (let i = 0; i < routes.length; i++) {
            const item = routes[i];
            //如果动画取消就开始动画
            var coord = ended ? item.routeCoords[item.routeLength - 1] : item.routeCoords[0];
            //将各个线段上的点重新定位到起始位置
            (item.geoMarker.getGeometry()).setCoordinates(coord);
        }

        //移除监听
        map.un('postcompose', moveFeature);
    }

    startButton.addEventListener('click', startAnimation, false);
</script>
</body>
</html>

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

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

相关文章

RocketMQ(消息中间件)

目录 一、为什么会出现消息中间件&#xff1f; 二、消息中间件是干嘛的&#xff1f; 三、应用解耦 四、流量削峰 五、异步处理 1.串行方式&#xff1a; 2.并行方式&#xff1a; 3.引入消息队列&#xff1a; 六、RocketMQ的架构及概念 一、为什么会出现消息中间件&#…

opencv-人脸识别

对https://blog.csdn.net/weixin_46291251/article/details/117996591这哥们代码的一些修改 import cv2 import numpy as np import os import shutil import threading import tkinter as tk from PIL import Image, ImageTkchoice 0# 首先读取config文件&#xff0c;第一行…

国内8个能用AI赚钱的在线兼职渠道整理

hi&#xff0c;同学们&#xff0c;我是赤辰&#xff0c;本期是赤辰第3篇AI工具类教程&#xff0c;文章底部准备了粉丝福利&#xff0c;看完可以领取&#xff01;即将迎来新学期了&#xff0c;今天就来聊聊学生党如何应用AI技能通过兼职&#xff0c;赚取生活费&#xff0c;这里指…

使用HTTPS模式建立高效爬虫IP服务器详细步骤

嘿&#xff0c;各位爬虫小伙伴们&#xff01;想要自己建立一个高效的爬虫IP服务器吗&#xff1f;今天我就来分享一个简单而强大的解决方案——使用HTTPS模式建立工具&#xff01;本文将为你提供详细的操作步骤和代码示例&#xff0c;让你快速上手&#xff0c;轻松建立自己的爬虫…

【枚举区间+线段树】CF Ehu 152 E

Problem - E - Codeforces 题意&#xff1a; 思路&#xff1a; 感觉是个套路题 对区间计数&#xff0c;按照CF惯用套路&#xff0c;枚举其中一个端点&#xff0c;对另一个端点计数 对于这道题&#xff0c;枚举右端点&#xff0c;对左端点计数 Code&#xff1a; #include &…

Form.Item创建自定义表单

一、概述 Antd是一个非常强大的UI组件库&#xff0c;里面的Form表单组件也基本能满足我们大多数场景。但是也有需要自定义表单的场景。 Vue2里我们使用v-model&#xff0c;结合子组件的model属性&#xff0c;来实现自定义组件的双向绑定。 Vue3里我们使用v-model&#xff0c;…

没有苹果开发者账号能否创建ios证书-最新

​ 目录 摘要&#xff1a; 引言&#xff1a; 步骤一&#xff1a; 步骤二&#xff1a; 步骤三&#xff1a; 步骤四&#xff1a; 步骤五&#xff1a; 总结&#xff1a; 摘要&#xff1a; 本文介绍了在没有Mac电脑的情况下&#xff0c;使用appuploader工具生成iOS证书和描…

高通QualComm、MTK耦合测频段工具和综测校准工具的自由定制 与工厂Mes对接、使用PowerMeter测量等

一.MTK耦合工具V1.3 20220409 MTK芯片 1.逻辑&#xff1a; 打开软件&#xff0c;扫码SN&#xff0c;自动连接&#xff0c;连接成功后 1).开启强发&#xff08;强发参数 LTE频段 1、3、5、7、28&#xff09; 2).读测量值 3).结果判定&#xff…

【计算机组成 课程笔记】3.2 算数运算和逻辑运算的硬件实现

课程链接&#xff1a; 计算机组成_北京大学_中国大学MOOC(慕课) 3 - 2 - 302-门电路的基本原理&#xff08;11-39--&#xff09;_哔哩哔哩_bilibili 现代计算机的CPU和其他很多功能部件都是基于晶体管的集成电路&#xff0c;想要了解计算机组成的基本原理&#xff0c;还是需要有…

电脑报错提示xinput1_3.dll缺失怎么办?xinput1_3.dll丢失的简单恢复方案

今天&#xff0c;我将为大家分享一个与我们日常工作息息相关的话题——xinput1_3.dll丢失的4种解决方法。在我们的日常工作和生活中&#xff0c;电脑出现问题是常有的事&#xff0c;而xinput1_3.dll丢失则是其中较为常见的一种问题。那么&#xff0c;什么是xinput1_3.dll?它为…

C盘扩容遇到的问题(BitLocker解密、)

120G的C盘不知不觉的就满了&#xff0c;忍了好久终于要动手了。 尽管电脑-管理--磁盘管理里可以进行磁盘大小调整&#xff0c;但由于各盘都在用&#xff0c;不能够连续调整&#xff0c;所以选用DiskGenius。 # DiskGenius调整分区大小遇到“您选择的分区不支持无损调整容量” …

undolog

一、原子性 ACID&#xff1a;通过undolog保证原子性 二、undolog 如何做 把回滚时所需的东西都给记下来&#xff1a; 1、插入一条记录时&#xff0c;至少要把这条记录的主键值记下来&#xff0c;回滚的时候只需要把这个主键值对应的记录删掉就好了。 2、删除了一条记录&…

10 行代码能做什么?

10 行代码能做什么&#xff1f; 写一串 SQL Join&#xff0c;统计全渠道市场营销的 ROI 用 JS 画个饼图&#xff0c;展示最有效的广告投放策略 用 Python 写段算法&#xff0c;分析销量骤降的原因是什么 …… 数据出错了&#xff0c;写个对数脚本 -_-|| AI 时代&#xff0c;…

AI图像行为分析算法 opencv

AI图像行为分析算法通过pythonopencv深度学习框架对现场操作行为进行全程实时分析&#xff0c;AI图像行为分析算法通过人工智能视觉能够准确判断出现场人员的作业行为是否符合SOP流程规定&#xff0c;并对违规操作行为进行自动抓拍告警。OpenCV是一个基于Apache2.0许可&#xf…

管理类联考——逻辑——汇总篇——知识点突破——形式逻辑——联言选言——定义

角度——本质定义 联言 联言命题是断定两种或两种以上事物情况同时存在的命题&#xff0c;用“A并且B”表示&#xff0c;逻辑符号为A ∧ B。 若“A ∧ B”为真&#xff0c;表明A是真的&#xff0c;同时B也是真的。 【定义】联言命题是表示若干判断同时成立的命题。 【刻画】…

Java与其他编程语言比较分析,编程语言选择与优点、缺点和适用场景详解

原文地址&#xff1a;Java与其他编程语言比较分析&#xff0c;编程语言选择与优点、缺点和适用场景详解 Java 擅长可移植性和可靠性&#xff0c;Python 擅长通用性和简单性&#xff0c;JavaScript 擅长 Web 开发&#xff0c;C 擅长性能&#xff0c;Go 擅长效率。网址:yii666.c…

网御星云-上网行为管理系统bottomframe.cgi接口存在SQL注入

一、免责声明&#xff1a; 本次文章仅限个人学习使用&#xff0c;如有非法用途均与作者无关&#xff0c;且行且珍惜&#xff1b;由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;公众号望雪阁及作者不为此…

OS 内存使用和分段

磁盘很大但是内存很小&#xff0c;当进程一堵塞的时候&#xff0c;就把进程一放入磁盘&#xff0c;然后进程二进去。进程三睡眠的时候在把二和三交换&#xff0c;所以载入之后重定位不太行 GDT是整个操作系统的段表&#xff0c;多个进程的 LDT时某个进程的段表

SpringBoot 博客网站

SpringBoot 博客网站 系统功能 登录注册 博客列表展示 搜索 分类 个人中心 文章分类管理 我的文章管理 发布文章 开发环境和技术 开发语言&#xff1a;Java 使用框架: SpringBoot jpa H2 Spring Boot是一个用于构建Java应用程序的开源框架&#xff0c;它是Spring框架的一…

Spring版本与JDK版本演变

Java各版本变更核心API Java8 lambada表达式函数式接口方法引用默认方法Stream API 对元素流进行函数式操作Optional 解决NullPointerExceptionDate Time API重复注解 RepeatableBase64使用元空间Metaspace代替持久代&#xff08;PermGen space&#xff09; Java7 switch 支…