一种新的leaflet+cesium二三维切换的解决方案
基于vue的二三维地图切换
一.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/2viewer.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
}
更多推荐
所有评论(0)