Chapter 11: Full Integration and Beyond

A fully integrated WebGIS viewer with all layers combined

Introduction

Over the course of all 11 chapters, we have built from scratch the internal architecture of web mapping libraries that GIS professionals have been using as a "black box." Starting from Scene/Camera/Renderer initialization, we experienced every element of a WebGIS engine: coordinate transformations, tile mesh generation, LOD control, integration of raster, vector, and 3D tiles, and styling with custom shaders.

In this chapter, we take a bird's-eye view of the GlobeViewer architecture, examine performance bottlenecks and optimization strategies, review advanced topics not covered in this book, and outline the next steps. This is the starting point for future extensions.

Layer Composition

The layers implemented in Chapters 7 through 9 are passed to GlobeViewer as an array in +page.svelte. The canvas on the left displays two layers combined: raster tiles and 3D Tiles. Adding a VectorTileLayer would enable a three-layer configuration.

Layer Integration Code
const viewer = new GlobeViewer({
  canvas: canvasEl,
  layers: [
    new RasterTileLayer(),
    new Tiles3DLayer({
      url: '...tileset.json'
    })
  ]
});
viewer.start();

Rendering Order

Layers are added to the scene in array order, and the depth buffer ensures correct front-to-back rendering.

  1. RasterTileLayer — Renders OSM tile images as spherical meshes (backmost)
  2. VectorTileLayer — Renders roads, rivers, and boundaries as lines (elevated 50m to avoid Z-fighting)
  3. Tiles3DLayer — Renders PLATEAU building models (placed on the ground surface)

Each layer has its own independent THREE.Group and does not interfere with other layers' resources. This independence allows you to freely change layer combinations.

Per-Frame Processing Flow

The core of GlobeViewer is the RenderLoop. Within the callback invoked every frame via requestAnimationFrame, it executes update() for all layers in sequence, then performs the final rendering.

requestAnimationFrame callback
│
├── RasterTileLayer.update(ctx)
│     ├── Camera change check
│     ├── Throttling (200ms limit)
│     ├── SSE tile selection
│     ├── Diff calculation (add / remove)
│     └── Start texture loading
│
├── VectorTileLayer.update(ctx)
│     ├── Build queue processing (frame-distributed)
│     ├── Destroy queue processing (budget-aware)
│     ├── Camera change check
│     ├── Throttling (200ms limit)
│     ├── SSE tile selection
│     ├── Diff calculation (add / remove)
│     └── MVT load → enqueue to build queue
│
├── Tiles3DLayer.update(ctx)
│     └── adapter.update(camera, renderer)
│           ├── processDisposalQueue()
│           ├── Camera change check
│           ├── traverseTileset()
│           │     ├── Bounding volume → SceneBounds
│           │     ├── Horizon + frustum culling
│           │     ├── SSE calculation + refinement decision
│           │     └── Generate desired / toRemove / toCancel
│           ├── Cancel + remove from scene
│           ├── Cache hit → add to scene
│           └── Cache miss → load request
│
└── ThreeRenderer.render()
      ├── Zoom interpolation (ellipsoid radius + surface dist)
      ├── Target transition (surface ↔ origin)
      ├── Rotation speed adjustment
      ├── Dynamic near/far adjustment (horizon dist)
      ├── controls.update()
      └── renderer.render(scene, camera)

The update → render order is critical. Layers first add and remove tiles, and then ThreeRenderer draws the result.

Frame Loop
const loop = new RenderLoop(() => {
  const ctx = createContext(
    performance.now()
  );
  for (const layer of layers) {
    layer.update(ctx);
  }
  threeRenderer.render();
});

LayerContext

The LayerContext passed to each layer's update() contains all the information a layer needs for rendering:

  • camera — PerspectiveCamera reference
  • screenHeight — Viewport height (for SSE calculation)
  • renderer — WebGLRenderer reference
  • frameBudgetMs / remainingBudgetMs() — Frame budget management
  • selectVisibleTiles() — SSE selection helper (with cache)

selectVisibleTiles has a built-in caching mechanism. When called with the same parameters from multiple layers, it reuses the same result within a single frame. This allows RasterTileLayer and VectorTileLayer to share the same SSE selection results, reducing the CPU cost of quadtree traversal.

Performance Bottlenecks

This implementation includes simplifications made for educational purposes that could become bottlenecks in production use.

1. CPU Cost of Tile Mesh Generation

computeTileGeometry recalculates vertices every time. Since tile geometry is immutable (same tile coordinates produce the same mesh), an LRU cache can be used to retain results and avoid recomputation.

2. Main Thread Load from MVT Decoding

MVT Protocol Buffers decoding and coordinate transformation (including trigonometric functions) run on the main thread (UI thread). Tiles with a large number of features can cause frame drops. The 200ms throttling in VectorTileLayer mitigates this, but a fundamental solution requires off-thread processing with Web Workers.

3. Draw Call Count

Since each tile has its own Mesh or Group, draw calls increase proportionally with the number of visible tiles. Batching with InstancedMesh or Texture Atlas can reduce this, but it is not implemented in this book.

Optimization Strategies

ProblemSolution
Mesh recomputationLRU cache for geometry
Main thread loadDecoding and transformation in Web Workers
Draw callsBatching with InstancedMesh
Texture memoryConsolidation via Texture Atlas
MVT rendering qualityearcut triangulation after spherical subdivision (improved polar accuracy)

Features Not Covered in This Book

Camera Pitch (Tilt)

Camera controls in this book are limited to OrbitControls rotation and custom zoom; tilting the camera toward the horizon (pitch/tilt) is not supported. To look across the cityscape near the ground — as in Google Maps, CesiumJS, or MapLibre GL — you would need to relax OrbitControls' polar angle limits or implement a custom camera controller. Because the frustum shape changes significantly with tilt angle, the tile selection algorithm (SSE calculation) and near/far clip plane adjustments must also be revisited accordingly.

Terrain

The tile meshes in this book are generated on the ellipsoid surface at elevation 0. The computeTileGeometry heightFn parameter is prepared for elevation support, but the implementation is not included. To achieve terrain meshes, you need to load elevation tiles (e.g., Terrain-RGB), supply elevation values to heightFn, and recalculate normals (cross product from adjacent vertices).

Atmospheric Scattering

The atmospheric scattering effect along the Earth's limb when viewed from space is achieved with custom shaders. The custom shader knowledge from Chapter 10 is directly applicable here. CesiumJS's atmospheric scattering shader is a useful reference.

Label Rendering

MVT label layers (such as place_labels) are currently rendered only as points. Rendering text labels requires SDF textures (Signed Distance Field) or Canvas-based texture generation.

Interaction

The viewer in this book only supports camera controls; click selection of objects and popup display are not implemented. This can be achieved using Three.js's Raycaster combined with feature attribute linking.

WebGPU Support

Three.js's WebGPU backend is available as WebGPURenderer, but the ShaderMaterial and onBeforeCompile used in this book are not supported by WebGPURenderer. Migration requires rewriting to node materials using TSL (Three.js Shading Language).

Architecture Review

┌──────────────────────────────────────────┐
│            +page.svelte                  │
│  GlobeViewer (Facade)                    │
│  ├── ThreeRenderer (rendering, camera,   │
│  │   zoom, pinch-to-zoom)                │
│  ├── RenderLoop (rAF)                    │
│  └── Layer[]                             │
│       ├── RasterTileLayer                │
│       │   ├── selectVisibleTiles (SSE)   │
│       │   ├── TileTextureLoader (LRU)    │
│       │   └── createTileMesh             │
│       ├── VectorTileLayer                │
│       │   ├── selectVisibleTiles (shared)│
│       │   ├── MvtTileLoader (PBF)        │
│       │   └── createMvtGroup             │
│       └── Tiles3DLayer                   │
│           └── Tiles3DAdapter             │
│               ├── tileset-parser         │
│               │   (tree construction)    │
│               ├── tile-traversal         │
│               │   (SSE traversal)        │
│               ├── bounding-volume        │
│               │   (coordinate transform) │
│               ├── tile-cache             │
│               │   (LRU + request mgmt)   │
│               └── content-loader         │
│                   (B3DM/GLB parsing)     │
├──────────────────────────────────────────┤
│  core/ (Three.js independent)            │
│  ellipsoid → coordinates → projection    │
│                   → tiles                │
│              tile-geometry → tile-tree    │
└──────────────────────────────────────────┘

Evaluation of Design Decisions

What Worked Well

  • Separating core/ made unit testing of coordinate transformations straightforward
  • The Layer abstract class makes it easy to add new layer types
  • The 3D Tiles loader was custom-built as 6 modules, limiting external dependencies to only GLTFLoader/DRACOLoader
  • selectVisibleTiles is shared between Raster and Vector, ensuring consistent LOD control
  • Extracting shared logic into tile-layer-utils.ts eliminated duplicate diff-update logic between Raster and Vector

Areas for Improvement

  • Throttling for VectorTileLayer was implemented, but Web Worker migration has not been started
  • Since earcut performs 2D triangulation, large polygons near the poles suffer from accuracy issues due to spherical distortion
  • Draw call batching with InstancedMesh is not implemented
  • Dynamic addition and removal of layers is not supported
  • 3D Tiles PNTS (Point Cloud) / I3DM (Instanced Model) / CMPT (Composite Tile) formats are not supported
  • Implicit Tiling is not supported

Module Design

The entire project is composed of modules with clearly separated responsibilities:

core/

Pure mathematical functions with no Three.js dependency. Provides coordinate transformations (geodetic/ECEF/ENU/scene), WGS84 ellipsoid constants, tile coordinate calculations, and quadtree traversal algorithms. Easy to unit test and portable to other rendering engines.

renderer/

Three.js-dependent rendering infrastructure. Handles WebGLRenderer, PerspectiveCamera, OrbitControls, custom zoom (wheel and pinch-to-zoom), and lighting setup and management. Also includes dynamic near/far adjustment and target transition. Includes custom shaders (custom-shader.ts), tile mesh generation, and MVT mesh generation.

renderer/tiles3d/

Six modules for the custom 3D Tiles loader. types.ts (type definitions), tileset-parser.ts (tree construction), tile-traversal.ts (SSE traversal), bounding-volume.ts (coordinate transformation), tile-cache.ts (LRU cache + request management), content-loader.ts (B3DM/GLB parsing + GLTFLoader). tiles3d-adapter.ts serves as the control layer that integrates all of these.

layers/

Layer lifecycle management. Based on the abstract Layer class, Raster/Vector/Tiles3D layers each manage rendering through the initialize/update/dispose pattern. tile-layer-utils.ts provides shared diff-update logic.

engine/

Render loop and frame budget management. Provides requestAnimationFrame abstraction and per-layer processing time control.

Next Steps

Using this book's implementation as a starting point, you can extend it in the following directions:

  1. Terrain Mesh — Representing terrain relief using elevation tiles (the heightFn foundation was implemented in Chapter 5)
  2. Web Worker Migration — Off-thread processing for MVT decoding and mesh generation
  3. Atmosphere and Water Shaders — Visual enhancements applying custom shader techniques from Chapter 10
  4. WebGPU Migration — Adopting the next-generation GPU API
  5. PNTS/I3DM Format Support — Adding support for point clouds and instanced models
  6. Implicit Tiling Support — Implementing implicit tiling via template URLs
  7. Layer Management UI — UI for toggling layers on and off
  8. Interaction — Feature selection and popups via Raycaster

Overall Learning Summary

From Chapter 1 through Chapter 11, we progressively built up the following elements:

Ch.TopicCore Techniques
1Three.js SetupLogarithmic depth buffer, lighting
2Camera ControlsSurface-distance-based zoom, ellipsoid directional radius
3Coordinate TransformsWGS84 ellipsoid, ECEF, ENU, axis conversion
4Projection and TilesWeb Mercator, Slippy Map Tiles
5Spherical MeshMercator Y interpolation, UV coordinates
6LOD ControlSSE, Quadtree, Frustum culling
7Raster TilesLRU cache, diff updates, parent-child transitions
8Vector TilesMVT decoding, earcut triangulation
93D TilesB3DM parser, SSE traversal, LRU cache, coordinate transform matrices
10Custom ShadersShaderMaterial, onBeforeCompile, uniform
11Full IntegrationFacade, frame budget, extensible design

The internals of a WebGIS engine are an intricate interplay of coordinate transformation mathematics, GPU rendering techniques, and asynchronous resource management architecture. We hope this book has helped you open that black box.