From 3a7bfbf0c29efde29e724ce9fd9b5fad87bbf20a Mon Sep 17 00:00:00 2001 From: Joao Figueiredo Date: Thu, 23 Jul 2026 01:44:30 +0100 Subject: [PATCH] feat: highlight dwelling parcels --- app.js | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index 11e6c22..cf07873 100644 --- a/app.js +++ b/app.js @@ -2,6 +2,7 @@ const DEFAULT_CENTER = [-8.65, 39.55]; const API_CAPABILITIES = "/api/capabilities"; const API_WMS = "/api/wms"; const PARCEL_LAYER_NAME = "xpro:parcelas"; +const DWELLING_PARCEL_LAYER_NAME = "xpro:parcelas_habitacao"; const FEATURE_INFO_LAYER_NAMES = new Set(["parcelas", "predios"]); const DEVICE_ORIENTATION_EVENTS = ["deviceorientationabsolute", "deviceorientation"]; const COMPASS_HEADING_DEADBAND = 2; @@ -67,6 +68,21 @@ const wmsLayer = new ol.layer.Image({ source: wmsSource, }); +const dwellingParcelSource = new ol.source.Vector(); +const dwellingParcelLayer = new ol.layer.Vector({ + source: dwellingParcelSource, + // The pale halo keeps the red dwelling boundary distinct over every + // supported basemap and the existing parcel fills. + style: [ + new ol.style.Style({ + stroke: new ol.style.Stroke({ color: "rgba(255, 255, 255, 0.9)", width: 6 }), + }), + new ol.style.Style({ + stroke: new ol.style.Stroke({ color: "#d71920", width: 3.5 }), + }), + ], +}); + const parcelSearchSource = new ol.source.Vector(); const parcelSearchLayer = new ol.layer.Vector({ source: parcelSearchSource, @@ -106,7 +122,14 @@ const map = new ol.Map({ tipLabel: "Reset map orientation to north", }), ]), - layers: [softBasemapLayer, openStreetMapLayer, satelliteLayer, wmsLayer, parcelSearchLayer], + layers: [ + softBasemapLayer, + openStreetMapLayer, + satelliteLayer, + wmsLayer, + dwellingParcelLayer, + parcelSearchLayer, + ], view: new ol.View({ center: ol.proj.fromLonLat(DEFAULT_CENTER), zoom: 7, @@ -494,6 +517,7 @@ async function loadLayers() { renderLayerList(); syncSelectedLayers(); + await loadDwellingParcels(); fitToLayerExtent(availableLayers[0]); updateStatus(`Showing ${availableLayers.length} layer(s) from GeoServer.`, "success"); @@ -502,6 +526,8 @@ async function loadLayers() { updateStatus(error.message || "Unable to load the WMS service.", "error"); availableLayers = []; selectedLayers = []; + dwellingParcelSource.clear(); + dwellingParcelLayer.setVisible(false); renderLayerList(); syncSelectedLayers(); } @@ -590,6 +616,50 @@ function syncSelectedLayers() { LAYERS: [...orderedSelectedLayers].reverse().join(","), _: Date.now(), }); + dwellingParcelLayer.setVisible( + selectedLayers.some((name) => { + const layerName = unqualifiedLayerName(name); + return layerName === "parcelas" || layerName === unqualifiedLayerName(DWELLING_PARCEL_LAYER_NAME); + }), + ); +} + +async function loadDwellingParcels() { + const publishedDwellingLayer = availableLayers.find( + (layer) => unqualifiedLayerName(layer.name) === unqualifiedLayerName(DWELLING_PARCEL_LAYER_NAME), + ); + + dwellingParcelSource.clear(); + if (!publishedDwellingLayer) { + console.warn("The dwelling parcel layer is not published by GeoServer."); + return; + } + + try { + const params = new URLSearchParams({ + service: "WFS", + version: "2.0.0", + request: "GetFeature", + typeNames: publishedDwellingLayer.name, + outputFormat: "application/json", + srsName: "EPSG:3857", + }); + const response = await fetch(`${API_WMS}?${params}`); + + if (!response.ok) { + throw new Error(`Dwelling parcel request failed with status ${response.status}.`); + } + + const featureCollection = await response.json(); + const features = new ol.format.GeoJSON().readFeatures(featureCollection, { + featureProjection: "EPSG:3857", + }); + dwellingParcelSource.addFeatures(features); + } catch (error) { + // Keep the normal map usable when a deployment has not yet published the + // optional layer; the error remains available for operational diagnosis. + console.error(error); + } } async function showFeatureInformation(event) {