Skip to content

13 minimap #42

Merged
merged 5 commits into from
Oct 27, 2025
Merged

13 minimap #42

merged 5 commits into from
Oct 27, 2025

Conversation

adriahso
Copy link
Member

@adriahso adriahso commented Oct 26, 2025

Issue number

Closes #13

Description

Add/set up minimap to view where you are from another perspective.

Note:

  • Have not yet understood what the D and T in the bottom left of the minimap actually does, but did not remove them in case they end up being useful.
  • The logo/attribution to OpenLayers in the bottom right links to their website. This will obviously not work when the application runs offline. We do not use Open Street Map anymore, and only use the GeoJson added by @kleinc, but OpenLayers is still used for map handling by potree. I was therefore not sure whether we can remove it or not with regards to copyright, so kept it for now (can be removed easily though). Any thoughts on this?

Testing steps

  • In order for the minimap to show up you must set "projection": "+proj=geocent +datum=WGS84 +units=m +no_defs" in the point cloud metadata.json file(s).
  • Play around with the minimap (zoom in and out, move around and so on).

Summary by CodeRabbit

  • New Features

    • Interactive minimap using offline GeoJSON basemap.
    • Improved 3D→2D projection logic (including EPSG:4978 support) for accurate pointer orientation and positioning.
    • Minimap initializes with a clearer background and centered view.
  • Changes

    • Removed tile‑download controls from the minimap.
    • Updated the viewer's initial camera position for a better default perspective.

@adriahso adriahso self-assigned this Oct 26, 2025
@adriahso adriahso linked an issue Oct 26, 2025 that may be closed by this pull request
@coderabbit
Copy link

coderabbit bot commented Oct 26, 2025

Walkthrough

Adds a Potree minimap integration: exports initMiniMap(viewer), replaces online OSM with an offline GeoJSON layer, removes tile-download controls, overrides minimap update to compute a 2D pointer from 3D camera coordinates (including EPSG:4978 handling), and recenters the map via proj4 EPSG:4326→EPSG:3857.

Changes

Cohort / File(s) Summary
Minimap implementation
src/MiniMap/miniMap.js
New export initMiniMap(viewer) plus internal helpers: setMiniMapBackground(map, color) (OL/Potree fallbacks), replaceMiniMapLayers(layers) (remove OSM, insert /data/geo/world_simplified.geojson vector at index 0, remove extent layer at index 6), removeTileTools(map) (removes controls named DownloadSelectionControl), and overrideMapViewUpdate(viewer) (monkey-patches mapView.update to compute 2D map pointer position, orientation, and gCamera from 3D viewer coordinates; includes EPSG:4978 handling using this.toMap.forward). Also sets background #d6ecff and recenters map via proj4 transform EPSG:4326→EPSG:3857.
Viewer integration
src/potreeViewer.js
Imports initMiniMap and invokes it during viewer initialization; adjusts the initial camera position while preserving the original target point.

Sequence Diagram(s)

sequenceDiagram
    participant Viewer as Potree Viewer
    participant Init as initMiniMap
    participant Map as Minimap (OL/Potree Map)
    participant Proj as proj4 / toMap

    Viewer->>Init: initMiniMap(viewer)
    Init->>Map: access mapView & layers
    Init->>Map: replace layers with offline GeoJSON
    Init->>Map: remove tile-download controls
    Init->>Map: set background color
    Init->>Map: override mapView.update

    Note over Viewer,Map: Runtime — camera moves
    Viewer->>Map: mapView.update() (patched)
    Map->>Proj: transform 3D camera coords (handles EPSG:4978)
    Proj-->>Map: 2D mapPos + orientation
    Map->>Map: update pointer position, rotation, and gCamera
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify EPSG:4978 handling and correctness of this.toMap.forward usage.
  • Confirm assumptions about layer indices (removal at index 0 and index 6) are safe across map setups.
  • Ensure /data/geo/world_simplified.geojson exists and loads correctly.
  • Review monkey-patch of mapView.update for side effects, memory leaks, or conflicts with other map extensions.

Poem

🐇 I swapped the web for offline plains,
I hop through coords and tiled refrains,
From 3D vault to 2D chart,
I point, I spin — a tiny art,
Mini-map heart that follows carts.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The pull request title "13 minimap" uses non-descriptive terms that lack clarity about the actual change. While it references the issue number and mentions minimap, the title does not convey what the change accomplishes—such as that a complete minimap integration with offline data support, EPSG:4978 handling, and view synchronization has been implemented. A developer scanning the history would struggle to understand the significance of this change from the title alone.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed The code changes directly implement the requirements from linked issue #13. The implementation includes minimap initialization that synchronizes with the camera position through overrideMapViewUpdate(viewer), which computes 2D map pointer position and orientation from 3D coordinates. The minimap setup supports zoom and pan functionality through the underlying OpenLayers/Potree integration. Both stated objectives—adding a minimap that follows the camera and enabling zoom in/out—are addressed by the code changes.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly scoped to implementing the minimap feature from issue #13. The new miniMap.js module and initMiniMap initialization in potreeViewer.js are essential for the minimap functionality. The changes include offline GeoJSON support, removal of unnecessary tile download controls (as noted in PR comments), and proper camera position synchronization—all core to addressing the issue requirements. No extraneous modifications or unrelated feature additions are evident.
Description Check ✅ Passed The pull request description follows the required template and includes all essential sections: an issue number (Closes #13), a clear description of the functionality (minimap to view position from another perspective), implementation notes addressing design decisions, and testing steps explaining how to verify the minimap works. While some sections are brief, they provide sufficient information about what was changed and how to test it.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 13-minimap

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a7af35 and df56307.

📒 Files selected for processing (1)
  • src/potreeViewer.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/potreeViewer.js

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbit coderabbit bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9284341 and c4f9d26.

📒 Files selected for processing (2)
  • src/MiniMap/miniMap.js (1 hunks)
  • src/potreeViewer.js (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/MiniMap/miniMap.js (2)
src/AcceptedFiltering/threePanels.js (1)
  • $ (188-188)
src/main.js (1)
  • init (10-37)
src/potreeViewer.js (1)
src/MiniMap/miniMap.js (1)
  • initMiniMap (6-17)
🔇 Additional comments (5)
src/potreeViewer.js (2)

3-3: LGTM!

The import follows the established pattern and correctly references the new minimap module.


94-94: Clarify the camera position change.

The initial camera position has been changed, but this modification isn't mentioned in the PR description. Please confirm whether this change is:

  1. Intentional and related to the minimap feature (e.g., aligning with the minimap's center coordinates)
  2. Intended to provide a better starting view
  3. An accidental change that should be reverted
src/MiniMap/miniMap.js (3)

25-42: Verify global ol availability and GeoJSON file path before finalizing.

Based on the script output, I can confirm some but not all concerns:

Confirmed concerns:

  1. Hardcoded layer index (line 40) ✓ Fragile—accessing layers.item(6) by index is problematic if the minimap's layer structure changes.

  2. Missing explicit ol import (lines 31, 32, 34) ✓ However, ol is used throughout the codebase as a global. The script shows ol.layer.Vector, ol.source.Vector, and ol.format.GeoJSON are available globally via the bundled Potree library (public/build/potree/potree.js), not via an explicit module import in this file.

Unconfirmed concern:
3. GeoJSON file path ⚠ The file /data/geo/world_simplified.geojson is referenced only in this one location. The search found no matching files or data/ directory in the repository. This file may exist only in the deployment/build output or needs verification that it's served correctly.

Recommendations:

  • The ol global availability appears intentional (via Potree), so the missing import may not be an issue if documented.
  • The hardcoded index 6 should be addressed if possible or documented with assumptions about the layer structure.
  • Verify the GeoJSON file path is correctly served from /data/geo/ in your deployment.

6-17: I need to check two things: (1) where and how proj4 is loaded globally, and (2) how initMiniMap is called to verify the execution order.The web search confirms that proj4 is exposed in the global variable and can be accessed as window.proj4. When a library like proj4 is loaded via a <script> tag, it becomes globally available in the browser. Now I need to verify the execution order and the reasonableness of the hardcoded coordinates.

No issues found after verification—proj4 is globally available and coordinates are intentionally fixed.

The verification confirms:

  1. proj4 availability: proj4 is exposed as a global variable accessible in the browser environment. The library is bundled in public/libs/proj4/ and becomes globally available when loaded, making the use on line 15 safe.

  2. Hardcoded center coordinates: The coordinates [2, 69] represent longitude 2°E and latitude 69°N, which corresponds to northern Norway. This appears to be an intentionally fixed value for this specific Potree viewer deployment, not an arbitrary error.

The code correctly uses proj4 without importing it locally because it's provided as a global library through the Potree environment's initialization. Both concerns in the original review are unfounded.


49-108: No action needed — globals and mapView are guaranteed to be available.

The global dependencies (THREE, jQuery $, ol, proj4) are intentionally loaded via script tags in index.html (lines 45, 49–50, 55) before the ES6 modules execute. The mapView is initialized in the Potree viewer before initMiniMap is called from potreeViewer.js line 68 within the loadGUI callback. The coordinate transformation logic is correct, including the z-coordinate handling for EPSG:4978 and the direction reversal. No changes are required.

@franmagn franmagn self-requested a review October 26, 2025 18:37
@franmagn
Copy link
Member

Note:

  • Have not yet understood what the D and T in the bottom left of the minimap actually does, but did not remove them in case they end up being useful.

I don't know if you have noticed, but if you hover on the buttons, there it appears the description of the two buttons. "D" downloads a .txt with the list of tiles in the map (not sure which tiles is it speaking about), "T" shows/hides the said list of tiles. I think we don't need those buttons, so we could delete them without problems.

  • The logo/attribution to OpenLayers in the bottom right links to their website. This will obviously not work when the application runs offline. We do not use Open Street Map anymore, and only use the GeoJson added by @kleinc, but OpenLayers is still used for map handling by potree. I was therefore not sure whether we can remove it or not with regards to copyright, so kept it for now (can be removed easily though). Any thoughts on this?

I think we are safe with deleting it. Maybe we could ask the customer for an opinion?

Copy link
Member

@franmagn franmagn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! And the "D" and "T" buttons and the logo could be kept for this sprint

@adriahso
Copy link
Member Author

Note:

  • Have not yet understood what the D and T in the bottom left of the minimap actually does, but did not remove them in case they end up being useful.

I don't know if you have noticed, but if you hover on the buttons, there it appears the description of the two buttons. "D" downloads a .txt with the list of tiles in the map (not sure which tiles is it speaking about), "T" shows/hides the said list of tiles. I think we don't need those buttons, so we could delete them without problems.

  • The logo/attribution to OpenLayers in the bottom right links to their website. This will obviously not work when the application runs offline. We do not use Open Street Map anymore, and only use the GeoJson added by @kleinc, but OpenLayers is still used for map handling by potree. I was therefore not sure whether we can remove it or not with regards to copyright, so kept it for now (can be removed easily though). Any thoughts on this?

I think we are safe with deleting it. Maybe we could ask the customer for an opinion?

Removed the D and T tools now as we don't need them.

Copy link

@coderabbit coderabbit bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (8)
src/MiniMap/miniMap.js (8)

1-5: Declare globals or import dependencies to avoid ReferenceErrors in bundled builds.

You reference ol, proj4, THREE, and $ without imports. If they’re not guaranteed globals at runtime, this will throw. At minimum, declare globals for linting or import them as modules where applicable.

Apply a minimal lint pragma:

+/* global ol, proj4, THREE, $ */
 /**
  * Initializes the Potree minimap to work with offline map and EPSG:4978 coordinates.
  *
  * @param {Potree.Viewer} viewer - The Potree viewer instance
  */

If you rely on globals provided by Potree script tags, please confirm they are always present before this module executes. Otherwise, switch to explicit imports.


17-20: Avoid hard‑coding the map center; derive it from the current camera via toMap with a safe fallback.

This keeps the minimap aligned with the actual dataset location and works in EPSG:4978.

-  // Transform minimap center [lon, lat] from EPSG:4326 to EPSG:3857
-  const center = proj4('EPSG:4326', 'EPSG:3857', [2, 69])
-  map.getView().setCenter(center)
+  // Center the minimap on the current camera position (EPSG:4978 → map projection), fallback to a static center.
+  const camera = viewer.scene.getActiveCamera()
+  let center
+  try {
+    const forward = viewer.mapView.toMap.forward([
+      camera.position.x,
+      camera.position.y,
+      camera.position.z
+    ])
+    if (Array.isArray(forward) && Number.isFinite(forward[0]) && Number.isFinite(forward[1])) {
+      center = [forward[0], forward[1]]
+    }
+  } catch {}
+  if (!center) {
+    center = proj4('EPSG:4326', 'EPSG:3857', [2, 69])
+  }
+  map.getView().setCenter(center)

28-45: Make layer replacement robust; don’t rely on magic indices.

Assuming OSM is layers.item(0) and “extent” is item(6) is brittle and can break across Potree/OL versions or config changes.

-function replaceMiniMapLayers(layers) {
-  // Remove the old Open Street Map layer
-  const oldOSMLayer = layers.item(0)
-  layers.remove(oldOSMLayer)
+function replaceMiniMapLayers(layers) {
+  // Remove the OSM base layer by type (more robust than assuming index 0)
+  const baseLayer = layers.getArray().find(
+    (l) => l instanceof ol.layer.Tile &&
+           l.getSource && l.getSource() instanceof ol.source.OSM
+  )
+  if (baseLayer) {
+    layers.remove(baseLayer)
+  }
 
   // Add new offline GeoJSON layer
   const newGeojsonLayer = new ol.layer.Vector({
     source: new ol.source.Vector({
-      url: '/data/geo/world_simplified.geojson',
+      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)
+  // Remove the extent layer if present (guard against missing/shifted index)
+  const extentLayer = layers.item(6)
+  if (extentLayer) {
+    layers.remove(extentLayer)
+  }
 }

If the “extent” layer has an identifiable property (e.g., name/title), prefer selecting it by that property instead of index.


35-38: Asset URL should be base‑path safe.

An absolute /data/... path breaks when the app is served under a sub‑path. Build a URL relative to the app base.

-      url: '/data/geo/world_simplified.geojson',
+      url: `${document.baseURI.replace(/\/$/, '')}/data/geo/world_simplified.geojson`,

Alternatively, if you use a bundler that supports it, prefer:

  • Vite: new URL('../../data/geo/world_simplified.geojson', import.meta.url).href
  • Webpack: import url from '.../world_simplified.geojson?url'
    Please pick the variant that matches your toolchain.

52-61: Remove both “D” and “T” controls; avoid brittle constructor.name checks.

constructor.name can change with minification and doesn’t catch the “T” button. Remove by class and by UI title/text heuristics.

-function removeTileTools(map) {
-  map.getControls().forEach((control) => {
-    if (
-      control.constructor &&
-      control.constructor.name === 'DownloadSelectionControl'
-    ) {
-      map.removeControl(control)
-    }
-  })
-}
+function removeTileTools(map) {
+  const toRemove = []
+  map.getControls().forEach((control) => {
+    const ctor = control && control.constructor && control.constructor.name
+    const el = control && control.element
+    const title = el && (el.title || (el.getAttribute && el.getAttribute('title')))
+    const text = el && el.textContent && el.textContent.trim()
+    if (
+      ctor === 'DownloadSelectionControl' ||
+      ctor === 'TileListControl' || // covers a common name for the "T" tool
+      (title && /download|tile/i.test(title)) ||
+      (text && /^[DT]$/.test(text))
+    ) {
+      toRemove.push(control)
+    }
+  })
+  toRemove.forEach((c) => map.removeControl(c))
+}

If you know the exact control classes Potree registers for D/T, prefer instanceof checks against those classes.


68-71: Preserve original mapView.update and fallback on errors to avoid regressions.

Monkey‑patching without a fallback can break the minimap if a transform fails. Keep the original update and call it on exceptions.

-function overrideMapViewUpdate(viewer) {
-  const Vector3 = THREE.Vector3
-  const Vector2 = THREE.Vector2
-  viewer.mapView.update = function (delta) {
+function overrideMapViewUpdate(viewer) {
+  const Vector3 = THREE.Vector3
+  const Vector2 = THREE.Vector2
+  const originalUpdate = viewer.mapView.update.bind(viewer.mapView)
+  viewer.mapView.update = function (delta) {
+    try {
       if (!this.sceneProjection) {
         return
       }
@@
-    this.gCamera.setCoordinates([p1, p2, p3, p1])
-  }
+      this.gCamera.setCoordinates([p1, p2, p3, p1])
+    } catch (e) {
+      // Fallback to original behavior to keep the minimap usable
+      if (typeof originalUpdate === 'function') {
+        return originalUpdate(delta)
+      }
+    }
+  }
 }

Also applies to: 125-126


76-87: Avoid per‑frame jQuery and extra DOM queries in the render loop.

This runs every update; prefer the map target element’s client size and cache lookups.

-    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()
-    }
+    const targetEl = this.map.getTargetElement()
+    if (targetEl) {
+      const [w, h] = [targetEl.clientWidth, targetEl.clientHeight]
+      const [cw, ch] = this.map.getSize()
+      if (w !== cw || h !== ch) {
+        this.map.updateSize()
+      }
+    }

If jQuery must stay, at least hoist the selection outside the loop and reuse it.


13-16: Optional: handle OpenLayers attribution/logo removal behind a flag.

If the customer decides to remove the OL attribution, add a small helper and a toggle to avoid hard‑coding legal choices.

   removeTileTools(map)
 
-  overrideMapViewUpdate(viewer)
+  // Optionally remove OL attribution/logo if permitted
+  // removeOlAttribution(map)
+
+  overrideMapViewUpdate(viewer)

Add helper:

function removeOlAttribution(map) {
  const toRemove = []
  map.getControls().forEach((c) => {
    const ctor = c && c.constructor && c.constructor.name
    if (ctor === 'Attribution') toRemove.push(c)
  })
  toRemove.forEach((c) => map.removeControl(c))
}

Confirm licensing before enabling by default.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4f9d26 and 63ee004.

📒 Files selected for processing (1)
  • src/MiniMap/miniMap.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/MiniMap/miniMap.js (2)
src/potreeViewer.js (1)
  • viewer (19-21)
src/AcceptedFiltering/threePanels.js (1)
  • $ (188-188)
🔇 Additional comments (1)
src/MiniMap/miniMap.js (1)

108-126: Pointer computation looks correct; reversing direction fixes orientation.

The arrow geometry (p1→p2→p3→p1) and side vector are consistent with OL coordinate space.

Please verify visually at multiple zoom levels and over dateline crossing to ensure no wrap issues.

@mariewah mariewah self-requested a review October 27, 2025 07:59
Copy link
Member

@mariewah mariewah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :D

Copy link
Member

@aliciawr aliciawr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great!

@adriahso adriahso merged commit 9186fa3 into dev Oct 27, 2025
2 checks passed
@adriahso adriahso deleted the 13-minimap branch October 27, 2025 09:12
Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Minimap
4 participants