MapTiler GeoSplats — format notes (reverse-engineered)
GeoSplats is MapTiler's georeferenced, HLOD Gaussian-splat format for streaming large splat
GeoSplats is MapTiler's georeferenced, HLOD Gaussian-splat format for streaming large splat
models onto a map. Rendered by the @maptiler/geosplats SDK (WebGPU). These notes are
reverse-engineered from a live model.json + metadata.json and the public SDK bundle
(https://cdn.maptiler.com/maptiler-geosplats/v1.0.4/maptiler-geosplats.mjs, 639 KB) — there is no
published byte-spec yet.
Links
- Product: https://www.maptiler.com/geosplats/
- SDK (npm): https://www.npmjs.com/package/@maptiler/geosplats · CDN:
https://cdn.maptiler.com/maptiler-geosplats/v1.0.4/maptiler-geosplats.mjs - MapTiler docs: https://docs.maptiler.com/ · 3D maps/editor: https://www.maptiler.com/3d-maps/
model.json (top manifest)
{
"global_position": { // georeferencing of the whole model
"model_scale": 0.0007, // uniform scale (local units → metres)
"offset": { "x":0,"y":0,"z":0, "x_rot":0,"y_rot":-178,"z_rot":180 }, // Euler offset (deg)
"lat": 54.829…, "lon": 18.314…, "alt": -307.6 }, // place on the globe
"models": [ // N spatially-tiled sub-models
{ "baseurl": "<uuid>", "sogs": true, "progressive_lod": true,
"lod": false, "detail": 5, "offset": { …per-model Euler/translation… } }, … ],
"attribution": "…"
}<baseurl>/metadata.json (per sub-model)
lod_mode("standalone"),splats_total,num_levels(8),format(0).lod_point_counts— cumulative splat count per LOD (≈2× each level → progressive, coarse-first);lod_level_point_counts— per-level deltas.bounding_box—{min,max}in local model coords.voxel_grids— multi-resolution octree index:grid_1,grid_8,grid_64,grid_512. Each cell:{ offset (into global splat order), total_count, counts[8] (cumulative/LOD), level_counts[8], center, bounding_box }. The renderer walks these to pick in-frustum octants at the right LOD.entries— bundle TOC[path, byteOffset, byteLength, flags, md5]into one packed blob; per octant×LOD the SOGS components aremeans_l.webp, means_u.webp, quats.webp, scales.webp, sh0.webp(+meta.json,profile.json).metadata.jsonitself is the tail entry of the blob.
Data primitive = SOGS
Per-octant data is SOGS (PlayCanvas spatially-ordered Gaussians): WebP textures for
means_l/means_u (low/high position bytes), quats, scales, sh0 (+ optional SH), described by a
small meta.json. This is the same primitive @playcanvas/splat-transform's writeSog/readSog
produce — so an octant's components can be decoded to a DataTable and re-encoded (SPZ/GLB) for 3D Tiles.
Two serving modes (from the SDK)
- Static hosting — files at
…/delta_data/grid_<s>/octant_<id>_LOD<n>/{means_l,…,sh0}.webp(+ per-octantmeta.json); octants may also be served as…/<level>/octant_<n>.ply. Decoded via the SDK'sdecode({model_type: SPLAT, format_type: PLY|SOGS}). - MapTiler API proxy —
https://api.maptiler.com/splats/<modelId>/<baseurl>/64/6/6?key=…returns individual WebPs. The/64/6/6params are opaque (e.g./64/6/6→ 200,/64/0/0→Invalid parameters); the exactgrid/x/y↔octant mapping is internal to the SDK. API keys are origin-restricted (a server-side fetch needsReferer: https://www.maptiler.com/; localhost referer is rejected).
Coordinate convention
global_position (lat/lon/alt + uniform scale + Euler offset, with y_rot≈-178, z_rot=180) places
the local model frame on the globe — analogous to our ENU-anchored splat-dump output. Local splat
data is Y-up-ish; the Euler offset reconciles it with the map frame.
Converter status (GeoSplats → OGC 3D Tiles) — implemented ✅, packages/tile-server/geosplats-live.js
GET /geosplats/tileset.json?url=<model.json> / GET /geosplats/tile.glb?... — live, per-octant
conversion (fetch → readSog → _spz_2-compressed KHR_gaussian_splatting GLB), HLOD octree from
voxel_grids, georeferenced root transform from global_position. The API-proxy octant URL scheme
(/<modelId>/<baseurl>/<grid>/<octant>/<lod>) is traced and the origin-locked key is handled
server-side (Referer: https://www.maptiler.com/) — the browser only talks to our middleware.
Verified end-to-end in CesiumJS and 3DTRJS. Full georeferencing math (scale/orientation/heading/
altitude, reverse-engineered from the SDK) is documented in the README's
/geosplats
section rather than duplicated here.