开源接流:一个方法搞定3D地图双屏联动
老大提需求:一份数据,在2D地图上可编辑,在3D地图上显示高度信息,关键是两个地图得支持视图同步,末了还来句"两天时间够了吧?"我饶了饶头,内心各种问候...,代码如何下手,特X的,什么是双屏联动?
掘金地址:https://juejin.cn/spost/7395385447294369830
功能实现了,顺便开源下
npm: mapboxgl-syncto-any
github: https://github.com/cnmapos/mapboxgl-syncto-any
首页提供有完整的使用说明,各位老铁有需要就标下star,关注下呐。 以下内容主要分享mapboxgl-syncto-any如何实现以及核心原理。
mapboxgl-syncto-any基于mapbox-gl,提供和其他任意地图视图同步,支持的同步参数主要有zoom、center、pitch、bearing等。如果你嫌麻烦,默认也 提供了mapbox-gl和cesium双屏同步,调一个方法就完事。
提供的方法都有对应的demo,如果嫌啰嗦,直接运行代码得了。
mapbox-gl和cesium双屏同步
方法定义:mapboxViewSyncWithCesium(mpboxViewer, cesiumViewer, options)
options参数属性包含:
initFrom:0 | 1, 初始化同步源, 0表示以mapbox地图初始化位置,1表示以其他地图位置初始化direction:0 | 1 | 2, 视图同步方向, 0为双向同步,1为mapbox->其他,2为其他->mapboxmapboxAllowPitch:true|false,mapbox支持俯仰角同步anyAllowPitch:true|false,其他地图支持俯仰角同步
例如下面这段代码,前两个参数传入两个地图实体mapboxMap、anyMap,第三个参数中: direction为0表示不管改变哪个地图位置,另外一个地图会自动同步;而anyAllowPitch表示改变mapbox地图俯视角,anyMap保持原始的俯视角度不变。
const mapboxMap = await mapboxSetup({ container: mboxMapEle.value });
const anyMap = await anyMapSetup({ container: anyMapEle.value });
const synchronizer = mapboxViewSyncWithCesium(
mapboxMap,
anyMap,
{ initFrom: 'mapbox', direction: 0, anyAllowPitch: false }
)
mapbox-gl和任意地图双屏同步
简单易用的同时,也得考虑更多的可能性,如何支持更多地图联动?如leaflet、openlayers、cesium、ArcGIS等等。为解决以上问题,mapboxgl-syncto-any提供了更底层的通用方法:
mapViewSync(mapboxContext, anyContext, options)
options参数和mapboxViewSyncWithCesium方法一致,mapboxContext和anyContext的类型为:
export interface AnyContext<T> {
map: T;
Handler: EventHandlerConstructor<T>
}
map为地图实体,而Handler为实现视图同步的处理器,高扩展性的核心也在EventHandlerConstructor<T>,其定义为:
export interface EventHandlerConstructor<T> {
new (params: EventHandlerParams<T>): IEventHandler;
};
简单说,你需要提供一个Class,构造函数参数为 EventHandlerParams<T>,需要实现IEventHandler规定的方法,具体有:
export interface IEventHandler {
// 当启动移动时,以当前地图为触发源
moveStart: (e?: any) => void;
// 当停止移动时,触发视图参数更新
moveEnd: (e?: any) => void;
// 对外方法,e为接收到的视图参数,方法体实现地图视角更新
updateView(e: ViewUpdateEvent): void;
destroy(): void;
}
单看定义不是太好理解,这里以mapbox的Handler实现说明,代码一目了然:
import { Map, MapMouseEvent } from "mapbox-gl";
import { EventFrom, EventHandlerParams, IEventHandler, TriggerEvent, ViewUpdateEvent } from "./types";
import _ from 'lodash';
import { getElevationByZoom, getZoomByElevation } from "./utils";
/***
* Mapbox视图同步处理器
*/
export class MapboxEventHander<T extends Map> implements IEventHandler {
private viewer: Map;
private onTrigger: (e: TriggerEvent) => void;
private onUpdateView: (e: ViewUpdateEvent) => void;
private getFrom: () => EventFrom;
constructor(params: EventHandlerParams<T>) {
this.viewer = params.map;
// onTrigger给外部通知,哪个地图源出发了视图更新
this.onTrigger = params.onTrigger;
this.getFrom = params.getFrom;
this.onUpdateView = params.onUpdateView;
this.viewer.on('mousedown', (this.moveStart = this.moveStart.bind(this)));
this.viewer.on('move', (this.moveEnd = this.moveEnd.bind(this)));
}
moveStart(e: MapMouseEvent) {
// 给外部通知mapbox触发了视图更新
this.onTrigger({...e, eventFrom: EventFrom.Mapbox });
}
moveEnd() {
if (this.getFrom() !== EventFrom.Mapbox) {
return
}
// 获取当前地图视图的参数:pitch、zoom、center、elevation
const pitch = this.viewer.getPitch();
const { lng, lat } = this.viewer.getCenter();
const zoom = this.viewer.getZoom();
const elevation = getElevationByZoom(this.viewer, zoom);
let bearing = this.viewer.getBearing();
if (bearing < 0) {
bearing = bearing + 360;
}
// 通知外部,由mapbox触发的视图更新结束
this.onUpdateView({
eventFrom: this.getFrom(),
center: [lng, lat],
zoom,
elevation,
pitch,
bearing
});
}
// 外部调用,更新mapbox地图视图参数
updateView(e: ViewUpdateEvent) {
const { elevation, center, bearing, pitch } = e;
if (_.isNumber(elevation)) {
const zoom = getZoomByElevation(this.viewer, elevation);
this.viewer.setZoom(zoom);
}
if (center) {
this.viewer.setCenter(center);
}
if (_.isNumber(bearing)) {
this.viewer.setBearing(bearing);
}
if (_.isNumber(pitch)) {
this.viewer.setPitch(pitch);
}
}
destroy() {
this.viewer.off('mousedown', this.moveStart);
this.viewer.off('move', this.moveEnd);
}
}
知其所以然
2D地图和3D地图视图参数存在差异,mapbox-gl视图参数有center、zoom、bearing、pitch,而3D地图视图参数有center、elevation(海拔)、bearing、pitch,差异点在2D的zoom和3D地图的elevation,这两个参数之间如何互转?这也算整个组件最棘手的问题。
zoom -> elevation
短短几行代码,但其结果经过了几次视图换算。
export function getElevationByZoom(viewer: Map, zoom: number) {
const circumference = 2 * Math.PI * earthHalfAxisLength;
return circumference / Math.pow(2, zoom) / 2 / Math.tan(viewer.transform._fov / 2);
}
在三维场景中,视场角fov和视图高度height、摄像机距离elevation,三者之间的关系有
tan(fov / 2) = (height / 2) / elevation
可得出
elevation = height / 2 / tan(fov / 2)
fov可根据viewer.transform._fov获取,那关键是计算出height,height在三维中表示视图范围内垂直方向的视图长度(米)。
上述等式中 circumference / Math.pow(2, zoom)等价于height值,其中circumference表示地球周长,至于为什么` circumference / Math.pow(2, zoom)`能表示height,对我来说目前还是个难题,有懂的老大帮忙解答下。
假如现在计算出elevation,要让cesium能同步至相同位置,还得进一步计算。cesium视图设置代码如下:
this.viewer.camera.lookAt(
lookTarget,
new HeadingPitchRange(heading, cameraPitch, range * Math.sin(((90 - pitch) * Math.PI) / 180))
);
其中除了range其他都是已知条件,range表示什么?
range表示摄像头到中心点center的距离,而pitch表示俯视角,根据三角关系可得range = elevation / sin(pitch)。到此,三维视图同步需要的参数都准备就绪。
反过来,三维视图变化通知二维更新,重点是elevation到zoom的转换,把上述流程反过来计算即可。
参考资料:
我的开源项目:
- react-native-mapa, react native地图组件库
- mapboxgl-syncto-any,三维地图双屏联动
开源接流:一个方法搞定3D地图双屏联动的更多相关文章
- iOS之下拉放大,上推缩小,一个方法搞定
先来看看效果吧. 讲讲大概的实现思路:1、创建头部的视图和tableview,需要注意的是tableview要设置contentInset,contentInsent 的顶部要和头部视图的背景图的高度 ...
- 【开源】简单4步搞定QQ登录,无需什么代码功底【无语言界限】
说17号发超简单的教程就17号,qq核审通过后就封装了这个,现在放出来~~ 这个是我封装的一个开源项目:https://github.com/dunitian/LoTQQLogin ————————— ...
- 简便方法搞定第三方SDK的Jar包在DelphiXE5中的引入
简便方法搞定第三方SDK的Jar包在DelphiXE5中的引入 (2014-02-21 17:30:17) 转载▼ 标签: android delphi xe5 jar sdk 分类: 编程杂集 折腾 ...
- Jquery一个slideToggle搞定div的隐藏与显示
Jquery一个slideToggle搞定div的隐藏与显示 <!DOCTYPE html> <html> <head> <script src=" ...
- 将你的前端应用打包成docker镜像并部署到服务器?仅需一个脚本搞定
1.前言 前段时间,自己搞了个阿里云的服务器.想自己在上面折腾,但是不想因为自己瞎折腾而污染了现有的环境.毕竟,现在的阿里云已经没有免费的快照服务了.要想还原的话,最简单的办法就是重新装系统.而一旦重 ...
- 手把手教你制作微信小程序,开源、免费、快速搞定
最近做了个"罗孚传车"的小程序 一时兴起,做了一个小程序,将个人收集的同汽车相关的行业资讯和学习资料,分享到小程序中,既作为历史资料保存,又提供给更多的人学习和了解,还能装一下:) ...
- 开源作品ThinkJDBC—一行代码搞定数据库操作
1 简介 ThinkJD,又名ThinkJDBC,一个简洁而强大的开源JDBC操作库.你可以使用Java像ThinkPHP框架的M方法一样,一行代码搞定数据库操作.ThinkJD会自动管理数据库连接, ...
- 收不到Win10正式版预订通知?一个批处理搞定
目前,已经有不少Win7.Win8.1用户在系统右下角收到Win10正式版的预订提示窗口.点击接受预订后,系统会将Win10正式版所需的安装文件提前下载好,7月29日正式发布的时候,就可以第一时间升级 ...
- 一个类搞定UIScrollView那些事
前言 UIScrollView可以说是我们在日常编程中使用频率最多.扩展性最好的一个类,根据不同的需求和设计,我们都能玩出花来,当然有一些需求是大部分应用通用的,今天就聊一下以下需求,在一个categ ...
- H5 拖拽,一个函数搞定,直接指定对象设置可拖拽
页面上,弹个小窗体,想让它可以拖拽,又不想 加载一堆js,就简单的能让他可以拖动? 嗯,下面有这样一个函数,调用下就好了! 1. 先来说说 H5的 拖拽 在 HTML5 中,拖放是标准的一部分,任何元 ...
随机推荐
- Solon(Spring 的替代方案)最近半年下载量突破 1200万!
不断突破 2023年04月,单月破100万(Maven 中央仓库单月下载量) 2023年06月,单月破200万 2023年11月,单月破250万 2024年11月,最近半年下载量突破 1200万 So ...
- python爬虫利器之Playwright
Playwright 是微软在 2020 年初开源的新一代自动化测试工具,它的功能类似于 Selenium.Pyppeteer 等,都可以驱动浏览器进行各种自动化操作.它的功能也非常强大,对市面上的主 ...
- 内网穿透之frp
官网文档:https://gofrp.org 1 Frp介绍frp 是一个开源.简洁易用.高性能的内网穿透和反向代理软件,支持 tcp, udp, http, https等协议.frp 项目官网是 h ...
- yum安装PHP,Redis,mysql,nginx
线上PHP环境的安装一般使用编译的方式,但是需要手动一个一个安装,这次使用yum来进行安装 一.PHP 1.安装EPEL源 rpm -ivh https://dl.fedoraproject.org/ ...
- Educational Codeforces Round 105 (Rated for Div
Educational Codeforces Round 105 (Rated for Div. 2) ABC String 给定一个字符串只有A.B和C构成.要求替换A.B.C为')'和'(',并且 ...
- Ubuntu桌面远程登陆配置
(1) 查看Ubuntu版本号 lsb_release -a (2) (a) 安装vim和Openssh-server sudo apt-get update sudo apt-get install ...
- 【解决方案】Error running,Command line is too long
一.现象 IDEA 提示 Error running,Command line is too long 二.原因 Java 命令行启动举例如下图,当命令行字符过多的时候,就会出现 Error runn ...
- GIT 使用SSH 方式提交代码
1.需求 一般情况下,我们在提交代码的时候,使用 HTTP的方式提交代码,这种方式有一个问题,提交时需要输入账号和密码,这个就不是很安全,git 提供了 SSH的方式. 下面就实际操作一下如何使用ss ...
- 华为云云日志服务 HarmonyOS NEXT采集最佳实践
鸿蒙背景介绍 华为鸿蒙HarmonyOS系统是面向万物互联的全场景分布式操作系统,支持手机.平板.智能穿戴.智慧屏等多种终端设备运行,提供应用开发.设备开发的一站式服务的平台.2024 年 1 月 1 ...
- C# 获取两经纬度之间的距离
C# 获取两经纬度之间的距离 迷恋自留地 //地球半径,单位米 private const double EARTH_RADIUS = 6378137; /// <summary> /// ...