基于i3s obb的在线编辑场景图层放置

简介

很多规划领域的用户在线修改规划设计方案时,需要修改建筑位置和朝向,想要进行平移旋转和改变基面高度操作,以重新放置建筑。
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角度后,求所有其他节点的坐标的方法。


计算一点绕另一点旋转angle度后的坐标.png(来源CSDN)

求出其他节点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,数据录入还是非常方便的。当然,最安全的方式还是自己写个中间件。这里只说明原理。

入库前期准备

  1. 配置couchDB允许跨域访问。通过Datastore安装目录tools下的listadminusers命令获取tile cache库的admin用户名和密码,并在浏览器登录。


    获得CouchDB的管理员用户密码

在Configuration中选择CORS,点击Enable CORS。开启CouchDB的跨域访问。


开启CouchDB跨域访问
  1. 通过listmanageduser命令获得couchDB的user用户名密码。并记录到程序中。


    获得CouchDB使用者的用户名密码
  2. 访问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)移除定向包围盒。可以移除掉开始编辑时加载的定向包围盒。


4个按钮

2.平移操作

鼠标放在内圈,可出现东西南北四个方向标,点击方向标拖拽可按一个方向定向位移。松开鼠标,平移停止。


平移操作

同时,也可以点选中间的橘黄色实心圆,任意拖动,平移场景图层的OBB。松开鼠标,平移停止。


选中橘黄色实心圆

长按中间的小白点,可以延Z轴上下平移。

沿Z轴平移

2.旋转操作

长按外圈可以顺时针和逆时针旋转。松开鼠标,旋转停止。


旋转操作

3.撤销上一步编辑操作

前提:Sketch微件必须在激活状态才能依次撤销本次编辑的所有步骤。

参考sketch的API:https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Sketch.html

4.点击保存即可保存SceneLayer。但需要注意,保存的效果虽然在服务器端立即生效,并且在其他初次浏览这个模型的用户那里生效。对于此浏览器的用户来说,需清除浏览器缓存后才会生效。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343