简介
很多规划领域的用户在线修改规划设计方案时,需要修改建筑位置和朝向,想要进行平移旋转和改变基面高度操作,以重新放置建筑。
ArcGIS 10.8.1 发布的场景图层(Scene Layer)在自身的产品体系中,不具有在浏览器上进行放置编辑例如平移、旋转、拉高等整体编辑的功能。但由于场景图层遵循i3s(index 3d scene layer)开放标准,使得开发者可以自己开发这项功能。
获取所有节点的i3s obb
在上一篇文章中,我们已经介绍了如何将i3s的obb转换为Graphic。在实际使用中,必须同步编辑所有OBB才能实现Scene Layer的整体平移旋转。所以请求nodepage得到全部obb后,将每个obb转换为一个graphic,创建一个GraphicsLayer添加并容纳所有的graphic。
var satelliteLayer = new GraphicsLayer();
esriRequest(sceneLayerUrl + "/layers/0/nodepages/0", {
responseType: "json"
}).then(function(response) {
var nodepage = response.data.nodes;
console.log(response.data.nodes);
for (var i = 0; i < nodepage.length; i++) {
const obb = nodepage[i].obb;
var satelliteLoc = new Point({
type: "point",
// autocasts as new Point()
x: obb.center[0],
y: obb.center[1],
z: obb.center[2],
spatialReference: {
wkid: wkid
}
});
//console.log(obb);
//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);
if (satelliteLoc !== 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);
}
}
return satelliteLayer;
}).then(function(satelliteLayer) {
//obb对象的集合
});
创建Sketch微件
通常使用Sketch微件对于三维样式的Graphic进行编辑。由于被编辑的obb对象必须先请求服务器并且实例化为graphic后,才能指定Sketch微件以特定的模式开始编辑,所以这里使用异步的方法创建Sketch并且在异步的请求回调里对graphic指定编辑模式。
为了一次编辑操作,整体移动,只需绑定一个root节点的obb进行编辑,其他所有节点的obb应根据平移或旋转操作进行联动。root节点一般情况下nodepage.index==0即graphic.attributes.ObjectId == 0。
.then(function(satelliteLayer){
myFirstPromise.then(function(sketch){
let graphic=satelliteLayer.graphics.find(function(graphic){
return graphic.attributes.ObjectId === 0;
});
sketch.update(graphic,{
tool: "transform",
enableScaling: false,
multipleSelectionEnabled:false
});
});
});
let sketch;
var myFirstPromise = new Promise(function(resolve, reject){
sketch = new Sketch({
layer: satelliteLayer,
view: view
});
sketch.on("update", onMove);
sketch.on("undo",undo);
resolve(sketch);
});
现在,只需将代表根节点obb的Graphic进行平移旋转和改变基面高度的操作,对其他obb同步平移旋转改变基面高度,并且暂存,然后转化为i3s obb提交入库持久化存储即可。
编辑和联动计算
在创建Sketch时,已经指定了监听更新事件和撤销编辑事件的方法。首先开发编辑事件。编辑包括平移(水平和垂直)、旋转。
sketch.on("update", onMove);
sketch.on("undo",undo);
//撤销编辑用的有序json数组,每次操作加一条,每次撤销减去一条最新的。
let operationList=[];
//初始坐标
let ddx = 0;
let ddy = 0;
let ddz = 0;
//平移坐标差
let dddx = 0;
let dddy = 0;
let dddz = 0;
//旋转开始方向角
let dangleyuanshi = 0;
//旋转角度差
let dangle = 0;
let graplinshi1;
function onMove(event) {
// If the edge graphic is moving, keep the center graphic
if (event.toolEventInfo) {
const toolType = event.toolEventInfo.type;
if (toolType === "move-start") {}
else if (toolType === "move") {}
else if (toolType === "move-stop") {}
else if (toolType === "rotate-start") {}
else if (toolType === "rotate-stop") {}
if (event.state === "complete") {}
}
}
平移OBB
首先在平移开始时记录初始obb中心点坐标,在结束时,通过移动后的obb中心点坐标求出移动的坐标差包括x,y,z的差值。遍历所有OBB也就是GraphicsLayer,让所有OBB的中心点都加上x,y,z的差值,从而实现同步。最后记录move操作日志到operationList中。
if (toolType === "move-start") {
ddx = event.toolEventInfo.mover.geometry.x;
ddy = event.toolEventInfo.mover.geometry.y;
ddz = event.toolEventInfo.mover.geometry.z;
} else if (toolType === "move-stop") {
let grflinshi = event.toolEventInfo.mover;
dddx = grflinshi.geometry.x - ddx;
dddy = grflinshi.geometry.y - ddy;
dddz = grflinshi.geometry.z - ddz;
satelliteLayer.graphics.forEach(function(item, i) {
if (grflinshi.attributes['ObjectId'] != item.attributes['ObjectId']) {
//对item做了修改
item.geometry = new Point({
type: "point",
// autocasts as new Point()
x: item.geometry.x + dddx,
y: item.geometry.y + dddy,
z: item.geometry.z + dddz,
spatialReference: {
wkid: wkid
}
});
}
});
dangle = 0;
let operation1 = {
"caozuo": "move",
"dddx": dddx,
"dddy": dddy,
"dddz": dddz
}
operationList.push(operation1);
}
旋转OBB
首先在旋转开始时记录初始方向角heading(正北顺时针)。在旋转结束时记录结束时的方向角,求出角度差dangle。遍历所有非root节点,让他们的symbolLyaer的heading属性做同样旋转。
这时,使用高中数学知识推导以root节点obb的中心点为圆心,在平面上旋转heading角度后,求所有其他节点的坐标的方法。
求出其他节点obb的中心点在旋转后的坐标覆盖写入GraphicsLayer。最后将旋转操作记录到操作记录operationList中。
else if (toolType === "rotate-start") {
dangleyuanshi = event.graphics[0].symbol.symbolLayers.items[0].heading;
} else if (toolType === "rotate-stop") {
dangle = event.graphics[0].symbol.symbolLayers.items[0].heading - dangleyuanshi;
satelliteLayer.graphics.forEach(function(item, i) {
//对item做了修改
let grflinshi = event.graphics[0];
if (grflinshi.attributes['ObjectId'] != item.attributes['ObjectId']) {
let symbolyresp = {
type: "point-3d",
// autocasts as new PointSymbol3D()
symbolLayers: [{
type: "object",
width: item.symbol.symbolLayers.items[0].width,
height: item.symbol.symbolLayers.items[0].height,
depth: item.symbol.symbolLayers.items[0].depth,
resource: item.symbol.symbolLayers.items[0].resource,
material: item.symbol.symbolLayers.items[0].material,
tilt: item.symbol.symbolLayers.items[0].tilt,
//around x axis
roll: item.symbol.symbolLayers.items[0].roll,
// around the y axis
heading: item.symbol.symbolLayers.items[0].heading + dangle,
//around z
}]
}
item.symbol = symbolyresp;
let x = item.geometry.x;
let y = item.geometry.y;
let xr = grflinshi.geometry.x;
let yr = grflinshi.geometry.y;
let xnew = xr + (x - xr) * Math.cos(specificToRad( - dangle)) - (y - yr) * Math.sin(specificToRad( - dangle));
let ynew = yr + (x - xr) * Math.sin(specificToRad( - dangle)) + (y - yr) * Math.cos(specificToRad( - dangle));
item.geometry = new Point({
type: "point",
x: xnew,
y: ynew,
z: item.geometry.z,
spatialReference: {
wkid: wkid
}
});
}
});
dddx = 0;
dddy = 0;
let operation2 = {
"caozuo": "rotate",
"dangle": dangle
}
operationList.push(operation2);
}
撤销编辑
撤销编辑的联动实现原理是给"undo"事件绑定方法。利用操作记录列表operationList去还原以前的操作。分为撤销平移操作 和 撤销旋转操作。
//撤销编辑 弄个有序json数组,每次操作加一条,每次撤销减去一条最新的。
//ctrl+z
function undo(event) {
console.log(event);
if (event.tool == "transform") {
let operation1 = operationList[operationList.length - 1];
if (operation1.caozuo == "move") {
//撤销平移操作
} else if (operation1.caozuo == "rotate") {
//撤销旋转操作
}
}
}
撤销平移操作
针对平移操作。由于记录了xyz平移的差值。只需减去平移的差值,就可以还原平移前的obb。最后执行operationList.pop()方法删除已还原的操作记录。
//撤销编辑 使用有序json数组,每次操作加一条,每次撤销减去一条最新的。
//ctrl+z也可以触发
let operation1 = operationList[operationList.length - 1];
if (operation1.caozuo == "move") {
let grflinshi = event.graphics[0];
satelliteLayer.graphics.forEach(function(item, i) {
// Do something here to each graphic like calculate area of its geometry
//calculateArea(item.geometry);
//console.log(grfslinshi[0].attributes['ObjectId']);
if (grflinshi.attributes['ObjectId'] != item.attributes['ObjectId']) {
//对item做了修改
item.geometry = new Point({
type: "point",
// autocasts as new Point()
x: item.geometry.x - operation1.dddx,
y: item.geometry.y - operation1.dddy,
z: item.geometry.z - operation1.dddz,
spatialReference: {
wkid: wkid
}
});
}
});
operationList.pop();
}
撤销旋转操作。
针对旋转操作。由于记录了旋转的角度和方向(正和负),只需减去已旋转角度即可还原原始角度。而非root节点的obb还原其空间位置也是使用原来的旋转算法,传入负的dangle即可。
else if (operation1.caozuo == "rotate") {
console.log(operation1.dangle);
satelliteLayer.graphics.forEach(function(item, i) {
//对item做了修改
let grflinshi = event.graphics[0];
if (grflinshi.attributes['ObjectId'] != item.attributes['ObjectId']) {
//item.symbol.symbolLayers.items[0].heading=item.symbol.symbolLayers.items[0].heading+operation1.dangle
let symbolyresp = {
type: "point-3d",
// autocasts as new PointSymbol3D()
symbolLayers: [{
type: "object",
// autocasts as new ObjectSymbol3DLayer()
width: item.symbol.symbolLayers.items[0].width,
// diameter of the object from east to west in meters
height: item.symbol.symbolLayers.items[0].height,
// height of the object in meters
depth: item.symbol.symbolLayers.items[0].depth,
// diameter of the object from north to south in meters
resource: item.symbol.symbolLayers.items[0].resource,
material: item.symbol.symbolLayers.items[0].material,
tilt: item.symbol.symbolLayers.items[0].tilt,
//around x axis
roll: item.symbol.symbolLayers.items[0].roll,
// around the y axis
heading: item.symbol.symbolLayers.items[0].heading - operation1.dangle,
//around z
}]
};
item.symbol = symbolyresp;
let x = item.geometry.x;
let y = item.geometry.y;
let xr = grflinshi.geometry.x;
let yr = grflinshi.geometry.y;
let xnew = xr + (x - xr) * Math.cos(specificToRad(operation1.dangle)) - (y - yr) * Math.sin(specificToRad(operation1.dangle));
let ynew = yr + (x - xr) * Math.sin(specificToRad(operation1.dangle)) + (y - yr) * Math.cos(specificToRad(operation1.dangle));
item.geometry = new Point({
type: "point",
// autocasts as new Point()
x: xnew,
y: ynew,
z: item.geometry.z,
spatialReference: {
wkid: wkid
}
});
}
});
operationList.pop();
}
入库
入库是将编辑后的GraphicsLayer进行数据转换并且保存入CouchDB库。由于中间件Server(端口80/443/6080/6443)未提供任何增删改接口,只能直连CouchDB(端口29081/29080),对所有改变的obb进行修改。CouchDB原生支持JSON,数据录入还是非常方便的。当然,最安全的方式还是自己写个中间件。这里只说明原理。
入库前期准备
-
配置couchDB允许跨域访问。通过Datastore安装目录tools下的listadminusers命令获取tile cache库的admin用户名和密码,并在浏览器登录。
在Configuration中选择CORS,点击Enable CORS。开启CouchDB的跨域访问。
-
通过listmanageduser命令获得couchDB的user用户名密码。并记录到程序中。
访问ArcGIS Server的配置目录下面的
/directories/arcgiscache/Hosted/服务名称SceneServer下面的cache_config.json
例如:得到信息为
{"layers":[{"id":0,"uuid":"9d2024ca250b44c7828d3309b4cee2c8","cache_name":"building3dobject_wkid_slpk"}]}
Couchdb里db名称是这段json信息的拼接,拼接格式为cache_name+""+id+"_"+uuid 结果为building3dobject_wkid_slpk_0_9d2024ca250b44c7828d3309b4cee2c8
将这个db名称和其他所有信息一起填入程序中相应位置。
//服务地址
let sceneLayerUrl = "https://linux111.esrichina.com/server/rest/services/Hosted/Building3DObject_WKID/SceneServer";
//couchdb地址
let couchdb = "http://linux111.esrichina.com:29080/"
//couchDB的服务对应db的地址,通常位于:/directories/arcgiscache/Hosted/Building3DObject_WKID_SceneServer下面的cache_config.json
//{"layers":[{"id":0,"uuid":"9d2024ca250b44c7828d3309b4cee2c8","cache_name":"building3dobject_wkid_slpk"}]}
//db的拼接格式为cache_name+"_"+id+"_"+uuid
let db = couchdb + "building3dobject_wkid_slpk_0_9d2024ca250b44c7828d3309b4cee2c8/";
let username = "usr_29fbe";
let password = "ln5ff18p51";
入库代码实现
针对I3S的平移旋转后的obb入库,同事已经有基于python语言的成果。不妨拿来参考和改写为JavaScript语言。
python核心代码为
if 'nodepage' in name:
doc=d.get(i)
jsondoc=json.dumps(doc)
jsondict=json.loads(jsondoc)
nodes=jsondict['nodes']
for q in range(0,len(nodes)):
nodepageObbx=nodes[q]['obb']['center'][0]
nodepageObby=nodes[q]['obb']['center'][1]
nodepageObbz=nodes[q]['obb']['center'][2]
nodes[q]['obb']['center'][0]=nodepageObbx+xoffset
nodes[q]['obb']['center'][1]=nodepageObby+yoffset
nodes[q]['obb']['center'][2]=nodepageObbz+zoffset
# print(name,nodepageObbx,nodepageObby,nodepageObbz)
d.save(jsondict)
elif 'shared' not in name and 'resources' not in name and 'features' not in name and 'nodepage' not in name:
doc=d.get(i)
jsondoc=json.dumps(doc)
jsondict=json.loads(jsondoc)
try:
mbsx=jsondict['mbs'][0]
mbsy=jsondict['mbs'][1]
obbx=jsondict['obb']['center'][0]
obby=jsondict['obb']['center'][1]
jsondict['mbs'][0]=mbsx+xoffset
jsondict['mbs'][1]=mbsy+yoffset
jsondict['obb']['center'][0]=obbx+xoffset
jsondict['obb']['center'][1]=obby+yoffset
d.save(jsondict)
if jsondict['parentNode']:
try:
mbsx1=jsondict['parentNode']['mbs'][0]
mbsy1=jsondict['parentNode']['mbs'][1]
obbx1=jsondict['parentNode']['obb']['center'][0]
obby1=jsondict['parentNode']['obb']['center'][1]
jsondict['parentNode']['mbs'][0]=mbsx1+xoffset
jsondict['parentNode']['mbs'][1]=mbsy1+yoffset
jsondict['parentNode']['obb']['center'][0]=obbx1+xoffset
jsondict['parentNode']['obb']['center'][1]=obby1+yoffset
d.save(jsondict)
except:
continue
elif jsondict['children']:
count1=len(jsondict['children'])
for w in range(count1):
try:
mbsx3=jsondict['children'][w]['mbs'][0]
mbsy3=jsondict['children'][w]['mbs'][1]
obbx3=jsondict['children'][w]['obb']['center'][0]
obby3=jsondict['children'][w]['obb']['center'][1]
jsondict['children'][w]['mbs'][0]=mbsx3+xoffset
jsondict['children'][w]['mbs'][1]=mbsy3+yoffset
jsondict['children'][w]['obb']['center'][0]=obbx3+xoffset
jsondict['children'][w]['obb']['center'][0]=obby3+yoffset
d.save(jsondict)
except:
continue
except:
continue
改写为JavaScript代码。由于没有使用任何类库,是原生写的JavaScript,和导入了couchdb包的python代码的优雅性没法比。
大致思路是:首先,请求couchdb的登录方法得到session并写入cookie。
然后查询所有doc,一方面,找到nodepage的doc并查询内容,以GraphicsLayer为基础,改写nodepage里的所有obb信息,再写入覆盖nodepage;
另一方面,查询所有node节点,替换其中所有obb信息,具有父节点和子节点的node的obb也要根据i3s node的id和Graphics的objectId的对应情况将Graphics里的obb写入到couchdb的i3s node的obb里。
//服务地址
let sceneLayerUrl = "https://linux111.esrichina.com/server/rest/services/Hosted/Building3DObject_WKID/SceneServer";
//couchdb地址
let couchdb = "http://linux111.esrichina.com:29080/"
//couchDB的服务对应db的地址,通常位于:/directories/arcgiscache/Hosted/Building3DObject_WKID_SceneServer下面的cache_config.json
//{"layers":[{"id":0,"uuid":"9d2024ca250b44c7828d3309b4cee2c8","cache_name":"building3dobject_wkid_slpk"}]}
//db的拼接格式为cache_name+"_"+id+"_"+uuid
let db = couchdb + "building3dobject_wkid_slpk_0_9d2024ca250b44c7828d3309b4cee2c8/";
let username = "usr_29fbe";
let password = "ln5ff18p51";
function savedb() {
//satelliteLayer.graphics的修改在这里生效了吗
let grfs = satelliteLayer.graphics.toArray()
//在这里加循环,把graphics按id更上去
let jsonData = {
'username': username,
'password': password
};
fetch(couchdb + '_session', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
body: JSON.stringify(jsonData),
}).then(function(res1) {
ddx = 0;
ddy = 0;
dangle = 0;
//按py思路先查询所有doc
fetch(db + '_all_docs', {
method: 'GET',
credentials: 'include',
//body: JSON.stringify(jsonData),
}).then(function(res) {
return res.json().then((data) = >{
console.log(data.rows);
let docs = data.rows;
for (let i = 0; i < docs.length; i++) {
//找到nodepage并修改它
let name = docs[i].id;
if (name.indexOf("nodepage") > -1) {
console.log(docs[i].id);
//先查询再对应写
fetch(db + name, {
method: 'GET',
credentials: 'include',
//body: JSON.stringify(jsonData),
}).then(function(res) {
return res.json().then((data) = >{
console.log(data);
let nodepage1 = data.nodes;
for (let j = 0; j < nodepage1.length; j++) {
//event.graphics
for (let k = 0; k < grfs.length; k++) {
if (nodepage1[j].index == grfs[k].attributes.ObjectId) {
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]
}
//console.log(obbresp);
nodepage1[j].obb = obbresp;
}
}
if (j == nodepage1.length - 1) {
data.nodes = nodepage1;
return data
}
}
}).then((data) = >{
//这里就是改变后的data了。
console.log(data)
//得到了nodepage1再写入couchdb里
let jsonData1 = data fetch(db + name, {
method: 'PUT',
credentials: 'include',
body: JSON.stringify(jsonData1),
}).then(function(res) {
return res.json().then((data1) = >{
console.log(data1);
})
});
});
});
} else if (name.indexOf('shared') == -1 && name.indexOf('resources') == -1 && name.indexOf('features') == -1 && name.indexOf('nodepage') == -1) {
fetch(db + name, {
method: 'GET',
credentials: 'include',
//body: JSON.stringify(jsonData),
}).then(function(res) {
return res.json().then((data) = >{
for (let k = 0; k < grfs.length; k++) {
let grf = grfs[k];
if ((data.id == "root" && grf.attributes.ObjectId == 0) || (parseInt(data.id) + 1) == grf.attributes.ObjectId) {
console.log(parseInt(data.id) + 1);
data.mbs[0] = grf.geometry.x data.mbs[1] = grf.geometry.y data.mbs[2] = grf.geometry.z 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]
}
data.obb = obbresp;
}
if (data.hasOwnProperty("parentNode")) {
if ((data.parentNode.id == "root" && grf.attributes.ObjectId == 0) || (parseInt(data.parentNode.id) + 1) == grf.attributes.ObjectId) {
data.parentNode.mbs[0] = grf.geometry.x data.parentNode.mbs[1] = grf.geometry.y data.parentNode.mbs[2] = grf.geometry.z 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]
}
data.parentNode.obb = obbresp;
}
}
if (data.hasOwnProperty("children")) {
for (let l = 0; l < data.children.length; l++) {
let children = data.children[l];
if ((children.id == "root" && grf.attributes.ObjectId == 0) || (parseInt(children.id) + 1) == grf.attributes.ObjectId) {
children.mbs[0] = grf.geometry.x children.mbs[1] = grf.geometry.y children.mbs[2] = grf.geometry.z 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]
}
children.obb = obbresp;
}
}
}
if (k == grfs.length - 1) {
return data;
}
}
}).then((data) = >{
//这里就是改变后的data了。
console.log(data)
//得到了nodepage1再写入couchdb里
let jsonData1 = data fetch(db + name, {
method: 'PUT',
credentials: 'include',
body: JSON.stringify(jsonData1),
}).then(function(res) {
return res.json().then((data1) = >{
console.log(data1);
})
});
});
})
}
}
})
});
});
}
成果展示
1.首先介绍右上角的4个功能操作按钮。
(1)开始编辑。点击后会发送nodepage的请求,获取每个node的obb并放在一个GraphicsLayer里。并且激活Sketch微件,可以进行拖拽、旋转等操作。
(2)保存编辑。将编辑后的GraphicsLayer转换为I3S Node的obb和obs的相关信息,并入库。
(3)撤销上一步操作。在编辑过程中,如果想要撤销上一步,可以点击此按钮。激活sketch的undo事件,实际上编辑过程中点击ctrl+z也是可以撤销上一步操作的。可多次撤销。注意:如果点击了空白处相当于触发了Sketch的编辑完成的事件,之前的操作不可撤销。如需撤销,请刷新该网页。
(4)移除定向包围盒。可以移除掉开始编辑时加载的定向包围盒。
2.平移操作
鼠标放在内圈,可出现东西南北四个方向标,点击方向标拖拽可按一个方向定向位移。松开鼠标,平移停止。
同时,也可以点选中间的橘黄色实心圆,任意拖动,平移场景图层的OBB。松开鼠标,平移停止。
长按中间的小白点,可以延Z轴上下平移。
2.旋转操作
长按外圈可以顺时针和逆时针旋转。松开鼠标,旋转停止。
3.撤销上一步编辑操作
前提:Sketch微件必须在激活状态才能依次撤销本次编辑的所有步骤。
参考sketch的API:https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Sketch.html
4.点击保存即可保存SceneLayer。但需要注意,保存的效果虽然在服务器端立即生效,并且在其他初次浏览这个模型的用户那里生效。对于此浏览器的用户来说,需清除浏览器缓存后才会生效。