Mapboxgl 生成飞行动画GIF

news2024/9/20 14:53:59

在这里插入图片描述

更多精彩内容尽在数字孪生平台,关注公众号【sky的数孪技术】,技术交流、源码下载请添加VX:digital_twin123

在这里插入图片描述

根据两点生成动画的工具。
首先,找到你想要开始的视图,点击“设置起点视图”,然后调整到目的视图,点击“设置终点视图”。
点击“开始动画”来创建一张gif图。

源码如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Fly to a location</title>
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
    <link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet">
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script>
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }
    </style>
</head>

<body>
<div id="map"></div>
<script>
   const map = new mapboxgl.Map({
        container: 'map',
        center: [-74.5, 40],
        zoom: 4,
        preserveDrawingBuffer: true
    });

    let guiData

    async function makeAnimation(frames, duration) {
        async function dataURLtoBlob(dataURL) {
            var arr = dataURL.split(','),
                mime = arr[0].match(/:(.*?);/)[1],
                bstr = atob(arr[1]),
                n = bstr.length,
                u8arr = new Uint8Array(n);

            while (n--) {
                u8arr[n] = bstr.charCodeAt(n);
            }

            return new Blob([u8arr], { type: mime });
        }

        async function saveBlobToFile(blob, fileName) {
            var a = document.createElement('a');
            document.body.appendChild(a);
            a.style = 'display: none';
            var url = window.URL.createObjectURL(blob);
            a.href = url;
            a.download = fileName;
            a.click();
            window.URL.revokeObjectURL(url);
        }

        async function startAnimation(frames, duration) {
            return new Promise((resolve, reject) => {
                let msRemaining = duration
                let frameCounter = 0
                let images

                if (guiData.exportPngs) {
                    images = new JSZip();
                } else {
                    images = []
                }

                async function assembleFrame(dataUrl) {
                    return new Promise((resolve, reject) => {
                        const img = new Image();
                        img.src = dataUrl;
                        resolve(img)
                    })
                }

                async function makeImage() {
                    frameCounter++
                    msRemaining = msRemaining - duration / frames;

                    if (msRemaining <= 0) {
                        clearInterval(intervalId);
                        resolve(images)
                    }

                    let dataUrl = map.getCanvas().toDataURL('image/png')
                    if (guiData.exportPngs) {
                        let blob = dataURLtoBlob(dataUrl);
                        images.file(`image${String(frameCounter).padStart(3, '0')}.png`, blob);
                    } else {
                        assembleFrame(dataUrl).then((img) => {
                            images.push(img)
                        })
                    }
                }
                const intervalId = setInterval(makeImage, duration / frames);
            })
        }


        startAnimation(frames, duration).then((images) => {
            if (guiData.exportPngs) {
                images.generateAsync({ type: 'blob' }).then(function (content) {
                    saveBlobToFile(content, 'images.zip');
                });
            } else {
                const gif = new GIF({
                    workers: 5,
                    quality: 10,
                    width: images[0].width,
                    height: images[0].height
                });

                for (var i = 0; i < images.length; i++) {
                    gif.addFrame(images[i], { delay: guiData.frameDelay });
                }

                gif.on('finished', function (blob) {
                    window.open(URL.createObjectURL(blob));
                });

                gif.render();
            }
        })
    }

    let startPosition, endPosition, directoryHandle;

    function addTerrainSource() {
        map.addSource('mapbox-dem', {
            'type': 'raster-dem',
            'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
            'tileSize': 512,
            'maxzoom': 14
        });
    }

    function toggleTerrain(value) {
        if (value) {
            map.setTerrain({ 'source': 'mapbox-dem', 'exaggeration': 1.5 });
        } else {
            map.setTerrain()
        }
    }

    map.on('load', (e) => {
        addTerrainSource()

        guiData = {
            setStart: function () {
                setStartPosition()
            },
            setEnd: function () {
                setEndPosition()
            },
            duration: 20000,
            frames: 100,
            frameDelay: 100,
            exportPngs: false,
            dryRun: true,
            mapStyle: 'Streets',
            terrain: true,
            startAnimation: function () {
                startAnimation()
            },
            showHelp: function () {
                showHelp()
            },
        };

        let mapStyles = {
            'Light': 'light-v10',
            'Dark': 'dark-v10',
            'Outdoors': 'outdoors-v11',
            'Satellite': 'satellite-v9',
            'Sat Streets': 'satellite-streets-v11',
            'Standard': 'standard',
            'Streets': 'streets-v11',
        }

        const gui = new dat.GUI()
        const positionsFolder = gui.addFolder('位置设置')
        positionsFolder.open()
        let setStart = positionsFolder.add(guiData, "setStart").name('设置起点视图');
        let setEnd = positionsFolder.add(guiData, "setEnd").name('设置终点视图');
        const mapFolder = gui.addFolder('地图设置')
        let mapStyle = mapFolder.add(guiData, "mapStyle", mapStyles).name('地图样式').setValue('standard').onChange(function (value) {
            let mapStylePath = `mapbox://styles/mapbox/${value}`
            map.setStyle(mapStylePath);
            map.once('idle', (e) => {
                addTerrainSource()
                toggleTerrain(guiData.terrain)
            })
        });
        let terrain = mapFolder.add(guiData, "terrain").name('地形').setValue(true).onChange(function (value) {
            toggleTerrain(value)
        })
        const animationFolder = gui.addFolder('动画设置')
        let durationGUI = animationFolder.add(guiData, "duration", 0, 30000, 100).name('持续时间 (ms)');
        let framesGUI = animationFolder.add(guiData, "frames", 0, 1000, 10).name('帧数');
        let frameDelay = animationFolder.add(guiData, "frameDelay", 1, 1000, 10).name('帧延迟');
        let exportPngs = animationFolder.add(guiData, "exportPngs").name('导出 PNG').onChange(function (value) {
            if (value) {
                alert("图片将下载为zip包,帧数太多的话文件也会很大.")
            }
        });
        let dryRun = animationFolder.add(guiData, "dryRun").name('只运行动画').onChange(function (value) { });
        let startAnimationGUI = gui.add(guiData, "startAnimation").name('开始动画');
        let showHelpGUI = gui.add(guiData, "showHelp").name('帮助');

        function setStartPosition() {
            startPosition = map.getFreeCameraOptions();
        }

        function setEndPosition() {
            endPosition = {
                center: map.getCenter(),
                bearing: map.getBearing(),
                pitch: map.getPitch(),
                zoom: map.getZoom()
            }
        }

        function showHelp() {
            alert(`根据两点生成动画的工具。
            首先,找到你想要开始的视图,点击“设置起点视图”,然后调整到目的视图,点击“设置终点视图”。
            点击“开始动画”来创建一张gif图。
                `)
        }

        function startAnimation() {
            if (endPosition == undefined) {
                alert("请设置起止点.")
            } else {
                function fly() {
                    map.flyTo({
                        center: endPosition.center,
                        bearing: endPosition.bearing,
                        pitch: endPosition.pitch,
                        zoom: endPosition.zoom,
                        essential: true,
                        duration: guiData.duration,
                        maxDuration: Infinity
                    });
                    if (!guiData.dryRun) {
                        makeAnimation(guiData.frames, guiData.duration)
                    }
                }

                if (startPosition !== undefined) {
                    map.setFreeCameraOptions(startPosition);
                    map.once('idle', (e) => {
                        fly()
                    })
                } else {
                    fly()
                }
            }
        }
    })


</script>
</body>

</html>

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

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

相关文章

Android中RecyclerView使用详解(一)

目录 概述优点列表布局RecyclerView一、创建RecyclerView并且在布局中绑定二、实现RecyclerView单个item的布局三、给RecyclerView写一个对应的适配器Adapter1.创建自定义的ViewHolder2.继承Adapter&#xff0c;泛型使用我们自定义的ViewHolder3.重写Adapter的三个方法onCreate…

如何提取视频中的音频?提取音频的几种方法

如何提取视频中的音频&#xff1f;提取视频中的音频&#xff0c;是许多人在处理多媒体内容时常遇到的需求。这一过程不仅仅是简单地从视听媒体中抽离音频部分&#xff0c;它背后蕴含着许多技术上的挑战和创意上的可能性。通过提取音频&#xff0c;你可以更方便地利用视频中的声…

网络安全——防御(防火墙)带宽以及双机热备实验

12&#xff0c;对现有网络进行改造升级&#xff0c;将当个防火墙组网改成双机热备的组网形式&#xff0c;做负载分担模式&#xff0c;游客区和DMZ区走FW3&#xff0c;生产区和办公区的流量走FW1 13&#xff0c;办公区上网用户限制流量不超过100M&#xff0c;其中销售部人员在其…

docker镜像导出与导入

布置程序出问题了&#xff0c;拉不下来镜像 程序的配置文件里面有镜像的名字 docker pull ubuntu/squid:latest 我是本地下载了镜像&#xff0c;使用本地的镜像导出 导出镜像使用 docker 导出导入镜像 要在Docker中导出和导入镜像&#xff0c;您可以使用docker save命令来导…

《昇思25天学习打卡营第5天|10使用静态图加速》

写在前面 跳过了模型训练和保存两节&#xff0c;模型训练的例子已经学习过&#xff0c;都是重复的内容&#xff0c;不进行详细的学习了&#xff0c;除非要使用类似的函数。 因此接下来开始学习初学教程的最后一节&#xff1a;使用静态图加速&#xff0c;希望能学习到一些内容。…

【分布式系统】CephFS文件系统之MDS接口详解

目录 一.服务端操作 1.在管理节点创建 mds 服务 2.查看各个节点的 mds 服务&#xff08;可选&#xff09; 3.创建存储池&#xff0c;启用 ceph 文件系统 4.查看mds状态&#xff0c;一个up&#xff0c;其余两个待命&#xff0c;目前的工作的是node01上的mds服务 5.创建用户…

从零开始搭建vue框架

流程图 开始 | |-- 2013 年底&#xff0c;尤雨溪开始开发 Vue 框架&#xff0c;最初命名为 Seed&#xff0c;后更名为 Vue | |-- 2013 年 12 月&#xff0c;Vue 0.6.0 版本 | |-- 2014 年 1 月 24 日&#xff0c;Vue 0.8.0 版本发布 | |-- 2014 年 2 月 25 日&#xff0c;…

数据中心内存RAS技术发展背景

随着数据量的爆炸性增长和云计算的普及&#xff0c;数据中心内存的多比特错误及由无法纠正错误(UE)导致的停机问题日益凸显&#xff0c;这些故障不仅影响服务质量&#xff0c;还会带来高昂的修复或更换成本。随着工作负载、硬件密度以及对高性能要求的增加&#xff0c;数据中心…

408数据结构-图的应用3-有向无环图、拓扑排序 自学知识点整理

前置知识&#xff1a;表达式&#xff0c;图的遍历 有向无环图描述表达式 有向无环图&#xff1a;若一个有向图中不存在环&#xff0c;则称为有向无环图&#xff0c;简称 D A G DAG DAG图 。 &#xff08;图片来自王道考研408数据结构2025&#xff09; 由王道考研-咸鱼学长的讲…

进销存管理系统设计

进销存管理系统&#xff08;Inventory Management System&#xff0c;简称IMS&#xff09;是一种帮助企业有效管理商品的入库、出库及库存情况的信息系统。良好的进销存管理系统能够提升库存周转率、减少库存成本、提高订单处理效率&#xff0c;从而增强企业的市场竞争力。以下…

SD card知识总结

一、基础知识 1、简介 SD Card 全称(Secure Digital Memory Card)&#xff0c;日本电子公司松下&#xff08;Panasonic&#xff09;、瑞典公司爱立信&#xff08;Ericsson&#xff09;、德国公司西门子&#xff08;Siemens&#xff09;共同开发的&#xff0c;于1999年发布根…

超声波清洗机排行榜,热门超声波清洗机哪个更值得入手?

用超声波清洗机洗眼镜已经不算是什么惊奇的事情了&#xff0c;并且很多戴眼镜的朋友更是因为超声波清洗机能够清洗眼镜而慕名前来。毕竟现在洗眼镜能够用超声波清洗机代劳实在是一件很省心的事情&#xff01;~但是&#xff0c;对于超声波清洗机你们真的了解吗&#xff1f;因此本…

从零开始学习cartographer源码 | 番外:如何在wsl内使用clion阅读cartographer源码

从零开始学习cartographer源码 | 番外&#xff1a;如何在wsl内使用clion阅读cartographer源码 安装WSL2及Clion安装WSL2-Ubuntu20.04安装Clion安装ROS 安装Cartographer一键安装Cartographer 在Clion打开cartographer工程安装gdb手动创建CMakeLists.txt打开项目配置wsl工具链配…

一款简单的音频剪辑工具

Hello&#xff0c;大家好呀&#xff0c;我是努力搬砖的小画。 今天小画给大伙分享一款强大的音频剪辑工具--【剪画】&#xff0c;无需下载就能使用&#xff0c;支持对MP3、M4A、AAC等多种格式文件进行剪辑、分割、拼接、混音、变声、淡入淡出、音频格式转换、视频转音频、消除…

网关设备BL122实现Modbus RTU/TCP转Profinet协议

Modbus与Profinet是两种广泛应用于工业自动化领域的通信协议&#xff1a;Modbus因其简单性和兼容性&#xff0c;在许多工业设备中得到广泛应用&#xff1b;而Profinet提供了高速、高精度的通信能力&#xff0c;适合于复杂控制系统和实时应用&#xff0c;但两者之间的差异导致了…

安防视频监控/视频汇聚EasyCVR平台浏览器http可以播放,https不能播放,如何解决?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台基于云边端一体化架构&#xff0c;兼容性强、支持多协议接入&#xff0c;包括国标GB/T 28181协议、部标JT808、GA/T 1400协议、RTMP、RTSP/Onvif协议、海康Ehome、海康SDK、大华SDK、华为SDK、宇视SDK、乐橙SDK、萤石云SD…

Pod网络、Service网络、网络插件Calico、网络插件Flannel(2024-07-12)

一、Pod网络 在K8S集群里&#xff0c;多个节点上的Pod相互通信&#xff0c;要通过网络插件来完成&#xff0c;比如Calico网络插件。 使用kubeadm初始化K8S集群时&#xff0c;有指定一个参数 --pod-networkcidr10.18.0.0/16 它用来定义Pod的网段。而我们在配置Calico的时候&…

LED显示屏中什么是光纤传输?什么是网线传输?

在科技日新月异的今天&#xff0c;LED显示屏已成为信息传播和视觉展示的重要工具。然而&#xff0c;一块亮丽的LED显示屏背后&#xff0c;数据传输技术发挥着至关重要的作用。今天&#xff0c;我们就来一起探索LED显示屏中两种常见的数据传输方式&#xff1a;光纤传输和网线传输…

护网--2

实验要求&#xff1a; 1、办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 2、分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的http服务器 3、多出口环境基于带宽比例进行选路&#xff0c;但是&#xff0c;…

力扣 二叉树 相关题目总结2

目录 一、101 对称二叉树 题目 题解 方法一&#xff1a;递归&#xff08;推荐&#xff09; 方法二&#xff1a;迭代 二、100 相同的树 题目 题解 方法一&#xff1a;递归法 方法二&#xff1a;深度优先搜索 三、111 二叉树的最小深度 题目 题解 方法一&#xff1…