Overlay web map tiles (OSM, aerial imagery, custom XYZ) onto an Autodesk APS Viewer as a camera-synced 3D ground plane.
By Infra Plan
The Problem
Placing geographic map tiles under a BIM model in Autodesk Viewer requires:
- Extracting the camera frustum and projecting it onto a ground plane
- Converting between the viewer's internal coordinate system and WGS84
- Figuring out which map tiles cover the visible area at the right zoom level
- Fetching, stitching, and caching those tiles into a GPU-friendly texture
- Positioning a THREE.js plane in 3D space aligned with the geographic bounds
- Updating everything in real-time as the camera moves
This library handles all of that in ~500 lines of code.
Quick Start
npm install bim-tile-overlay proj4
import { TileOverlay, CoordinateTransformer } from 'bim-tile-overlay'; // After viewer has loaded a model: const transformer = CoordinateTransformer.fromAPSViewer( viewer, // proj4 definition for your local CRS (this example uses Croatia HTRS96/TM) '+proj=tmerc +lat_0=0 +lon_0=16.5 +k=0.9999 +x_0=500000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs' ); const overlay = new TileOverlay(viewer, transformer, { urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', zoomRange: [14, 19], maxBounds: transformer.getModelBoundsLL84(), }); await overlay.enable();
How It Works
Camera frustum corners
→ Ray-cast to ground plane (Z elevation)
→ Convert hit points to WGS84 lon/lat
→ Determine visible geographic bounds
→ Calculate optimal tile zoom level
→ Fetch XYZ tiles in parallel
→ Stitch into single canvas texture
→ Map onto THREE.js plane in viewer space
→ Update on camera change (debounced)
Coordinate pipeline:
WGS84 (lon, lat)
↔ Local CRS (meters) → via proj4
↔ CRS in feet → × 3.28084
↔ BIM internal coords → via refPointTransform rotation + translation
↔ Viewer display coords → minus globalOffset
API Reference
TileOverlay
Main class. Creates a map tile overlay on the viewer.
const overlay = new TileOverlay(viewer, transformer, options);
Options:
| Option | Type | Default | Description |
|---|---|---|---|
urlTemplate |
string |
required | Tile URL with {z}, {x}, {y} placeholders |
zoomRange |
[number, number] |
required | Min and max zoom levels |
maxBounds |
GeoBounds |
required | Geographic bounds to clip tile fetching |
groundZ |
number |
modelBBox.min.z - 5 |
Z elevation of the ground plane |
debounceMs |
number |
150 |
Camera change debounce delay (ms) |
maxCacheSize |
number |
6 |
Max cached stitched tile canvases |
zoomScaleFactor |
number |
12 |
Tile detail vs. camera distance. Higher = more detail |
progressInterval |
number |
5 |
Texture refresh frequency during tile loading (every N tiles). Always fires on the last tile. Set to 1 for per-tile updates. |
sceneName |
string |
'bim-tile-overlay' |
Viewer overlay scene name |
Methods:
| Method | Description |
|---|---|
enable() |
Show the overlay and start tracking camera |
disable() |
Hide the overlay, preserve cache for re-enabling |
destroy() |
Fully dispose all GPU resources and cache |
update() |
Force an immediate tile refresh |
CoordinateTransformer
Converts between WGS84 and the viewer's coordinate system.
// From viewer (recommended): const transformer = CoordinateTransformer.fromAPSViewer(viewer, crsDefinition); // Manual config: const transformer = new CoordinateTransformer({ crs: '+proj=tmerc +lat_0=0 +lon_0=16.5 ...', refPointTransform: [...], // From model metadata globalOffset: { x, y, z }, modelBBox: { min: { x, y, z }, max: { x, y, z } }, });
Methods:
| Method | Description |
|---|---|
lonLatToViewer(lon, lat, z?) |
WGS84 → viewer coordinates |
viewerToLonLat(x, y, z?) |
Viewer coordinates → WGS84 |
getModelBoundsLL84() |
Model bounding box in WGS84 |
Utility Functions
import { lonLatToTile, tileToLonLat, getViewportBounds, createTileCache } from 'bim-tile-overlay';
| Function | Description |
|---|---|
lonLatToTile(lon, lat, zoom) |
WGS84 → tile {x, y} coordinates |
tileToLonLat(x, y, zoom) |
Tile coordinates → WGS84 (NW corner) |
getViewportBounds(camera, transformer, options) |
Camera frustum → geographic bounds + zoom |
createTileCache(maxSize?) |
LRU cache that frees bitmap memory on eviction |
Finding Your CRS
Your BIM model needs a local Coordinate Reference System (CRS) for accurate positioning. Common ones:
| Region | CRS | Code |
|---|---|---|
| Croatia | HTRS96/TM | EPSG:3765 |
| UK | British National Grid | EPSG:27700 |
| Germany | ETRS89/UTM zone 32N | EPSG:25832 |
| US (New York) | NAD83/NY Long Island | EPSG:2263 |
Find your CRS definition at epsg.io and pass the proj4 string.
Requirements
- Autodesk Viewer (APS / Forge) with a loaded, georeferenced model — the Viewer script provides
THREE.jsandAutodeskglobals that this library uses internally; you do not need to install them separately - proj4 (peer dependency) —
npm install proj4 - The BIM model must have
refPointTransformin its metadata (set via Revit's survey/project point)
Contributing
Contributions are welcome! Please open an issue or pull request.
License
MIT - Infra Plan

