Overlay web map tiles (OSM, aerial imagery, custom XYZ) onto an Autodesk APS Viewer as a camera-synced 3D ground plane.
By Infra Plan
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.
npm install bim-tile-overlay proj4import { 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();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
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 |
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 |
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 |
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.
- 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)
Contributions are welcome! Please open an issue or pull request.
MIT - Infra Plan

