首页 > 建站教程 > 地图,GIS教程 >  解决Cesium在3Dtiles上拾取坐标不准确的问题正文

解决Cesium在3Dtiles上拾取坐标不准确的问题

我爱模板网要在3Dtiles三维模型上实现单体化, 那么就要获取到需要单体化的建筑物的四周坐标。 于是使用Cesium的pickEllipsoid方法来拾取坐标, 发现拾取的坐标最终渲染出来, 偏差总是非常大, 如下:



我明明拾取的是中间的房子, 得到的四个点就是房子的四个角, 但是最终渲染出来, 将两侧的房子、 后面的树木以及前面的马路都选染上了。 使用代码如下:
// 鼠标事件
drawLine() {
    var _this = this;
    _this.drawLineString(function(positions) {
        var wgs84_positions = [];
        for (var i = 0; i < positions.length; i++) {
            var wgs84_point = _this.Cartesian3_to_WGS84({
                x: positions[i].x,
                y: positions[i].y,
                z: positions[i].z,
            });
            wgs84_positions.push(wgs84_point);
        }
        // wgs84_positions为最终得到的结果
        _this.coordinates = arr.join(",");
    });
},

// drawLineString方法,此方法直接使用pickEllipsoid在三维模型上获取点,结果不准确
drawLineString: function(callback) {
    var _this = this;
    var PolyLinePrimitive = (function() {
        function _(positions) {
            this.options = {
                polyline: {
                    show: true,
                    positions: [],
                    material: Cesium.Color.RED,
                    width: 3,
                },
            };
            this.positions = positions;
            this._init();
        }

        _.prototype._init = function() {
            var _self = this;
            var _update = function() {
                return _self.positions;
            };
            //实时更新polyline.positions
            this.options.polyline.positions = new Cesium.CallbackProperty(
                _update,
                false
            );
            _this.viewer.entities.add(this.options);
        };
        return _;
    })();

    var handler = new Cesium.ScreenSpaceEventHandler(
        _this.viewer.scene.canvas
    );
    var positions = [];
    var poly = undefined;
    //鼠标左键单击画点
    handler.setInputAction(function(movement) {
        var cartesian = _this.viewer.scene.camera.pickEllipsoid(
            movement.position,
            _this.viewer.scene.globe.ellipsoid
        );
        if (positions.length == 0) {
            positions.push(cartesian.clone());
        }
        positions.push(cartesian);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    //鼠标移动
    handler.setInputAction(function(movement) {
        var cartesian = _this.viewer.scene.camera.pickEllipsoid(
            movement.endPosition,
            _this.viewer.scene.globe.ellipsoid
        );
        if (positions.length >= 2) {
            if (!Cesium.defined(poly)) {
                poly = new PolyLinePrimitive(positions);
            } else {
                if (cartesian != undefined) {
                    positions.pop();
                    cartesian.y += 1 + Math.random();
                    positions.push(cartesian);
                }
            }
        }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
    //单击鼠标右键结束画线
    handler.setInputAction(function(movement) {
        handler.destroy();
        callback(positions);
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
},

后来在百度上找到了一个非常全面的方法,它将鼠标在地球上、地形上还是3DTILES上点击,进行了判断,不同实体进行使用不同的方法进行坐标拾取,如pick、pickEllipsoid等(参考:屏幕坐标转世界坐标的方法对比 http://www.5imoban.net/jiaocheng/mapgis/2021/0622/4869.html),并且对坐标进行了转换,非常不错:
/**
 * 拾取位置点,能够根据鼠标位置判断出是画在3dtils上,还是画在地球上,还是画在地形上
 *
 * @param {Object} px 屏幕坐标
 *
 * @return {Object} Cartesian3 三维坐标
 */
getCatesian3FromPX: function(px) {
    if (this.viewer && px) {
        var picks = this.viewer.scene.drillPick(px);
        var cartesian = null;
        var isOn3dtiles = false,
            isOnTerrain = false;
        // drillPick
        for (let i in picks) {
            let pick = picks[i];

            if (
                (pick && pick.primitive instanceof Cesium.Cesium3DTileFeature) ||
                (pick && pick.primitive instanceof Cesium.Cesium3DTileset) ||
                (pick && pick.primitive instanceof Cesium.Model)
            ) {
                //模型上拾取
                isOn3dtiles = true;
            }
            // 3dtilset
            if (isOn3dtiles) {
                this.viewer.scene.pick(px); // pick
                cartesian = this.viewer.scene.pickPosition(px);
                if (cartesian) {
                    let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
                    if (cartographic.height < 0) cartographic.height = 0;
                    let lon = Cesium.Math.toDegrees(cartographic.longitude),
                        lat = Cesium.Math.toDegrees(cartographic.latitude),
                        height = cartographic.height;
                    cartesian = this.transformWGS84ToCartesian({
                        lng: lon,
                        lat: lat,
                        alt: height,
                    });
                }
            }
        }
        // 地形
        let boolTerrain =
            this.viewer.terrainProvider instanceof
        Cesium.EllipsoidTerrainProvider;
        // Terrain
        if (!isOn3dtiles && !boolTerrain) {
            var ray = this.viewer.scene.camera.getPickRay(px);
            if (!ray) return null;
            cartesian = this.viewer.scene.globe.pick(ray, this.viewer.scene);
            isOnTerrain = true;
        }
        // 地球
        if (!isOn3dtiles && !isOnTerrain && boolTerrain) {
            cartesian = this.viewer.scene.camera.pickEllipsoid(
                px,
                this.viewer.scene.globe.ellipsoid
            );
        }
        if (cartesian) {
            let position = this.transformCartesianToWGS84(cartesian);
            if (position.alt < 0) {
                cartesian = this.transformWGS84ToCartesian(position, 0.1);
            }
            return cartesian;
        }
        return false;
    }
},
/***
 * 坐标转换 笛卡尔转84
 *
 * @param {Object} Cartesian3 三维位置坐标
 *
 * @return {Object} {lng,lat,alt} 地理坐标
 */
transformCartesianToWGS84: function(cartesian) {
    if (this.viewer && cartesian) {
        var ellipsoid = Cesium.Ellipsoid.WGS84;
        var cartographic = ellipsoid.cartesianToCartographic(cartesian);
        return {
            lng: Cesium.Math.toDegrees(cartographic.longitude),
            lat: Cesium.Math.toDegrees(cartographic.latitude),
            alt: cartographic.height,
        };
    }
},
/***
 * 坐标转换 84转笛卡尔
 *
 * @param {Object} {lng,lat,alt} 地理坐标
 *
 * @return {Object} Cartesian3 三维位置坐标
 */
transformWGS84ToCartesian: function(position, alt) {
    if (this.viewer) {
        return position ?
            Cesium.Cartesian3.fromDegrees(
                position.lng || position.lon,
                position.lat,
                position.alt = alt || position.alt,
                Cesium.Ellipsoid.WGS84
            ) :
            Cesium.Cartesian3.ZERO
    }
},

将上面的代码,放到项目中(注意修改里面的this、viewer等问题),然后,修改之前的drawLineString方法:
drawLineString: function(callback) {
    var _this = this;
    var PolyLinePrimitive = (function() {
        function _(positions) {
            this.options = {
                polyline: {
                    show: true,
                    positions: [],
                    material: Cesium.Color.RED,
                    width: 3,
                },
            };
            this.positions = positions;
            this._init();
        }

        _.prototype._init = function() {
            var _self = this;
            var _update = function() {
                return _self.positions;
            };
            //实时更新polyline.positions
            this.options.polyline.positions = new Cesium.CallbackProperty(
                _update,
                false
            );
            _this.viewer.entities.add(this.options);
        };
        return _;
    })();

    var handler = new Cesium.ScreenSpaceEventHandler(
        _this.viewer.scene.canvas
    );
    var positions = [];
    var poly = undefined;
    //鼠标左键单击画点
    handler.setInputAction(function(movement) {
        // 使用getCatesian3FromPX方法来获取坐标
        var cartesian = _this.getCatesian3FromPX(movement.position);
        //之前的单纯的pickEllipsoid拾取坐标不可取,这里注释了
        // var cartesian = _this.viewer.scene.camera.pickEllipsoid(
        //   movement.position,
        //   _this.viewer.scene.globe.ellipsoid
        // );
        if (positions.length == 0) {
            positions.push(cartesian.clone());
        }
        positions.push(cartesian);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    //鼠标移动
    handler.setInputAction(function(movement) {
        // 使用getCatesian3FromPX方法来获取坐标
        var cartesian = _this.getCatesian3FromPX(movement.position);
        //之前的单纯的pickEllipsoid拾取坐标不可取,这里注释了
        // var cartesian = _this.viewer.scene.camera.pickEllipsoid(
        //   movement.position,
        //   _this.viewer.scene.globe.ellipsoid
        // );
        if (positions.length >= 2) {
            if (!Cesium.defined(poly)) {
                poly = new PolyLinePrimitive(positions);
            } else {
                if (cartesian != undefined) {
                    positions.pop();
                    cartesian.y += 1 + Math.random();
                    positions.push(cartesian);
                }
            }
        }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
    //单击鼠标右键结束画线
    handler.setInputAction(function(movement) {
        handler.destroy();
        callback(positions);
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
},

修改后,拾取的坐标,再次渲染结果(换了一栋建筑物):