Contours hillshade layer
- Wired to the new endpoint POST /api/contours/get_by_district.php. It sits in
its own /contours module rather than /spatial_planning, so remotedb gained a
resolveEndpoint() helper: a bare filename still resolves inside
/spatial_planning, a path resolves against the API root. The endpoint also
names the district parameter `districtid` (numeric, no underscore) unlike
every other endpoint, which is an easy trap — documented at the call site.
- Each contour is labelled with its `elev` value. Labels are decluttered
(addGeoJSONLayer gained a declutter passthrough, since OpenLayers only allows
it at construction time) and gated by resolution, so the map shows an
occasional elevation value rather than one per segment.
- Verified against the live endpoint: district 1 returns 2,676 features, 11
elevation values at 10 m intervals, median feature ~50 m. The response uses
the standard {success, data:[…]} envelope; the reader also tolerates a bare
array.
Analyse: fail loudly instead of silently
- Overlays are polygon-only, but line inputs were being filtered out silently,
producing an empty result and the misleading "the layers may not overlap".
They now raise a message naming the geometry actually found and pointing at
Zonal statistics. Union checks each layer separately, so a line layer paired
with a polygon layer can no longer be dropped unnoticed.
- Zonal statistics returns warnings alongside the result — shown in the panel
and written into the Excel workbook — for figures that are valid but easy to
misread: total area over non-polygon inputs (always 0), and mean over line
inputs, which is not an area-weighted average of the surface. The mean
warning is restricted to lines; averaging over points is a normal statistic.
Measurements are read-only
- The Measurements layer carries selectable:false, honoured by both the Select
interaction and box-select. Previously a measured circle showed vertex
handles while the Digitise tool was active, and dragging one reshaped the
measurement: the intersection test followed the new shape while the reported
radius and area did not. Double-click analysis is unaffected — it hit-tests
the map directly rather than going through Select.
- Circle Analysis now reads the radius from the geometry instead of the
`_radius` snapshot taken at draw time, so the reported figure cannot drift
from the area analysed (that figure also feeds the PDF export). The property
is kept but marked as a snapshot, not a source of truth.
Documents
- New Buffer (Circle) tool user guide: the tool is named Circle rather than
Buffer, it takes two clicks rather than a drag, and the analysis only appears
on a double-click — the three things users were getting stuck on.
- New technician training plan: 12 topics over 60 days, derived from the
technologies actually used in this code base, including Docker.
- Analyse guide: elevation per zone from the contours layer, with the
min/max-versus-mean caveat.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Excel export (from TWG feedback)
- src/analysis/xlsx.js: a dependency-free XLSX writer — an .xlsx is a ZIP of
XML, so this packs the required parts with a small stored-ZIP writer. Avoids
SheetJS (stale npm package with advisories) and ExcelJS (heavy for an
offline-first field app), and does not rely on the JSZip that only reaches us
transitively via shp-write. Lazy-loaded as a ~5.6 kB chunk.
- After a zonal run the Analysis panel offers "Export table (Excel)", writing a
two-sheet workbook: Results (figures as real numbers) and Parameters (zone
and input layers with feature counts, the "Apply to" scope, membership rule,
statistics, numeric field and export time) — so a table can be verified or
reproduced later rather than being an unattributed set of numbers.
The button is hidden for overlay runs and cleared when the mode changes.
- Verified against two independent readers: openpyxl loads it with zero
warnings and correct numeric types, and LibreOffice Calc opens it as a
spreadsheet. Also exercised end-to-end through the real zonal pipeline.
COG raster entry point
- Add External Layer gains a COG type alongside WMS/WFS/XYZ, with a URL
pre-flight check that distinguishes a web page, a 404, a CORS block and a
server without byte-range support — geotiff.js otherwise reports these only
as an opaque "AggregateError: Request failed".
Digital Earth Africa ETL
- etl/deafrica_dem_to_minio.py exports a DE Africa DEM for a district as a COG
and uploads it to the LUSPA MinIO bucket raster-objects, then verifies the
object is anonymously readable and range-capable. Credentials come from the
environment; the existing PHP integration hardcodes them and an earlier key
pair reached Gitea. NOTE: not yet run against the Sandbox — start with
--list-products to confirm the DEM product name.
Documents
- Concept note: Digital Earth Africa added as a raster source (§6.3), separating
the live WMS route from the batch Sandbox export; corrected the in-house
contour table's provenance to OpenTopography (gdal_contour over an SRTM 30 m /
Copernicus 30 m DEM), and added the ToR §2.4.2 / FS §2 alignment chapter.
- TWG presentation, architecture and two integration workflow charts (SVG
sources kept in the repo so they stay editable), and a user guide for the
Analyse tools.
Service worker v13 -> v14. .gitignore: exclude Python bytecode from etl/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>