Search code examples
cesiumjsmap-projectionsprojection-matrix

CesiumJS: compute the projected size of the globe (in pixels)


We are using CesiumJS as an overlay in a spherical geometry app where it renders its own sphere with a known size (in pixels) and the user may zoom in/out to change its size. We would like to superimpose CesiumJS as another layer in the app and have the globe rendered to match the size of the app's sphere. In the screenshow below, the red circle is our sphere (with a known radius in pixels, and the earth globe is rendered by CesiumJS)

Cesium overlay

We were able to compute the projected size of the globe (in pixels) at the camera projection plane using the camera frustum:

const frustum = camera.frustum as PerspectiveFrustum;
// Compute the projected area of a pixel at the globe XY-plane?
const pixelDimensions = frustum.getPixelDimensions(
  scene.drawingBufferWidth,
  scene.drawingBufferHeight,
  camera.getMagnitude(), // distance of the camera from the projection plane?
  (scene as any).pixelRatio,
  tmpCartesian2
);
const globeRadius = CESIUM_GLOBE_RADIUS_IN_METERS / pixelDimensions.y

For additional background

  • CESIUM_GLOBE_RADIUS_IN_METERS the actual earth ellipsoid radius (= 6.371 million meters)
  • The function getPixelDimensions() gives the width/height (in meters) of the area covered by a screen pixel at a certain distance (the third argument of the function call)

Using this approach we calculate the required distance to move CesiumJS camera to match the sphere dimension. Our debugging output shows that after the camera translates, the CesiumJS globe is projected at the same size as the unit sphere of our app (close to several decimal places). However, the screenshot shows otherwise (it is off by hundred pixels when zoomed in closer). We also notice that the difference becomes smaller as we zoom out:

enter image description here

We suspect we supply an incorrect value in the third argument, but we don't know what better value to pass here.


Solution

  • I think your problem is the perspective projection. The third argument camera.getMagnitude() is giving you the distance to the center of the planet (when the view is centered on the planet, like this), but, what you're looking for is the visible limb of the Earth (not the middle of the Earth) from that point of view.

    Take a look at your zoomed-out picture above. You can see all of Saudi Arabia, surrounded by the Red Sea (to the West of it) and the Persian Gulf (to the East of it), and some lands even further East. But now take a look at the zoomed-in picture, taken from the same vantage point but closer to Earth. The Persian Gulf is almost disappearing over the horizon, but the camera is actually closer to it.

    As the camera moves closer to Earth, the visible horizon (the apparent limb of the Earth) changes. Lower-altitude perspective cameras can't see as far around the curvature. The camera isn't seeing a full hemisphere of the Earth from these lower altitudes.

    This means both your distance and your radius are incorrect, unfortunately. The full radius of the Earth is hidden behind the part that's close to the camera. The horizon visible to this camera is a smaller radius at a closer distance.

    To make this a 2D problem, draw a point near a circle, and then draw tangent lines from the circle connecting at the point. You need to calculate where these tangent lines touch the circle, not just the middle of the circle.