pwaLUPMIS2/OpenTopography_Workflow.svg
ekke ef12e4477b Offline tile cache, polygon Divide, topographic layer integrations
Major feature batch covering drawing-tool improvements, layer additions,
and offline-first capabilities. Largest changes in MapView.js (+1700),
main.js (+1500), public/sw.js (+367), and new modules under src/.

Drawing & editing toolkit
  * Polygon Divide tool — sub-button under Split, divides a polygon into
    N equal-area pieces via binary search; user picks the cutting edge
  * UPN pick phase after Split and Divide — non-picked pieces have their
    identifier fields cleared automatically
  * Improved Merge algorithm — vertex-to-edge proximity (5 m tol.) with
    hybrid lockstep extension; bold A/B labels on selected polygons
  * Persistent vertex highlights — all vertices of the selected polygon
    rendered as dots while edit mode is on, without subclassing ol-ext
  * Toast notifications for merge/split/divide outcomes
  * Shapefile import — addGeoJSONLayer now includes an image style so
    Point features render (previously invisible)

Background & overlay layers
  * DEAfrica Coastlines v0.4 (WMS) in Biophysical Environment
  * DEAfrica Slope (SRTM 30m, style_slope) — semi-transparent background
  * Contours hillshade — get_contours_hillshade.php → local SQLite cache
  * OSM_roads — get_osm_roads.php → local SQLite cache, casing-stroke
    style (black 3.5 px outer, #F0F1F0 1.5 px inner)
  * External Source dialog — green + button in LayerSwitcher lets users
    add WMS / WFS / XYZ layers at runtime
  * Generic addWMSLayer / addXYZLayer with style, opacity, zIndex,
    legendUrl, onlineOnly options
  * TileWMS replaces ImageWMS (fixes 'Width exceeds 512' WMS errors)
  * Legend panel — bottom-right, auto-shown for visible layers that
    register a legendUrl
  * Default base map setting in Settings, persisted in localStorage;
    setBaseMap() on MapView

Offline tile cache (Phase 1 + 2)
  * Service worker: per-host tile caches (osm / topo / satellite /
    carto-light / carto-dark), counter-based eviction to prevent
    iOS Safari memory-pressure reloads, GET_TILE_STATS /
    CLEAR_TILE_CACHES message API
  * pwa.js helpers: getActiveServiceWorker, onServiceWorkerControllerChange,
    getTileCacheStats, clearTileCaches, getStorageEstimate
  * Settings: Offline Map Tiles card with per-provider stats + clear
  * Phase 2 download dialog: form to pick base map, area (current view /
    district / Ghana), zoom range; live tile-count + size estimate;
    progress bar with cancel; OfflineTileDownloader class with
    concurrency + throttling

Local database management
  * osm_roads table + saveOSMRoads / getLocalOSMRoads helpers
  * CACHED_LAYER_TABLES allow-list with clearTable / clearAllCachedLayers
  * Local Database Tables card: per-row Clear button (cached layers
    only) + 'Refresh cached layers' header button with reload prompt

Build & infrastructure
  * Shpjs lazy-loaded via dynamic import (saves ~140 kB from initial JS)
  * chunkSizeWarningLimit raised to 900 kB (openlayers + sqlite3.wasm
    can't be split further)
  * Toast notification module (src/toast.js)
  * Units module (src/units.js) for metric / imperial conversions
  * PDF export module (src/pdf-export.js)

Documentation & SQL
  * Topographic_Background_Layers_for_LUPMIS2.docx — research report
  * OpenTopography_Workflow.svg/.png — ETL pipeline diagram
  * LUPMIS2_Development_Status_Report.docx — April update section
  * sql/create_landuse_parcels.sql — PostgreSQL schema for the LUSPA
    land-use parcel specification (Feb 2026, revised), with PostGIS
    geometry column and standard indices

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:55:30 +02:00

182 lines
11 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 900" font-family="Arial, Helvetica, sans-serif">
<!-- ====================================================== -->
<!-- OpenTopography ETL Workflow for LUPMIS2 -->
<!-- A one-off data pipeline: download → process → serve -->
<!-- ====================================================== -->
<defs>
<!-- Arrowhead -->
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#4b5563"/>
</marker>
<!-- Drop shadow -->
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="2"/>
<feOffset dx="1" dy="2"/>
<feComponentTransfer><feFuncA type="linear" slope="0.18"/></feComponentTransfer>
<feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<!-- Background -->
<rect width="960" height="900" fill="#f9fafb"/>
<!-- Title -->
<text x="480" y="42" text-anchor="middle" font-size="22" font-weight="700" fill="#1e1a4b">
OpenTopography → LUPMIS2 Topographic Workflow
</text>
<text x="480" y="66" text-anchor="middle" font-size="13" fill="#6b7280">
One-off ETL pipeline: download DEM → generate products → serve to the PWA
</text>
<!-- ====================================================== -->
<!-- STAGE 1 — SOURCE (blue) -->
<!-- ====================================================== -->
<g filter="url(#shadow)">
<rect x="330" y="100" width="300" height="80" rx="10" ry="10" fill="#0ea5e9" stroke="#0369a1" stroke-width="1.5"/>
</g>
<text x="480" y="128" text-anchor="middle" font-size="15" font-weight="700" fill="#fff">OpenTopography API</text>
<text x="480" y="148" text-anchor="middle" font-size="12" fill="#e0f2fe">SRTM 30m / Copernicus 30m DEM</text>
<text x="480" y="166" text-anchor="middle" font-size="11" font-style="italic" fill="#bae6fd">API key required · 50 calls/24h (non-academic)</text>
<!-- Arrow 1 -->
<path d="M480,180 L480,220" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<text x="495" y="205" font-size="11" fill="#6b7280">one-off download · Ghana bbox ≈ 240,000 km²</text>
<!-- ====================================================== -->
<!-- STAGE 2 — RAW DATA (grey) -->
<!-- ====================================================== -->
<g filter="url(#shadow)">
<rect x="340" y="230" width="280" height="70" rx="10" ry="10" fill="#fff" stroke="#6b7280" stroke-width="1.5" stroke-dasharray="4,3"/>
</g>
<text x="480" y="258" text-anchor="middle" font-size="14" font-weight="700" fill="#1f2937">DEM GeoTIFF</text>
<text x="480" y="278" text-anchor="middle" font-size="12" fill="#4b5563">Ghana elevation raster (single file)</text>
<text x="480" y="294" text-anchor="middle" font-size="10" font-style="italic" fill="#6b7280">EPSG:4326 · ≈ 1 3 GB</text>
<!-- Split arrows -->
<path d="M480,300 L480,340 L230,340 L230,380" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<path d="M480,340 L730,340 L730,380" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<text x="420" y="332" text-anchor="end" font-size="11" fill="#6b7280">contours path</text>
<text x="540" y="332" font-size="11" fill="#6b7280">hillshade path</text>
<!-- ====================================================== -->
<!-- STAGE 3 — PROCESSING (green) -->
<!-- ====================================================== -->
<!-- Left: gdal_contour -->
<g filter="url(#shadow)">
<rect x="90" y="390" width="280" height="80" rx="10" ry="10" fill="#10b981" stroke="#047857" stroke-width="1.5"/>
</g>
<text x="230" y="418" text-anchor="middle" font-size="14" font-weight="700" fill="#fff">gdal_contour</text>
<text x="230" y="438" text-anchor="middle" font-size="11" fill="#d1fae5">extract contour polylines at fixed intervals</text>
<text x="230" y="456" text-anchor="middle" font-size="10" font-family="Menlo, monospace" fill="#ecfccb">-i 10 (10 m) or -i 20 (20 m)</text>
<!-- Right: gdaldem hillshade -->
<g filter="url(#shadow)">
<rect x="590" y="390" width="280" height="80" rx="10" ry="10" fill="#10b981" stroke="#047857" stroke-width="1.5"/>
</g>
<text x="730" y="418" text-anchor="middle" font-size="14" font-weight="700" fill="#fff">gdaldem hillshade</text>
<text x="730" y="438" text-anchor="middle" font-size="11" fill="#d1fae5">render shaded relief PNG</text>
<text x="730" y="456" text-anchor="middle" font-size="10" font-family="Menlo, monospace" fill="#ecfccb">-z 2 -az 315 -alt 45</text>
<!-- Arrows 3 → 4 -->
<path d="M230,470 L230,510" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<path d="M730,470 L730,510" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<!-- ====================================================== -->
<!-- STAGE 4 — DERIVATIVES (orange) -->
<!-- ====================================================== -->
<g filter="url(#shadow)">
<rect x="90" y="520" width="280" height="70" rx="10" ry="10" fill="#fff" stroke="#f59e0b" stroke-width="2"/>
</g>
<text x="230" y="548" text-anchor="middle" font-size="14" font-weight="700" fill="#92400e">Contour polylines</text>
<text x="230" y="568" text-anchor="middle" font-size="11" fill="#78350f">Shapefile / GeoPackage / GeoJSON</text>
<text x="230" y="584" text-anchor="middle" font-size="10" font-style="italic" fill="#b45309">vector</text>
<g filter="url(#shadow)">
<rect x="590" y="520" width="280" height="70" rx="10" ry="10" fill="#fff" stroke="#f59e0b" stroke-width="2"/>
</g>
<text x="730" y="548" text-anchor="middle" font-size="14" font-weight="700" fill="#92400e">Hillshade raster</text>
<text x="730" y="568" text-anchor="middle" font-size="11" fill="#78350f">GeoTIFF / PNG tile pyramid</text>
<text x="730" y="584" text-anchor="middle" font-size="10" font-style="italic" fill="#b45309">raster</text>
<!-- Arrows 4 → 5 -->
<path d="M230,590 L230,630" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<path d="M730,590 L730,630" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<!-- ====================================================== -->
<!-- STAGE 5 — SERVE (purple) -->
<!-- ====================================================== -->
<g filter="url(#shadow)">
<rect x="40" y="640" width="180" height="80" rx="10" ry="10" fill="#8b5cf6" stroke="#5b21b6" stroke-width="1.5"/>
</g>
<text x="130" y="668" text-anchor="middle" font-size="14" font-weight="700" fill="#fff">GeoServer</text>
<text x="130" y="688" text-anchor="middle" font-size="11" fill="#ede9fe">WMS endpoint</text>
<text x="130" y="706" text-anchor="middle" font-size="10" font-style="italic" fill="#ddd6fe">on-demand rendering</text>
<g filter="url(#shadow)">
<rect x="240" y="640" width="180" height="80" rx="10" ry="10" fill="#8b5cf6" stroke="#5b21b6" stroke-width="1.5"/>
</g>
<text x="330" y="668" text-anchor="middle" font-size="14" font-weight="700" fill="#fff">MBTiles</text>
<text x="330" y="688" text-anchor="middle" font-size="11" fill="#ede9fe">XYZ tile server</text>
<text x="330" y="706" text-anchor="middle" font-size="10" font-style="italic" fill="#ddd6fe">pre-rendered, fast</text>
<g filter="url(#shadow)">
<rect x="640" y="640" width="180" height="80" rx="10" ry="10" fill="#8b5cf6" stroke="#5b21b6" stroke-width="1.5"/>
</g>
<text x="730" y="668" text-anchor="middle" font-size="14" font-weight="700" fill="#fff">Tile pyramid</text>
<text x="730" y="688" text-anchor="middle" font-size="11" fill="#ede9fe">XYZ / WMTS</text>
<text x="730" y="706" text-anchor="middle" font-size="10" font-style="italic" fill="#ddd6fe">gdal2tiles.py</text>
<!-- Split arrow from contour derivatives to BOTH GeoServer and MBTiles -->
<path d="M180,610 L130,610 L130,640" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<path d="M280,610 L330,610 L330,640" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<path d="M230,590 L230,610" stroke="#4b5563" stroke-width="2" fill="none"/>
<line x1="130" y1="610" x2="330" y2="610" stroke="#4b5563" stroke-width="2"/>
<!-- Label the "OR" on the left side -->
<text x="230" y="627" text-anchor="middle" font-size="10" font-weight="700" fill="#6b7280">serve as WMS or XYZ</text>
<!-- Arrows 5 → 6 — converging to LUPMIS2 -->
<path d="M130,720 L130,770 L460,770 L460,800" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<path d="M330,720 L330,770" stroke="#4b5563" stroke-width="2" fill="none"/>
<line x1="330" y1="770" x2="460" y2="770" stroke="#4b5563" stroke-width="2"/>
<path d="M730,720 L730,770 L500,770 L500,800" stroke="#4b5563" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<!-- ====================================================== -->
<!-- STAGE 6 — CONSUMER (brand) -->
<!-- ====================================================== -->
<g filter="url(#shadow)">
<rect x="280" y="800" width="400" height="70" rx="10" ry="10" fill="#1e1a4b" stroke="#0f0c2a" stroke-width="1.5"/>
</g>
<text x="480" y="828" text-anchor="middle" font-size="15" font-weight="700" fill="#fff">LUPMIS2 PWA</text>
<text x="480" y="848" text-anchor="middle" font-size="11" fill="#c7d2fe">OpenLayers · addWMSLayer() / addXYZLayer()</text>
<text x="480" y="863" text-anchor="middle" font-size="10" font-style="italic" fill="#a5b4fc">"Biophysical Environment" group</text>
<!-- ====================================================== -->
<!-- LEGEND -->
<!-- ====================================================== -->
<g transform="translate(20,100)">
<rect width="140" height="180" rx="6" ry="6" fill="#fff" stroke="#d1d5db" stroke-width="1"/>
<text x="70" y="20" text-anchor="middle" font-size="12" font-weight="700" fill="#1f2937">Legend</text>
<rect x="10" y="32" width="20" height="14" rx="2" fill="#0ea5e9"/>
<text x="36" y="43" font-size="10" fill="#374151">External source</text>
<rect x="10" y="54" width="20" height="14" rx="2" fill="#fff" stroke="#6b7280" stroke-dasharray="3,2"/>
<text x="36" y="65" font-size="10" fill="#374151">Raw data file</text>
<rect x="10" y="76" width="20" height="14" rx="2" fill="#10b981"/>
<text x="36" y="87" font-size="10" fill="#374151">Processing step</text>
<rect x="10" y="98" width="20" height="14" rx="2" fill="#fff" stroke="#f59e0b" stroke-width="1.5"/>
<text x="36" y="109" font-size="10" fill="#374151">Derived product</text>
<rect x="10" y="120" width="20" height="14" rx="2" fill="#8b5cf6"/>
<text x="36" y="131" font-size="10" fill="#374151">Serving layer</text>
<rect x="10" y="142" width="20" height="14" rx="2" fill="#1e1a4b"/>
<text x="36" y="153" font-size="10" fill="#374151">Consumer</text>
<text x="70" y="172" text-anchor="middle" font-size="9" font-style="italic" fill="#6b7280">Run once · serve forever</text>
</g>
<!-- Footer note -->
<text x="480" y="888" text-anchor="middle" font-size="10" font-style="italic" fill="#6b7280">
Prepared for LUSPA · April 2026 · One-off ETL job — no runtime OpenTopography API calls from the PWA
</text>
</svg>