Add guided operational GIS workflow
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-04 23:52:25 +02:00
parent 069de33080
commit 14b66e642b
7 changed files with 219 additions and 4 deletions
+3 -1
View File
@@ -10,7 +10,9 @@ Data and Map have an additional usability layout pass for the core daily workflo
The Map workspace defaults to an OpenStreetMap road basemap with visible attribution so uploaded vectors, AOIs and QA overlays appear on a real street context. Set `VITE_MAP_STYLE_URL` to a managed MapLibre style URL to override this for production or high-volume deployments.
Operational GIS testing is now available directly in the Map workspace. Users can choose a persisted vector database layer, load it on the map, reuse the selected AOI or active layer extent, and run the existing persisted `vector_features` bbox query without creating fake data or a parallel backend path.
When the public OpenStreetMap fallback is active, the Map workspace shows a basemap usage notice. This keeps the local/demo default honest and reminds operators to configure a managed style URL before production or heavier tile traffic.
Operational GIS testing is now available directly in the Map workspace. Users can choose a persisted vector database layer, load it on the map, reuse the selected AOI or active layer extent, run the existing persisted `vector_features` bbox query, save the result as a derived dataset, export the selection GeoJSON, choose a reference dataset and launch QA/QC without creating fake data or a parallel backend path.
QA/QC and Exports follow the same calmer density model. QA/QC keeps metric evidence, feature ids and raw findings available but compresses provenance and history surfaces so review starts from the selected check and map evidence actions. Exports uses denser handoff cards, latest-artifact cards and history filters so artifact creation and download paths are easier to scan.
@@ -308,6 +308,7 @@ export function MapWorkspace({
)
const selectedFeatureFilename = selectedFeatureStem === 'selected-feature' ? DEFAULT_SELECTED_FEATURE_FILENAME : `${selectedFeatureStem}.geojson`
const selectedMapDataset = availableMapDatasets.find((dataset) => dataset.id === selectedMapDatasetId) ?? null
const usesDefaultOsmBasemap = !import.meta.env.VITE_MAP_STYLE_URL
useEffect(() => {
setBboxInput(bboxToInputState(mapSelectionBbox))
@@ -437,6 +438,12 @@ export function MapWorkspace({
</div>
<div className="map-control-surface" aria-label="Map workspace controls">
{usesDefaultOsmBasemap ? (
<div className="basemap-policy-notice" aria-label="Basemap usage notice">
<strong>Local/demo basemap</strong>
<span>Public OpenStreetMap tiles are active. Configure VITE_MAP_STYLE_URL for production or heavier use.</span>
</div>
) : null}
<div className="map-toolbar">
<label>
Database layer
@@ -664,6 +671,92 @@ export function MapWorkspace({
</button>
</div>
{mapSelectionError ? <p className="error">{mapSelectionError}</p> : null}
<div className="guided-gis-flow" aria-label="Guided operational GIS workflow">
<div className="guided-gis-steps">
<div className={selectedMapDataset ? 'complete' : ''}>
<span>1</span>
<strong>Layer</strong>
<small>{selectedMapDataset ? selectedMapDataset.name : 'Select database layer'}</small>
</div>
<div className={currentSelectionBbox ? 'complete' : ''}>
<span>2</span>
<strong>Extent</strong>
<small>{currentSelectionBbox ? 'AOI/layer bbox ready' : 'Use AOI or layer extent'}</small>
</div>
<div className={mapSelectionResult ? 'complete' : ''}>
<span>3</span>
<strong>Query</strong>
<small>{mapSelectionResult ? `${mapSelectionResult.feature_count} persisted features` : 'Run PostGIS selection'}</small>
</div>
<div className={latestSelectionDatasetName ? 'complete' : ''}>
<span>4</span>
<strong>Dataset</strong>
<small>{latestSelectionDatasetName ?? 'Save query result'}</small>
</div>
<div className={mapSelectionQaResult ? 'complete' : ''}>
<span>5</span>
<strong>QA/QC</strong>
<small>{mapSelectionQaResult ? `F1 ${mapSelectionQaResult.f1_score ?? 'n/a'}` : 'Compare with reference'}</small>
</div>
<div className={latestSelectionExportPath ? 'complete' : ''}>
<span>6</span>
<strong>Export</strong>
<small>{latestSelectionExportPath ? 'GeoJSON artifact ready' : 'Save handoff artifact'}</small>
</div>
</div>
<div className="guided-gis-actions">
<button
className="secondary-action"
disabled={!mapSelectionResult || !currentSelectionBbox || selectionDatasetSaving}
type="button"
onClick={saveAreaSelectionDataset}
>
{selectionDatasetSaving ? 'Saving dataset...' : 'Save result dataset'}
</button>
<button
className="secondary-action"
disabled={!mapSelectionResult || !currentSelectionBbox || selectionExporting}
type="button"
onClick={saveAreaSelectionExport}
>
{selectionExporting ? 'Saving export...' : 'Save GeoJSON export'}
</button>
<label>
Reference
<select
value={selectedMapQaReferenceDatasetId}
onChange={(event) => onSelectMapQaReferenceDataset(event.target.value)}
disabled={!latestSelectionDatasetName || mapQaReferenceDatasets.length === 0 || mapSelectionQaRunning}
>
<option value="">Select reference</option>
{mapQaReferenceDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{dataset.name}
</option>
))}
</select>
</label>
<button
className="primary-action"
disabled={!latestSelectionDatasetName || !selectedMapQaReferenceDatasetId || mapSelectionQaRunning}
type="button"
onClick={onRunMapSelectionQa}
>
{mapSelectionQaRunning ? 'Running QA...' : 'Run QA/QC'}
</button>
<button
className="secondary-action"
disabled={!latestMapSelectionQualityCheckId}
type="button"
onClick={onOpenMapSelectionQualityEvidence}
>
Open evidence
</button>
</div>
{selectionDatasetError ? <p className="error">{selectionDatasetError}</p> : null}
{selectionExportError ? <p className="error">{selectionExportError}</p> : null}
{mapSelectionQaError ? <p className="error">{mapSelectionQaError}</p> : null}
</div>
</div>
<div className="bbox-select-surface" aria-label="Area selection and extract">
<div className="panel-title-row">
+109 -2
View File
@@ -4769,6 +4769,110 @@ section {
white-space: nowrap;
}
.basemap-policy-notice {
display: flex;
flex-wrap: wrap;
gap: 0.28rem 0.55rem;
align-items: center;
border: 1px solid #fde68a;
border-radius: 6px;
padding: 0.46rem 0.58rem;
background: #fffbeb;
color: #713f12;
font-size: 0.76rem;
}
.basemap-policy-notice strong {
font-size: 0.72rem;
letter-spacing: 0.045em;
text-transform: uppercase;
}
.basemap-policy-notice span {
color: #854d0e;
}
.guided-gis-flow {
display: grid;
gap: 0.5rem;
border-top: 1px solid var(--line);
padding-top: 0.54rem;
}
.guided-gis-steps {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.38rem;
}
.guided-gis-steps > div {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 0.08rem 0.38rem;
align-items: center;
min-width: 0;
border: 1px solid var(--line);
border-radius: 6px;
padding: 0.4rem 0.44rem;
background: #ffffff;
}
.guided-gis-steps > div.complete {
border-color: rgba(15, 118, 110, 0.26);
background: #f0fdfa;
}
.guided-gis-steps span {
display: inline-grid;
place-items: center;
grid-row: span 2;
width: 1.35rem;
height: 1.35rem;
border-radius: 999px;
background: var(--panel-soft);
color: var(--muted);
font-size: 0.68rem;
font-weight: 850;
}
.guided-gis-steps .complete span {
background: var(--accent);
color: #ffffff;
}
.guided-gis-steps strong,
.guided-gis-steps small {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.guided-gis-steps strong {
font-size: 0.76rem;
}
.guided-gis-steps small {
color: var(--muted);
font-size: 0.68rem;
}
.guided-gis-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.38rem;
align-items: end;
}
.guided-gis-actions label {
margin-top: 0;
}
.guided-gis-actions button {
min-height: 2.15rem;
padding: 0.38rem 0.55rem;
font-size: 0.78rem;
}
.bbox-select-surface {
border-left-width: 3px;
}
@@ -4829,7 +4933,8 @@ section {
.dataset-action-grid,
.map-context-summary,
.map-workspace-shell .map-toolbar,
.layer-provenance-rail {
.layer-provenance-rail,
.guided-gis-steps {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@@ -4842,7 +4947,9 @@ section {
.map-context-summary,
.map-workspace-shell .map-toolbar,
.layer-provenance-rail,
.bbox-select-grid {
.bbox-select-grid,
.guided-gis-steps,
.guided-gis-actions {
grid-template-columns: 1fr;
}