Summary
Adding a RasterOverlay to a Tileset that has already loaded causes every affected tile to have its content unloaded and reloaded from scratch. In a real-time application this is very visible: the geometry disappears and reappears, i.e. the map flickers.
We reproduce this with two different overlay types, via Cesium for Unity:
GeoJsonDocumentRasterOverlay
- WMTS overlays (
CesiumWebMapTileServiceRasterOverlay, i.e. WebMapTileServiceRasterOverlay)
It also recurs on every add rather than being a one-time cost.
Mechanism
Cesium3DTilesSelection/src/TilesetContentManager.cpp (~line 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.
// In the future, we could add the ability to add the required
// texture coordinates without starting over from scratch.
unloadTileContent(tile);
return;
}
The "missing projection" state is produced in
Cesium3DTilesSelection/src/RasterMappedTo3DTile.cpp (~line 344):
} else {
// We don't have a precise rectangle for this projection, which means the
// tile was loaded before we knew we needed this projection. We'll need to
// reload the tile (later).
...
}
To be clear, we are not claiming the reload itself is a defect. Having read through it, it looks like a sound tradeoff: overlay texture coordinates are per-vertex glTF attributes (_CESIUMOVERLAY_n) generated by createRasterOverlayTextureCoordinates by projecting every vertex position, along with the per-projection rectangles in RasterOverlayDetails. They are produced in the load thread from the glTF, and downstream consumers do not necessarily keep the glTF afterwards -- Cesium for Unity discards it once the model has been converted to Unity meshes, retaining only the GameObject and a primitive index map. Retrofitting a new UV set onto an already-uploaded mesh would mean reconstructing that data and re-uploading to the GPU. The comment already notes the better answer as future work.
Why it is painful in practice
It recurs indefinitely. A tile only generates texture coordinates for the projections present in the collection at the moment it loads. As the camera moves and tiles stream in and out, newly loaded tiles again lack the second projection's UVs, so the next add reloads them. There is no point at which the application has "paid" the cost.
GeoJSON overlays cannot avoid it at all. GeoJsonDocumentRasterOverlay hardcodes GeographicProjection and offers no override --
CesiumVectorOverlays/src/GeoJsonDocumentRasterOverlay.cpp (~line 690):
GeoJsonDocumentRasterOverlayTileProvider(...)
: RasterOverlayTileProvider(
pCreator,
parameters,
GeographicProjection(geoJsonOptions.ellipsoid),
projectRectangleSimple(
GeographicProjection(geoJsonOptions.ellipsoid),
GlobeRectangle::MAXIMUM)),
GeoJsonDocumentRasterOverlayOptions carries only defaultStyle, ellipsoid and mipLevels. Since Web Mercator is the common case for imagery overlays, a GeoJSON overlay on such a tileset is guaranteed to introduce a new projection.
WMTS overlays can avoid it, but only by accident of configuration. They do expose a projection setting, so an application that keeps every overlay on one projection is fine. Ours mixes them (some layers are configured Geographic, others use the WebMercator default), and adding a
mismatched one produces the same flicker.
Use case
Hovering an item in a UI list draws that item's footprint as a GeoJSON overlay over the base map; separately, users toggle WMTS imagery layers on and off. Both make the base map's loaded tiles reload, so the tileset visibly blinks and the interactions feel broken.
We confirmed the diagnosis by matching projections: setting our WMTS base overlay to Geographic where the service supports EPSG:4326 removes the flicker completely. That workaround is only available when the imagery service actually serves that projection.
One thing we could not confirm
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. It would be useful to have that confirmed, since it is the property applications would need to rely on to work around this: is adding a raster overlay whose projection already matches an existing overlay on the same tileset guaranteed not to reload tile content?
Possible resolutions (in rough order of preference)
-
Implement the TODO already noted in the comment: add the missing texture coordinates to an
already-loaded tile without discarding and re-fetching its content. This is the general fix and
covers every overlay type.
-
Allow projections to be pre-registered on the tileset (e.g. via TilesetOptions), so an
application that knows it will later add a Geographic overlay can have tiles generate those
texture coordinates up front. This also covers every overlay type and may be considerably simpler
than (1).
-
Expose the projection through GeoJsonDocumentRasterOverlayOptions, defaulting to
GeographicProjection so existing behaviour is unchanged. This is the smallest change, and it
closes the one case where an application currently has no way out at all. Implementation note:
the coverage rectangle in the tile provider constructor would need to use the selected projection
too; loadTileImage already goes through this->getProjection() and looks projection-agnostic.
Even without (1) or (2), documenting the constraint would help a great deal -- particularly the
positive guarantee that matching projections avoids the reload.
Summary
Adding a
RasterOverlayto aTilesetthat has already loaded causes every affected tile to have its content unloaded and reloaded from scratch. In a real-time application this is very visible: the geometry disappears and reappears, i.e. the map flickers.We reproduce this with two different overlay types, via Cesium for Unity:
GeoJsonDocumentRasterOverlayCesiumWebMapTileServiceRasterOverlay, i.e.WebMapTileServiceRasterOverlay)It also recurs on every add rather than being a one-time cost.
Mechanism
Cesium3DTilesSelection/src/TilesetContentManager.cpp(~line 2126):The "missing projection" state is produced in
Cesium3DTilesSelection/src/RasterMappedTo3DTile.cpp(~line 344):To be clear, we are not claiming the reload itself is a defect. Having read through it, it looks like a sound tradeoff: overlay texture coordinates are per-vertex glTF attributes (
_CESIUMOVERLAY_n) generated bycreateRasterOverlayTextureCoordinatesby projecting every vertex position, along with the per-projection rectangles inRasterOverlayDetails. They are produced in the load thread from the glTF, and downstream consumers do not necessarily keep the glTF afterwards -- Cesium for Unity discards it once the model has been converted to Unity meshes, retaining only the GameObject and a primitive index map. Retrofitting a new UV set onto an already-uploaded mesh would mean reconstructing that data and re-uploading to the GPU. The comment already notes the better answer as future work.Why it is painful in practice
It recurs indefinitely. A tile only generates texture coordinates for the projections present in the collection at the moment it loads. As the camera moves and tiles stream in and out, newly loaded tiles again lack the second projection's UVs, so the next add reloads them. There is no point at which the application has "paid" the cost.
GeoJSON overlays cannot avoid it at all.
GeoJsonDocumentRasterOverlayhardcodesGeographicProjectionand offers no override --CesiumVectorOverlays/src/GeoJsonDocumentRasterOverlay.cpp(~line 690):GeoJsonDocumentRasterOverlayOptionscarries onlydefaultStyle,ellipsoidandmipLevels. Since Web Mercator is the common case for imagery overlays, a GeoJSON overlay on such a tileset is guaranteed to introduce a new projection.WMTS overlays can avoid it, but only by accident of configuration. They do expose a projection setting, so an application that keeps every overlay on one projection is fine. Ours mixes them (some layers are configured
Geographic, others use theWebMercatordefault), and adding amismatched one produces the same flicker.
Use case
Hovering an item in a UI list draws that item's footprint as a GeoJSON overlay over the base map; separately, users toggle WMTS imagery layers on and off. Both make the base map's loaded tiles reload, so the tileset visibly blinks and the interactions feel broken.
We confirmed the diagnosis by matching projections: setting our WMTS base overlay to
Geographicwhere the service supports EPSG:4326 removes the flicker completely. That workaround is only available when the imagery service actually serves that projection.One thing we could not confirm
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. It would be useful to have that confirmed, since it is the property applications would need to rely on to work around this: is adding a raster overlay whose projection already matches an existing overlay on the same tileset guaranteed not to reload tile content?
Possible resolutions (in rough order of preference)
Implement the TODO already noted in the comment: add the missing texture coordinates to an
already-loaded tile without discarding and re-fetching its content. This is the general fix and
covers every overlay type.
Allow projections to be pre-registered on the tileset (e.g. via
TilesetOptions), so anapplication that knows it will later add a Geographic overlay can have tiles generate those
texture coordinates up front. This also covers every overlay type and may be considerably simpler
than (1).
Expose the projection through
GeoJsonDocumentRasterOverlayOptions, defaulting toGeographicProjectionso existing behaviour is unchanged. This is the smallest change, and itcloses the one case where an application currently has no way out at all. Implementation note:
the coverage rectangle in the tile provider constructor would need to use the selected projection
too;
loadTileImagealready goes throughthis->getProjection()and looks projection-agnostic.Even without (1) or (2), documenting the constraint would help a great deal -- particularly the
positive guarantee that matching projections avoids the reload.