Skip to content

Commit

Permalink
refactor(#47): 💄 add Latitude, Longitude and Elevation to the measure…
Browse files Browse the repository at this point in the history
…ment panel
  • Loading branch information
franmagn committed Oct 29, 2025
1 parent 1514c98 commit 3707551
Showing 1 changed file with 143 additions and 4 deletions.
147 changes: 143 additions & 4 deletions src/MeasurementControl/measurementsPanel.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ecef, wgs84 } from '../config.js'

/**
* Measurements Panel
* Injects a custom Measurements tab section, shows grouped measurement
Expand Down Expand Up @@ -198,6 +200,8 @@ export function initMeasurementsPanel(viewer) {
}
requestAnimationFrame(() => {
roundCoordinates(originalPropertiesPanel)
insertLatLonRows(originalPropertiesPanel)
pruneMeasurementRows(originalPropertiesPanel)
initCoordObserver()
})
}
Expand Down Expand Up @@ -234,6 +238,7 @@ export function initMeasurementsPanel(viewer) {
requestAnimationFrame(() => {
roundCoordinates(originalPropertiesPanel)
pruneMeasurementRows(originalPropertiesPanel)
insertLatLonRows(originalPropertiesPanel)
})
})
coordRoundObserver.observe(originalPropertiesPanel, {
Expand Down Expand Up @@ -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)) {
Expand All @@ -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
Expand Down

0 comments on commit 3707551

Please sign in to comment.