How to Implement Screen-Space Detection Overlays in CesiumJS
God's Eye View provides a high-performance, reusable screen-space detection overlay for CesiumJS that uses dual HTML5 Canvas layers with CSS blend modes to render detection markers without modifying the WebGL scene graph.
Screen-space detection overlays in CesiumJS allow you to annotate 3D geospatial data with 2D markers that track world positions while remaining sharp and readable at any camera distance. The God's Eye View (GEV) repository implements this pattern using thin Canvas2D layers that sit above the Cesium canvas but below UI chrome, enabling real-time rendering of detection markers without polluting the scene graph or interfering with the 3D render pipeline.
Architecture of the Overlay System
The GEV overlay architecture isolates presentation logic from Cesium-specific rendering by separating DOM management from drawing operations.
Dual-Canvas DOM Structure
At the core of src/overlays/worldOverlay.js, the ensureOverlayDom() function (lines 19-34) lazily creates a DOM host containing two canvas elements. The parent div (id="world-overlay-root") contains a standard canvas (id="world-overlay-canvas") for normal cards and a detection surface canvas (id="world-overlay-detection-surface") for detection graphics. This separation allows the system to apply different blending strategies to informational labels versus interactive detection markers.
CSS Blend Mode Integration
The detection surface is appended directly to the Cesium container and configured with mix-blend-mode: screen (discussed in lines 34-42). This CSS rule ensures that detection graphics blend with the underlying 3D scene while maintaining the ability to draw over it. By positioning the canvases outside the WebGL context but inside the viewer container, the overlay avoids creating intermediate stacking contexts that would block the blend operation.
Coordinate Projection and Viewport Synchronization
Accurate screen-space positioning requires continuous synchronization between the 3D world, the camera, and the 2D canvas pixel grid.
World-to-Screen Transformation
To project a Cesium Cartesian position into pixel coordinates, the system calls Cesium.SceneTransforms.worldToWindowCoordinates(viewer.scene, position). This returns a Cartesian2 object containing x and y values representing the screen-space location. The paintOverlayEntry function in src/overlays/worldOverlayDraw.js consumes these coordinates to position markers using the standard Canvas2D API.
Handling High-DPI Displays
The ensureCanvasSize() function in src/overlays/worldOverlay.js (lines 105-112) recomputes canvas dimensions on every viewport resize, accounting for window.devicePixelRatio. This ensures that detection markers remain crisp on high-DPI displays by sizing the canvas backing store to match physical pixels rather than CSS pixels, then scaling the 2D context appropriately.
Rendering Detection Markers
All drawing logic resides in src/overlays/worldOverlayDraw.js, which provides pure-JavaScript Canvas2D utilities independent of Cesium scene management.
Canvas2D Drawing Utilities
The drawing module exports several performance-critical helpers. The roundedRectPath() function (lines 16-27) generates path data for rounded rectangles without allocating new objects. For text rendering, measureWorldOverlayText() (lines 80-99) implements a cached width measurement system that prevents the expensive cost of recalculating text metrics every frame. These utilities are used by the main rendering loop to draw detection markers as filled circles, rings, or custom SVG-derived graphics.
Distance and Altitude Scaling
To maintain consistent readability regardless of camera height, the overlay system implements distance-based scaling utilities within worldOverlayDraw.js. These functions calculate the apparent size of markers based on the camera's altitude and distance to the target, ensuring that detection graphics do not become unreadably small when zoomed out or overwhelming when zoomed in close.
UI Occlusion Prevention
The overlay respects UI chrome through WORLD_OVERLAY_OCCLUDER_SELECTORS, which enumerates elements like #title-bar and .hud-top-left that must remain visible. Before committing a marker placement, the overlayRectIntersectsAny() function (lines 85-103 in worldOverlay.js) checks candidate rectangles against these occluder boundaries. If a collision is detected, the algorithm either slides the label to a non-overlapping position or drops it entirely, guaranteeing that critical interface elements are never obscured.
Integration and Lifecycle Management
Applications interact with the overlay system through the public API exposed in src/ui.js.
Initialization API
To activate the overlay, call initWorldOverlay(viewer), which creates the DOM host, attaches the canvases, and registers a requestRender loop that drives the drawWorldOverlay() function. Once initialized, setDetectionMode() controls the density of displayed markers, accepting values of 'OFF', 'SPARSE', 'BALANCED', or 'DENSE' to filter visualization intensity based on operational requirements.
Data Source Interface
The overlay host expects data sources to implement a minimal interface: an entries property containing a Map of identifiers to objects with position data, and an update(viewer) method called each frame. When you add a source via viewer.scene.primitives.add(), GEV automatically iterates through entries, projects positions to screen space, and renders corresponding detection markers.
Cleanup and Destruction
When removing the map or changing contexts, destroyWorldOverlayDraw() (exported from worldOverlayDraw.js, lines 50-66) removes font-loading listeners, clears the text-measure cache, and deletes DOM elements. The companion installWorldOverlayFontInvalidation() function ensures that cached text measurements are recalculated if web fonts load after initialization, preventing layout shifts or measurement errors.
Complete Implementation Example
The following snippet demonstrates enabling the detection overlay with a custom data source:
import * as Cesium from 'cesium';
import { initWorldOverlay, setDetectionMode } from './src/ui.js';
// 1️⃣ Create the Cesium Viewer
const viewer = new Cesium.Viewer('cesiumContainer', {
// ... your Cesium options …
});
// 2️⃣ Initialise the screen‑space overlay host
await initWorldOverlay(viewer); // ⇢ creates DOM + canvases
// 3️⃣ Turn the detection overlay on
setDetectionMode('BALANCED'); // ⇢ draws detection markers for the active source
// 4️⃣ Add a custom detection source
const mySource = {
entries: new Map(),
update(viewer) {
// Example: put a detection marker at the Eiffel Tower
const position = Cesium.Cartesian3.fromDegrees(2.2945, 48.8584, 0);
this.entries.set('eiffel', { position, variant: 'track' });
},
};
viewer.scene.primitives.add(mySource); // GEV will pick it up automatically
Summary
- Screen-space detection overlays in CesiumJS are implemented in God's Eye View using dual HTML5 Canvas layers that sit outside the WebGL render pipeline but inside the viewer container.
- The
ensureOverlayDom()function creates a dedicated DOM host with separate canvases for standard cards and detection graphics, usingmix-blend-mode: screenfor seamless visual integration. - World positions are projected to pixels using
Cesium.SceneTransforms.worldToWindowCoordinates, withensureCanvasSize()handling high-DPI viewport synchronization. - Drawing operations are optimized through cached text measurement in
measureWorldOverlayText()and path generation inroundedRectPath(), all contained insrc/overlays/worldOverlayDraw.js. - UI occlusion is prevented via
overlayRectIntersectsAny(), which checks against selectors defined inWORLD_OVERLAY_OCCLUDER_SELECTORS. - The public API in
src/ui.jsprovidesinitWorldOverlay()andsetDetectionMode()for initialization and density control, whiledestroyWorldOverlayDraw()handles complete teardown.
Frequently Asked Questions
How does the overlay system avoid interfering with Cesium's WebGL renderer?
The overlay appends HTML5 Canvas elements directly to the Cesium container as DOM siblings to the WebGL canvas, rather than injecting geometry into the scene graph. According to the implementation in src/overlays/worldOverlay.js, the detection surface uses CSS mix-blend-mode: screen to composite with the 3D imagery without requiring depth buffer access or primitive commands. This separation ensures that camera movements, terrain loading, and entity updates in Cesium do not trigger expensive JavaScript-side re-renders of the overlay.
What is the purpose of the two separate canvas elements?
The dual-canvas architecture separates concerns between informational cards and detection graphics. As defined in ensureOverlayDom() (lines 19-34), the world-overlay-canvas handles standard annotation cards, while the world-overlay-detection-surface receives detection-specific graphics with different blending requirements. This separation allows detection markers to use mix-blend-mode: screen for highlighting effects without affecting the opacity or blending of standard text labels, which may need different compositing rules.
How does the system handle high-DPI displays and resizing?
The ensureCanvasSize() function in src/overlays/worldOverlay.js (lines 105-112) executes on every viewport resize event. It queries window.devicePixelRatio and sets the canvas internal width and height to match the physical pixel count, then scales the 2D context to maintain correct coordinate mapping. This approach ensures that detection markers remain sharp on Retina displays and 4K monitors without requiring the application code to manually handle pixel density conversions.
How are detection markers prevented from covering critical UI elements?
Before rendering each marker, the system checks for collisions with UI chrome using overlayRectIntersectsAny() (lines 85-103). This function compares the candidate marker rectangle against the bounding boxes of elements listed in WORLD_OVERLAY_OCCLUDER_SELECTORS, such as #title-bar and .hud-top-left. If an intersection is detected, the placement algorithm either shifts the marker to an adjacent clear area or suppresses rendering for that frame, ensuring that navigation controls and status indicators remain fully visible and interactive.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →