Skip to content

Add functionality to create tilesets for S2 cells#202

Draft
javagl wants to merge 5 commits into
mainfrom
create-tileset-json-s2
Draft

Add functionality to create tilesets for S2 cells#202
javagl wants to merge 5 commits into
mainfrom
create-tileset-json-s2

Conversation

@javagl
Copy link
Copy Markdown
Contributor

@javagl javagl commented Apr 23, 2026

This PR currently adds two classes that can be used for creating tilesets based on the S2 cell hierarchy, with contents that are identified by S2 cell tokens.


The TilesetJsonCreatorS2 class offers a function to create the Tileset object. The structure of this tileset will be:

  • The root has 6 children, one for each S2 face ID
  • Each child of the root is the root of an S2 quadtree, with a configurable number of levels
  • Each leaf refers to a content that is identified by the S2 cell token.

For example, for 2 S2 levels, the output will look as follows:

{
  "asset": {
    "version": "1.1"
  },
  "extensionsRequired": [
    "3DTILES_bounding_volume_S2"
  ],
  "extensionsUsed": [
    "3DTILES_bounding_volume_S2"
  ],
  "geometricError": 1048576,
  "root": {
    "refine": "ADD",
    "boundingVolume": {
      "region": [
        -3.141592653589793,
        -1.5707963267948966,
        3.141592653589793,
        1.5707963267948966,
        -12000,
        -9000
      ]
    },
    "geometricError": 524288,
    "children": [
      {
        "geometricError": 524288,
        "boundingVolume": {
          "extensions": {
            "3DTILES_bounding_volume_S2": {
              "token": "1",
              "minimumHeight": -12000,
              "maximumHeight": -9000
            }
          }
        },
        "children": [
          {
            "geometricError": 524288,
            "boundingVolume": {
              "extensions": {
                "3DTILES_bounding_volume_S2": {
                  "token": "04",
                  "minimumHeight": -12000,
                  "maximumHeight": -9000
                }
              }
            },
            "children": [
              {
                "geometricError": 262144,
                "boundingVolume": {
                  "extensions": {
                    "3DTILES_bounding_volume_S2": {
                      "token": "01",
                      "minimumHeight": -12000,
                      "maximumHeight": -9000
                    }
                  }
                },
                "content": {
                  "uri": "content-01"
                }
              },

The content URI is configurable with a template URI.


For a larger number of S2 levels, that tileset will grow quickly. For example, for 7 S2 levels, the resulting tileset JSON will have roughly 100MB.

I therefore added another class here, which was on my "implicit TODO list" for quite a while now anyhow: The TilesetSplitter class receieves a tileset, and converts individual tiles into external tilesets.

The criterion for converting a tile into an external tileset is pretty generic internally. But externally (on the API level), it can currently only be configured by defining the "global" levels on which a split should occur.

This TilesetSplitter can then be applied to the tileset that is generated for the S2 cells. The following snippet illustrates that:

import fs from "fs";
import { TilesetJsonCreatorS2 } from "./src";
import { TilesetSplitter } from "./src";

async function runExample() {
  const creator = new TilesetJsonCreatorS2();
  const tileset = creator.createTileset();

  const tilesetSplitter = new TilesetSplitter();
  tilesetSplitter.setGlobalSplittingLevels([4]);
  const newExternalTilesets = tilesetSplitter.splitTileset(tileset);

  fs.writeFileSync(
    "./data/tileset.json",
    Buffer.from(JSON.stringify(tileset, null, 2))
  );

  for (const entry of newExternalTilesets.entries()) {
    const name = entry[0];
    const value = entry[1];
    fs.writeFileSync(
      "./data/" + name,
      Buffer.from(JSON.stringify(value, null, 2))
    );
  }
}

void runExample();

This will generate the tileset with 7 S2 levels, split it at level 4, and write out the results. The results will then be..

  • A tileset that only has 270KB, and refers to 384 external tilesets
  • The external tilesets, with names like external-00c.json, which each have 170KB

@javagl
Copy link
Copy Markdown
Contributor Author

javagl commented May 3, 2026

The last commit adds a CLI command for the S2 tileset creation.

There are quite a few configuration options for the resulting tileset. These could be routed through the command line, but at a certain number, it would probably make more sense to summarize them in some plain JSON file, and pass in that file as some ... -options allTheOptions.json command line argument.

For now, only a few options are offered at the command line, namely the ones that I considered to be the most important ones:

  • maximumLevelInclusive: The maximum S2 level up to which the tileset should be created
  • contentTemplateUri: A string that is used for the content URI of the leaf tiles, including a {cellIdToken} template parameter
  • optional: globalSplittingLevels: The global levels at which the tileset should be split into external ones

For example:
npx ts-node .\src\cli\main.ts createTilesetJsonS2 -o ./data/tileset.json --maximumLevelInclusive 7 --contentTemplateUri "content-{cellIdToken}.json" --globalSplittingLevels 4

This will create tileset JSONs for the S2 cells up to level 7, with the leaf tiles having content URIs like "content-00b1a4.json". and this tileset will be split at level 4, to refer to external tilesets.

(An aside: The globalSplittingLevels: [4] is a bit arbitrary, but seems to be a resonable trade-off: The root tileset JSON then has ~270KB, and it refers to 384 external tilesets with 170KB each. Using [3] instead will create a root tileset with 60KB, and 96 external ones with 758KB each. Using [3,5] will create a root with 60KB, then 96 external ones with 9KB, which refer to 1633 external ones with 38KB. We don't really know what's "good" or "bad" here...)

Specs still have to be added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant