From 370755161ef1d9f4551016c84ac96897236c75c8 Mon Sep 17 00:00:00 2001 From: franmagn Date: Wed, 29 Oct 2025 12:21:32 +0100 Subject: [PATCH] refactor(#47): :lipstick: add Latitude, Longitude and Elevation to the measurement panel --- src/MeasurementControl/measurementsPanel.js | 147 +++++++++++++++++++- 1 file changed, 143 insertions(+), 4 deletions(-) diff --git a/src/MeasurementControl/measurementsPanel.js b/src/MeasurementControl/measurementsPanel.js index 28dc3ac..1152df6 100644 --- a/src/MeasurementControl/measurementsPanel.js +++ b/src/MeasurementControl/measurementsPanel.js @@ -1,3 +1,5 @@ +import { ecef, wgs84 } from '../config.js' + /** * Measurements Panel * Injects a custom Measurements tab section, shows grouped measurement @@ -198,6 +200,8 @@ export function initMeasurementsPanel(viewer) { } requestAnimationFrame(() => { roundCoordinates(originalPropertiesPanel) + insertLatLonRows(originalPropertiesPanel) + pruneMeasurementRows(originalPropertiesPanel) initCoordObserver() }) } @@ -234,6 +238,7 @@ export function initMeasurementsPanel(viewer) { requestAnimationFrame(() => { roundCoordinates(originalPropertiesPanel) pruneMeasurementRows(originalPropertiesPanel) + insertLatLonRows(originalPropertiesPanel) }) }) coordRoundObserver.observe(originalPropertiesPanel, { @@ -294,20 +299,34 @@ export function initMeasurementsPanel(viewer) { const tables = rootEl.querySelectorAll('table.measurement_value_table') if (!tables || tables.length === 0) return // Labels we want to keep - const keep = new Set(['point source id', 'accepted', 'tvu', 'thu']) + const keep = new Set([ + 'point source id', + 'accepted', + 'tvu', + 'thu', + 'latitude', + 'longitude', + 'elevation' + ]) tables.forEach((tbl) => { - // Detect coordinate table (header with th: x y z) and skip pruning for it + // Detect if this table is the coordinates table (header with th: x y z) const headerRow = tbl.querySelector('tr') + let isCoordTable = false if (headerRow) { const ths = [...headerRow.querySelectorAll('th')].map((th) => (th.textContent || '').trim().toLowerCase() ) if (ths.length >= 3 && ths[0] === 'x' && ths[1] === 'y' && ths[2] === 'z') { - return + isCoordTable = true } } + ;[...tbl.querySelectorAll('tr')].forEach((row) => { - const firstTd = row.querySelector('td') + const tds = [...row.querySelectorAll('td')] + // If this is the coordinates table, skip pruning rows that look like + // coordinate rows (they have 3 or more td columns). + if (isCoordTable && tds.length >= 3) return + const firstTd = tds[0] if (!firstTd) return const txt = (firstTd.textContent || '').trim().toLowerCase() if (!keep.has(txt)) { @@ -327,6 +346,126 @@ export function initMeasurementsPanel(viewer) { }) }) } + + // Insert Latitude / Longitude / Elevation rows into the attribute table + function insertLatLonRows(rootEl) { + if (!rootEl) rootEl = originalPropertiesPanel + if (!rootEl) return + const tables = Array.from(rootEl.querySelectorAll('table.measurement_value_table')) + if (!tables.length) return + + // helper to parse numeric strings + const parseNum = (s) => { + if (!s) return null + const cleaned = (s + '').replace(/[^0-9+\-.,]/g, '').replace(/,+/g, '') + return /[-+]?\d*\.?\d+/.test(cleaned) ? Number(cleaned) : null + } + + // Find coord table (header x,y,z) and first data row + let coordTable = null + let coordinateRow = null + for (const tbl of tables) { + const header = tbl.querySelector('tr') + if (!header) continue + const ths = [...header.querySelectorAll('th')].map((t) => (t.textContent || '').trim().toLowerCase()) + if (ths.length >= 3 && ths[0] === 'x' && ths[1] === 'y' && ths[2] === 'z') { + coordTable = tbl + const rows = Array.from(tbl.querySelectorAll('tr')) + for (let i = 0; i < rows.length; i++) { + if (rows[i] === header) { + for (let j = i + 1; j < rows.length; j++) { + const tds = Array.from(rows[j].querySelectorAll('td')) + if (tds.length >= 3) { + const nx = parseNum(tds[0].textContent) + const ny = parseNum(tds[1].textContent) + const nz = parseNum(tds[2].textContent) + if (nx != null && ny != null && nz != null) { + coordinateRow = rows[j] + break + } + } + } + break + } + } + break + } + } + + let x = null, y = null, z = null + if (coordinateRow) { + const tds = Array.from(coordinateRow.querySelectorAll('td')) + x = parseNum(tds[0].textContent) + y = parseNum(tds[1].textContent) + z = parseNum(tds[2].textContent) + } + + // compute lat/lon/elev + let lat = null, lon = null, elev = null + const hasProj4 = (typeof proj4 !== 'undefined') || (typeof window !== 'undefined' && typeof window.proj4 !== 'undefined') + const proj = typeof proj4 !== 'undefined' ? proj4 : (typeof window !== 'undefined' ? window.proj4 : undefined) + if (hasProj4 && typeof ecef !== 'undefined' && typeof wgs84 !== 'undefined' && x != null) { + try { + const res = proj(ecef, wgs84, [x, y, z]) + lon = res[0] + lat = res[1] + elev = res[2] + } catch (_e) {} + } + + // find first attribute-style table + let attrTable = tables.find((t) => Array.from(t.querySelectorAll('tr')).some((r) => r.querySelectorAll('td').length === 2)) || coordTable || tables[0] + + const ids = { lat: 'mp_coord_latitude', lon: 'mp_coord_longitude', elev: 'mp_coord_elevation' } + + const fmt = (v, decimals) => (v == null ? '' : Number(v).toFixed(decimals)) + const latStr = lat != null ? fmt(lat, 4) + '˚' : '' + const lonStr = lon != null ? fmt(lon, 4) + '˚' : '' + const elevStr = elev != null ? fmt(elev, 4) + 'm' : '' + + // Prepare rows (create new ones and update existing ones). We collect + // newly-created rows and insert them as a fragment before the first + // child so they appear at the top in the desired order. + const createRowNode = (id, label, value) => { + const row = document.createElement('tr') + row.id = id + row.className = 'attr-row' + const tdLabel = document.createElement('td') + tdLabel.className = 'property-name' + tdLabel.textContent = label + const tdValue = document.createElement('td') + tdValue.className = 'property-value' + tdValue.textContent = value + row.appendChild(tdLabel) + row.appendChild(tdValue) + return row + } + + const newRows = [] + const entries = [ + { id: ids.lat, label: 'Latitude', value: latStr }, + { id: ids.lon, label: 'Longitude', value: lonStr }, + { id: ids.elev, label: 'Elevation', value: elevStr } + ] + + for (const e of entries) { + const existing = rootEl.querySelector(`#${e.id}`) + if (existing) { + const valTd = existing.querySelector('td:last-child') || existing.querySelectorAll('td')[1] + if (valTd) valTd.textContent = e.value + } else { + newRows.push(createRowNode(e.id, e.label, e.value)) + } + } + + if (newRows.length > 0) { + const tbody = (attrTable.tBodies && attrTable.tBodies[0]) ? attrTable.tBodies[0] : attrTable + const first = tbody.firstElementChild + const frag = document.createDocumentFragment() + for (const r of newRows) frag.appendChild(r) + tbody.insertBefore(frag, first) + } + } // Helper to decide if a uuid is a measurement-like object function isMeasurementUUID(uuid) { if (!uuid) return false