-
Notifications
You must be signed in to change notification settings - Fork 3
Cesium
一般使用的是WGS84坐标系统, 例如纬度经度: 121.631568,30.041551
. 而Cesium中通常使用Cartesian3(世界坐标)和Cartographic(绘图坐标)坐标系统, 需要注意他们之间的转换. Cartographic基本上等同于WGS84坐标系统, 但纬度和经度单位不同, 高度单位都是米! 纬度和经度的单位在Cartographic中是弧度, 在WGS84中是度, 例如度 121.631568,30.041551
对应的弧度为 2.1228713359633744,0.524323977355795
.
- 度转弧度:
Cesium.Math.toRadians(degrees)
- 弧度转度:
Cesium.Math.toDegrees(radians)
- 度:
Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
- 弧度:
Cesium.Cartesian3.fromRadians(longitude, latitude, height)
- Cartographic转Cartesian3:
Cesium.Cartographic.toCartesian(Cartographic)
- Cartesian3转Cartographic:
Cesium.Cartographic.fromCartesian(Cartesian3)
或Cesium.Ellipsoid.WGS84.cartesianToCartographic(Cartesian3)
viewer.scene.cartesianToCanvasCoordinates(cartesian3)
当需要在指定cartesian3位置叠加显示HTML元素时, 需要添加事件来实现. 因为一旦地图移动, cartesian3对应的场景Canvas坐标是会变化的.
//注意:有图片时需要在图片加载完毕后再定位,否则位置不准.
//将元素在视图中定位
viewer.scene.preRender.addEventListener(() => {
let canvasPosition = viewer.scene.cartesianToCanvasCoordinates(cartesian3);
if (Cesium.defined(canvasPosition)) {
div.style.top = canvasPosition.y + 'px';
div.style.left = canvasPosition.x + 'px';
div.style.marginTop = `-${div.clientHeight}px`;
}
});
注意: 测试发现如果在3D Tiles模型上选取cartesian3, 当移动地图或旋转相机时无法与预期坐标对应上, 会发生偏移!
例如相机Cesium.Camera的positionWC是Cartesian3, 要获取WGS84则需要先将其转为Cartographic, 然后再从弧度转度.
let viewer = new Cesium.Viewer(
'容器元素id',
{
homeButton: false,
geocoder: false,
baseLayerPicker: false,
sceneModePicker: false,
navigationHelpButton: false,
animation: false,
timeline: false,
selectionIndicator: false,
infoBox: false
}
);
- 移除所有底图
viewer.scene.imageryLayers.removeAll();
- 设置地球颜色
viewer.scene.globe.baseColor = Cesium.Color.GRAY;
/**
* 获取相机位置
* @param camera 相机
* @returns {Readonly<{destination: {longitude: *, latitude: *, height: *}, orientation: {heading: *, pitch: *, roll: *, direction: any, up: any}}>}
* 设置位置方法见https://127.0.0.1/Cesium/Build/Documentation/Camera.html?classFilter=camera#setView
*/
function getCameraPosition(camera) {
let cartographicPosition = Cesium.Ellipsoid.WGS84.cartesianToCartographic(camera._positionWC);
let longitude = Cesium.Math.toDegrees(cartographicPosition.longitude);
let latitude = Cesium.Math.toDegrees(cartographicPosition.latitude);
let height = cartographicPosition.height;
console.debug('坐标:', longitude, ',', latitude, ',', height);
let heading = camera.heading;
let pitch = camera.pitch;
let roll = camera.roll;
console.debug(
'取向( 弧度,转角度Cesium.Math.toDegrees() )-heading:',
heading,
',pitch:',
pitch,
',roll:',
roll
);
console.debug(
'取向( 世界坐标 )-direction:',
JSON.stringify(camera.directionWC),
',up:',
JSON.stringify(camera.upWC)
);
return Object.freeze({
destination: {
longitude: longitude,
latitude: latitude,
height: height
},
orientation: {
heading: heading,
pitch: pitch,
roll: roll,
direction: JSON.parse(
JSON.stringify(camera.directionWC)
),
up: JSON.parse(
JSON.stringify(camera.upWC)
)
}
});
}
https://127.0.0.1/Cesium/Build/Documentation/Camera.html?classFilter=camera#setView
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(99.9999, 29.9999, 12),
orientation: {
//使用单位为弧度:Radians, 弧度转角度使用https://127.0.0.1/Cesium/Build/Documentation/Math.html?classFilter=Math#.toDegrees
heading: Cesium.Math.toRadians(42), // 水平, default value is 0.0 (north)
//使用单位为弧度:Radians, 弧度转角度使用https://127.0.0.1/Cesium/Build/Documentation/Math.html?classFilter=Math#.toDegrees
pitch: Cesium.Math.toRadians(-16), // 垂直, default value (looking down)
roll: 0 // 倾斜, default value
}
});
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(-75.60556729521, 40.03367068884268, 10.787266059890012),
orientation: {
direction: {x: -0.7972709114482522, y: 0.34092160817878747, z: 0.4981280466258275},
up: {x: 0.12231562048372338, y: -0.7168662684303597, z: 0.6863975831632955}
}
});
以 Cesium.ScreenSpaceEventType.LEFT_CLICK
事件为例, 如果是 Cesium.ScreenSpaceEventType.MOUSE_MOVE
需将 movement.position
替换为 movement.endPosition
.
let cartesian3 = viewer.camera.pickEllipsoid(movement.position, viewer.scene.globe.ellipsoid);
let ray = viewer.camera.getPickRay(movement.position);
let cartesian3 = viewer.scene.globe.pick(ray, viewer.scene);
//选取
let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
//鼠标左键点击时
handler.setInputAction((movement) => {
let feature = viewer.scene.pick(movement.position);
console.debug('鼠标左键点击', feature);
if (!Cesium.defined(feature) || !(feature.id instanceof Cesium.Entity)) {
return;
}
let entity = feature.id;
//显示属性
console.debug('模型属性:', entity.properties);
entity.propertyNames.forEach(name => {
//注意:这里暂时存在问题.
console.debug(name, entity.properties(name));
});
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//模型选取
let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
let handlerTemp = {};
//鼠标左键点击模型时
handler.setInputAction((movement) => {
let feature = viewer.scene.pick(movement.position);
if (!Cesium.defined(feature) || !(feature instanceof Cesium.Cesium3DTileFeature)) {
return;
}
//颜色设为亮绿
if (handlerTemp.lastClickFeature) {
handlerTemp.lastClickFeature.color = feature.color;
}
feature.color = Cesium.Color.GREENYELLOW;
viewer.scene.requestRender(); //当viewer.scene.requestRenderMode = true时必须调用才立即生效
handlerTemp.lastClickFeature = feature;
//显示属性
console.debug('模型属性:');
feature.getPropertyNames().forEach(name => {
console.debug(name, feature.getProperty(name));
});
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
https://github.com/PrincessGod/objTo3d-tiles
注意:安装后Ubuntu无法运行,提示
/usr/bin/env: "node\r": 没有那个文件或目录
. 解决方法(参考 https://github.com/PrincessGod/objTo3d-tiles/issues/19#issuecomment-415023540 )如下:
$ sudo vim /usr/lib/node_modules/obj23dtiles/bin/obj23dtiles.js
输入
:se ff=unix
:wq
处理之后就OK了.
注意:需要真实地理坐标时, 必须设置customTilesetOptions.json
.
{
"longitude": 1.998183827377008, // Tile origin's(models' point (0,0,0)) longitude in radian.
"latitude": 0.5319920639711395, // Tile origin's latitude(models' point (0,0,0)) in radian.
"transHeight": 0.0, // Tile origin's height in meters.
"region": true, // Using region bounding volume.
"box": false, // Using box bounding volume.
"sphere": false // Using sphere bounding volume.
}
注意:纬度经度单位为弧度. transHeight为原点的高度, 如果不设置模型将放置在地球表面(即height=0). 上面的注释需要从json文件中去掉, 仅仅是为了说明.
obj23dtiles -p customTilesetOptions.json --tileset --outputBatchTable --checkTransparency -i /path/to/box.obj
如包含透明纹理, 应加参数
--checkTransparency
. 如使用PBR材质, 应加--useOcclusion
参数.
特别注意, 完全可以在OBJ to 3D Tiles(使用objTo3d-tiles)时设置地理位置(通过customTilesetOptions.json). 这样就无需下面两种方案了, 模型可以正确摆放. terrainProvider
方案时必须联网使用Cesium地形, modelMatrix
方案模型会出现翻转(试过很多种方法都无法掰正...).
注意: 此方式必须联网!!!感谢https://github.com/AnalyticalGraphicsInc/3d-tiles-tools/issues/20#issuecomment-399687836, 解决了通过modelMatrix设置位置时模型翻转问题. 但
Cesium.createWorldTerrain()
需要联网加载cesium的资源.
创建Cesium.Viewer时设置terrainProvider: Cesium.createWorldTerrain()
let viewer = new Cesium.Viewer(
containerId,
{
terrainProvider: Cesium.createWorldTerrain()
}
);
//不设置3D Tiles将自动位于地上(位于地下时容易丢失模型) 文档:Documentation/Globe.html#depthTestAgainstTerrain
viewer.scene.globe.depthTestAgainstTerrain = true;
tileset就绪后设置制图坐标
tileset.readyPromise
.then((tileset) => {
let cartographic = Cesium.Cartographic.fromDegrees(114.4875, 30.4809);
Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [cartographic]).then(function() {
// cartographic.height is updated
let cartesian = Cesium.Cartographic.toCartesian(cartographic);
let transform = Cesium.Transforms.headingPitchRollToFixedFrame(cartesian, new Cesium.HeadingPitchRoll());
tileset._root.transform = transform;
viewer.zoomTo(tileset);
});
});
这样3D Tiles模型就显示在预期位置114.4875, 30.4809
了.
这种方式存在的问题时,模型的xyz方位不对. 测试模型垂直戳在地上, 试过多种方式均没能成功调整. 后来发现根本原因是使用objTo3d-tiles
转换时没有设置地理位置, 于是工具自己给了一个默认transform. 这个transform很难被掰正...
tileset.readyPromise
.then((tileset) => {
let boundingSphere = tileset.boundingSphere;
console.debug('当前原点笛卡尔:', boundingSphere.center);
//获取原3d笛卡尔点后,转换为制图实例
let centerCartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);
console.debug('当前原点制图:', centerCartographic);
//将高度设置为0后,创建3d笛卡尔点
let centerCartesian3 = Cesium.Cartesian3.fromRadians(centerCartographic.longitude, centerCartographic.latitude, 0);
console.debug('当前原点笛卡尔(高度为0):', centerCartesian3);
//创建需要放置地点3d笛卡尔点
let realPlaceCartesian3 = Cesium.Cartesian3.fromDegrees(114.4875, 30.4809, 0);
console.debug('实际原点笛卡尔:', realPlaceCartesian3);
//计算两个3d笛卡尔点的分量差(注意:调整后位置为left参数.)
let translationCartesian3 = Cesium.Cartesian3.subtract(realPlaceCartesian3, centerCartesian3, new Cesium.Cartesian3());
console.debug('当前与实际原点笛卡尔分量差:', translationCartesian3);
//设置4x4变换矩阵
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translationCartesian3);
// console.warn('m:', tileset.modelMatrix);
// console.warn('c:', tileset.boundingSphere.center);
// let modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(
// tileset.boundingSphere.center
// );
// console.warn('m:', modelMatrix);
// tileset.modelMatrix = modelMatrix;
// console.warn('c:', tileset.boundingSphere.center);
// //获取角度
// let centerMatrix3 = new Cesium.Matrix3();
// Cesium.Matrix4.getRotation(tileset.modelMatrix, centerMatrix3);
// console.warn('当前:', centerMatrix3);
//
// //准备旋转角度
// let realMatrix3 = Cesium.Matrix3.fromRotationX(
// Cesium.Math.toRadians(10)
// );
// console.warn('实际:', realMatrix3);
//
// //计算
// let translationMatrix3 = Cesium.Matrix3.subtract(realMatrix3, centerMatrix3, new Cesium.Matrix3());
// console.warn('分量差:', translationMatrix3);
//
// //设置
// let m = tileset.modelMatrix;
// Cesium.Matrix4.multiplyByMatrix3(m, centerMatrix3, m);
// tileset.modelMatrix = m;
// //旋转模型角度
// let mx = Cesium.Matrix3.fromRotationX(
// Cesium.Math.toRadians(0.05)
// );
// tileset.modelMatrix = Cesium.Matrix4.fromRotationTranslation(mx);
// let m = tileset.modelMatrix;
// let rotateMatrix = Cesium.Matrix3.fromRotationZ(
// Cesium.Math.toRadians(0.05)
// );
// Cesium.Matrix4.multiplyByMatrix3(m, rotateMatrix, m);
// tileset.modelMatrix = m;
//显示到瓦片
viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.0, -0.5, tileset.boundingSphere.radius * 2.0));
});
注意下面注释掉片段, 为试过的4种调整模型方位方法. 基本上都是将模型的位置改了, 而且移动模型就不见了.
使用Cesium3DTileContent.getFeature(batchId)
即可获取. Cesium3DTileContent可以通过Cesium3DTile.content
获取. 这个信息可以在转换b3dm时输出batchTable.json来查看, batchId就是每个mesh在b3dm中的索引位置. 一般有一个name数组与其对应, name数组中存储着模型的名称. 所以我们通过模型名称找到其在name数组中的索引, 然后就可以确定batchId. 也可以直接遍历, 通过Cesium3DTileContent.featuresLength
可以获取mesh总数.
objTo3d-tiles https://github.com/PrincessGod/objTo3d-tiles
流程如下:
tileset.tileLoad.addEventListener((tile) => {
let featuresLength = tile.content.featuresLength;
console.debug('瓦片Feature数量', featuresLength);
});
这样就拿到Cesium3DTile了, 然后通过tile.content.getFeature(batchId)
就可以获取指定mesh了. 然后我们可以设置颜色, 比如tile.content.getFeature(batchId).color = Cesium.Color.GREENYELLOW;