Skip to content

13 minimap #42

Merged
merged 5 commits into from
Oct 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions src/MiniMap/miniMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* Initializes the Potree minimap to work with offline map and EPSG:4978 coordinates.
*
* @param {Potree.Viewer} viewer - The Potree viewer instance
*/
export function initMiniMap(viewer) {
if (!viewer.mapView) return
const map = viewer.mapView.map
const layers = map.getLayers()

replaceMiniMapLayers(layers)

removeTileTools(map)

overrideMapViewUpdate(viewer)

setMiniMapBackground(map, '#d6ecff')

// Transform minimap center [lon, lat] from EPSG:4326 to EPSG:3857
const center = proj4('EPSG:4326', 'EPSG:3857', [2, 69])
map.getView().setCenter(center)
}

/**
* Sets the background color for the Minimap so that it is not seethrough.
* Makes it easier to get an overview when deep in a survey.
*
* @param {*} map
* @param {*} color
*/
function setMiniMapBackground(map, color) {
// OL ≥ 6.5 supports a map background property
try {
map.set && map.set('background', color)
} catch {}

// Fallbacks that work on older OL + Potree
const viewport = map.getViewport && map.getViewport()
if (viewport) viewport.style.background = color

const target = map.getTargetElement && map.getTargetElement()
if (target) target.style.background = color

// As a last resort, paint the canvas background
const canvas = viewport && viewport.querySelector('canvas')
if (canvas) canvas.style.background = color
}

/**
* Replaces default Open Street Map layer with custom GeoJSON in order for it to work offline.
* Also removes the extent layer.
*
* @param {ol.Collection} layers - Collection of OpenLayers layers
*/
function replaceMiniMapLayers(layers) {
// Remove the old Open Street Map layer
const oldOSMLayer = layers.item(0)
layers.remove(oldOSMLayer)

// Add new offline GeoJSON layer
const newGeojsonLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: '/data/geo/world_simplified.geojson',
format: new ol.format.GeoJSON()
})
})
layers.insertAt(0, newGeojsonLayer)

// Remove the extent layer
const extentLayer = layers.item(6)
layers.remove(extentLayer)
}

/**
* Removes the tools for downloading and showing/hiding tiles, denoted D and T in the minimap.
*
* @param {ol.Map} map - The OpenLayers map instance
*/
function removeTileTools(map) {
map.getControls().forEach((control) => {
if (
control.constructor &&
control.constructor.name === 'DownloadSelectionControl'
) {
map.removeControl(control)
}
})
}

/**
* Overrides the minimap pointer update logic to use 3D coordinates and correct direction.
*
* @param {Potree.Viewer} viewer - The Potree viewer instance
*/
function overrideMapViewUpdate(viewer) {
const Vector3 = THREE.Vector3
const Vector2 = THREE.Vector2
viewer.mapView.update = function (delta) {
if (!this.sceneProjection) {
return
}

let pm = $('#potree_map')

if (!this.enabled) {
return
}

let mapSize = this.map.getSize()
let resized = pm.width() !== mapSize[0] || pm.height() !== mapSize[1]
if (resized) {
this.map.updateSize()
}

let camera = this.viewer.scene.getActiveCamera()

let scale = this.map.getView().getResolution()
let campos = camera.position
let camdir = camera.getWorldDirection(new Vector3())
let sceneLookAt = camdir
.clone()
.multiplyScalar(30 * scale)
.add(campos)
let geoPos = camera.position
let geoLookAt = sceneLookAt

// Include z-coordinate to handle EPSG:4978 properly
let mapPos = new Vector2().fromArray(
this.toMap.forward([geoPos.x, geoPos.y, geoPos.z])
)
let mapLookAt = new Vector2().fromArray(
this.toMap.forward([geoLookAt.x, geoLookAt.y, geoLookAt.z])
)

// Reverse direction so pointer faces correct way
let mapDir = new Vector2().subVectors(mapPos, mapLookAt).normalize()

mapLookAt = mapPos.clone().add(mapDir.clone().multiplyScalar(30 * scale))
let mapLength = mapPos.distanceTo(mapLookAt)
let mapSide = new Vector2(-mapDir.y, mapDir.x)

let p1 = mapPos.toArray()
let p2 = mapLookAt
.clone()
.sub(mapSide.clone().multiplyScalar(0.3 * mapLength))
.toArray()
let p3 = mapLookAt
.clone()
.add(mapSide.clone().multiplyScalar(0.3 * mapLength))
.toArray()

this.gCamera.setCoordinates([p1, p2, p3, p1])
}
}
4 changes: 3 additions & 1 deletion src/potreeViewer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { initAnnotationsPanel } from './AnnotationControl/annotationPanel.js'
import { initMeasurementsPanel } from './MeasurementControl/measurementsPanel.js'
import { initMiniMap } from './MiniMap/miniMap.js'
import {
initThreePanels,
toggleAcceptedLegend
Expand Down Expand Up @@ -126,11 +127,12 @@ export async function createPotreeViewer(

initMeasurementsPanel(viewer)
initAnnotationsPanel(viewer)
initMiniMap(viewer)
})

// Initialize camera position and target point (manually chosen)
viewer.scene.view.setView(
[3961574.044, 1494736.334, 8348318.575], // Initial camera position
[4094989.813, 59057.337, 8363694.681], // Initial camera position
[1500922.651, 510673.03, 5427934.722] // Initial target point
)

Expand Down