Trouble using clampToHeightMostDetailed for generated points

Hi,

I’m generating a grid of points based on where the user clicks on the globe. I can create the gridpoints fine, and they display on the ground properly, but I’m also trying to get the cartesian data associated with these points. Everything I’ve tried doesn’t seem to clamp the points to the ground. Here’s the code (I don’t know how to link a sandcastle project):

const viewer = new Cesium.Viewer("cesiumContainer", {
  terrain: Cesium.Terrain.fromWorldTerrain(),
});

const numRowsCols = 10;
const gridSize = 50;

const rectangle = Cesium.Rectangle.fromDegrees(-110.0, 38.0, -105.0, 41.0);
const targetHeight = 3000; // meters above ground

flyToRectangleWithHeight(viewer, rectangle, targetHeight, () => {
  waitForTerrainToLoad(viewer).then(() => {
    console.log("✅ Terrain loaded at desired height.");
  });
});

function generateClampedGrid(centerLat, centerLon, rows, cols, spacingMeters, viewer) {
  const metersPerDegreeLat = 111320;
  const metersPerDegreeLon = 111320 * Math.cos(Cesium.Math.toRadians(centerLat));

  const rowOffset = -(rows - 1) / 2;
  const colOffset = -(cols - 1) / 2;
  const halfSpacingDegLat = (spacingMeters / 2) / metersPerDegreeLat;
  const halfSpacingDegLon = (spacingMeters / 2) / metersPerDegreeLon;
  
  viewer.entities.add({
    position: Cesium.Cartesian3.fromRadians(centerLat, centerLon),
    point: {
      pixelSize: 10,
      color: Cesium.Color.RED,
      heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
    }
  });

  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      const deltaLat = (r + rowOffset) * spacingMeters / metersPerDegreeLat;
      const deltaLon = (c + colOffset) * spacingMeters / metersPerDegreeLon;

      const lat = centerLat + deltaLat;
      const lon = centerLon + deltaLon;

//      console.log(`Row: ${r}, Col: ${c}, Location: ${lat},${lon}`);
      // Clamp point to ground
      viewer.entities.add({
        position: Cesium.Cartesian3.fromDegrees(lon, lat),
        point: {
          pixelSize: 8,
          color: Cesium.Color.WHITE,
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
        }
      });

      const corners = Cesium.Cartesian3.fromDegreesArray([
        lon - halfSpacingDegLon, lat - halfSpacingDegLat,
        lon + halfSpacingDegLon, lat - halfSpacingDegLat,
        lon + halfSpacingDegLon, lat + halfSpacingDegLat,
        lon - halfSpacingDegLon, lat + halfSpacingDegLat
      ]);

      viewer.entities.add({
        polygon: {
          hierarchy: new Cesium.PolygonHierarchy(corners),
          material: Cesium.Color.YELLOW.withAlpha(0.3),
          perPositionHeight: false
        }
      });
    }
  }
}

function waitForTerrainToLoad(viewer, timeoutMs = 10000) {
  return new Promise((resolve, reject) => {
    const start = Date.now();

    function checkReady() {
      const globe = viewer.scene.globe;
      const tilesLoaded = globe._surface._tilesToRender.length === 0;

      if (tilesLoaded || (Date.now() - start > timeoutMs)) {
        resolve(); // resolve after timeout or when terrain is done
      } else {
        requestAnimationFrame(checkReady);
      }
    }

    checkReady();
  });
}

function flyToRectangleWithHeight(viewer, rectangle, height, callback) {
  const center = Cesium.Rectangle.center(rectangle);
  const destination = Cesium.Cartesian3.fromRadians(center.longitude, center.latitude, height);

  viewer.camera.flyTo({
    destination: destination,
    complete: () => {
      console.log("Camera reached target at specified height:", height);
      if (typeof callback === "function") {
        callback();
      }
    }
  });
}

// A handler hat will respond to left mouse clicks, be performing
// a visibility computation based on a point slightly above the
// clicked point, and visualize the result
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (action) {
  const cartesian = viewer.scene.pickPosition(action.position);
  if (!cartesian) {
    return;
  }
  const cartographic = Cesium.Cartographic.fromCartesian(
    cartesian,
    Cesium.Ellipsoid.WGS84,
    new Cesium.Cartographic()
  );
  cartographic.height += 10;
  const centerPoint = Cesium.Cartographic.toCartesian(
    cartographic,
    Cesium.Ellipsoid.WGS84,
    new Cesium.Cartesian3()
  );
  const longitudeDeg = Cesium.Math.toDegrees(cartographic.longitude);
  const latitudeDeg = Cesium.Math.toDegrees(cartographic.latitude);
  generateClampedGrid(latitudeDeg, longitudeDeg, numRowsCols, numRowsCols, gridSize, viewer);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);


Hi @Matt5, welcome to the CesiumJS community!

I created this sandcastle based on the code you provided. (For future reference, you can create a sandcastle here and, when you’re ready to share it, just click the Share button and copy the link from the drop down menu:)

To me, it looks like your code works as you described you intended it to! When I click on the terrain, a grid of dots appears and conforms to the terrain:

If this isn’t what you’re looking to achieve, let me know what’s not correct, and we can figure out how to get the right results!

P.S. You might be interested in reading about the EntityCollection API, which might make what you’re trying to do easier.

Thanks for the reply. I’m not only interested in visualizing the dots on the terrain, but also being able to get the cartesian coordinates of those dots. From what I can tell, I’m supposed to be using clampToHeightMostDetailed with the lat,lon I calculate, but I can never get it to return a set of cartesian coordinates.

Is this what you’re looking for? (sandcastle)

I commented CHANGES START HERE (and another for the end) so you can focus on what I modified. The line console.log('Clamped point ${index}:', point); will log all the cartesian coordinates of the points after clamping.

Awesome, thank you.

Maybe it’s an issue with my setup or environment? My log looks like this:

Camera reached target at specified height:
✅ Terrain loaded at desired height.
Clamped point 0:
Clamped point 1:
Clamped point 2:
Clamped point 3:
Clamped point 4:
Clamped point 5:
Clamped point 6:

Interesting. I might have more questions, but let’s start with these two:

  1. What browser and OS are you working on?
  2. According to the documentation for clampToHeightMostDetailed, the viewer must be in 3D mode, and depth textures must be supported. Without one or both of these, an error will be thrown - have you seen any developer errors in the console? (I’m assuming not, but worth checking!)

I changed the console.log statement to

console.log(Clamped point ${index}:${point});

And now I see the output. Also commented out
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
line and the points are still on the ground, so I think this is exactly what I need.

Thanks again for your help.

1 Like