Live Demo
This page explains the demo as a working integration blueprint.
If you are integrating Potree and mapprism-2d in one app, demo/index.html is the reference implementation.
What this demo teaches
The demo shows how to run two renderers in one host layout:
- Potree in
panel-3d MapPrism2Dinpanel-2d- A shared header that controls view mode and active panel
- Runtime synchronization between 3D and 2D view state
In practical terms, it answers:
- how to initialize both viewers from one bootstrap script
- how to hide/show panels without breaking MapLibre sizing
- how to keep context when users move between 3D and 2D
- how to ship usable built-in 2D controls (layers, HUD, measurement) on day one
Runtime architecture at a glance
demo/index.html is intentionally monolithic so you can trace the full flow in one file:
- Load Potree and related global scripts
- Load
MapPrism2Das an ES module - Create the dual-panel DOM structure (
panel-3d,panel-2d) - Start Potree (
window.viewer) - Start
MapPrism2D(window.map2d) with sync enabled against Potree - Wire UI state transitions (split/alternating + panel switch)
Think of it as a host app skeleton. You can later split this into framework components, but the integration order should stay similar.
UI state model (the part most teams need)
The demo keeps UI logic simple and explicit:
viewMode:"alternate"or"split"activePanel:"3d"or"2d"(only relevant in alternating mode)PANELSmetadata object: maps panel ids to labels and switch targets
Two small functions drive almost all behavior:
setViewMode(nextMode)applies mode classes and updates header/buttonssetActivePanel(next)toggles which panel is visible in alternating mode
Important integration detail: after any panel/mode change, it calls:
window.map2d.resize()
This is required because MapLibre must recalculate the canvas when its container visibility/size changes.
Potree bootstrap (3D side)
The demo creates Potree first and exposes it globally:
window.viewer = new Potree.Viewer(...)- sets FOV, point budget, and GUI options
- calls
viewer.fitToScreen(...)after the point cloud loads
A dataset selector at the top of the demo switches between two real-world datasets:
| Dataset | CRS | Notes |
|---|---|---|
| Piney Dam | EPSG:3857 (Web Mercator) | Default. Standard case — no srs needed. |
| Holtwood Dam | EPSG:6565 (Pennsylvania South, survey feet) | Demonstrates non-EPSG:3857 support. Includes a tailrace DSM raster layer. |
Switching datasets reinitializes both viewers with dataset-specific configs (including srs for Holtwood).
Why this matters: MapPrism2D sync receives the Potree viewer reference, so Potree must exist before the 2D map is initialized.
MapPrism2D bootstrap (2D side)
The demo then creates:
window.map2d = new MapPrism2D(document.getElementById("panel-2d"), config)
The config is close to real integration defaults:
initialView: center + zoom + bearingbasemap: "uai"layerPanel: truehud: truetools.measurement.controls: truelayers: raster + PMTiles stack
This gives you a production-like starting point where users can immediately inspect layers and measurements without custom UI code.
3D <-> 2D sync wiring
The demo enables continuous sync using:
sync.enabled: truesync.potree.viewer: window.viewersync.potree.throttleMs: 50
For the Holtwood Dam dataset, sync.potree.srs: "EPSG:6565" is also set. The library reads the proj4 definition from the loaded point cloud metadata and converts survey-foot state plane coordinates to WGS84 transparently — the map still opens on the correct location despite the scene not being in Web Mercator.
That setup keeps both views spatially coherent while users navigate either panel.
If your product does not need continuous coupling, start with transition-time handoff (getView() / setView()) and enable continuous sync later.
Working with non-EPSG:3857 datasets
The Holtwood Dam dataset in the demo is the fastest way to see srs in action:
- Select Holtwood Dam from the dataset dropdown.
- Both viewers initialize. The 2D map opens correctly over south-central Pennsylvania despite the Potree scene being in survey feet (EPSG:6565), not metres or Web Mercator.
- Navigate in either panel — sync stays accurate because radius/zoom conversion accounts for the survey-foot unit scale automatically.
To replicate this for your own non-EPSG:3857 dataset, add srs to your config:
sync: {
enabled: true,
potree: {
viewer: window.viewer,
srs: 'EPSG:6565', // replace with your scene's EPSG code
},
}See the Potree Sync Reference — Non-EPSG:3857 scenes for the full projection resolution flow.
Measurement tools and debug instrumentation
The measurement UI is enabled through tools.measurement.controls: true.
The demo also adds a debug helper:
window.measureDebug
It wraps measurement method calls and logs drawings after each action. This is useful while integrating custom toolbars or validating keyboard finalize/cancel behavior (Enter / Escape).
How to adapt this demo into your app
Use demo/index.html as your migration checklist:
- Keep the same container contract (
panel-3d,panel-2d) or equivalent sized regions. - Preserve initialization order: Potree first, then
MapPrism2Dwith Potree reference. - Keep explicit view state (
viewMode,activePanel) in your host state store. - Call
map2d.resize()whenever panel visibility or layout changes. - Start with built-in controls; replace with custom controls only after behavior is stable.
- Replace demo layer URLs with your environment-specific sources.
Local run
npm install
npm run dev:siteThen open /demo/index.html and keep this guide next to demo/index.html while reading the source.