GeoJsonDocumentRasterOverlay alternative/work around as it doesn't work well with dynamic data

Hi,
In my use case i need to add, update and remove GeoJson objects (points, polygons, etc.) dynamically

So i can renderer them draped over the cesium tileset.

It works nicely but i have 2 major issues:

  • Adding or removing GeoJson objects to the GeoJson document itself causes the entire tileset to “flick” as the tileset rebuilds itself
  • I have an issue with how many material keys and GeoJson documents can be added to a single tileset

Is there a better approach that is recommanded by cesium to add, update and remove GeoJson entity?
Other mechanizem to use?

Note: the GeoJson entity is created by me dynamically and isn’t fixed or served from remote server.

Hi @asaf.masa ,

Thank you for reaching out and providing detailed context about your use case—working with dynamic GeoJSON objects in Cesium for Unity. I understand the challenges you’re facing with tileset flickering and limitations around material keys/documents when using GeoJsonDocumentRasterOverlay dynamically.

**Performance Issue:** The flickering likely results from the architectural design of GeoJsonDocumentRasterOverlay in Cesium for Unity, where changes to the GeoJSON may trigger a full rebuild of tileset overlays. While this helps ensure up-to-date rendering, it’s less suited for real-time dynamic updates.

**Scalability Concern:** The material key and document limitations you mentioned could be related to internal restrictions in the Cesium for Unity plugin or its interaction with Unity’s rendering pipelines (e.g., URP or HDRP). Adjustments to these limits could require engineering-level changes.

**Alternatives:** One approach to explore may involve bypassing GeoJsonDocumentRasterOverlay for dynamic data. For example:

  • You could dynamically create meshes or textures at runtime in Unity from your GeoJSON data, which can then be draped onto the tileset using Unity’s rendering features (e.g., mesh rendering with custom shaders).
  • Unity’s Entity Component System (ECS) or dynamic buffer components [documentation](Introducing dynamic buffer components | Entities | 1.0.16) could help manage dynamic object creation efficiently.

If these limitations aren’t mentioned directly in Cesium for Unity documentation, we can escalate this to the engineering team for further input or even open a GitHub issue to track a feature request or performance improvement. Let me know if you’d like help with that!

In the meantime, could you confirm:

  1. Are these limitations explicitly documented anywhere you’ve seen?
  2. Does the flicker occur consistently only when modifying GeoJsonDocumentOverlay, or is it also present in other scenarios?

Looking forward to hearing from you, and thanks again for highlighting this challenge!

Hi Jake,

We traced this through cesium-native. The flicker isn’t about GeoJSON or about dynamic data - it’s about projections, and it’s avoidable once you know that. The material key limit is a separate and harder problem.

Mechanism

Not an overlay rebuild - the tile geometry is unloaded and re-fetched. Cesium3DTilesSelection/src/TilesetContentManager.cpp:2126:

if (status.firstIndexWithMissingProjection) {
  // The mesh doesn't have the right texture coordinates for this
  // overlay's projection, so we need to kick it back to the unloaded
  // state to fix that.
  unloadTileContent(tile);
  return;
}

Overlay UVs (_CESIUMOVERLAY_n) are baked per-vertex at load time, only for the projections present at that moment. Adding an overlay with a new projection invalidates every loaded tile. That looks like a fair tradeoff, not a defect - Unity keeps only the GameObject and primitive info after conversion (CesiumGltfGameObject, UnityPrepareRendererResources.h:66), so the UVs can’t be retrofitted cheaply. RasterOverlayCollection::add/remove themselves are cheap and never touch geometry; all the cost is this one check.

GeoJSON overlays always hit it: the tile provider hardcodes GeographicProjection (CesiumVectorOverlays/src/GeoJsonDocumentRasterOverlay.cpp:697), GeoJsonDocumentRasterOverlayOptions
has no projection field, and the Unity component exposes none. And every setter - document
included - calls Refresh() = remove + add (CesiumRasterOverlay.cs:207). So editing the document re-adds the overlay, which reintroduces Geographic as missing. On a Web Mercator base map this is guaranteed.

Your two questions

Documented anywhere? No. Nothing on CesiumRasterOverlay or its subclasses mentions that adding an overlay can destroy tile content, or that projections must match.

Only with GeoJSON? No --it’s projection-specific, not GeoJSON-specific. We get the identical
flicker from CesiumWebMapTileServiceRasterOverlay when projections are mixed (some of our layers are Geographic, others use the WebMercator default). WMTS overlays can avoid it because they expose projection. GeoJSON overlays can’t.

Matching projections avoids it completely

When the tile already has a rectangle for the incoming projection, mapOverlayToTile goes through addRealTile and never touches missingProjections, so nothing unloads
(RasterMappedTo3DTile.cpp:326). In practice: setting our WMTS base overlay to Geographic where the service serves EPSG:4326 removes the flicker entirely, and we can then add and remove GeoJSON overlays freely.

The catch, which we think belongs in the docs: the projection must be present when each tile
loads, not when the overlay is added. Attach a Geographic overlay only when needed and tiles that streamed in meanwhile still reload. So the rule is “keep every overlay on one projection
permanently”. For us that’s awkward because an empty FeatureCollection crashes, which rules out parking an empty overlay to hold the projection.

On the suggested alternatives

Hand-rolled meshes draped with a custom shader: conforming vector geometry to streaming LOD terrain is the hard problem RasterOverlay already solves – re-conforming on refinement, LOD seams, depth fighting. That’s a lot of work to replace something that works once projections match. Unity ECS doesn’t apply here; tilesets are GameObject-based and the difficulty is the conforming, not object management.

Material keys

There’s no limit in the plugin. TilesetMaterialProperties::updateOverlayParameterIDs builds property names from whatever key an overlay carries, and the overlay collection is unbounded. The ceiling is the material: the stock materials declare _0, _1, _2 and _Clipping, and _Clipping is taken by CesiumPolygonRasterOverlay. Three usable slots.

Overflow is invisible where it matters. attachRasterInMainThread
(UnityPrepareRendererResources.cpp:1892) is three if (maybeID) checks with no else, and Shader::PropertyToID returns an ID even for a property the shader doesn’t declare, so the overlay just doesn’t appear. There is an inspector warning
(CesiumRasterOverlayEditor.cs:170), but it’s Editor-only - it never fires at runtime or in a
build, which is where we hit it, since we create overlays from code.

We did try authoring extra slots in the shader graphs, and it wasn’t sufficient for our case. What
would you recommend for applications needing many simultaneous overlays - is compositing several sources into one slot the intended pattern?

One thing we couldn’t explain

In loadTileImage, moreDetailAvailable is false only for tiles entirely outside the document
bounds (GeoJsonDocumentRasterOverlay.cpp:752) and unconditionally true otherwise (:764). That feeds doSubdivide in updateDoneState (TilesetContentManager.cpp:2140) and generates upsampled children on leaf tiles across the document’s footprint. Screen-space error still gates selection, so it isn’t unbounded - but is it intended, and can it be capped?

What would help

  1. The TODO already in that comment: add missing texture coordinates to a loaded tile without discarding its content. Covers every overlay type.
  2. Let projections be pre-registered on the tileset (e.g. via TilesetOptions), so tiles generate
    the UVs up front. Also general, and probably much simpler.
  3. Expose the projection on GeoJsonDocumentRasterOverlayOptions, defaulting to
    GeographicProjection so nothing changes for existing users. Smallest change, and it closes the one case with no workaround. loadTileImage already uses this->getProjection() (:731); the coverage rectangle in the provider constructor would need it too.
  4. At minimum, document it on CesiumRasterOverlay - including that matching projections is safe, and how many slots the stock materials provide.

Two minor items while we’re here: updateOverlayParameterIDs (TilesetMaterialProperties.cpp:171) only reserve()s its maps and never clear()s them, so keys from removed overlays persist. And the materialKey XML doc names _overlayTranslationScale_KEY when the property is _overlayTranslationAndScale_KEY, in a <list> block that’s malformed and doesn’t render.

Environment

Cesium for Unity 1.23.3, with the cesium-native revision it vendors. Unity 6000.3.2f1, URP,
Windows 10.