背景知识
首先了解一下i3s的基本概念以及树状结构的介绍。参考资料:https://github.com/Esri/i3s-spec
索引3D场景图层(i3s)格式是一种开放的3D内容交付格式,用于快速流式传输和分发大量3D GIS数据到移动、Web和桌面客户端。ArcGIS场景层和场景服务使用i3s基础结构。场景图层为客户提供了一种结构化的方式来存储和可视化大量3D数据。i3s将信息组织到节点层次结构中,这些节点层次结构包含具有几何,纹理和属性的要素。
树状结构:为确保可视化3D内容时的高性能,数据在空间上分为节点。递归地重复分组过程以创建节点树。给定节点的空间范围包含其所有子级,以创建边界体积层次结构。支持数据的空间规则(例如四叉树)和空间不规则(例如R树)组织。
边界体积定义为最小边界球(MBS)或定向边界盒(OBB)表示。
OBB是更理想的表示形式,建议实现者以OBB格式输出节点边界量。点云配置文件仅支持OBB表示。
为了提供原始数据的可伸缩表示,父节点包含其子级的简化表示,从而创建了Level of Details(LOD)。
定向边界盒(Oriented Bounding Box)
定向边界盒(OBB)是紧凑的边界体积表示形式,与它所表示的几何形状紧密匹配。OBB对平移和旋转的不变性使其成为i3s中最佳和默认边界体积表示形式的理想选择。
构造供i3s使用的OBB时,需要基于该层的坐标参考系统(CRS)来考虑实现者两个方面:
为全球场景图层构造OBB
在WGS84地理坐标系的一个i3s图层中,被称为全球场景图层和通过的4326的WKID值标识:
- OBB应该在与图层的CRS等效的地心固定地球坐标系(ECEF)中构建。
- OBB的中心部分指定为经度,纬度(以十进制度度)和以米为单位的高程(z)。
- OBB的halfSize组件以米为单位指定。
- 四元数分量参考ECEF坐标系。
为全局场景层构造OBB时,必须执行以下步骤:
- 将感兴趣的顶点投影到ECEF。
- 在ECEF中计算OBB。
- 仅将OBB的中心从ECEF转换回该图层的CRS。
为本地场景图层构造OBB
对于i3s层,具有CRS 其他比4326 WKID值,被称为本地场景图层:
- OBB应该在与图层相同的CRS中构建。
- OBB的center和halfSize组件的单位应为CRS的单位。
- 参考该图层的CRS来指定中心和四元数。
上面的全局场景层用例中描述的OBB构造步骤也适用于此。但是,对于局部场景层,顶点,OBB计算和所得的OBB分量都保留在该层的CRS中。OBB组件的单位与图层的单位相同。
OBB对象的属性
属性 | 类型 | 描述 |
---|---|---|
center |
number[3] |
以CRS单位指定的定向边界盒的中心点。对于全局场景图层,将中心指定为经度和纬度(以十进制度为单位),以及高程(以米为单位)。 |
harfSize | number[3] | 以CRS为单位的定向边界盒的一半大小。对于全局场景层,所有值均以米为单位。 |
quaternion | number[4] | 定向边界盒的定向为4分量四元数。对于全局场景层,四元数位于以地球为中心,固定于地球的(ECEF)笛卡尔空间中。(Z +:北,Y +:东,X +:lon = lat = 0.0)。注意:四元数是一个四元素矢量,可用于编码3D坐标系中的任何旋转。四元数分量的顺序为x,y,z,w。 |
示例
/*局部场景图层 (Lambert, Wkid: 2227)*/
{
"center": [
6011913.2692229711,
2117599.0498975096,
441.1241036703866
],
"halfSize": [
100.45386505126953,
91.120384216308594,
426.03338623046875
],
"quaternion": [
0.64432936906814575,
0.76474469900131226,
-0.0020481476094573736,
0.0010012148413807154
]
}
/*全球场景图层 (WSG84, wkid: 4326)*/
{
"center": [
-122.40277014424709,
37.795204290863012,
134.5439856108278
],
"halfSize": [
30.701572418212891,
27.71544075012207,
129.72760009765625
],
"quaternion": [
-0.50688880681991577,
0.74475228786468506,
0.1719556450843811,
0.39854612946510315
]
}
定向边界盒(OBB)前端实体类
由于定向边界盒(OBB)决定着模型的整体位置、朝向。为了能让用户在网页端进行场景图层的平移、旋转、改变高度等交互式操作,选择作为定向边界盒(OBB)的前端实体类的条件为:
- 可视化
- 可交互
- 可转换
由于需要实现一个盒(Box),最初尝试使用ArcGIS API for JavaScript的Mesh几何类型的Box来实现Oriented Bounding Box。
require(["esri/geometry/Mesh"], function(Mesh) { /* code goes here */ });
// Create a box mesh geometry
var mesh = Mesh.createBox(location, {
size: {
width: 100,
height: 50,
depth: 50
},
material: {
color: "red"
}
});
// Create a graphic and add it to the view
var graphic = new Graphic({
geometry: mesh,
symbol: {
type: "mesh-3d",
symbolLayers: [ { type: "fill" } ]
}
});
但是发现Mesh的Box盒没有方向属性,无法模拟"定向"边界盒。并且没有现成的交互编辑微件。所以几何类型Mesh是无法胜任这项工作的。
然后,在示例Edit features in 3D with the Editor widget中,我找到了灵感。点要素Graphic在三维中通过设置PointSymbol3D并进一步设置为ObjectSymbol3DLayer可以被编辑、可以作为三维盒可视化。
ObjectSymbol3DLayer
ObjectSymbol3DLayer用于在SceneView中使用带有PointSymbol3D的体积3D形状(例如,球体或圆柱体)来渲染Point几何。列举一下用到的属性。
属性 | 类型 | 示例 | 描述 |
---|---|---|---|
resource | Object | {primitive: "cube"} | 用于使点可视化的原始形状(primitive)或外部3D模型(href)。 |
width | Number | 10 | 物体的宽度或直径,从东到西,以米为单位。 |
depth | Number | 10 | 从北到南的深度或直径,以米为单位。 |
height | Number | 10 | 对象的高度(以米为单位)。 |
material | Object | {color: [51, 204, 51, 0.3]} | 用于为对象着色的材料。此属性定义对象的颜色。 |
tilt | Number | 45 | 符号在纵向垂直平面中(即绕x轴)的旋转。旋转以度为单位指定,并且相对于y轴。在0度时,模型是水平的。正值点将抬高模型的前面,并降低模型的后面。如果将符号资源对齐,使其前向侧面指向y轴方向(y轴始终在WGS84或WebMercator坐标中指向北,则其朝上的一面指向z轴方向,并且其右侧指向x轴方向(x轴始终在WGS84或WebMercator坐标中指向东),因此该角度对应于符号的tilt。 |
roll | Number | 90 | 符号在横向垂直平面(即绕y轴)中的旋转。旋转以度为单位指定,并且相对于x轴。在0度时,模型是水平的。正值可提升模型的左侧部分,并降低其右侧部分。如果将符号资源对齐,使其朝前的侧面指向y轴的方向(y轴始终在WGS84或WebMercator坐标中指向北,则其朝上的一面指向z轴的方向,并且其右侧指向x轴方向(x轴始终在WGS84或WebMercator坐标中指向东),因此该角度对应于符号的roll。 |
heading | Number | 180 | 符号在水平面(即绕z轴)中的顺时针旋转。旋转以度为单位指定,并且相对于y轴。如果将符号资源对齐,使其前向侧面指向y轴方向(y轴始终在WGS84或WebMercator坐标中指向北,则其朝上的一面指向z轴方向,并且其右侧指向x轴方向(x轴始终在WGS84或WebMercator坐标中指向东),因此该角度对应于符号的heading。 |
示例
let graphic = new Graphic({
geometry: point,
symbol: {
type: "point-3d", // autocasts as new PointSymbol3D()
symbolLayers: [{
type: "object", // autocasts as new ObjectSymbol3DLayer()
width: 5, // diameter of the object from east to west in meters
height: 20, // height of the object in meters
depth: 15, // diameter of the object from north to south in meters
resource: { primitive: "cube" },
material: { color: [255,0,0,0.5] }, //0.5 transparent
tilt:45,//around x axis degrees
roll:0,// around the y axis degrees
heading:90,//around z degrees
}]
},
attributes: {
ObjectId: nodepage[i].index,
all:nodepage[i]
}
});
ObjectSymbol3DLayer满足功能需求,接下来就是转换问题了。
i3s obb 转换为ObjectSymbol3DLayer的Graphic
想要将一个obb对象变为前端带有立方体样式的Graphic。分为位置转换、形状转换、角度转换(四元数转欧拉角)。
位置转换
将i3s obb的center(中心点)赋值一个Point对象即可,坐标系取场景图层的坐标系。
var point=new Point({
type: "point", // autocasts as new Point()
x: obb.center[0],
y: obb.center[1],
z: obb.center[2],
spatialReference:{wkid:wkid}
});
形状转换
将i3s obb的halfSize(半长)乘2,分别赋值给ObjectSymbol3DLayer的width、depth、height即可。
width: obb.halfSize[0]*2, // diameter of the object from east to west in meters
depth: obb.halfSize[1]*2, // diameter of the object from north to south in meters
height: obb.halfSize[2]*2, // height of the object in meters
角度转换
将i3s obb的quaternion(四元数)转换为欧拉角再由弧度转角度。赋值给ObjectSymbol3DLayer的tilt、roll、heading。这里引入three.js的四元数转欧拉角的方法。
//Three.js四元数转欧拉角
var vectorQuaternion = new THREE.Quaternion();
vectorQuaternion.w = obb.quaternion[3];
vectorQuaternion.x = obb.quaternion[0];
vectorQuaternion.y = obb.quaternion[1];
vectorQuaternion.z = obb.quaternion[2];
var vectorEuler = new THREE.Euler(0, 0, 0, eulerOrder);
vectorEuler.setFromQuaternion(vectorQuaternion, eulerOrder);
//弧度转角度degree的方法
function radToSpecific(radiansIn) {
if (eulerAngleFormat == "Radians") {return radiansIn;}
return THREE.Math.radToDeg(radiansIn);
}
//样式赋值
symbol:{
tilt:radToSpecific(vectorEuler.x),//around x axis
roll:radToSpecific(vectorEuler.y),
heading:radToSpecific(vectorEuler.z),
}
汇总上述三个属性的转换,完整代码如下:
//将i3s obb转换为Graphic的完整代码
const obb=nodepage[i].obb;
var point=new Point({
type: "point", // autocasts as new Point()
x: obb.center[0],
y: obb.center[1],
z: obb.center[2],
spatialReference:{wkid:wkid}
});
var vectorQuaternion = new THREE.Quaternion();
vectorQuaternion.w = obb.quaternion[3];
vectorQuaternion.x = obb.quaternion[0];
vectorQuaternion.y = obb.quaternion[1];
vectorQuaternion.z = obb.quaternion[2];
var vectorEuler = new THREE.Euler(0, 0, 0, eulerOrder);
vectorEuler.setFromQuaternion(vectorQuaternion, eulerOrder);
if (point !== null) {
let graphic = new Graphic({
geometry: satelliteLoc,
symbol: {
type: "point-3d", // autocasts as new PointSymbol3D()
symbolLayers: [{
type: "object", // autocasts as new ObjectSymbol3DLayer()
width: obb.halfSize[0]*2, // diameter of the object from east to west in meters
height: obb.halfSize[2]*2, // height of the object in meters
depth: obb.halfSize[1]*2, // diameter of the object from north to south in meters
resource: { primitive: "cube" },
material: { color: [255,0,0,0.5] },
tilt:radToSpecific(vectorEuler.x),//around x axis
roll:radToSpecific(vectorEuler.y),// around the y axis
heading:radToSpecific(vectorEuler.z),//around z
}]
},
attributes: {
ObjectId: nodepage[i].index,
all:nodepage[i]
}
});
satelliteLayer.add(graphic);
}
ObjectSymbol3DLayer的Graphic转换为i3s obb
在前端对obb进行各种变换(如旋转、平移、拉高)的业务结束后,i3s obb最终还是需要以center、halfSize、quaternion的三个属性入库来持久化obb的变更。这时就需要把Graphic对象反向转换回i3s obb json对象里去。和上面同理但过程相反。
//角度转弧度方法
function specificToRad(DegreesIn) {
if (eulerAngleFormat == "Radians") {return DegreesIn;}
return THREE.Math.degToRad(DegreesIn);
}
//将名叫grf的graphic赋值给obb
let grf=grfs[k];
let symbolLayer=grf.symbol.symbolLayers.items[0]
let vectorQuaternionresp = new THREE.Quaternion();
let vectorEulerresp = new THREE.Euler(specificToRad(symbolLayer.tilt),
specificToRad(symbolLayer.roll),
specificToRad(symbolLayer.heading),
eulerOrder);
vectorQuaternionresp.setFromEuler(vectorEulerresp);
let obbresp={
"center":[grf.geometry.x, grf.geometry.y, grf.geometry.z],
"halfSize":[symbolLayer.width/2, symbolLayer.height/2, symbolLayer.depth/2],
"quaternion":[vectorQuaternionresp.x, vectorQuaternionresp.y,vectorQuaternionresp.z,vectorQuaternionresp.w]
}
总结
将obb在前端实例化,然后持久化,是实现index 3D SceneLayer在前端整体交互操作的前提。后续我还将介绍如何在Web端交互操作i3s obb 实现已发布的场景图层通过web端进行平移旋转拉高等编辑功能。