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

**URL:** <https://community.cesium.com/t/geojsondocumentrasteroverlay-alternative-work-around-as-it-doesnt-work-well-with-dynamic-data/46780>\
**Category:** Cesium for Unity\
**Created:** [July 22, 2026, 10:09am UTC](https://community.cesium.com/t/geojsondocumentrasteroverlay-alternative-work-around-as-it-doesnt-work-well-with-dynamic-data/46780 "2026-07-22T10:09:51Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![asaf.masa](https://sea2.discourse-cdn.com/cesium/user_avatar/community.cesium.com/asaf.masa/32/27744_2.png) [@asaf.masa](https://community.cesium.com/u/asaf.masa)\
**Post date:** [July 22, 2026, 10:09am UTC](https://community.cesium.com/t/geojsondocumentrasteroverlay-alternative-work-around-as-it-doesnt-work-well-with-dynamic-data/46780/1 "2026-07-22T10:09:51Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![Jake](https://sea2.discourse-cdn.com/cesium/user_avatar/community.cesium.com/jake/32/33676_2.png) [@Jake](https://community.cesium.com/u/Jake)\
**Post date:** [July 30, 2026, 5:38pm UTC](https://community.cesium.com/t/geojsondocumentrasteroverlay-alternative-work-around-as-it-doesnt-work-well-with-dynamic-data/46780/3 "2026-07-30T17:38:49Z")

</div>

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](https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/components-buffer-introducing.html)) 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!

---

<div class="post-metadata">

**Author:** ![Baruch\_Haivatov](https://sea2.discourse-cdn.com/cesium/user_avatar/community.cesium.com/baruch_haivatov/32/28663_2.png) [@Baruch\_Haivatov](https://community.cesium.com/u/Baruch_Haivatov)\
**Post date:** [August 5, 2026, 9:53am UTC](https://community.cesium.com/t/geojsondocumentrasteroverlay-alternative-work-around-as-it-doesnt-work-well-with-dynamic-data/46780/4 "2026-08-05T09:53:08Z")

</div>

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`:

```cpp
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.

---

<div class="post-metadata">

**Author:** ![asaf.masa](https://sea2.discourse-cdn.com/cesium/user_avatar/community.cesium.com/asaf.masa/32/27744_2.png) [@asaf.masa](https://community.cesium.com/u/asaf.masa)\
**Post date:** [October 18, 2026, 7:19am UTC](https://community.cesium.com/t/geojsondocumentrasteroverlay-alternative-work-around-as-it-doesnt-work-well-with-dynamic-data/46780/5 "2026-10-18T07:19:57Z")

</div>

Related issues

> <https://github.com/CesiumGS/cesium-unity/issues/729>
>
> \### Feature
> 
> Depends on CesiumGS/cesium-native#1469.
> 
> \## Use case
> 
> We highlight …and restyle individual GeoJSON features at runtime, for example a selected line or a user editing a polygon's color. We do this with \`CesiumGeoJsonObject.SetStyle()\` / \`ClearStyle()\` on the document assigned to
> \`CesiumGeoJsonDocumentRasterOverlay.document\`.
> 
> \## Current state
> 
> \- \`CesiumGeoJsonDocumentRasterOverlayImpl::AddToTileset\` passes the document's
> shared \`std::shared\_ptr\<GeoJsonDocument\>\` straight to the native overlay. That is
> intentional, to preserve per-feature styles, but it means \`SetStyle()\` changes
> memory the native tile provider reads from worker threads. See the native
> issue for the effects: changes appear only on newly loaded tiles, there is a
> race, and \`ClearStyle()\` leaves a dangling pointer.
> \- The only reliable way to show a change is \`CesiumRasterOverlay.Refresh()\`
> (\`RemoveFromTileset()\` + \`AddToTileset()\`, which creates a new native overlay).
> #672 used the same workflow (\`SetStyle\`, then re-assign \`document\`); its
> styles-not-applied bug was fixed by CesiumGS/cesium-native#1337. That
> workflow now works, but it re-rasterizes the whole overlay, and
> when the overlay's projection is new to the tileset it also reloads tile
> geometry, which flickers (#712 / CesiumGS/cesium-native#1422).
> \- Setting \`defaultStyle\` also goes through a full \`Refresh()\`.
> 
> \## Proposal
> 
> \`\`\`csharp
> // on CesiumGeoJsonDocumentRasterOverlay
> public partial void RefreshStyles(); // whole overlay, no reload flicker
> public partial void RefreshStyles(double2 southwestLonLat, // only the affected region
> double2 northeastLonLat);
> \`\`\`
> 
> \- Have the \`defaultStyle\` setter (and \`document\`, when the new document comes from the old one) use the cheaper native path instead of \`Refresh()\`.
> \- Document that \`CesiumGeoJsonObject.SetStyle\` / \`ClearStyle\` on an active overlay must be followed by \`RefreshStyles()\`, or make \`SetStyle\` do that automatically.
> 
> Implementation would follow the existing pattern: \`partial\` methods bound through \`CesiumGeoJsonDocumentRasterOverlayImpl\` (which holds \`\_pOverlay\`), with matching entries in the GeoJSON block of \`ConfigureReinterop.cs\`.

> <https://github.com/CesiumGS/cesium-unity/issues/712>
>
> \## Summary
> 
> Adding a \`CesiumRasterOverlay\` to a \`Cesium3DTileset\` that has alrea…dy loaded causes every loaded tile to be destroyed and rebuilt. The tileset visibly blinks. The same happens on remove and re-add, and it recurs on every add rather than being a one-time cost.
> 
> We reproduce this with both overlay types we use:
> 
> \- \`CesiumGeoJsonDocumentRasterOverlay\`
> \- \`CesiumWebMapTileServiceRasterOverlay\`
> 
> The underlying mechanism lives in cesium-native and, having read through it, appears to be a sound
> tradeoff rather than a defect (details below). We are raising it here because from the Unity side it
> is neither discoverable nor, for GeoJSON overlays, avoidable:
> 
> 1. Nothing in \`CesiumRasterOverlay\`, \`CesiumGeoJsonDocumentRasterOverlay\` or
> \`CesiumWebMapTileServiceRasterOverlay\` suggests that adding an overlay can destroy tile content,
> or that projection compatibility between overlays on the same tileset matters at all.
> 2. \`CesiumGeoJsonDocumentRasterOverlay\` exposes no projection setting, so even once you understand
> the cause there is no way to avoid it from Unity.
> 
> So this is a documentation request plus a small API request, not a bug report.
> 
> Our use cases: hovering an item in a UI list draws that item's footprint as a GeoJSON overlay on the
> base map, and users toggle WMTS imagery layers on and off. Both make the base map flicker, which
> makes them unusable as interactive affordances.
> 
> \## It is not RecreateTileset
> 
> Worth stating up front, since it is the natural first guess: nothing calls
> \`Cesium3DTileset.RecreateTileset()\` on this path. We checked every caller.
> 
> \- The \`CesiumRasterOverlay\` base class only ever calls \`Refresh()\` (\`RemoveFromTileset()\` +
> \`AddToTileset()\`), never \`RecreateTileset\` -- see \`Source/Runtime/CesiumRasterOverlay.cs\`.
> \- The per-overlay \`AddToTileset\` / \`RemoveFromTileset\` implementations operate on the live
> \`Tileset\`'s overlay collection only.
> \- \`RecreateTileset\` is called only from \`Cesium3DTileset\` property setters, the "Refresh Tileset"
> inspector button, \`CesiumGeoreference.ReloadEllipsoid\`, and some editor-window paths
> (\`CesiumEditorWindowImpl.cpp\`, \`IonAssetsTreeViewImpl.cpp\`, \`SelectIonTokenWindowImpl.cpp\`). None
> are in play here, and our project never calls it.
> 
> What actually happens is per-tile content reload in
> \`Cesium3DTilesSelection/src/TilesetContentManager.cpp\` (~line 2126):
> 
> \`\`\`cpp
> 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.
> // In the future, we could add the ability to add the required
> // texture coordinates without starting over from scratch.
> unloadTileContent(tile);
> return;
> }
> \`\`\`
> 
> Adding an overlay whose projection is not among those a loaded tile generated texture coordinates
> for forces that tile to reload. This looks justified: overlay texture coordinates are per-vertex
> glTF attributes (\`\_CESIUMOVERLAY\_n\`) generated during load, and Cesium for Unity discards the glTF
> once conversion is done -- \`CesiumGltfGameObject\`
> (\`native~/src/Runtime/UnityPrepareRendererResources.h\`) retains only the \`GameObject\` and
> \`primitiveInfos\`. Retrofitting a UV set onto an already-uploaded Unity mesh would mean
> reconstructing that data and re-uploading to the GPU.
> 
> Filed against cesium-native: \[\<LINK\>
> \](https://github.com/CesiumGS/cesium-native/issues/1422)
> \## Why each overlay type hits it
> 
> \*\*GeoJSON overlays always hit it.\*\* \`GeoJsonDocumentRasterOverlay\` hardcodes \`GeographicProjection\`
> with no override, while \`CesiumWebMapTileServiceRasterOverlay.projection\` defaults to \`WebMercator\`
> (\`Source/Runtime/CesiumWebMapTileServiceRasterOverlay.cs\`). On a Web Mercator tileset the mismatch
> is guaranteed.
> 
> \*\*WMTS overlays hit it when projections are mixed.\*\* They do expose \`projection\`, so an application
> that keeps every overlay on one projection is fine. Ours mixes them -- our configuration sets
> \`Geographic\` for some layers and leaves others at the \`WebMercator\` default -- and adding a
> mismatched one produces the same flicker.
> 
> We have not isolated a case where an overlay is added with a projection that \*is\* already in use by
> the tileset and the reload still happens. Our reading of the code says that case should leave loaded
> tiles untouched, and confirmation of that would be valuable, since it is the property applications
> would need to rely on to work around this.
> 
> \## Steps to reproduce
> 
> With a GeoJSON overlay:
> 
> 1. Create a \`Cesium3DTileset\` with a \`CesiumWebMapTileServiceRasterOverlay\` left at its default
> \`WebMercator\` projection.
> 2. Enter play mode and let the tileset load.
> 3. At runtime, \`AddComponent\<CesiumGeoJsonDocumentRasterOverlay\>()\` on the same GameObject and
> assign a valid \`document\`.
> 4. Observe the tileset's tiles disappear and reload.
> 
> With a WMTS overlay:
> 
> 1. Same tileset and \`WebMercator\` base overlay as above, fully loaded.
> 2. At runtime, \`AddComponent\<CesiumWebMapTileServiceRasterOverlay\>()\` on the same GameObject,
> configure it, set \`projection = Geographic\`, and enable it.
> 3. Observe the same reload.
> 
> Expected: the new overlay is draped over the existing tiles.
> Actual: all loaded tile GameObjects are destroyed and rebuilt; the tileset flickers.
> 
> \## Workarounds we tried
> 
> \- Reusing a single overlay component and swapping only \`document\` does not help. The document setter
> calls \`Refresh()\`, which removes and re-adds the overlay, so the projection is reintroduced as
> "missing" for any tile that loaded in the meantime.
> \- Keeping an overlay permanently attached so its projection is always registered does avoid the
> reload, but for GeoJSON overlays only if it always holds a non-empty document -- an empty
> \`FeatureCollection\` crashes the process (filed separately against cesium-native: \<LINK\>).
> \- Matching the projections (setting the WMTS overlay to \`Geographic\` where the service supports
> EPSG:4326) removes the flicker completely, which confirms the diagnosis. This is only possible
> when the imagery service actually serves that projection.
> 
> \## What would help
> 
> \- \*\*Documentation.\*\* A note on \`CesiumRasterOverlay\` -- the base class, since this affects every
> subclass -- that adding an overlay whose projection is not already in use by the tileset will
> force loaded tiles to reload, and that this makes such overlays unsuitable for frequently toggled
> visuals. This alone would have saved us a lot of investigation. Ideally it would also state the
> positive guarantee: that matching projections avoids the reload.
> \- \*\*A projection setting on \`CesiumGeoJsonDocumentRasterOverlay\`,\*\* once the corresponding option
> exists in \`GeoJsonDocumentRasterOverlayOptions\` in cesium-native (see linked issue). WMTS
> overlays already expose \`projection\`, so applications can avoid the mismatch there; GeoJSON
> overlays are currently the only ones with no way out.
