Behavior based on layer.json "available" arrays

Hi, I’ve implemented a custom terrain tile cutter through a web controller, which serves up Cesium quantized-mesh .terrain files on demand, and caches them for subsequent calls. I understand that the layer.json file identifies the available X/Y tiles for each LOD.

What I’m seeing seems odd, though, so I’m hoping for a deeper explanation. When I clear my cache, fire up the service, and launch a browser, the default view is at Level 6. However, I am seeing network traffic to my controller requesting terrain tiles for LOD above 6; requests all the way up to the maximum LOD defined in my layer.json.

So, say my layer.json defines LOD 0 - 12. Even when the map initializes on Level 6, I am seeing requests for terrain tiles up to Level 12.

Are these higher LODs really being loaded by Cesium all the way up to the max defined in layer.json?

Part of why I’m asking is that I currently have the terrain cutting working in conjunction with my hill-shading and other tile operations, such that when the Z/X/Y GET request is handled, I only have to poll my source GeoTIFF file with GDAL once to get the projected DEM. That DEM then feeds each process that needs it. As such, any given .terrain file will only be truly available when the map specifically asks for a given tile.

But if Cesium uses layer.json to check for all tiles at all listed available levels, even if the tile has not been requested for visualization, I’ll need to duplicate the terrain cutting functionality in that controller and fetch the DEM again.

Any help with understanding more deeply what’s going on would be appreciated!

I can not answer specific quesitons about some technical details here from the tip of my head, but wanted to point to Specify the JSON structure · Issue #15 · CesiumGS/quantized-mesh · GitHub , where some related questions have been brought up (and maybe the answers to your questions are part of that).

I can try reaching out to someone with more expertise on this topic. In the meantime, though, it might be helpful if you can attach an example layer.jsonfrom your application.

The contents of the layer.json, defining LOD 0 - 9:

{
  "tilejson": "2.1.0",
  "name": "etopo1_spm_f4",
  "description": "",
  "version": "1.1.0",
  "format": "quantized-mesh-1.0",
  "attribution": "",
  "schema": "tms",
  "extensions": [ ],
  "tiles": [ "{z}/{x}/{y}.terrain?v={version}" ],
  "projection": "EPSG:4326",
  "bounds": [ -180.00, -90.00, 180.00, 90.00 ],
  "available": [
    [ { "startX": 0, "startY": 0, "endX": 1, "endY": 0 } ]
   ,[ { "startX": 0, "startY": 0, "endX": 3, "endY": 1 } ]
   ,[ { "startX": 0, "startY": 0, "endX": 7, "endY": 3 } ]
   ,[ { "startX": 0, "startY": 0, "endX": 15, "endY": 7 } ]
   ,[ { "startX": 0, "startY": 0, "endX": 31, "endY": 15 } ]
   ,[ { "startX": 0, "startY": 0, "endX": 63, "endY": 31 } ]
   ,[ { "startX": 0, "startY": 0, "endX": 127, "endY": 63 } ]
   ,[ { "startX": 0, "startY": 1, "endX": 255, "endY": 126 } ]
   ,[ { "startX": 0, "startY": 2, "endX": 511, "endY": 253 } ]
   ,[ { "startX": 0, "startY": 5, "endX": 1023, "endY": 506 } ]
  ]
}

Thank you for the link to related questions. I didn’t find anything to specifically answer my question, but the discussion was educational for me.

Hi @Andrew_Paradis, I’m happy to answer your questions about layer.json, but I’m not sure if I entirely understand your question.

The layer.json you shared defines availability for levels 0-9. So CesiumJS (is that your client?) will request any of the available tiles that it thinks it needs for the current view. It sounds like you’re saying that you think only through level 6 is needed for the current view, yet the client is requesting deeper levels?

If that’s the case, how are you determining that only level 6 is needed?

In CesiumJS, which LOD is required is a function of screen-space error, which is a projection of geometric error (which with layer.json / quantized-mesh is constant per LOD) onto the screen. The projection is a function of distance, and the distance used is the closest point on the tile’s bounding volume. The bounding volume, in turn, is created based on the tile’s horizontal dimensions (which are fixed for any given tile) as well as the minimum and maximum height within that tile (which is specified in the tile). So if the LOD is switching at farther-away distances than you expect, one possibility is that your min/max heights in the tiles are wrong.

(As an aside, you may note in the explanation above that the bounding volume controls whether a tile is loaded, but the bounding volume is built from the content of the tile… which sounds like a chicken-and-egg problem. CesiumJS resolves this by estimating the bounding volume of child tiles based on the min/max heights in the parent, and basing loading decisions on that estimate.)

Let me know if I’ve misunderstood!

Hi Kevin, thanks very much for the helpful explanation!

I realized that the issue was not with CesiumJS, but was in fact self inflicted…

We have a mouse-over behavior that displays the bathymetric depth at the cursor, and calls Cesium.sampleTerrainMostDetailed(…) to determine the depth. Of course, “most detailed” is defined in the layer.json file, and that’s why I was seeing network traffic asking for LOD 14 when the map was sitting at LOD 6… because I was moving my mouse across the map.

So, thank you again for the explanation of LOD, and sorry to have used up your time on a red herring!

As an aside, if we wanted to use Cesium.sampleTerrain(…) rather than sampleTerrainMostDetailed, is there a way to know the LOD of the displayed tile programatically, so we can pass it as a parameter into sampleTerrain?

I don’t think this can be done without accessing private class members (though Kevin can correct me if I’m wrong). You can probably create a proxy metric for LOD though - using viewer.camera.getPixelSize or viewer.camera.frustum.getPixelDimensions, to get a pixel’s size in meters, and compare that to your tileset’s meters-per-pixel at its lowest/highest levels.

Hi @Andrew_Paradis,

Glad to hear you were able to figure it out!

To sample heights based on the currently-displayed LOD, use getHeight on the Globe class instead: