一种新的leaflet+cesium二三维切换的解决方案

一.leaflet转cesium
比较简单,
先用leaflet的getBounds获取边界
再使用cesium的viewer.camera.setView({
destination:Cesium.Rectangle.fromDegrees(args)
})来实现相机位置切换,最后效果还不错。

2TO3

二.cesium转leaflet
首先获取屏幕内地球的二维中心点坐标(有多种办法,我是使用viewer.camera.pickEllipsoid配合二分查找拿到的),将相机移动至中点上方并姿态调整,使之正对地球。获取中心点对应的经纬度坐标P(lng,lat)。

请添加图片描述

接下来比较困难的是三维地图高度与二维地图上zoom的对应关系,即在变形较小的前提下将三维地图转换为二维。
我尝试过几种办法
1.viewer.camera.computeViewRectangle
这也是我看到目前网络上传的最多的方法,不过这种有两个问题,第一个问题是只有在地球上下左右四个边界都超出屏幕后才能使用,否则获取的矩形坐标不对(我对着中国调用,每次都定位到科特迪瓦)。第二个问题是就算将三维地球放大,因为球体的投影在矩形转换过程中也会出现leaflet的zoom不是最佳缩放比例。
2.通过viewer.scene.globe._surface._tilesToRender[0]._level获取当前三维地球的正对瓦片的层级,然后将leaflet的zoom设置相同值。这种也存在视差问题,比如三维上看同等大小的区域是3级,在二维地图上需要5级,因此使用这种方法需要设置几个层级关系,一般三个就够,效果还行,但这种方法总感觉不够优雅。
3.通过墨卡托投影实现完美转换
@#%?!~反正没搞出来
4.通过两点间的经纬度与二维像素长度关系实现转换。这种方法不需要专业的gis投影知识也能简单实现。首先分析问题:①想要转换形变小,也就是三维地图中某区域大小形状在二维地图看也差不多,但毕竟一个是球体一个是平面,所以想全局无形变也是不现实的,所以我们的目标是让屏幕中心一块区域实现形变很小的转换。②球体的俯视图,使用九宫格划分后,它的中间一块接近一个平面,越靠近边缘倾角越大,所以我们只需要中间的区域的转换效果最佳。③区域的转换可以用固定线段的转换来模拟,因此我们在三维地球的中心区域取1/3可视地球高度的垂直线段,两端点分别为P1、P2。获取线段P1P2的长度h,转换为2维地图后,使P1、P2两坐标像素间距最接近h的zoom,即为所求。
解决方法:
①先通过viewer.camera.pickEllipsoid获取屏幕内可见地球的上下界坐标,然后取x=1/2屏幕宽,y分别是可见上下界1/3和2/3的两点P1、P2。

请添加图片描述

P1=[1/2viewer.camera.canvas.width,y1]
P2=[1/2
viewer.camera.canvas.width,y2]
此时P1,P2均为像素坐标
h = y2 - y1 = 1/3H’
然后通过viewer.camera.pickEllipsoid将屏幕坐标P1、P2转为经纬度坐标P1’,P2’
②leaflet的project方法可以获取指定坐标在指定zoom下的相对CRS原点P0的像素位置,使用for循环获得不同zoom下p1’与p2’的对应像素坐标P1’‘、P2’‘,因为两个坐标的x’‘相同,因此Math.abs(y2’‘-y1’‘)就是两点间像素距离h’,找到刚好h’≥h时的zoom,就是我们需要的结果。
请添加图片描述

3TO2


leaflet to cesium 示例代码

const bound = Lmap.getBounds();
Lmap.remove();
Lmap = null;
var Cmap = this.init3DMap([
	bound._southWest.lng,
    bound._southWest.lat,
    bound._northEast.lng,
    bound._northEast.lat
]);

init3DMap(defaultView){
	let map = new Cesium.Viewer("map", {
        imageryProvider: new Cesium.UrlTemplateImageryProvider({
            url: "http://localhost:8080/mapTiles/{z}/{x}/{y}.png"
        }),
        baseLayerPicker: false,
        animation: false,
        shouldAnimate: true,
        geocoder: false,
        navigationHelpButton: false,
        timeline: false,
        fullscreenButton: false,
        homeButton: false,
        infoBox: false,
        scene3DOnly: true,
        selectionIndicator: false,
        navigationInstructionsInitiallyVisible: false,
        useDefaultRenderLoop: true,
        showRenderLoopErrors: true,
        projectionPicker: false,
        vrButton: false
    });
    map._cesiumWidget._creditContainer.style.display = "none";
    if (defaultView) {
        map.camera.setView({
            destination: Cesium.Rectangle.fromDegrees(...defaultView)
        });
    } else {
        map.camera.setView({
            destination: Cesium.Cartesian3.fromDegrees(
                centerLng,
                centerLat,
                centerHei
            )
        });
    }
    defaultView = null
    return map
}

cesium to leaflet 示例代码

this.switchTo2D(Cmap).then(args => {
    Cmap.destroy();
    Cmap = null;
    var Lmap = this.init2DMap(args);
});

switchTo2D(map){
	return new Promise((resolve, reject) => {
        const ellipsoid = map.scene.globe.ellipsoid;
        const canvas = map.scene.canvas;
        let if_top = map.camera.pickEllipsoid(
            new Cesium.Cartesian2(canvas.width / 2, 0)
        );
        let if_bottom = map.camera.pickEllipsoid(
            new Cesium.Cartesian2(canvas.width / 2, canvas.height)
        );
        let top_view = 0;
        let bottom_view = canvas.height;
        if (if_top === undefined) {
            top_view = getViewBorder(
                map,
                [
                    [top_view, bottom_view]
                ],
                canvas.width / 2,
                ellipsoid,
                "top"
            );
        }
        if (if_bottom === undefined) {
            bottom_view = getViewBorder(
                map,
                [
                    [top_view, bottom_view]
                ],
                canvas.width / 2,
                ellipsoid,
                "bottom"
            );
        }
        let newCenterLng = centerLng
        let newCenterLat = centerLat
        let newCenterHei = centerHei
        if (top_view !== undefined && bottom_view !== undefined) {
            let lnglat = Cartesian2ToLngLat(
                map,
                [canvas.width / 2, (top_view + bottom_view) / 2],
                ellipsoid
            );
            //将相机高度传入,重新转换为笛卡尔三维坐标
            newCenterLng = (lnglat.longitude * 180) / Math.PI;
            newCenterLat = (lnglat.latitude * 180) / Math.PI;
            newCenterHei = map.camera.positionCartographic.height;
        }
        map.camera.flyTo({
            destination: Cesium.Cartesian3.fromDegrees(
                newCenterLng,
                newCenterLat,
                newCenterHei,
                ellipsoid
            ),
            duration: 1.5,
            complete: () => {
                let screenDistance = null;
                let topLngLat = [];
                let bottomLngLat = [];
                // 当视口内地球高度像素大于200px时,获取距离中心点1/6地球可视高度的上下两点处位置的坐标
                // 当地球高度像素小于等于200px时,只传入中心点,并让2维地图zoom为最小
                if (Math.abs(bottom_view - top_view) > 200) {
                    screenDistance = Math.abs(bottom_view - top_view) / 3;
                    const centerX = canvas.width / 2;
                    const centerY = (top_view + bottom_view) / 2;
                    let topPoint = Cartesian2ToLngLat(
                        map,
                        [centerX, centerY + screenDistance / 2],
                        ellipsoid
                    );
                    let bottomPoint = Cartesian2ToLngLat(
                        map,
                        [centerX, centerY - screenDistance / 2],
                        ellipsoid
                    );
                    topLngLat = [
                        (topPoint.longitude * 180) / Math.PI,
                        (topPoint.latitude * 180) / Math.PI
                    ];
                    bottomLngLat = [
                        (bottomPoint.longitude * 180) / Math.PI,
                        (bottomPoint.latitude * 180) / Math.PI
                    ];
                }
                map = null
                resolve([
                    [newCenterLng, newCenterLat],
                    topLngLat,
                    bottomLngLat,
                    screenDistance
                ])
            }
        });
    })
}

init2DMap(defaultView){
	let satelliteMap = L.tileLayer(
        "http://localhost:8080/mapTiles/{z}/{x}/{y}.png", {}
    );
    let center = [34.3227, 108.5525];
    const minZoom = 3,
        maxZoom = 9;
    let zoom = 5;
    let type = "satellite";
    if (defaultView) {
        center = [defaultView[0][1], defaultView[0][0]];
        zoom = minZoom;
        type = defaultView[4];
    }
    let map = L.map("map", {
        layers: satelliteMap, //默认卫星图
        center: center, // 地图中心
        zoom: zoom,
        minZoom: minZoom,
        maxZoom: maxZoom,
        zoomControl: false,
        trackResize: true,
        dragging: true,
        scrollWheelZoom: true,
        doubleClickZoom: false,
        attributionControl: false // 移除右下角leaflet标识
    });
    // 添加比例尺
    L.control
        .scale({
            maxWidth: 100,
            metric: true,
            imperial: false,
            updateWhenIdle: true,
            position: "bottomleft"
        })
        .addTo(map);
    if (defaultView && defaultView[3] !== null) {
        let topPoint,
            bottomPoint,
            flag = true;
        for (let zoom = minZoom; zoom <= maxZoom; zoom++) {
            topPoint = map.project([defaultView[1][1], defaultView[1][0]], zoom);
            bottomPoint = map.project(
                [defaultView[2][1], defaultView[2][0]],
                zoom
            );
            if (Math.abs(topPoint.y - bottomPoint.y) > defaultView[3]) {
                map.setZoomAround(center, zoom);
                flag = false;
                break;
            }
        }
        if (flag) {
            map.setZoomAround(center, maxZoom);
        }
    }
    defaultView = null
    return map
}
  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值