Skip to content

Merge sprint 3 changes into main #63

Merged
merged 73 commits into from
Nov 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
1514c98
refactor(47): :lipstick: remove all unused values from measurement panel
franmagn Oct 28, 2025
d2b1b58
feat(#15): show compass by default
Oct 29, 2025
6f25a4d
refactor($48): :lipstick: Made the Accepted filtering more intuitive
mariewah Oct 29, 2025
3707551
refactor(#47): :lipstick: add Latitude, Longitude and Elevation to th…
franmagn Oct 29, 2025
02fc74b
refactor(#48): :recycle: Make measurements tools more intuitive
kleinc Oct 29, 2025
c9a8dbd
Merge branch '48-make-sidebar-more-intuitive' of git.ntnu.no:TDT4290-…
kleinc Oct 29, 2025
1aba790
refactor(#48): :lipstick: Collapsed some panels for a better visual r…
mariewah Oct 29, 2025
2d3b97d
Merge branch '48-make-sidebar-more-intuitive' of git.ntnu.no:TDT4290-…
mariewah Oct 29, 2025
500894b
refactor(#48): :fire: Remove the menu_filter panel in the sidebar
mariewah Oct 29, 2025
b4a0e8c
Merge pull request #49 from TDT4290-group-4/15-compass
adriahso Oct 29, 2025
c77e6f5
feat(#50): make menu toggle keyboard accessible
Oct 29, 2025
9c0c73d
fix(#47): :bug: fix conflict bug with other measurement tools visuali…
franmagn Oct 29, 2025
f8b22b0
Merge remote-tracking branch 'origin/48-make-sidebar-more-intuitive' …
franmagn Oct 29, 2025
090da51
feat(#50): make minimap keyboard accessible
Oct 29, 2025
8189a40
feat(#50): make panels keyboard accessible
Oct 29, 2025
ed883b3
feat(#50): make elevation panel keyboard accessible
Oct 29, 2025
e6c6a30
feat(#50): make accepted filtering keyboard accessible
Oct 29, 2025
9e1a69c
feat(#48): :sparkles: Added hide/show button for saved positions
mariewah Oct 29, 2025
89a736d
fix(#50): make activate buttons not loose focus on click
Oct 29, 2025
fc1c376
style: :art: Ran prettier format
mariewah Oct 29, 2025
5d9fdbd
Merge branch 'dev', remote-tracking branch 'origin' into 48-make-side…
mariewah Oct 29, 2025
1ead61b
style(#48): :lipstick: Fix spacing in elevation control
kleinc Oct 29, 2025
926a95b
Co-authored-by: klein2303 <klein2303@users.noreply.github.com>
aliciawr Oct 29, 2025
b476006
refactor(#53): :recycle: Change inital target position
kleinc Oct 29, 2025
be4bf5f
style(#48): :lipstick: Remove unecessary lines and code commented out
kleinc Oct 29, 2025
d51c4aa
feat(#50): make appearance panel keyboard accessible
Oct 29, 2025
a3c4875
fix(#46): :bug: Fixed display bug (I think)
mariewah Oct 30, 2025
a016ed7
refactor(#48): :truck: Updated elevation and acepted code to be more …
mariewah Oct 30, 2025
02f7d20
refactor: :adhesive_bandage: One file name changes
mariewah Oct 30, 2025
087cefd
feat(#47): add label for each measurement tool placed on the pointclo…
franmagn Oct 30, 2025
dbc1782
style: :art: Ran prettier
mariewah Oct 30, 2025
3d90cb3
style(#46): :art: Ran prettier
mariewah Oct 30, 2025
df109d3
refactor(#47): :zap: improve updating of labels on the UI
franmagn Oct 30, 2025
0711b75
Merge pull request #54 from TDT4290-group-4/53-initial-target-points-fix
kleinc Oct 30, 2025
e902c80
fix(#46): :bug: Fixed doubleclick disable scrolling bug
mariewah Oct 30, 2025
385185c
Update src/MeasurementControl/measurementsPanel.css
mariewah Oct 30, 2025
1703cb7
Update src/potreeViewer.js
mariewah Oct 30, 2025
923d8d6
Merge pull request #55 from TDT4290-group-4/46-fix-elevation-control-…
mariewah Oct 30, 2025
891daef
Merge branch 'dev' of git.ntnu.no:TDT4290-group-4/MolloyExplorer into…
kleinc Oct 31, 2025
73059de
refactor(#48): :recycle: Remove unecessary code, make annotations alw…
kleinc Oct 31, 2025
1d5c5de
refactor(#48): :recycle: Make elevation control button and accepted f…
kleinc Nov 2, 2025
4e0b45e
fix(#56): :bug: 2D profile measurement shows lat,lon,elevation instea…
gautegf Nov 2, 2025
c138179
Merge pull request #57 from TDT4290-group-4/56-display-latlon-and-ele…
gautegf Nov 2, 2025
9958f86
feat(#50): make tools panel keyboard accessible
Nov 2, 2025
0016d6d
Merge pull request #52 from TDT4290-group-4/48-make-sidebar-more-intu…
kleinc Nov 3, 2025
242443d
Merge branch 'dev' into 50-make-navigation-controls-tabbable-for-acce…
mariewah Nov 3, 2025
3a99955
fix(#50): :bug: Selecting a navigation tool with tab should work
gautegf Nov 3, 2025
5e34abd
feat(#50): :sparkles: Make measurment panel tabbable
mariewah Nov 4, 2025
39ac3f2
style(#47): :lipstick: add background to the labels
franmagn Nov 4, 2025
c00de21
style(#47): ran prettier
franmagn Nov 4, 2025
b0c7bfc
Merge branch 'dev' of git.ntnu.no:TDT4290-group-4/MolloyExplorer into…
aliciawr Nov 5, 2025
5815109
fix(#50): fix globe hiding for navigation controls
Nov 5, 2025
4ada576
feat(#47): visualize name labels OR value labels, or hide both
franmagn Nov 5, 2025
e382e64
Merge branch 'dev' of https://git.ntnu.no/TDT4290-group-4/MolloyExplo…
franmagn Nov 5, 2025
7d2fb71
style(#47): :art: moved stylings to .css
franmagn Nov 5, 2025
e269d39
fix(#47): :bug: fix deletion of name labels in UI
franmagn Nov 5, 2025
9f67143
Merge pull request #58 from TDT4290-group-4/47-improve-inspected-poin…
franmagn Nov 5, 2025
ac23d5a
feat(#50): :sparkles: scene is now more tabbable
gautegf Nov 7, 2025
e6f738d
Merge pull request #59 from TDT4290-group-4/50-make-navigation-contro…
gautegf Nov 9, 2025
fc23a69
Added complete user guide with screenshots, linked to README
aliciawr Nov 10, 2025
ea38e2c
Merge remote-tracking branch 'origin/dev' into 18-write-the-readme
aliciawr Nov 10, 2025
effc475
Update Userguide.md
aliciawr Nov 10, 2025
eb9e1b5
fix(#61): :bug: Ensure potreeViewer is always enabled
gautegf Nov 10, 2025
e6734a8
docs(#18): updated the user guide with the reviewed comments, moved s…
aliciawr Nov 10, 2025
a8f2d97
fix(#61): :bug: fix init of potreeViewer
gautegf Nov 10, 2025
e7f5de5
refactor(#61): :recycle: ran format
gautegf Nov 10, 2025
7c5bbdb
Merge branch '18-write-the-readme' of git.ntnu.no:TDT4290-group-4/Mol…
aliciawr Nov 10, 2025
61dcd55
docs(#18): fixing the user guide
aliciawr Nov 10, 2025
9e16702
feat: #18 sidebar
aliciawr Nov 10, 2025
c0af632
style: #18 sidebar prettier
aliciawr Nov 10, 2025
274515e
Merge pull request #60 from TDT4290-group-4/18-write-the-readme
aliciawr Nov 10, 2025
fd801f9
Merge branch 'dev' into 61-small-fix-ensure-camera-animations-show-up
gautegf Nov 10, 2025
cfb22a9
Merge pull request #62 from TDT4290-group-4/61-small-fix-ensure-camer…
gautegf Nov 10, 2025
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,7 @@ Then in a new terminal run the tests:
```bash
npm run test
```

### User Guide

For a complete user guide on fundamentals and all functionality click [here](Userguide.md).
443 changes: 443 additions & 0 deletions Userguide.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
href="/src/MeasurementControl/measurementsPanel.css"
/>
<link rel="stylesheet" href="/src/AnnotationControl/annotationPanel.css" />
<link rel="stylesheet" href="src/AcceptedFiltering/threePanels.css" />
<link rel="stylesheet" href="src/Filter/filter.css" />
<link rel="stylesheet" href="src/2DProfileOverride/2DProfileOverride.css" />
</head>

<body>
Expand Down
Binary file added public/img/00_icons.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/01_menuBarTabs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/02_ElevationControlTab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/03_ElevationControlTab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/04_AcceptedFilter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/05_AcceptedFilter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/06_Measurements.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/07_SavedLocations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/08_Appearance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/09_Tools.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/10_Scene.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/11_SavedLocations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/12_Measurements_updated.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/13_Tools_updated.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/14_Animation.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/2DProfileOverride/2DProfileOverride.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* Keep profile SVG content (axes/labels) from being clipped */
#profileSVG {
overflow: visible !important;
}

/* Improve axis label readability against varying backgrounds */
#profile_window .axis text {
paint-order: stroke;
stroke: #000;
stroke-width: 3px;
stroke-linejoin: round;
}
224 changes: 224 additions & 0 deletions src/2DProfileOverride/2DProfileOverride.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { ecef, wgs84 } from '../config.js'

/**
* Initialize runtime overrides for Potree 2D profile behavior.
* - Patches ProfileWindow.addPoints: converts each point's Z to elevation instead.
* - Rewrites selection info table to show lon/lat/elevation.
*/
export function init2DProfileOverride(viewer) {
const tryPatchAddPoints = (target) => {
if (!target || target.__elevationPatchApplied) return false
const originalAddPoints = target.addPoints
if (typeof originalAddPoints !== 'function') return false

target.addPoints = function patchedAddPoints(pointcloud, points) {
try {
if (!points || !points.data || !points.data.position) {
return originalAddPoints.call(this, pointcloud, points)
}

const srcPos = points.data.position
// Divide by 3 because positions are [x0,y0,z0, x1,y1,z1, ...]
const count = points.numPoints || Math.floor(srcPos.length / 3)
const posArray = srcPos.constructor || Float32Array

// Clone without changing original buffers
const cloned = {
...points,
data: { ...points.data, position: new posArray(srcPos) }
}
const dstPos = cloned.data.position

const pcx = pointcloud?.position?.x || 0
const pcy = pointcloud?.position?.y || 0
const pcz = pointcloud?.position?.z || 0

// Preserve world ECEF Z per point (best-effort). Some Potree UIs attach
// selectedPoint from these attributes; we keep it on the cloned structure
// for later retrieval in the selection panel override.
const ecefZWorld = new Float64Array(count)

for (let i = 0; i < count; i++) {
const ix = 3 * i
const x = srcPos[ix + 0] + pcx
const y = srcPos[ix + 1] + pcy
const z = srcPos[ix + 2] + pcz

const [, , elevation] = proj4(ecef, wgs84, [x, y, z])
// Internally, Potree adds pointcloud.position.z back later.
dstPos[ix + 2] = elevation - pcz

ecefZWorld[i] = z
}

cloned.data.ecefZWorld = ecefZWorld

const result = originalAddPoints.call(this, pointcloud, cloned)

// Try to tag currently selectedPoint with the original ECEF Z if available
try {
if (
this &&
this.selectedPoint &&
Number.isFinite(this.selectedPoint.index)
) {
const idx = this.selectedPoint.index
if (ecefZWorld && idx >= 0 && idx < ecefZWorld.length) {
this.selectedPoint.ecefZWorld = ecefZWorld[idx]
}
}
} catch {}

return result
} catch (err) {
console.warn(
'2DProfileOverride: failed to apply elevation override',
err
)
return originalAddPoints.call(this, pointcloud, points)
}
}

target.__elevationPatchApplied = true
return true
}

let patched = false
if (viewer && viewer.profileWindow) {
patched = tryPatchAddPoints(viewer.profileWindow)
}
if (window.Potree?.ProfileWindow?.prototype) {
patched =
tryPatchAddPoints(window.Potree.ProfileWindow.prototype) || patched
}

const afterPatched = () => {
attachSelectionInfoOverride(viewer)
}

if (patched) {
afterPatched()
} else {
// Poll until the profile window/prototype is available
let tries = 0
const maxTries = 40
const timer = setInterval(() => {
tries += 1
if (
tryPatchAddPoints(window.Potree?.ProfileWindow?.prototype) ||
(viewer?.profileWindow && tryPatchAddPoints(viewer.profileWindow))
) {
clearInterval(timer)
afterPatched()
} else if (tries >= maxTries) {
clearInterval(timer)
}
}, 250)
}
}

// Rewrites the selection properties table inside the profile window
function attachSelectionInfoOverride(viewer) {
const getInfoEl = () => document.getElementById('profileSelectionProperties')
let infoEl = getInfoEl()
if (!infoEl) {
const obs = new MutationObserver(() => {
infoEl = getInfoEl()
if (infoEl) {
obs.disconnect()
monitorSelectionInfo(viewer, infoEl)
}
})
obs.observe(document.body, { childList: true, subtree: true })
} else {
monitorSelectionInfo(viewer, infoEl)
}
}

function monitorSelectionInfo(viewer, infoEl) {
let scheduled = false
let selfUpdating = false

const updateOnce = () => {
scheduled = false
selfUpdating = true
try {
const table = infoEl.querySelector('table')
if (!table) return
const rows = [...table.querySelectorAll('tr')]
if (rows.length < 3) return

const pw = viewer?.profileWindow
const pos = pw?.viewerPickSphere?.position
const sp = pw?.selectedPoint
if (!pos || !sp) return

// Use preserved ECEF Z if we have it; otherwise fall back to pos.z
const trueZ = Number(sp?.ecefZWorld ?? NaN)
let lon, lat
if (Number.isFinite(trueZ)) {
;[lon, lat] = proj4(ecef, wgs84, [pos.x, pos.y, trueZ])
} else {
;[lon, lat] = proj4(ecef, wgs84, [pos.x, pos.y, pos.z])
}
const elevation = pos.z

const setRow = (row, label, val) => {
const tds = row.querySelectorAll('td')
if (tds[0] && tds[0].textContent !== label) tds[0].textContent = label
if (tds[1]) {
const txt = Number.isFinite(val)
? val.toLocaleString(undefined, {
minimumFractionDigits: 4,
maximumFractionDigits: 4
})
: ''
if (tds[1].textContent !== txt) tds[1].textContent = txt
}
}

setRow(rows[0], 'lon', lon)
setRow(rows[1], 'lat', lat)
setRow(rows[2], 'elevation', elevation)

// Remove unwanted rows from the hover info table
const labelsToHide = new Set([
'intensity',
'return number',
'number of returns',
'classification flags',
'classification',
'user data',
'scan angle',
'gps time',
'gps-time',
'rgba'
])

// iterate over a snapshot to avoid issues while removing
const allRows = [...table.querySelectorAll('tr')]
for (let i = 3; i < allRows.length; i++) {
const row = allRows[i]
const labelCell = row.querySelector('td')
const label = (labelCell?.textContent || '').trim().toLowerCase()
if (labelsToHide.has(label)) {
row.remove()
}
}
} finally {
setTimeout(() => {
selfUpdating = false
}, 0)
}
}

const mo = new MutationObserver(() => {
if (selfUpdating) return
if (!scheduled) {
scheduled = true
requestAnimationFrame(updateOnce)
}
})
mo.observe(infoEl, { childList: true, subtree: true })
requestAnimationFrame(updateOnce)
}
Loading