Search code examples
javascriptmapscesiumjs

How to add markers on cesium terrain


In order to add markers on terrain in cesium I used the sampleTerrain function to get the height.

I'm able to add a marker on top of a mountain but the marker is located far from the mouse click. How can I add a marker on Cesium terrain so the marker will be added exactly in the clicked position?

For reference I'll put my code here:

async leftClickInputAction(event:{position: {x:number, y:number}}):Promise<any> {

let positionCartesian3 = this.cesium.viewer.camera.pickEllipsoid(event.position);
let positionCartographic = Cesium.Cartographic.fromCartesian(positionCartesian3);
await Cesium.sampleTerrain(this.cesium.viewer.terrainProvider, 9, [positionCartographic]);

let height = positionCartographic.height;
let cart3_with_height = Cesium.Cartesian3.fromRadians(positionCartographic.longitude, positionCartographic.latitude, positionCartographic.height );
let window_coordinates = this.cesium.calcService.toFixes7Obj(Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.cesium.viewer.scene,cart3_with_height));
positionCartesian3 = this.cesium.viewer.camera.pickEllipsoid(event.position);
positionCartographic = Cesium.Cartographic.fromCartesian(positionCartesian3);
positionCartographic.height = height;*/

let lngDeg:number = Cesium.Math.toDegrees(positionCartographic.longitude);
let latDeg:number = Cesium.Math.toDegrees(positionCartographic.latitude);
let position: [number, number, number] = [lngDeg, latDeg, positionCartographic.height];
let color:string = this.cesium.positionFormService.getSelectedColor();
let marker_picker = {position};
if(color != "blue") marker_picker['color'] = color;
this.cesium.queryParamsHelperService.addMarker(marker_picker);

Solution

  • This line is your problem:

        let positionCartesian3 = this.cesium.viewer.camera.pickEllipsoid(event.position);
    

    The pickEllipsoid function picks values off the WGS84 ellipsoid (think "similar to sea level" but don't confuse it with actual MSL). This function will never return a point from the top of a mountain.

    Of course, you augmented this function by calling sampleTerrain, which is good but doesn't fix the problem. The "pick" misses the mountaintop, and ends up picking a spot on the ellipsoid far below and behind the mountain, not near where the user thought they clicked.

    There's another function you can use, viewer.scene.pickPosition, that will return positions by reading them out of WebGL's depth buffer. This means the function is only reliable at close range, when the camera is near enough to see individual mountains for example, not zoomed out to the whole globe. But it does let you pick mountain tops and such.

    Here's a Sandcastle demo. It uses code lifted from the Picking Demo and modified to work at Mount Everest.

    var terrain = Cesium.createDefaultTerrainProviderViewModels();
    var viewer = new Cesium.Viewer('cesiumContainer', {
        animation: false,
        timeline: false,
        geocoder : false,
        selectionIndicator : false,
        infoBox : false,
        terrainProviderViewModels: terrain,
        selectedTerrainProviderViewModel: terrain[1]
    });
    
    function lookAtMtEverest() {
        var target = new Cesium.Cartesian3(300770.50872389384, 5634912.131394585, 2978152.2865545116);
        var offset = new Cesium.Cartesian3(6344.974098678562, -793.3419798081741, 2499.9508860763162);
        viewer.camera.lookAt(target, offset);
        viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
    }
    
    lookAtMtEverest();
    
    var labelEntity = viewer.entities.add({
        label : {
            show : false,
            showBackground : true,
            font : '14px monospace',
            horizontalOrigin : Cesium.HorizontalOrigin.LEFT,
            verticalOrigin : Cesium.VerticalOrigin.TOP,
            pixelOffset : new Cesium.Cartesian2(15, 0)
        }
    });
    
    var sceneModeWarningPosted = false;
    
    // Mouse over the globe to see the cartographic position
    var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function(movement) {
        var foundPosition = false;
        var scene = viewer.scene;
        var pickedObject = scene.pick(movement.endPosition);
        if (scene.pickPositionSupported) {
            if (scene.mode === Cesium.SceneMode.SCENE3D) {
                var cartesian = viewer.scene.pickPosition(movement.endPosition);
    
                if (Cesium.defined(cartesian)) {
                    var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
                    var longitudeString = Cesium.Math.toDegrees(cartographic.longitude).toFixed(3);
                    var latitudeString = Cesium.Math.toDegrees(cartographic.latitude).toFixed(3);
                    var heightString = cartographic.height.toFixed(2);
    
                    labelEntity.position = cartesian;
                    labelEntity.label.show = true;
                    labelEntity.label.text =
                        'Lon: ' + ('   ' + longitudeString).slice(-8) + '\u00B0' +
                        '\nLat: ' + ('   ' + latitudeString).slice(-8) + '\u00B0' +
                        '\nAlt: ' + ('   ' + heightString).slice(-7) + 'm';
    
                    var camera = scene.camera;
                    labelEntity.label.eyeOffset = new Cesium.Cartesian3(0.0, 0.0, camera.frustum.near * 1.5 - Cesium.Cartesian3.distance(cartesian, camera.position));
    
                    foundPosition = true;
                }
            } else if (!sceneModeWarningPosted) {
                sceneModeWarningPosted = true;
                console.log("pickPosition is currently only supported in 3D mode.");
            }
        }
    
        if (!foundPosition) {
            labelEntity.label.show = false;
        }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);