Cesium how to scale a polygon to match Lat-Lon positions while zoom-in/zoom-out

Deadly 提交于 2019-12-02 12:19:42

问题


I'm looking for a Cesium Guru for a little help finding what I need. I'm new to Cesium but I've been working with the tutorials and some existing code that I've inherited.

In my Cesium app, I enter my address and the view zooms in to my street. Yay! Then I zoom in closer, so I can draw a polygon around my house. The existing code does this very well. However when I zoom out and then zoom in again, my polygon does not stay true to the Lat-Lon position of my house.

Does Cesium contain a utility to scale pixels to lat-lon coordinates or do I need to use something like distanceToBoundingSphere(boundingSphere) and calculate it myself? I only want the x,y coordinates; I don't care about height at all.

I have been looking around in the demos and tutorials and so far haven't found what I think I'm looking for. Maybe I have found something close but I don't know enough yet to know whether I've found it or not. Help!

============================ CODE ==================================

Collecting the positions for the polygon:

A singleClick only captures the coordinates for that point and draws a polyline as user drags mouse to a new point. Hence, a bunch of polylines and a collection of coordinates for each point.

    positionHandler.setInputAction(function (click) {
        cartesian = scene.camera.pickEllipsoid(click.position, ellipsoid);
        if (cartesian) {
            var setCartographic = ellipsoid.cartesianToCartographic(cartesian);
            asset.latlonalt.push(
                Cesium.Math.toDegrees(setCartographic.latitude).toFixed(15),
                Cesium.Math.toDegrees(setCartographic.longitude).toFixed(15),
                Cesium.Math.toDegrees(setCartographic.height).toFixed(15)
            );
            lla.push(Cesium.Math.toDegrees(setCartographic.longitude), Cesium.Math.toDegrees(setCartographic.latitude));
            if (lla.length >= 4) {
                self.loggingMessage((lla.length / 2) + ' Points Added');
            }
            Cesium.sampleTerrain(terrainProvider, 11, [cartographic])
                .then(function (updatedPositions) {
                    asset.latlonalt[2] = updatedPositions[0].height;
                    stage = 1;
                });
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

A doubleClick then takes the coordinates captured in the singleClick function and calls self.createAsset('add', asset) to create the polygon.

    positionHandler.setInputAction(function (doubleClick){
        if (asset.shape == 'Polygon') {
            var len = asset.latlonalt.length;
            if(len > 9) {
                asset.rad = (len / 3);
                console.log("Creating Asset");
                self.loggingMessage("Creating Asset");
                socket.emit('newElement', asset.cType, asset);
                self.createAsset('add', asset);
                viewer.entities.remove(entity);
                viewer.entities.remove(newCircle);
                viewer.entities.remove(newPolygon);
                viewer.entities.remove(newOutline);
                positionHandler = positionHandler && positionHandler.destroy();
            }else{
                console.log('3+ Positions Required');
                loggingMessage('3+ Positions Required.');
            }
        }
    }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)

Creating the polygon:

            var newPolygon = viewer.entities.add({
                name : asset.id,
                polygon : {
                    hierarchy : Cesium.Cartesian3.fromDegreesArray(vertices),
                    material : rgba[0],
                    outline : true,
                    outlineColor : rgba[1]
                }
            });
            var newLabel = viewer.entities.add({
                position: Cesium.Cartesian3.fromDegrees(asset.latlonalt[1], asset.latlonalt[0], 1000),
                name: asset.id,
                label: {
                    text: asset.name,
                    font : '16px Helvetica'
                }
            });
            var newPoint = viewer.entities.add({
                position: Cesium.Cartesian3.fromDegrees(asset.latlonalt[1], asset.latlonalt[0], 0),
                name: asset.id,
                point : {
                pixelSize : 5,
                    color : Cesium.Color.RED,
                    outlineColor : Cesium.Color.RED,
                    outlineWidth : 2
                }
            });
            self.currentGeometry[asset.id] = {shape: newPolygon, label: newLabel, point: newPoint};

It looks like we're using Terrain (I think).

Which numbers should I be interested in:

When the coordinates are collected, only the first z is not zeros:

I take that value and populated the other z values:

Now that I have added the z values something is erroring out in the createAsset method. I need to track that problem down to see the results. Right now, it looks like this:

REALLY BIG and the outlines are not getting removed.


回答1:


There's a lot going on for one question, but I'll try to address the key parts of this.

First, the Cartesian3 class itself. Internally, after construction, this class contains x, y, z members containing values like 314923.1. You should probably think of these as black-box, opaque values. In fact they represent a Cartesian position, in meters, from the center of the planet, which is needed by the rendering engine but not normally useful to human cartographers. The key thing to understand is that z will always be populated with a real value, and this does NOT imply that height was taken into account or not when creating the value.

There is a separate class Cartographic that contains the familiar Longitude (Radians), Latitude (Radians), and Altitude (in meters) values. Generally these must be converted to Cartesian3 before being handed off to the rendering engine. This conversion requires knowledge of the Ellipsoid (which defaults to the WGS84 ellipsoid), and as such, a zero-altitude value indicates a point sitting on that ellipsoid (typically meaning the point is underground when terrain is turned on).

An assortment of helper functions take common values (such as lon/lat in degrees) and convert to either of these two formats. Some of these helper functions leave off the altitude parameter, others include it. The documentation for these two classes enumerates these helpers.

Getting the exact lon/lat from a mouse click is simple when terrain is turned off, but more complex when it's on, due to the use of the perspective 3D camera. Without terrain, you can just call scene.camera.pickEllipsoid as you've done in the first code sample above, and get the exact spot. But when terrain is on, even a click on a flat plain will calculate the wrong coordinates, finding an underground spot below and behind the terrain you're looking at.

A casual search doesn't turn up the correct code for this, but the gold standard appears to be baked into the existing camera controller here. It looks like this:

    var depthIntersection;
    if (scene.pickPositionSupported) {
        depthIntersection = scene.pickPosition(mousePosition, scratchDepthIntersection);
    }

    var ray = camera.getPickRay(mousePosition, pickGlobeScratchRay);
    var rayIntersection = globe.pick(ray, scene, scratchRayIntersection);

    var pickDistance = defined(depthIntersection) ?
        Cartesian3.distance(depthIntersection, camera.positionWC) :
        Number.POSITIVE_INFINITY;
    var rayDistance = defined(rayIntersection) ?
        Cartesian3.distance(rayIntersection, camera.positionWC) :
        Number.POSITIVE_INFINITY;

    if (pickDistance < rayDistance) {
        return Cartesian3.clone(depthIntersection, result);
    }

    return Cartesian3.clone(rayIntersection, result);

This code is attempting a two-pronged approach: It tries to pick the ellipsoid, as before, and is also attempts to pick from the "depth buffer", which is part of the 3D graphics system that allows Cesium to inspect how far polygons were from the camera when rendered. The two results are compared, and whichever is closer to the camera is declared the winner. This avoids the need for a sampleTerrain call at all, because the mouse location has been used to directly pick a Cartesian point in space where a polygon was rendered (which is likely terrain, but could even be the top of a building, etc).

In your next code block, you use asset.latlonalt to populate the lon and the lat, but then you have alt hard-coded to 0 or 1000 rather than coming from the same data structure. This may be where the altitude information is getting lost, if it was there to begin with (which it is not, if you're just picking the ellipsoid itself, although it may be being added by sampleTerrain later. Beware that sampleTerrain is asynchronous due to terrain tiles being loaded from the server). If you decide to try the depth-picking technique, that will yield Cartesian3 directly, so you won't have to worry about converting it or preserving altitude etc.

One last comment, recent versions of Cesium do support a GroundPrimitive that allows polygons to drape on terrain. This won't save you from the need to "pick" the right lon/lat to begin with (taking perspective terrain into account), but will allow a polygon to lay on a lumpy surface and not stick through.



来源:https://stackoverflow.com/questions/35929240/cesium-how-to-scale-a-polygon-to-match-lat-lon-positions-while-zoom-in-zoom-out

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!