-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(#56): 🐛 2D profile measurement shows lat,lon,elevation instead of…
… x,y,z Because of the change of coordinate system the z coordinate no longer represents the elevation of a point. Instead of displaying x,y,z it now displays lat, lon, and elevation which is more useful to the user. Also removed some of the attributes which are 0 and cluttering the space
- Loading branch information
Showing
4 changed files
with
241 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
| } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters