From f92a3d43c1191394625f97ae2c0db15017740317 Mon Sep 17 00:00:00 2001 From: ekke Date: Thu, 20 Aug 2026 17:57:03 +0200 Subject: [PATCH] Show the Markers layer when a location is created or focused MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Points placed with Draw > Point were written to the database but never appeared on the map. The cause is not in the new workflow: the Markers layer is built with visible: false so a fresh map is uncluttered, and nothing in the application ever switched it on — only the LayerSwitcher could. So the record was saved, the map panned to it, and nothing was drawn. This predates the workflow change; making Draw > Point the single way to add a point is what made it obvious, because placing a point is now the common gesture rather than an occasional one. MapView.showMarkers() switches the layer on, and the three places that act on a specific location call it: adding one from the map form, adding one from the sidebar form, and picking one from the Locations list. The default stays hidden, so a map with no locations in play still opens clean. Traced the marker count and layer visibility through the whole sequence — add, enter Digitise, draw, save, pick Select, leave Digitise. The count was always right and the layer was false throughout, which is what pinned the cause; it is now true from the save onward. Service Worker v20 -> v21. Co-Authored-By: Claude Opus 4.8 --- ...Bt1CVsnO.js => analysis-modal-_rBzLxQd.js} | 4 +- ....js.map => analysis-modal-_rBzLxQd.js.map} | 2 +- .../{index-CF9fisV-.js => index-BQfxXCp7.js} | 42 +++++++++---------- dist/assets/index-BQfxXCp7.js.map | 1 + dist/assets/index-CF9fisV-.js.map | 1 - dist/index.html | 2 +- dist/sw.js | 7 +++- main.js | 8 ++++ public/sw.js | 7 +++- src/components/MapView.js | 17 ++++++++ 10 files changed, 63 insertions(+), 28 deletions(-) rename dist/assets/{analysis-modal-Bt1CVsnO.js => analysis-modal-_rBzLxQd.js} (99%) rename dist/assets/{analysis-modal-Bt1CVsnO.js.map => analysis-modal-_rBzLxQd.js.map} (99%) rename dist/assets/{index-CF9fisV-.js => index-BQfxXCp7.js} (93%) create mode 100644 dist/assets/index-BQfxXCp7.js.map delete mode 100644 dist/assets/index-CF9fisV-.js.map diff --git a/dist/assets/analysis-modal-Bt1CVsnO.js b/dist/assets/analysis-modal-_rBzLxQd.js similarity index 99% rename from dist/assets/analysis-modal-Bt1CVsnO.js rename to dist/assets/analysis-modal-_rBzLxQd.js index ed5ff9d..db9a4e5 100644 --- a/dist/assets/analysis-modal-Bt1CVsnO.js +++ b/dist/assets/analysis-modal-_rBzLxQd.js @@ -1,4 +1,4 @@ -import{_ as de}from"./geotiff-BaoeLn6q.js";import{M as pe}from"./bootstrap-D1-uvFxm.js";import{ax as ye}from"./openlayers-J9qS6Th1.js";import{i as X,f as j,a as fe,b as me,c as he,d as ge,e as be,g as we}from"./turf-B-0GbNlN.js";import{s as F}from"./index-CF9fisV-.js";import"./pako-Xa-UToif.js";import"./ol-ext-C5frl4dY.js";import"./shpjs-Doisr2ug.js";const Q=4e5,ve=new ye;function $(e,n="EPSG:3857"){const o=[];for(const s of e||[]){if(!(s.getGeometry&&s.getGeometry()))continue;const a=ve.writeFeatureObject(s,{dataProjection:"EPSG:4326",featureProjection:n});a&&a.geometry&&o.push(a)}return o}function N(e){const n=e&&e.geometry;if(!n||!n.coordinates)return null;let o=1/0,s=1/0,r=-1/0,a=-1/0;const c=l=>{if(typeof l[0]=="number"){l[0]r&&(r=l[0]),l[1]a&&(a=l[1]);return}for(const i of l)c(i)};return c(n.coordinates),Number.isFinite(o)?[o,s,r,a]:null}function D(e,n){return!e||!n?!1:!(e[2]=e[0]&&n<=e[2]&&o>=e[1]&&o<=e[3]:!1}function _(e,n,o){if(e.length>0||n.length===0)return;const s=[...new Set(n.map(a=>a?.geometry?.type).filter(Boolean))],r=s.length?s.join(", "):"no geometry";throw new Error(`${o} contains no polygons (found ${r}). Overlay operations work on polygon layers only — line layers such as contours or roads cannot be used here. To summarise a line layer per zone, use Zonal statistics instead.`)}function I(e){return(e||[]).filter(n=>{const o=n?.geometry?.type;return o==="Polygon"||o==="MultiPolygon"})}function Be(e,n,o="b_"){const s={...e||{}};for(const[r,a]of Object.entries(n||{}))s[Object.prototype.hasOwnProperty.call(s,r)?`${o}${r}`:r]=a;return s}function z(e){return{type:"FeatureCollection",features:e}}function ee(e,n,o={}){const{keepBAttributes:s=!0}=o,r=$(e),a=$(n),c=I(r),l=I(a);_(c,r,"Layer A"),_(l,a,"Layer B");const i=l.map(N);let u=0;const f=[];for(const d of c){const y=N(d);for(let m=0;mQ)throw te(u);let p=null;try{p=me(j([d,l[m]]))}catch{p=null}!p||!p.geometry||(p.properties=s?Be(d.properties,l[m].properties):{...d.properties||{}},f.push(p))}}return z(f)}function Ee(e,n,o={}){return ee(e,n,{...o,keepBAttributes:!1})}function Le(e,n){const o=$(e),s=$(n),r=I(o),a=I(s);_(r,o,"Layer A"),_(a,s,"Layer B");const c=a.map(N);let l=0;const i=[];for(const u of r){const f=N(u);let d=u;for(let y=0;yQ)throw te(l);try{const m=fe(j([d,a[y]]));d=m&&m.geometry?m:null}catch{}}d&&d.geometry&&(d.properties={...u.properties||{}},i.push(d))}return z(i)}function Ae(e,n=[]){const o=$(e),s=$(n),r=I(o),a=I(s);_(r,o,"Layer A"),_(a,s,"Layer B");const c=[...r,...a];if(c.length===0)return z([]);if(c.length===1)return z([c[0]]);let l=null;try{l=X(j(c))}catch{l=c[0];for(let i=1;i{const A=w?.geometry?.type;return A==="Polygon"||A==="MultiPolygon"}),l=$(n)||[];if(c.length===0)throw new Error("The zone layer contains no polygons.");const i=[],u=new Set(l.map(w=>w?.geometry?.type).filter(Boolean)),f=u.has("Polygon")||u.has("MultiPolygon");o.includes("area")&&!f&&l.length>0&&i.push(`Total area is 0 because the features being summarised are ${[...u].join("/")} — only polygons have an area.`);const d=u.has("LineString")||u.has("MultiLineString");o.includes("mean")&&d&&!f&&s&&i.push(`Mean is the average of “${s}” across the individual line features in each zone, not an area-weighted average of the surface they describe. For contour data, Minimum and Maximum (the elevation range) are the figures you can rely on.`);const y=r==="centroid"?l.map(w=>{try{return he(w)}catch{return null}}):null,m=r==="centroid"?null:l.map(N),p=c.map(N);let L=0;const W=[];for(let w=0;wY)throw q(L);b=ge(E,A)}else{if(!D(U,m[h]))continue;if(++L>Y)throw q(L);b=be(l[h],A)}}catch{b=!1}b&&O.push(l[h])}const B=s?O.map(h=>ke(h.properties?h.properties[s]:null)).filter(h=>h!==null):[],S={};for(const h of o)switch(h){case"count":S[`${a}count`]=O.length;break;case"sum":S[`${a}sum`]=B.length?C(B.reduce((b,E)=>b+E,0)):0;break;case"mean":S[`${a}mean`]=B.length?C(B.reduce((b,E)=>b+E,0)/B.length):null;break;case"min":S[`${a}min`]=B.length?C(Math.min(...B)):null;break;case"max":S[`${a}max`]=B.length?C(Math.max(...B)):null;break;case"area":{let b=0;for(const E of O)try{b+=we(E)}catch{}S[`${a}area_m2`]=Math.round(b);break}}A.properties={...A.properties||{},...S},W.push({...Me(A),...S})}return{geojson:{type:"FeatureCollection",features:c},rows:W,warnings:i}}function q(e){return new Error(`More than ${e.toLocaleString()} features fall within the zones' bounding boxes, which is too much to summarise in the browser. Narrow the inputs using “Apply to” (current map view or selected features).`)}function C(e){return Math.round(e*1e3)/1e3}function Me(e){const n=e.properties||{};for(const o of["zone_name","name","ZONE_NAME","NAME","upn","UPN","id","ID"])if(n[o]!==void 0&&n[o]!==null&&String(n[o]).trim()!=="")return{zone:String(n[o])};return{zone:"(unnamed)"}}const Ne=9001,H="Analysis",t={};let P=null,v=null,M=[],x=null,k=null;function _e(){t.root||(t.root=document.getElementById("analysisModal"),t.mode=Array.from(document.querySelectorAll('input[name="analysis-mode"]')),t.overlayBox=document.getElementById("analysis-overlay-box"),t.zonalBox=document.getElementById("analysis-zonal-box"),t.op=document.getElementById("analysis-op"),t.opHint=document.getElementById("analysis-op-hint"),t.layerA=document.getElementById("analysis-layer-a"),t.layerB=document.getElementById("analysis-layer-b"),t.layerBWrap=document.getElementById("analysis-layer-b-wrap"),t.zoneLayer=document.getElementById("analysis-zone-layer"),t.inputLayer=document.getElementById("analysis-input-layer"),t.membership=document.getElementById("analysis-membership"),t.memberHint=document.getElementById("analysis-membership-hint"),t.field=document.getElementById("analysis-field"),t.statsBox=document.getElementById("analysis-stats-box"),t.outName=document.getElementById("analysis-output-name"),t.result=document.getElementById("analysis-result"),t.btnRun=document.getElementById("analysis-run"),t.btnExport=document.getElementById("analysis-export-table"),t.scope=document.getElementById("analysis-scope"),t.scopeHint=document.getElementById("analysis-scope-hint"),t.root.dataset.wired||(t.root.dataset.wired="1",t.mode.forEach(e=>e.addEventListener("change",oe)),t.op.addEventListener("change",re),t.membership.addEventListener("change",se),t.inputLayer.addEventListener("change",ne),t.scope.addEventListener("change",le),t.btnRun.addEventListener("click",Ce),t.btnExport.addEventListener("click",je)))}function J(e=null){return M.map((n,o)=>``).join("")}function Ie(){t.op.innerHTML=G.map(e=>``).join(""),t.membership.innerHTML=Z.map(e=>``).join(""),t.statsBox.innerHTML=R.map(e=>` +import{_ as de}from"./geotiff-BaoeLn6q.js";import{M as pe}from"./bootstrap-D1-uvFxm.js";import{ax as ye}from"./openlayers-J9qS6Th1.js";import{i as X,f as j,a as fe,b as me,c as he,d as ge,e as be,g as we}from"./turf-B-0GbNlN.js";import{s as F}from"./index-BQfxXCp7.js";import"./pako-Xa-UToif.js";import"./ol-ext-C5frl4dY.js";import"./shpjs-Doisr2ug.js";const Q=4e5,ve=new ye;function $(e,n="EPSG:3857"){const o=[];for(const s of e||[]){if(!(s.getGeometry&&s.getGeometry()))continue;const a=ve.writeFeatureObject(s,{dataProjection:"EPSG:4326",featureProjection:n});a&&a.geometry&&o.push(a)}return o}function N(e){const n=e&&e.geometry;if(!n||!n.coordinates)return null;let o=1/0,s=1/0,r=-1/0,a=-1/0;const c=l=>{if(typeof l[0]=="number"){l[0]r&&(r=l[0]),l[1]a&&(a=l[1]);return}for(const i of l)c(i)};return c(n.coordinates),Number.isFinite(o)?[o,s,r,a]:null}function D(e,n){return!e||!n?!1:!(e[2]=e[0]&&n<=e[2]&&o>=e[1]&&o<=e[3]:!1}function _(e,n,o){if(e.length>0||n.length===0)return;const s=[...new Set(n.map(a=>a?.geometry?.type).filter(Boolean))],r=s.length?s.join(", "):"no geometry";throw new Error(`${o} contains no polygons (found ${r}). Overlay operations work on polygon layers only — line layers such as contours or roads cannot be used here. To summarise a line layer per zone, use Zonal statistics instead.`)}function I(e){return(e||[]).filter(n=>{const o=n?.geometry?.type;return o==="Polygon"||o==="MultiPolygon"})}function Be(e,n,o="b_"){const s={...e||{}};for(const[r,a]of Object.entries(n||{}))s[Object.prototype.hasOwnProperty.call(s,r)?`${o}${r}`:r]=a;return s}function z(e){return{type:"FeatureCollection",features:e}}function ee(e,n,o={}){const{keepBAttributes:s=!0}=o,r=$(e),a=$(n),c=I(r),l=I(a);_(c,r,"Layer A"),_(l,a,"Layer B");const i=l.map(N);let u=0;const f=[];for(const d of c){const y=N(d);for(let m=0;mQ)throw te(u);let p=null;try{p=me(j([d,l[m]]))}catch{p=null}!p||!p.geometry||(p.properties=s?Be(d.properties,l[m].properties):{...d.properties||{}},f.push(p))}}return z(f)}function Ee(e,n,o={}){return ee(e,n,{...o,keepBAttributes:!1})}function Le(e,n){const o=$(e),s=$(n),r=I(o),a=I(s);_(r,o,"Layer A"),_(a,s,"Layer B");const c=a.map(N);let l=0;const i=[];for(const u of r){const f=N(u);let d=u;for(let y=0;yQ)throw te(l);try{const m=fe(j([d,a[y]]));d=m&&m.geometry?m:null}catch{}}d&&d.geometry&&(d.properties={...u.properties||{}},i.push(d))}return z(i)}function Ae(e,n=[]){const o=$(e),s=$(n),r=I(o),a=I(s);_(r,o,"Layer A"),_(a,s,"Layer B");const c=[...r,...a];if(c.length===0)return z([]);if(c.length===1)return z([c[0]]);let l=null;try{l=X(j(c))}catch{l=c[0];for(let i=1;i{const A=w?.geometry?.type;return A==="Polygon"||A==="MultiPolygon"}),l=$(n)||[];if(c.length===0)throw new Error("The zone layer contains no polygons.");const i=[],u=new Set(l.map(w=>w?.geometry?.type).filter(Boolean)),f=u.has("Polygon")||u.has("MultiPolygon");o.includes("area")&&!f&&l.length>0&&i.push(`Total area is 0 because the features being summarised are ${[...u].join("/")} — only polygons have an area.`);const d=u.has("LineString")||u.has("MultiLineString");o.includes("mean")&&d&&!f&&s&&i.push(`Mean is the average of “${s}” across the individual line features in each zone, not an area-weighted average of the surface they describe. For contour data, Minimum and Maximum (the elevation range) are the figures you can rely on.`);const y=r==="centroid"?l.map(w=>{try{return he(w)}catch{return null}}):null,m=r==="centroid"?null:l.map(N),p=c.map(N);let L=0;const W=[];for(let w=0;wY)throw q(L);b=ge(E,A)}else{if(!D(U,m[h]))continue;if(++L>Y)throw q(L);b=be(l[h],A)}}catch{b=!1}b&&O.push(l[h])}const B=s?O.map(h=>ke(h.properties?h.properties[s]:null)).filter(h=>h!==null):[],S={};for(const h of o)switch(h){case"count":S[`${a}count`]=O.length;break;case"sum":S[`${a}sum`]=B.length?C(B.reduce((b,E)=>b+E,0)):0;break;case"mean":S[`${a}mean`]=B.length?C(B.reduce((b,E)=>b+E,0)/B.length):null;break;case"min":S[`${a}min`]=B.length?C(Math.min(...B)):null;break;case"max":S[`${a}max`]=B.length?C(Math.max(...B)):null;break;case"area":{let b=0;for(const E of O)try{b+=we(E)}catch{}S[`${a}area_m2`]=Math.round(b);break}}A.properties={...A.properties||{},...S},W.push({...Me(A),...S})}return{geojson:{type:"FeatureCollection",features:c},rows:W,warnings:i}}function q(e){return new Error(`More than ${e.toLocaleString()} features fall within the zones' bounding boxes, which is too much to summarise in the browser. Narrow the inputs using “Apply to” (current map view or selected features).`)}function C(e){return Math.round(e*1e3)/1e3}function Me(e){const n=e.properties||{};for(const o of["zone_name","name","ZONE_NAME","NAME","upn","UPN","id","ID"])if(n[o]!==void 0&&n[o]!==null&&String(n[o]).trim()!=="")return{zone:String(n[o])};return{zone:"(unnamed)"}}const Ne=9001,H="Analysis",t={};let P=null,v=null,M=[],x=null,k=null;function _e(){t.root||(t.root=document.getElementById("analysisModal"),t.mode=Array.from(document.querySelectorAll('input[name="analysis-mode"]')),t.overlayBox=document.getElementById("analysis-overlay-box"),t.zonalBox=document.getElementById("analysis-zonal-box"),t.op=document.getElementById("analysis-op"),t.opHint=document.getElementById("analysis-op-hint"),t.layerA=document.getElementById("analysis-layer-a"),t.layerB=document.getElementById("analysis-layer-b"),t.layerBWrap=document.getElementById("analysis-layer-b-wrap"),t.zoneLayer=document.getElementById("analysis-zone-layer"),t.inputLayer=document.getElementById("analysis-input-layer"),t.membership=document.getElementById("analysis-membership"),t.memberHint=document.getElementById("analysis-membership-hint"),t.field=document.getElementById("analysis-field"),t.statsBox=document.getElementById("analysis-stats-box"),t.outName=document.getElementById("analysis-output-name"),t.result=document.getElementById("analysis-result"),t.btnRun=document.getElementById("analysis-run"),t.btnExport=document.getElementById("analysis-export-table"),t.scope=document.getElementById("analysis-scope"),t.scopeHint=document.getElementById("analysis-scope-hint"),t.root.dataset.wired||(t.root.dataset.wired="1",t.mode.forEach(e=>e.addEventListener("change",oe)),t.op.addEventListener("change",re),t.membership.addEventListener("change",se),t.inputLayer.addEventListener("change",ne),t.scope.addEventListener("change",le),t.btnRun.addEventListener("click",Ce),t.btnExport.addEventListener("click",je)))}function J(e=null){return M.map((n,o)=>``).join("")}function Ie(){t.op.innerHTML=G.map(e=>``).join(""),t.membership.innerHTML=Z.map(e=>``).join(""),t.statsBox.innerHTML=R.map(e=>`
@@ -8,4 +8,4 @@ import{_ as de}from"./geotiff-BaoeLn6q.js";import{M as pe}from"./bootstrap-D1-uv ${o} ${s}
`}function Xe(e,n={}){if(v=e,_e(),x=n.scopeFeatures&&n.scopeFeatures.length?{features:new Set(n.scopeFeatures),label:n.scopeLabel||"drawn area"}:null,He(),M=v.listVectorLayers({nonEmptyOnly:!0}),M.length===0){F("No vector layers with features are loaded — nothing to analyse yet.","warning",5e3);return}Ie();const o=J(0),s=J(M.length>1?1:0);t.layerA.innerHTML=o,t.layerB.innerHTML=s,t.zoneLayer.innerHTML=o,t.inputLayer.innerHTML=s,t.outName.dataset.touched="",t.outName.addEventListener("input",()=>{t.outName.dataset.touched="1"},{once:!0}),t.result.innerHTML="",k=null,t.scope.value=x?"drawn":"all",ne(),re(),se(),le(),oe(),P=P||new pe(t.root),P.show()}function He(){const e=t.scope.querySelector('option[value="drawn"]');if(!x){e&&e.remove();return}const n=`From ${x.label} (${x.features.size.toLocaleString()} features)`;if(e)e.textContent=n;else{const o=document.createElement("option");o.value="drawn",o.textContent=n,t.scope.insertBefore(o,t.scope.firstChild)}}function g(e){return String(e).replace(/[&<>"']/g,n=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[n])}export{Xe as openAnalysisModal}; -//# sourceMappingURL=analysis-modal-Bt1CVsnO.js.map +//# sourceMappingURL=analysis-modal-_rBzLxQd.js.map diff --git a/dist/assets/analysis-modal-Bt1CVsnO.js.map b/dist/assets/analysis-modal-_rBzLxQd.js.map similarity index 99% rename from dist/assets/analysis-modal-Bt1CVsnO.js.map rename to dist/assets/analysis-modal-_rBzLxQd.js.map index 6151f94..7adcfe8 100644 --- a/dist/assets/analysis-modal-Bt1CVsnO.js.map +++ b/dist/assets/analysis-modal-_rBzLxQd.js.map @@ -1 +1 @@ -{"version":3,"mappings":"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","names":["MAX_OVERLAY_CANDIDATES","fmt","GeoJSON","toGeoJSONFeatures","features","mapProjection","out","f","gj","bboxOf","feature","geom","minX","minY","maxX","maxY","walk","c","sub","bboxOverlaps","a","b","bboxContainsPoint","box","x","y","assertPolygons","polygons","original","role","types","found","polygonsOnly","gjFeatures","t","mergeProps","propsA","propsB","prefixB","k","v","fc","intersectFeatures","aFeatures","bFeatures","opts","keepBAttributes","gjA","gjB","A","B","bBoxes","candidates","aBox","i","candidateOverflow","piece","intersect","featureCollection","clipFeatures","clipFeatures_","differenceFeatures","remainder","next","difference","unionFeatures","polyA","polyB","all","merged","union","count","OVERLAY_OPS","runOverlay","op","MAX_ZONAL_CANDIDATES","MEMBERSHIP_MODES","STATS","listNumericFields","counts","props","num","value","zonalStatistics","zoneFeatures","inputFeatures","stats","field","membership","prefix","zones","z","inputs","warnings","inputTypes","inputsHavePolygons","inputsAreLines","centroids","centroid","inputBoxes","zoneBoxes","rows","zone","zBox","members","inside","cx","cy","booleanPointInPolygon","booleanIntersects","values","result","s","round","total","m","area","labelOf","n","p","ANALYSIS_GROUP_ID","ANALYSIS_GROUP_TITLE","els","modal","mapView","layerChoices","drawnScope","lastZonal","cacheEls","r","onModeChange","onOpChange","onMembershipChange","refreshFieldOptions","onScopeChange","onRun","onExportTable","layerOptionsHtml","selectedIdx","escapeHtml","populateOps","o","choice","feats","fields","currentMode","syncExportButton","overlay","suggestName","needsB","featuresOf","select","source","scope","map","extent","selected","ensureAnalysisGroup","group","mode","name","geojson","summary","zonalWarnings","res","renderRows","membershipLabel","labelOfSelect","scopeLabel","warnHtml","w","showToast","err","opt","exportXlsx","__vitePreload","params","keys","resultRows","paramRows","base","head","h","body","openAnalysisModal","view","syncDrawnScopeOption","optsA","optsB","Modal","existing","label"],"ignoreList":[],"sources":["../../src/analysis/overlay.js","../../src/analysis/zonal.js","../../src/analysis-modal.js"],"sourcesContent":["/**\n * overlay.js — client-side vector overlay operations (Stage 1 of the\n * LUPMIS2 GIS Analytical Tools concept).\n *\n * Pure geometry helpers built on Turf.js. No DOM, no map — the caller hands\n * in OpenLayers features and gets a GeoJSON FeatureCollection back, ready for\n * MapView.addGeoJSONLayer().\n *\n * All Turf work happens in EPSG:4326 (Turf's area/length functions assume\n * WGS84), so features are reprojected out of the map's EPSG:3857 on the way\n * in and the result is handed back as WGS84 GeoJSON — the same convention\n * gis-export.js uses.\n *\n * intersectFeatures(a, b) → parts of A that fall inside B (attrs: A + B)\n * clipFeatures(a, b) → parts of A that fall inside B (attrs: A only)\n * differenceFeatures(a, b) → parts of A that fall OUTSIDE B (attrs: A)\n * unionFeatures(a[, b]) → dissolved outline of A (and B)\n */\n\nimport GeoJSON from 'ol/format/GeoJSON';\nimport intersect from '@turf/intersect';\nimport union from '@turf/union';\nimport difference from '@turf/difference';\nimport { featureCollection } from '@turf/helpers';\n\n/**\n * Guard: only *candidate* pairs (those whose bounding boxes actually overlap)\n * reach the expensive geometry test, so this ceiling is about real work, not\n * the raw n×m product. District-scale data (tens of thousands of parcels\n * against a few dozen zones) sits far below it.\n */\nexport const MAX_OVERLAY_CANDIDATES = 400000;\n\nconst fmt = new GeoJSON();\n\n/**\n * Convert OpenLayers features (map projection) to WGS84 GeoJSON features.\n * @param {Array} features\n * @param {string} [mapProjection='EPSG:3857']\n * @returns {Array} GeoJSON Features\n */\nexport function toGeoJSONFeatures(features, mapProjection = 'EPSG:3857') {\n const out = [];\n for (const f of features || []) {\n const geom = f.getGeometry && f.getGeometry();\n if (!geom) continue;\n const gj = fmt.writeFeatureObject(f, {\n dataProjection: 'EPSG:4326',\n featureProjection: mapProjection,\n });\n if (gj && gj.geometry) out.push(gj);\n }\n return out;\n}\n\n/**\n * Bounding box of a GeoJSON feature as [minX, minY, maxX, maxY].\n * Walks the coordinate arrays directly — no extra dependency, and fast enough\n * to run over tens of thousands of features.\n * @param {Object} feature GeoJSON Feature\n * @returns {number[]|null}\n */\nexport function bboxOf(feature) {\n const geom = feature && feature.geometry;\n if (!geom || !geom.coordinates) return null;\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n const walk = (c) => {\n if (typeof c[0] === 'number') {\n if (c[0] < minX) minX = c[0];\n if (c[0] > maxX) maxX = c[0];\n if (c[1] < minY) minY = c[1];\n if (c[1] > maxY) maxY = c[1];\n return;\n }\n for (const sub of c) walk(sub);\n };\n walk(geom.coordinates);\n return Number.isFinite(minX) ? [minX, minY, maxX, maxY] : null;\n}\n\n/** Do two bounding boxes overlap? Cheap pre-filter before real geometry work. */\nexport function bboxOverlaps(a, b) {\n if (!a || !b) return false;\n return !(a[2] < b[0] || b[2] < a[0] || a[3] < b[1] || b[3] < a[1]);\n}\n\n/** Is a point [x, y] inside a bounding box? */\nexport function bboxContainsPoint(box, x, y) {\n if (!box) return false;\n return x >= box[0] && x <= box[2] && y >= box[1] && y <= box[3];\n}\n\n/**\n * Reject a non-polygon input with a message that says what is actually wrong.\n *\n * Overlay operations are polygon-only, but the inputs are silently filtered by\n * polygonsOnly() — so a line layer (contours, roads) used to produce an empty\n * result and the misleading \"the layers may not overlap\". Failing loudly here\n * tells the user the real reason.\n *\n * @param {Array} polygons the surviving polygonal features\n * @param {Array} original everything that was passed in\n * @param {string} role 'Layer A' / 'Layer B'\n */\nfunction assertPolygons(polygons, original, role) {\n if (polygons.length > 0 || original.length === 0) return;\n const types = [...new Set(original.map((f) => f?.geometry?.type).filter(Boolean))];\n const found = types.length ? types.join(', ') : 'no geometry';\n throw new Error(\n `${role} contains no polygons (found ${found}). Overlay operations work on ` +\n `polygon layers only — line layers such as contours or roads cannot be used here. ` +\n `To summarise a line layer per zone, use Zonal statistics instead.`\n );\n}\n\n/** Keep only polygonal features — overlay is undefined for points/lines here. */\nfunction polygonsOnly(gjFeatures) {\n return (gjFeatures || []).filter((f) => {\n const t = f?.geometry?.type;\n return t === 'Polygon' || t === 'MultiPolygon';\n });\n}\n\n/** Merge two property bags, prefixing B's keys on collision so nothing is lost. */\nfunction mergeProps(propsA, propsB, prefixB = 'b_') {\n const out = { ...(propsA || {}) };\n for (const [k, v] of Object.entries(propsB || {})) {\n out[Object.prototype.hasOwnProperty.call(out, k) ? `${prefixB}${k}` : k] = v;\n }\n return out;\n}\n\nfunction fc(features) {\n return { type: 'FeatureCollection', features };\n}\n\n/**\n * Intersection: every piece of A that also lies inside B.\n * Result carries A's attributes merged with the matching B feature's.\n *\n * @param {Array} aFeatures OL features (layer A)\n * @param {Array} bFeatures OL features (layer B)\n * @param {Object} [opts]\n * @param {boolean} [opts.keepBAttributes=true] merge B's properties into the result\n * @returns {Object} GeoJSON FeatureCollection (WGS84)\n */\nexport function intersectFeatures(aFeatures, bFeatures, opts = {}) {\n const { keepBAttributes = true } = opts;\n const gjA = toGeoJSONFeatures(aFeatures);\n const gjB = toGeoJSONFeatures(bFeatures);\n const A = polygonsOnly(gjA);\n const B = polygonsOnly(gjB);\n assertPolygons(A, gjA, 'Layer A');\n assertPolygons(B, gjB, 'Layer B');\n\n // Pre-compute B's boxes once, then only test pairs that can possibly meet.\n const bBoxes = B.map(bboxOf);\n let candidates = 0;\n const out = [];\n for (const a of A) {\n const aBox = bboxOf(a);\n for (let i = 0; i < B.length; i++) {\n if (!bboxOverlaps(aBox, bBoxes[i])) continue;\n if (++candidates > MAX_OVERLAY_CANDIDATES) throw candidateOverflow(candidates);\n let piece = null;\n try { piece = intersect(featureCollection([a, B[i]])); } catch { piece = null; }\n if (!piece || !piece.geometry) continue;\n piece.properties = keepBAttributes\n ? mergeProps(a.properties, B[i].properties)\n : { ...(a.properties || {}) };\n out.push(piece);\n }\n }\n return fc(out);\n}\n\n/**\n * Clip: same geometry result as intersect, but the output keeps ONLY layer A's\n * attributes — the usual \"cut this layer to that boundary\" operation.\n */\nexport function clipFeatures(aFeatures, clipFeatures_, opts = {}) {\n return intersectFeatures(aFeatures, clipFeatures_, { ...opts, keepBAttributes: false });\n}\n\n/**\n * Difference: the parts of A that do NOT fall inside any feature of B.\n * Each A feature is reduced by every overlapping B feature in turn; features\n * fully covered by B drop out of the result.\n *\n * @returns {Object} GeoJSON FeatureCollection (WGS84)\n */\nexport function differenceFeatures(aFeatures, bFeatures) {\n const gjA = toGeoJSONFeatures(aFeatures);\n const gjB = toGeoJSONFeatures(bFeatures);\n const A = polygonsOnly(gjA);\n const B = polygonsOnly(gjB);\n assertPolygons(A, gjA, 'Layer A');\n assertPolygons(B, gjB, 'Layer B');\n\n const bBoxes = B.map(bboxOf);\n let candidates = 0;\n const out = [];\n for (const a of A) {\n const aBox = bboxOf(a);\n let remainder = a;\n for (let i = 0; i < B.length; i++) {\n if (!remainder) break;\n // Only subtract a B feature that can actually touch what's left.\n if (!bboxOverlaps(aBox, bBoxes[i])) continue;\n if (++candidates > MAX_OVERLAY_CANDIDATES) throw candidateOverflow(candidates);\n try {\n const next = difference(featureCollection([remainder, B[i]]));\n remainder = next && next.geometry ? next : null;\n } catch {\n // A malformed pair shouldn't kill the whole run — keep what we have.\n }\n }\n if (remainder && remainder.geometry) {\n remainder.properties = { ...(a.properties || {}) };\n out.push(remainder);\n }\n }\n return fc(out);\n}\n\n/**\n * Union / dissolve: merge everything into a single outline. Pass only `a` to\n * dissolve one layer, or both to merge two layers together.\n *\n * @returns {Object} GeoJSON FeatureCollection with 0 or 1 feature (WGS84)\n */\nexport function unionFeatures(aFeatures, bFeatures = []) {\n // Check each layer separately rather than the combined set: if only the\n // second layer had polygons, a combined check would pass and the first\n // layer's features would be dropped without the user being told.\n const gjA = toGeoJSONFeatures(aFeatures);\n const gjB = toGeoJSONFeatures(bFeatures);\n const polyA = polygonsOnly(gjA);\n const polyB = polygonsOnly(gjB);\n assertPolygons(polyA, gjA, 'Layer A');\n assertPolygons(polyB, gjB, 'Layer B'); // no-op when no second layer is given\n const all = [...polyA, ...polyB];\n if (all.length === 0) return fc([]);\n if (all.length === 1) return fc([all[0]]);\n\n let merged = null;\n try {\n merged = union(featureCollection(all));\n } catch {\n // Fall back to a pairwise fold if the bulk union chokes on bad geometry.\n merged = all[0];\n for (let i = 1; i < all.length; i++) {\n try {\n const next = union(featureCollection([merged, all[i]]));\n if (next && next.geometry) merged = next;\n } catch { /* skip the offending feature */ }\n }\n }\n if (!merged || !merged.geometry) return fc([]);\n merged.properties = { parts: all.length };\n return fc([merged]);\n}\n\n/** @private Raised only when genuinely overlapping work exceeds the budget. */\nfunction candidateOverflow(count) {\n return new Error(\n `This overlay has more than ${count.toLocaleString()} genuinely overlapping feature pairs, ` +\n `which is too much to run in the browser. Narrow the inputs using “Apply to” ` +\n `(current map view or selected features), or run it server-side.`\n );\n}\n\n/** Operation registry used by the Analysis panel. */\nexport const OVERLAY_OPS = [\n { key: 'intersect', label: 'Intersect', needsB: true,\n hint: 'Keep the parts of layer A that fall inside layer B (attributes from both).' },\n { key: 'clip', label: 'Clip', needsB: true,\n hint: 'Cut layer A to the boundary of layer B (keeps only A’s attributes).' },\n { key: 'difference', label: 'Difference', needsB: true,\n hint: 'Keep the parts of layer A that fall outside layer B.' },\n { key: 'union', label: 'Union / Dissolve', needsB: false,\n hint: 'Merge features into a single outline (optionally with a second layer).' },\n];\n\n/**\n * Run an overlay operation by key.\n * @param {string} op one of OVERLAY_OPS keys\n * @param {Array} aFeatures\n * @param {Array} bFeatures\n * @returns {Object} GeoJSON FeatureCollection (WGS84)\n */\nexport function runOverlay(op, aFeatures, bFeatures) {\n switch (op) {\n case 'intersect': return intersectFeatures(aFeatures, bFeatures);\n case 'clip': return clipFeatures(aFeatures, bFeatures);\n case 'difference': return differenceFeatures(aFeatures, bFeatures);\n case 'union': return unionFeatures(aFeatures, bFeatures);\n default: throw new Error(`Unknown overlay operation: ${op}`);\n }\n}\n","/**\n * zonal.js — vector-in-vector zonal statistics (Stage 1 of the LUPMIS2 GIS\n * Analytical Tools concept).\n *\n * Answers questions like \"how many parcels are in each collector zone?\" or\n * \"what is the mean plot size per zone?\" — the classic *summarize within*\n * operation. Raster × vector zonal statistics arrives with the raster pipeline\n * in Stage 2; this module is pure vector and runs fully offline.\n *\n * Membership rules:\n * 'centroid' — a feature belongs to the zone containing its centroid\n * (default; each feature counts exactly once)\n * 'intersects' — a feature belongs to every zone it touches\n * (features straddling a boundary are counted more than once)\n */\n\nimport booleanPointInPolygon from '@turf/boolean-point-in-polygon';\nimport booleanIntersects from '@turf/boolean-intersects';\nimport centroid from '@turf/centroid';\nimport area from '@turf/area';\nimport { toGeoJSONFeatures, bboxOf, bboxOverlaps, bboxContainsPoint } from './overlay.js';\n\n/**\n * Guard on *candidate* pairs only — a bounding-box pre-filter means a feature\n * is geometry-tested against a zone only when it could plausibly fall in it.\n * A district of 25,000 parcels across 40-odd zones produces roughly one\n * candidate per parcel, so real workloads sit far below this ceiling.\n */\nexport const MAX_ZONAL_CANDIDATES = 400000;\n\nexport const MEMBERSHIP_MODES = [\n { key: 'centroid', label: 'Centroid inside zone',\n hint: 'Each feature is counted once, in the zone containing its centre.' },\n { key: 'intersects', label: 'Any overlap with zone',\n hint: 'A feature is counted in every zone it touches (may double-count).' },\n];\n\n/** Statistics offered by the Analysis panel. */\nexport const STATS = [\n { key: 'count', label: 'Count', needsField: false },\n { key: 'sum', label: 'Sum', needsField: true },\n { key: 'mean', label: 'Mean', needsField: true },\n { key: 'min', label: 'Minimum', needsField: true },\n { key: 'max', label: 'Maximum', needsField: true },\n { key: 'area', label: 'Total area (m²)', needsField: false },\n];\n\n/**\n * List the numeric attribute names available across a set of OL features, so\n * the panel can offer only fields that can actually be summed or averaged.\n * @param {Array} features OL features\n * @returns {string[]}\n */\nexport function listNumericFields(features) {\n const counts = new Map();\n for (const f of features || []) {\n const props = f.getProperties ? f.getProperties() : {};\n for (const [k, v] of Object.entries(props)) {\n if (k === 'geometry' || k.startsWith('_')) continue;\n const n = typeof v === 'number' ? v : (v !== '' && v != null && !Number.isNaN(Number(v)) ? Number(v) : null);\n if (n === null) continue;\n counts.set(k, (counts.get(k) || 0) + 1);\n }\n }\n return Array.from(counts.keys()).sort();\n}\n\n/** Coerce a property to a finite number, or null when it isn't numeric. */\nfunction num(value) {\n if (value === null || value === undefined || value === '') return null;\n const n = typeof value === 'number' ? value : Number(value);\n return Number.isFinite(n) ? n : null;\n}\n\n/**\n * Compute zonal statistics.\n *\n * @param {Object} params\n * @param {Array} params.zoneFeatures OL features defining the zones (polygons)\n * @param {Array} params.inputFeatures OL features to summarise\n * @param {string[]} params.stats keys from STATS\n * @param {string} [params.field] numeric attribute (for sum/mean/min/max)\n * @param {string} [params.membership] 'centroid' | 'intersects'\n * @param {string} [params.prefix='zs_'] prefix for the new attribute names\n * @returns {{ geojson: Object, rows: Array }}\n * geojson — the zones with statistics attached (WGS84), ready to add as a layer\n * rows — a plain table of the same numbers, for display/export\n * warnings — figures that are valid but easy to misread (e.g. area over a\n * line layer); shown to the user rather than thrown\n */\nexport function zonalStatistics({\n zoneFeatures,\n inputFeatures,\n stats = ['count'],\n field = null,\n membership = 'centroid',\n prefix = 'zs_',\n}) {\n const zones = (toGeoJSONFeatures(zoneFeatures) || []).filter((z) => {\n const t = z?.geometry?.type;\n return t === 'Polygon' || t === 'MultiPolygon';\n });\n const inputs = toGeoJSONFeatures(inputFeatures) || [];\n\n if (zones.length === 0) throw new Error('The zone layer contains no polygons.');\n\n // Warnings are returned alongside the result rather than thrown: the run is\n // still valid and useful, but some figures would be silently meaningless\n // without an explanation.\n const warnings = [];\n const inputTypes = new Set(inputs.map((f) => f?.geometry?.type).filter(Boolean));\n const inputsHavePolygons = inputTypes.has('Polygon') || inputTypes.has('MultiPolygon');\n\n // Lines and points have no area, so \"Total area\" would report a silent 0.\n if (stats.includes('area') && !inputsHavePolygons && inputs.length > 0) {\n warnings.push(\n `Total area is 0 because the features being summarised are ` +\n `${[...inputTypes].join('/')} — only polygons have an area.`\n );\n }\n\n // Line inputs only. Averaging a field over line segments is not an\n // area-weighted average of the surface those lines describe — the classic\n // trap being \"mean elevation per parcel\" from contours, where segment count\n // has no relation to the area at each elevation. (Averaging over points is a\n // normal statistic, so it is not flagged.)\n const inputsAreLines = inputTypes.has('LineString') || inputTypes.has('MultiLineString');\n if (stats.includes('mean') && inputsAreLines && !inputsHavePolygons && field) {\n warnings.push(\n `Mean is the average of “${field}” across the individual line features in ` +\n `each zone, not an area-weighted average of the surface they describe. ` +\n `For contour data, Minimum and Maximum (the elevation range) are the ` +\n `figures you can rely on.`\n );\n }\n\n // Pre-compute once: each input's centroid (centroid mode) or bbox\n // (intersects mode), and every zone's bbox. The bbox comparisons below are\n // cheap arithmetic; only survivors reach the real geometry test.\n const centroids = membership === 'centroid'\n ? inputs.map((f) => { try { return centroid(f); } catch { return null; } })\n : null;\n const inputBoxes = membership === 'centroid' ? null : inputs.map(bboxOf);\n const zoneBoxes = zones.map(bboxOf);\n\n let candidates = 0;\n const rows = [];\n for (let z = 0; z < zones.length; z++) {\n const zone = zones[z];\n const zBox = zoneBoxes[z];\n const members = [];\n for (let i = 0; i < inputs.length; i++) {\n let inside = false;\n try {\n if (membership === 'centroid') {\n const c = centroids[i];\n if (!c) continue;\n const [cx, cy] = c.geometry.coordinates;\n if (!bboxContainsPoint(zBox, cx, cy)) continue; // cheap reject\n if (++candidates > MAX_ZONAL_CANDIDATES) throw candidateOverflow(candidates);\n inside = booleanPointInPolygon(c, zone);\n } else {\n if (!bboxOverlaps(zBox, inputBoxes[i])) continue; // cheap reject\n if (++candidates > MAX_ZONAL_CANDIDATES) throw candidateOverflow(candidates);\n inside = booleanIntersects(inputs[i], zone);\n }\n } catch { inside = false; }\n if (inside) members.push(inputs[i]);\n }\n\n const values = field\n ? members.map((f) => num(f.properties ? f.properties[field] : null)).filter((v) => v !== null)\n : [];\n\n const result = {};\n for (const s of stats) {\n switch (s) {\n case 'count':\n result[`${prefix}count`] = members.length; break;\n case 'sum':\n result[`${prefix}sum`] = values.length ? round(values.reduce((a, b) => a + b, 0)) : 0; break;\n case 'mean':\n result[`${prefix}mean`] = values.length\n ? round(values.reduce((a, b) => a + b, 0) / values.length) : null; break;\n case 'min':\n result[`${prefix}min`] = values.length ? round(Math.min(...values)) : null; break;\n case 'max':\n result[`${prefix}max`] = values.length ? round(Math.max(...values)) : null; break;\n case 'area': {\n let total = 0;\n for (const m of members) { try { total += area(m); } catch { /* skip */ } }\n result[`${prefix}area_m2`] = Math.round(total); break;\n }\n default: break;\n }\n }\n\n zone.properties = { ...(zone.properties || {}), ...result };\n rows.push({ ...labelOf(zone), ...result });\n }\n\n return { geojson: { type: 'FeatureCollection', features: zones }, rows, warnings };\n}\n\n/** @private Raised only when genuinely plausible pairs exceed the budget. */\nfunction candidateOverflow(count) {\n return new Error(\n `More than ${count.toLocaleString()} features fall within the zones' bounding boxes, ` +\n `which is too much to summarise in the browser. Narrow the inputs using “Apply to” ` +\n `(current map view or selected features).`\n );\n}\n\n/** Round to 3 decimals to keep attribute tables readable. */\nfunction round(n) {\n return Math.round(n * 1000) / 1000;\n}\n\n/**\n * Pick a human label for a zone row — first of the usual naming fields,\n * else a generic index.\n */\nfunction labelOf(zone) {\n const p = zone.properties || {};\n for (const k of ['zone_name', 'name', 'ZONE_NAME', 'NAME', 'upn', 'UPN', 'id', 'ID']) {\n if (p[k] !== undefined && p[k] !== null && String(p[k]).trim() !== '') {\n return { zone: String(p[k]) };\n }\n }\n return { zone: '(unnamed)' };\n}\n","/**\n * Analysis panel controller — Stage 1 of the GIS Analytical Tools concept.\n *\n * openAnalysisModal(mapView)\n *\n * Drives two families of client-side analysis:\n * • Overlays — intersect / clip / difference / union between layers\n * • Zonal statistics — summarise features (or a numeric field) per zone\n *\n * Both run entirely in the browser via Turf.js, so they work offline. Results\n * are added to the map as a new styled layer in an \"Analysis\" group, from\n * where they can be exported with the existing GIS export tools.\n *\n * Same Bootstrap modal pattern as src/export-gis-modal.js and src/import-modal.js.\n */\n\nimport { Modal } from 'bootstrap';\nimport { runOverlay, OVERLAY_OPS } from './analysis/overlay.js';\nimport { zonalStatistics, listNumericFields, STATS, MEMBERSHIP_MODES } from './analysis/zonal.js';\nimport { showToast } from './toast.js';\n\nconst ANALYSIS_GROUP_ID = 9001;\nconst ANALYSIS_GROUP_TITLE = 'Analysis';\n\nconst els = {};\nlet modal = null;\nlet mapView = null;\nlet layerChoices = []; // [{ layer, title, count }]\nlet drawnScope = null; // { features: Set, label } when opened from a Circle/Area popup\nlet lastZonal = null; // { rows, params } — the most recent zonal run, for export\n\n// ---------------------------------------------------------------------------\n// Element wiring\n// ---------------------------------------------------------------------------\n\nfunction cacheEls() {\n if (els.root) return;\n els.root = document.getElementById('analysisModal');\n els.mode = Array.from(document.querySelectorAll('input[name=\"analysis-mode\"]'));\n els.overlayBox = document.getElementById('analysis-overlay-box');\n els.zonalBox = document.getElementById('analysis-zonal-box');\n els.op = document.getElementById('analysis-op');\n els.opHint = document.getElementById('analysis-op-hint');\n els.layerA = document.getElementById('analysis-layer-a');\n els.layerB = document.getElementById('analysis-layer-b');\n els.layerBWrap = document.getElementById('analysis-layer-b-wrap');\n els.zoneLayer = document.getElementById('analysis-zone-layer');\n els.inputLayer = document.getElementById('analysis-input-layer');\n els.membership = document.getElementById('analysis-membership');\n els.memberHint = document.getElementById('analysis-membership-hint');\n els.field = document.getElementById('analysis-field');\n els.statsBox = document.getElementById('analysis-stats-box');\n els.outName = document.getElementById('analysis-output-name');\n els.result = document.getElementById('analysis-result');\n els.btnRun = document.getElementById('analysis-run');\n els.btnExport = document.getElementById('analysis-export-table');\n els.scope = document.getElementById('analysis-scope');\n els.scopeHint = document.getElementById('analysis-scope-hint');\n\n if (!els.root.dataset.wired) {\n els.root.dataset.wired = '1';\n els.mode.forEach((r) => r.addEventListener('change', onModeChange));\n els.op.addEventListener('change', onOpChange);\n els.membership.addEventListener('change', onMembershipChange);\n els.inputLayer.addEventListener('change', refreshFieldOptions);\n els.scope.addEventListener('change', onScopeChange);\n els.btnRun.addEventListener('click', onRun);\n els.btnExport.addEventListener('click', onExportTable);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Population\n// ---------------------------------------------------------------------------\n\nfunction layerOptionsHtml(selectedIdx = null) {\n return layerChoices.map((c, i) =>\n ``\n ).join('');\n}\n\nfunction populateOps() {\n els.op.innerHTML = OVERLAY_OPS\n .map((o) => ``).join('');\n els.membership.innerHTML = MEMBERSHIP_MODES\n .map((m) => ``).join('');\n els.statsBox.innerHTML = STATS.map((s) => `\n
\n \n \n
`).join('');\n}\n\nfunction refreshFieldOptions() {\n const choice = layerChoices[Number(els.inputLayer.value)];\n const feats = choice ? choice.layer.getSource().getFeatures() : [];\n const fields = listNumericFields(feats);\n els.field.innerHTML = '' +\n fields.map((f) => ``).join('');\n els.field.disabled = fields.length === 0;\n}\n\n// ---------------------------------------------------------------------------\n// UI reactions\n// ---------------------------------------------------------------------------\n\nfunction currentMode() {\n return (els.mode.find((r) => r.checked) || {}).value || 'overlay';\n}\n\nfunction onModeChange() {\n // A previous zonal table doesn't belong to a new overlay run.\n lastZonal = null; syncExportButton();\n const overlay = currentMode() === 'overlay';\n els.overlayBox.classList.toggle('d-none', !overlay);\n els.zonalBox.classList.toggle('d-none', overlay);\n suggestName();\n}\n\nfunction onOpChange() {\n const op = OVERLAY_OPS.find((o) => o.key === els.op.value);\n els.opHint.textContent = op ? op.hint : '';\n // Union can run on a single layer; the others need a second one.\n const needsB = op ? op.needsB : true;\n els.layerBWrap.querySelector('label').textContent =\n needsB ? 'Layer B' : 'Layer B (optional)';\n suggestName();\n}\n\nfunction onMembershipChange() {\n const m = MEMBERSHIP_MODES.find((x) => x.key === els.membership.value);\n els.memberHint.textContent = m ? m.hint : '';\n}\n\nfunction suggestName() {\n if (els.outName.dataset.touched === '1') return;\n if (currentMode() === 'overlay') {\n const op = els.op.value || 'overlay';\n els.outName.value = `${op}_result`;\n } else {\n els.outName.value = 'zonal_stats';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Run\n// ---------------------------------------------------------------------------\n\n/**\n * Features of the chosen layer, narrowed by the \"Apply to\" scope.\n * 'view' uses the source's spatial index, so it stays fast on large layers.\n */\nfunction featuresOf(select) {\n const choice = layerChoices[Number(select.value)];\n if (!choice) return [];\n const source = choice.layer.getSource();\n const scope = els.scope ? els.scope.value : 'all';\n\n if (scope === 'view') {\n const map = mapView.getMap();\n const extent = map.getView().calculateExtent(map.getSize());\n return source.getFeaturesInExtent(extent);\n }\n if (scope === 'selection') {\n const selected = new Set(mapView.getSelectedFeatures());\n return source.getFeatures().filter((f) => selected.has(f));\n }\n if (scope === 'drawn' && drawnScope) {\n return source.getFeatures().filter((f) => drawnScope.features.has(f));\n }\n return source.getFeatures();\n}\n\nfunction onScopeChange() {\n const scope = els.scope.value;\n if (scope === 'drawn' && drawnScope) {\n els.scopeHint.textContent =\n `${drawnScope.features.size.toLocaleString()} feature(s) caught by the ${drawnScope.label}.`;\n } else if (scope === 'selection') {\n const n = mapView ? mapView.getSelectedFeatures().length : 0;\n els.scopeHint.textContent = n === 0\n ? 'Nothing selected yet — use the Digitise tool: shift-click features, or hold Ctrl/Cmd and drag a box.'\n : `${n.toLocaleString()} feature(s) currently selected.`;\n } else if (scope === 'view') {\n els.scopeHint.textContent = 'Only features inside the current map view are analysed — zoom to the area of interest first.';\n } else {\n els.scopeHint.textContent = 'Every feature in the chosen layer. Narrow this if a large layer is slow.';\n }\n}\n\nfunction ensureAnalysisGroup() {\n let group = mapView.getLayerGroupByTitle(ANALYSIS_GROUP_TITLE);\n if (!group) {\n mapView.addLayerGroup(ANALYSIS_GROUP_ID, ANALYSIS_GROUP_TITLE,\n 'Results of client-side spatial analysis');\n group = mapView.getLayerGroupByTitle(ANALYSIS_GROUP_TITLE);\n }\n return group;\n}\n\nfunction onRun() {\n els.result.innerHTML = '';\n els.btnRun.disabled = true;\n // Yield a frame so the button state paints before a heavy synchronous run.\n setTimeout(() => {\n try {\n const mode = currentMode();\n const name = (els.outName.value || '').trim() ||\n (mode === 'overlay' ? 'overlay_result' : 'zonal_stats');\n\n let geojson, summary;\n let zonalWarnings = [];\n if (mode === 'overlay') {\n lastZonal = null;\n const op = els.op.value;\n const a = featuresOf(els.layerA);\n const b = featuresOf(els.layerB);\n const needsB = (OVERLAY_OPS.find((o) => o.key === op) || {}).needsB;\n if (a.length === 0) throw new Error('Layer A has no features.');\n if (needsB && b.length === 0) throw new Error('This operation needs a second layer with features.');\n geojson = runOverlay(op, a, b);\n summary = `${geojson.features.length.toLocaleString()} feature(s) produced.`;\n } else {\n const zones = featuresOf(els.zoneLayer);\n const inputs = featuresOf(els.inputLayer);\n const stats = Array.from(els.statsBox.querySelectorAll('input:checked')).map((i) => i.value);\n const field = els.field.value || null;\n if (stats.length === 0) throw new Error('Select at least one statistic.');\n const needsField = stats.some((s) => (STATS.find((x) => x.key === s) || {}).needsField);\n if (needsField && !field) throw new Error('Sum / mean / min / max need a numeric field.');\n\n const res = zonalStatistics({\n zoneFeatures: zones, inputFeatures: inputs,\n stats, field, membership: els.membership.value,\n });\n geojson = res.geojson;\n zonalWarnings = res.warnings || [];\n summary = renderRows(res.rows);\n\n // Keep the table AND the settings that produced it, so the Excel export\n // is self-describing — a table of numbers with no record of how it was\n // produced can't be checked or reproduced later.\n const membershipLabel = (MEMBERSHIP_MODES.find((m) => m.key === els.membership.value) || {}).label;\n lastZonal = {\n rows: res.rows,\n warnings: res.warnings || [],\n params: {\n 'Analysis': 'Zonal statistics',\n 'Zone layer': labelOfSelect(els.zoneLayer),\n 'Features summarised': labelOfSelect(els.inputLayer),\n 'Applied to': scopeLabel(),\n 'Zones analysed': zones.length,\n 'Features analysed': inputs.length,\n 'Counted when': membershipLabel || els.membership.value,\n 'Statistics': stats.map((k) => (STATS.find((x) => x.key === k) || {}).label || k).join(', '),\n 'Numeric field': field || '(none)',\n 'Result layer': name,\n },\n };\n }\n\n if (!geojson.features.length) {\n els.result.innerHTML =\n `
No result features — the layers may not overlap.
`;\n return;\n }\n\n const group = ensureAnalysisGroup();\n mapView.addGeoJSONLayer(geojson, name, {\n strokeColor: '#d97706', strokeWidth: 2, fillColor: 'rgba(217,119,6,0.18)',\n }, group);\n\n syncExportButton();\n const warnHtml = zonalWarnings.length\n ? `
` +\n zonalWarnings.map((w) => `
${escapeHtml(w)}
`).join('') + `
`\n : '';\n els.result.innerHTML =\n `
Added layer “${escapeHtml(name)}”.
` +\n warnHtml +\n (typeof summary === 'string' && summary.startsWith('<') ? summary : `
${escapeHtml(summary)}
`);\n showToast(`Analysis complete — “${name}” added to the Analysis group.`, 'success', 4000);\n } catch (err) {\n console.error('[Analysis] run failed:', err);\n els.result.innerHTML =\n `
${escapeHtml(err.message || String(err))}
`;\n } finally {\n els.btnRun.disabled = false;\n }\n }, 20);\n}\n\n/** Human label of a layer \n \n `).join('');\n}\n\nfunction refreshFieldOptions() {\n const choice = layerChoices[Number(els.inputLayer.value)];\n const feats = choice ? choice.layer.getSource().getFeatures() : [];\n const fields = listNumericFields(feats);\n els.field.innerHTML = '' +\n fields.map((f) => ``).join('');\n els.field.disabled = fields.length === 0;\n}\n\n// ---------------------------------------------------------------------------\n// UI reactions\n// ---------------------------------------------------------------------------\n\nfunction currentMode() {\n return (els.mode.find((r) => r.checked) || {}).value || 'overlay';\n}\n\nfunction onModeChange() {\n // A previous zonal table doesn't belong to a new overlay run.\n lastZonal = null; syncExportButton();\n const overlay = currentMode() === 'overlay';\n els.overlayBox.classList.toggle('d-none', !overlay);\n els.zonalBox.classList.toggle('d-none', overlay);\n suggestName();\n}\n\nfunction onOpChange() {\n const op = OVERLAY_OPS.find((o) => o.key === els.op.value);\n els.opHint.textContent = op ? op.hint : '';\n // Union can run on a single layer; the others need a second one.\n const needsB = op ? op.needsB : true;\n els.layerBWrap.querySelector('label').textContent =\n needsB ? 'Layer B' : 'Layer B (optional)';\n suggestName();\n}\n\nfunction onMembershipChange() {\n const m = MEMBERSHIP_MODES.find((x) => x.key === els.membership.value);\n els.memberHint.textContent = m ? m.hint : '';\n}\n\nfunction suggestName() {\n if (els.outName.dataset.touched === '1') return;\n if (currentMode() === 'overlay') {\n const op = els.op.value || 'overlay';\n els.outName.value = `${op}_result`;\n } else {\n els.outName.value = 'zonal_stats';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Run\n// ---------------------------------------------------------------------------\n\n/**\n * Features of the chosen layer, narrowed by the \"Apply to\" scope.\n * 'view' uses the source's spatial index, so it stays fast on large layers.\n */\nfunction featuresOf(select) {\n const choice = layerChoices[Number(select.value)];\n if (!choice) return [];\n const source = choice.layer.getSource();\n const scope = els.scope ? els.scope.value : 'all';\n\n if (scope === 'view') {\n const map = mapView.getMap();\n const extent = map.getView().calculateExtent(map.getSize());\n return source.getFeaturesInExtent(extent);\n }\n if (scope === 'selection') {\n const selected = new Set(mapView.getSelectedFeatures());\n return source.getFeatures().filter((f) => selected.has(f));\n }\n if (scope === 'drawn' && drawnScope) {\n return source.getFeatures().filter((f) => drawnScope.features.has(f));\n }\n return source.getFeatures();\n}\n\nfunction onScopeChange() {\n const scope = els.scope.value;\n if (scope === 'drawn' && drawnScope) {\n els.scopeHint.textContent =\n `${drawnScope.features.size.toLocaleString()} feature(s) caught by the ${drawnScope.label}.`;\n } else if (scope === 'selection') {\n const n = mapView ? mapView.getSelectedFeatures().length : 0;\n els.scopeHint.textContent = n === 0\n ? 'Nothing selected yet — use the Digitise tool: shift-click features, or hold Ctrl/Cmd and drag a box.'\n : `${n.toLocaleString()} feature(s) currently selected.`;\n } else if (scope === 'view') {\n els.scopeHint.textContent = 'Only features inside the current map view are analysed — zoom to the area of interest first.';\n } else {\n els.scopeHint.textContent = 'Every feature in the chosen layer. Narrow this if a large layer is slow.';\n }\n}\n\nfunction ensureAnalysisGroup() {\n let group = mapView.getLayerGroupByTitle(ANALYSIS_GROUP_TITLE);\n if (!group) {\n mapView.addLayerGroup(ANALYSIS_GROUP_ID, ANALYSIS_GROUP_TITLE,\n 'Results of client-side spatial analysis');\n group = mapView.getLayerGroupByTitle(ANALYSIS_GROUP_TITLE);\n }\n return group;\n}\n\nfunction onRun() {\n els.result.innerHTML = '';\n els.btnRun.disabled = true;\n // Yield a frame so the button state paints before a heavy synchronous run.\n setTimeout(() => {\n try {\n const mode = currentMode();\n const name = (els.outName.value || '').trim() ||\n (mode === 'overlay' ? 'overlay_result' : 'zonal_stats');\n\n let geojson, summary;\n let zonalWarnings = [];\n if (mode === 'overlay') {\n lastZonal = null;\n const op = els.op.value;\n const a = featuresOf(els.layerA);\n const b = featuresOf(els.layerB);\n const needsB = (OVERLAY_OPS.find((o) => o.key === op) || {}).needsB;\n if (a.length === 0) throw new Error('Layer A has no features.');\n if (needsB && b.length === 0) throw new Error('This operation needs a second layer with features.');\n geojson = runOverlay(op, a, b);\n summary = `${geojson.features.length.toLocaleString()} feature(s) produced.`;\n } else {\n const zones = featuresOf(els.zoneLayer);\n const inputs = featuresOf(els.inputLayer);\n const stats = Array.from(els.statsBox.querySelectorAll('input:checked')).map((i) => i.value);\n const field = els.field.value || null;\n if (stats.length === 0) throw new Error('Select at least one statistic.');\n const needsField = stats.some((s) => (STATS.find((x) => x.key === s) || {}).needsField);\n if (needsField && !field) throw new Error('Sum / mean / min / max need a numeric field.');\n\n const res = zonalStatistics({\n zoneFeatures: zones, inputFeatures: inputs,\n stats, field, membership: els.membership.value,\n });\n geojson = res.geojson;\n zonalWarnings = res.warnings || [];\n summary = renderRows(res.rows);\n\n // Keep the table AND the settings that produced it, so the Excel export\n // is self-describing — a table of numbers with no record of how it was\n // produced can't be checked or reproduced later.\n const membershipLabel = (MEMBERSHIP_MODES.find((m) => m.key === els.membership.value) || {}).label;\n lastZonal = {\n rows: res.rows,\n warnings: res.warnings || [],\n params: {\n 'Analysis': 'Zonal statistics',\n 'Zone layer': labelOfSelect(els.zoneLayer),\n 'Features summarised': labelOfSelect(els.inputLayer),\n 'Applied to': scopeLabel(),\n 'Zones analysed': zones.length,\n 'Features analysed': inputs.length,\n 'Counted when': membershipLabel || els.membership.value,\n 'Statistics': stats.map((k) => (STATS.find((x) => x.key === k) || {}).label || k).join(', '),\n 'Numeric field': field || '(none)',\n 'Result layer': name,\n },\n };\n }\n\n if (!geojson.features.length) {\n els.result.innerHTML =\n `
No result features — the layers may not overlap.
`;\n return;\n }\n\n const group = ensureAnalysisGroup();\n mapView.addGeoJSONLayer(geojson, name, {\n strokeColor: '#d97706', strokeWidth: 2, fillColor: 'rgba(217,119,6,0.18)',\n }, group);\n\n syncExportButton();\n const warnHtml = zonalWarnings.length\n ? `
` +\n zonalWarnings.map((w) => `
${escapeHtml(w)}
`).join('') + `
`\n : '';\n els.result.innerHTML =\n `
Added layer “${escapeHtml(name)}”.
` +\n warnHtml +\n (typeof summary === 'string' && summary.startsWith('<') ? summary : `
${escapeHtml(summary)}
`);\n showToast(`Analysis complete — “${name}” added to the Analysis group.`, 'success', 4000);\n } catch (err) {\n console.error('[Analysis] run failed:', err);\n els.result.innerHTML =\n `
${escapeHtml(err.message || String(err))}
`;\n } finally {\n els.btnRun.disabled = false;\n }\n }, 20);\n}\n\n/** Human label of a layer - `}).join(""),T.tbody.querySelectorAll(".import-field-map").forEach(o=>{o.addEventListener("change",a=>{const i=a.target.dataset.col;N.mapping[i]=a.target.value||null})})}function _r(){const n=T.target.value;N.targetType=n,N.mapping=wr(N.fc,n),n==="other"?(T.targetHint.innerHTML="This dataset will be visible on the map but cannot be uploaded to the database. You can change the type later.",T.btnSave.disabled=!1,T.btnSaveUpload.disabled=!0):(T.targetHint.innerHTML="Each LUPMIS2 column is matched to a source field where possible. You can override any choice below.",T.btnSave.disabled=!1,T.btnSaveUpload.disabled=!1),Os()}function fn(n){if(!N||N._resolved)return;N._resolved=!0;const{targetType:e,mapping:t,onResult:r}=N;yo.hide(),r&&r({action:n,targetType:e,mapping:e==="other"?null:{...t}}),N=null}function Ds(n){if(Fs(),!T.root){console.warn("[ImportModal] Modal element missing — calling onResult with cancel"),n.onResult?.({action:"cancel"});return}const e=n.fc,t=e?.features?.length??0,r=Is(e);N={importId:n.importId,filename:n.filename,fc:e,sourceFields:Cs(e),sourceSamples:As(e),targetType:r,mapping:wr(e,r),onResult:n.onResult,_resolved:!1},T.filename.textContent=n.filename||"imported dataset",T.summary.textContent=`— ${t} feature${t===1?"":"s"}`,T.target.value=N.targetType,_r(),yo=ht.getOrCreateInstance(T.root),yo.show()}function Tt(n){return String(n).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function vt(n){return Tt(n)}function Rs(n){let e="";for(const t of String(n)){const r=t.codePointAt(0);r>=97&&r<=122?e+=String.fromCodePoint(r-97+119834):r>=65&&r<=90?e+=String.fromCodePoint(r-65+119808):r>=48&&r<=57?e+=String.fromCodePoint(r-48+120782):e+=t}return e}var ot={},Jt={},hn;function Co(){return hn||(hn=1,Jt.geometries={NULL:0,POINT:1,POLYLINE:3,POLYGON:5,MULTIPOINT:8,POINTZ:11,POLYLINEZ:13,POLYGONZ:15,MULTIPOINTZ:18,POINTM:21,POLYLINEM:23,POLYGONM:25,MULTIPOINTM:28,MULTIPATCH:31}),Jt}var Zt={},Qt,gn;function vr(){return gn||(gn=1,Qt={C:254,L:1,D:8,N:18,M:18,F:18,B:8}),Qt}var nt={},mn;function Ns(){return mn||(mn=1,nt.lpad=function(e,t,r){for(;e.length"u"||c)&&(i[l]=s[l])}return i}function o(i){var s={},l=[];for(var c in i)s[c]=i[c]===null?"null":typeof i[c];for(var d in s){var u=e[s[d]];u&&l.push({name:d,type:u,size:n[u]})}return l}function a(i){return i.reduce(function(s,l){return s+l.size},1)}return rt}var eo,bn;function Bs(){if(bn)return eo;bn=1,vr();var n=Ns(),e=$s();return eo=function(r,o){var a=o||e.multi(r),i=32*a.length+1,s=e.bytesPer(a),l=new ArrayBuffer(i+32+s*r.length+1),c=new Date,d=new DataView(l);d.setUint8(0,3),d.setUint8(1,c.getFullYear()-1900),d.setUint8(2,c.getMonth()),d.setUint8(3,c.getDate()),d.setUint32(4,r.length,!0);var u=i+32;return d.setUint16(8,u,!0),d.setUint16(10,s,!0),d.setInt8(32+i-1,13),a.forEach(function(f,p){f.name.split("").slice(0,8).forEach(function(h,g){d.setInt8(32+p*32+g,h.charCodeAt(0))}),d.setInt8(32+p*32+11,f.type.charCodeAt(0)),d.setInt8(32+p*32+16,f.size),f.type=="N"&&d.setInt8(32+p*32+17,3)}),offset=i+32,r.forEach(function(f,p){d.setUint8(offset,32),offset++,a.forEach(function(h){var g=f[h.name];switch((g===null||typeof g>"u")&&(g=""),h.type){case"L":d.setUint8(offset,g?84:70),offset++;break;case"D":offset=n.writeField(d,8,n.lpad(g.toString(),8," "),offset);break;case"N":offset=n.writeField(d,h.size,n.lpad(g.toString(),h.size," ").substr(0,18),offset);break;case"C":offset=n.writeField(d,h.size,n.rpad(g.toString(),h.size," "),offset);break;default:throw new Error("Unknown field type")}})}),d.setUint8(offset,26),d},eo}var wn;function Gs(){return wn||(wn=1,Zt.structure=Bs()),Zt}var to,_n;function Er(){return _n||(_n=1,to='GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'),to}var at={},vn;function Ao(){return vn||(vn=1,at.enlarge=function(e,t){return t[0]e.xmax&&(e.xmax=t[0]),t[1]e.ymax&&(e.ymax=t[1]),e},at.enlargeExtent=function(e,t){return t.xmax>e.xmax&&(e.xmax=t.xmax),t.xmine.ymax&&(e.ymax=t.ymax),t.yminr.getGeometry()).map(r=>{const o=r.clone(),a=kr(r.getProperties()),i={};for(const[l,c]of Object.entries(e||{}))c&&Object.prototype.hasOwnProperty.call(a,l)&&(i[c]=a[l]);o.setProperties(i,!0);const s=Object.values(e||{}).find(l=>l&&i[l]!=null&&i[l]!=="");return s&&o.set("name",String(i[s])),o});return new Gn({extractStyles:!1}).writeFeatures(t,{dataProjection:Tr,featureProjection:Lr})}async function Js(n,e){const t=Zs(n);return new Promise((r,o)=>{try{Ks.download(t,{folder:e,outputType:"blob",compression:"DEFLATE",types:{point:`${e}_point`,polygon:`${e}_polygon`,polyline:`${e}_line`}}),r()}catch(a){o(a)}})}function Zs(n){const e=new Set;for(const o of n.features)for(const a of Object.keys(o.properties||{}))e.add(a);const t=new Set,r={};for(const o of e){let a=String(o).replace(/[^A-Za-z0-9_]+/g,"_").slice(0,10)||"field",i=a,s=1;for(;t.has(i);){const l=String(s++);i=a.slice(0,Math.max(1,10-l.length))+l}t.add(i),r[o]=i}return{type:"FeatureCollection",features:n.features.map(o=>{const a={};for(const[i,s]of Object.entries(o.properties||{}))a[r[i]]=s;return{type:"Feature",geometry:o.geometry,properties:a}})}}function Mn(n,e){const t=URL.createObjectURL(n),r=document.createElement("a");r.href=t,r.download=e,document.body.appendChild(r),r.click(),document.body.removeChild(r),setTimeout(()=>URL.revokeObjectURL(t),1e3)}const A={};let bo=null,Se=null;function Qs(){A.root||(A.root=document.getElementById("exportGisModal"),A.summary=document.getElementById("export-gis-summary"),A.filename=document.getElementById("export-gis-filename"),A.tbody=document.getElementById("export-gis-fields-tbody"),A.fmtHint=document.getElementById("export-gis-format-hint"),A.fieldsWrap=document.getElementById("export-gis-fields-wrap"),A.btnGo=document.getElementById("export-gis-go"),A.fmtInputs=Array.from(document.querySelectorAll('input[name="export-gis-format"]')),A.root.dataset.wired||(A.root.dataset.wired="1",A.fmtInputs.forEach(n=>n.addEventListener("change",Pr)),A.btnGo.addEventListener("click",ol)))}function el(n){const e=[];for(const t of n.parcelFeatures||[])e.push(ao(t,"Parcels"));for(const t of n.zoneFeatures||[])e.push(ao(t,"Zones"));for(const[t,r]of Object.entries(n.otherByLayer||{}))for(const o of r)e.push(ao(o,t));return e}function ao(n,e){const t=n.clone();return t.set("_source",e),t}function tl(n){const e=new Set(["geometry","_layerType"]),t=new Map;for(const o of n)for(const a of Object.keys(o.getProperties()||{}))e.has(a)||t.has(a)||t.set(a,!0);t.delete("_source");const r=Array.from(t.keys());return r.push("_source"),r}function Ir(){const n=Rt();A.tbody.innerHTML=Se.keys.map(e=>{const t=Se.rename[e]??e,o=n==="shp"&&t.length>10?`
+ `}).join(""),T.tbody.querySelectorAll(".import-field-map").forEach(o=>{o.addEventListener("change",a=>{const i=a.target.dataset.col;N.mapping[i]=a.target.value||null})})}function _r(){const n=T.target.value;N.targetType=n,N.mapping=wr(N.fc,n),n==="other"?(T.targetHint.innerHTML="This dataset will be visible on the map but cannot be uploaded to the database. You can change the type later.",T.btnSave.disabled=!1,T.btnSaveUpload.disabled=!0):(T.targetHint.innerHTML="Each LUPMIS2 column is matched to a source field where possible. You can override any choice below.",T.btnSave.disabled=!1,T.btnSaveUpload.disabled=!1),Os()}function fn(n){if(!N||N._resolved)return;N._resolved=!0;const{targetType:e,mapping:t,onResult:r}=N;yo.hide(),r&&r({action:n,targetType:e,mapping:e==="other"?null:{...t}}),N=null}function Ds(n){if(Fs(),!T.root){console.warn("[ImportModal] Modal element missing — calling onResult with cancel"),n.onResult?.({action:"cancel"});return}const e=n.fc,t=e?.features?.length??0,r=Is(e);N={importId:n.importId,filename:n.filename,fc:e,sourceFields:Cs(e),sourceSamples:As(e),targetType:r,mapping:wr(e,r),onResult:n.onResult,_resolved:!1},T.filename.textContent=n.filename||"imported dataset",T.summary.textContent=`— ${t} feature${t===1?"":"s"}`,T.target.value=N.targetType,_r(),yo=ht.getOrCreateInstance(T.root),yo.show()}function Tt(n){return String(n).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function vt(n){return Tt(n)}function Rs(n){let e="";for(const t of String(n)){const r=t.codePointAt(0);r>=97&&r<=122?e+=String.fromCodePoint(r-97+119834):r>=65&&r<=90?e+=String.fromCodePoint(r-65+119808):r>=48&&r<=57?e+=String.fromCodePoint(r-48+120782):e+=t}return e}var ot={},Jt={},hn;function Co(){return hn||(hn=1,Jt.geometries={NULL:0,POINT:1,POLYLINE:3,POLYGON:5,MULTIPOINT:8,POINTZ:11,POLYLINEZ:13,POLYGONZ:15,MULTIPOINTZ:18,POINTM:21,POLYLINEM:23,POLYGONM:25,MULTIPOINTM:28,MULTIPATCH:31}),Jt}var Zt={},Qt,gn;function vr(){return gn||(gn=1,Qt={C:254,L:1,D:8,N:18,M:18,F:18,B:8}),Qt}var nt={},mn;function Ns(){return mn||(mn=1,nt.lpad=function(e,t,r){for(;e.length"u"||c)&&(i[l]=s[l])}return i}function o(i){var s={},l=[];for(var c in i)s[c]=i[c]===null?"null":typeof i[c];for(var d in s){var u=e[s[d]];u&&l.push({name:d,type:u,size:n[u]})}return l}function a(i){return i.reduce(function(s,l){return s+l.size},1)}return rt}var eo,bn;function Bs(){if(bn)return eo;bn=1,vr();var n=Ns(),e=$s();return eo=function(r,o){var a=o||e.multi(r),i=32*a.length+1,s=e.bytesPer(a),l=new ArrayBuffer(i+32+s*r.length+1),c=new Date,d=new DataView(l);d.setUint8(0,3),d.setUint8(1,c.getFullYear()-1900),d.setUint8(2,c.getMonth()),d.setUint8(3,c.getDate()),d.setUint32(4,r.length,!0);var u=i+32;return d.setUint16(8,u,!0),d.setUint16(10,s,!0),d.setInt8(32+i-1,13),a.forEach(function(f,p){f.name.split("").slice(0,8).forEach(function(h,g){d.setInt8(32+p*32+g,h.charCodeAt(0))}),d.setInt8(32+p*32+11,f.type.charCodeAt(0)),d.setInt8(32+p*32+16,f.size),f.type=="N"&&d.setInt8(32+p*32+17,3)}),offset=i+32,r.forEach(function(f,p){d.setUint8(offset,32),offset++,a.forEach(function(h){var g=f[h.name];switch((g===null||typeof g>"u")&&(g=""),h.type){case"L":d.setUint8(offset,g?84:70),offset++;break;case"D":offset=n.writeField(d,8,n.lpad(g.toString(),8," "),offset);break;case"N":offset=n.writeField(d,h.size,n.lpad(g.toString(),h.size," ").substr(0,18),offset);break;case"C":offset=n.writeField(d,h.size,n.rpad(g.toString(),h.size," "),offset);break;default:throw new Error("Unknown field type")}})}),d.setUint8(offset,26),d},eo}var wn;function Gs(){return wn||(wn=1,Zt.structure=Bs()),Zt}var to,_n;function Er(){return _n||(_n=1,to='GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'),to}var at={},vn;function Ao(){return vn||(vn=1,at.enlarge=function(e,t){return t[0]e.xmax&&(e.xmax=t[0]),t[1]e.ymax&&(e.ymax=t[1]),e},at.enlargeExtent=function(e,t){return t.xmax>e.xmax&&(e.xmax=t.xmax),t.xmine.ymax&&(e.ymax=t.ymax),t.yminr.getGeometry()).map(r=>{const o=r.clone(),a=kr(r.getProperties()),i={};for(const[l,c]of Object.entries(e||{}))c&&Object.prototype.hasOwnProperty.call(a,l)&&(i[c]=a[l]);o.setProperties(i,!0);const s=Object.values(e||{}).find(l=>l&&i[l]!=null&&i[l]!=="");return s&&o.set("name",String(i[s])),o});return new Gn({extractStyles:!1}).writeFeatures(t,{dataProjection:Tr,featureProjection:Lr})}async function Js(n,e){const t=Zs(n);return new Promise((r,o)=>{try{Vs.download(t,{folder:e,outputType:"blob",compression:"DEFLATE",types:{point:`${e}_point`,polygon:`${e}_polygon`,polyline:`${e}_line`}}),r()}catch(a){o(a)}})}function Zs(n){const e=new Set;for(const o of n.features)for(const a of Object.keys(o.properties||{}))e.add(a);const t=new Set,r={};for(const o of e){let a=String(o).replace(/[^A-Za-z0-9_]+/g,"_").slice(0,10)||"field",i=a,s=1;for(;t.has(i);){const l=String(s++);i=a.slice(0,Math.max(1,10-l.length))+l}t.add(i),r[o]=i}return{type:"FeatureCollection",features:n.features.map(o=>{const a={};for(const[i,s]of Object.entries(o.properties||{}))a[r[i]]=s;return{type:"Feature",geometry:o.geometry,properties:a}})}}function Mn(n,e){const t=URL.createObjectURL(n),r=document.createElement("a");r.href=t,r.download=e,document.body.appendChild(r),r.click(),document.body.removeChild(r),setTimeout(()=>URL.revokeObjectURL(t),1e3)}const A={};let bo=null,Se=null;function Qs(){A.root||(A.root=document.getElementById("exportGisModal"),A.summary=document.getElementById("export-gis-summary"),A.filename=document.getElementById("export-gis-filename"),A.tbody=document.getElementById("export-gis-fields-tbody"),A.fmtHint=document.getElementById("export-gis-format-hint"),A.fieldsWrap=document.getElementById("export-gis-fields-wrap"),A.btnGo=document.getElementById("export-gis-go"),A.fmtInputs=Array.from(document.querySelectorAll('input[name="export-gis-format"]')),A.root.dataset.wired||(A.root.dataset.wired="1",A.fmtInputs.forEach(n=>n.addEventListener("change",Pr)),A.btnGo.addEventListener("click",ol)))}function el(n){const e=[];for(const t of n.parcelFeatures||[])e.push(ao(t,"Parcels"));for(const t of n.zoneFeatures||[])e.push(ao(t,"Zones"));for(const[t,r]of Object.entries(n.otherByLayer||{}))for(const o of r)e.push(ao(o,t));return e}function ao(n,e){const t=n.clone();return t.set("_source",e),t}function tl(n){const e=new Set(["geometry","_layerType"]),t=new Map;for(const o of n)for(const a of Object.keys(o.getProperties()||{}))e.has(a)||t.has(a)||t.set(a,!0);t.delete("_source");const r=Array.from(t.keys());return r.push("_source"),r}function Ir(){const n=Rt();A.tbody.innerHTML=Se.keys.map(e=>{const t=Se.rename[e]??e,o=n==="shp"&&t.length>10?`
${wo(t.length)} characters — Shapefile will truncate / rename.
`:"";return` @@ -774,7 +774,7 @@ This only affects the current session — built-in layers cannot be removed.`))r ${o} - `}).join(""),A.tbody.querySelectorAll(".export-field-rename").forEach(e=>{e.addEventListener("input",t=>{const r=t.target.dataset.src;Se.rename[r]=t.target.value,Rt()==="shp"&&Ir()})})}function Pr(){const n=Rt();A.fmtHint.innerHTML={geojson:"GeoJSON keeps all attributes as-is and is the safest default.",shp:"Shapefile attribute names are limited to 10 characters and alphanumeric/underscore only. Over-length names will be truncated (collisions are auto-numbered). One file per geometry type is written, all zipped into one download.",kml:"KML preserves attribute names; the first non-empty renamed field is used as each feature's in Google Earth.",pdf:"A one-page summary report of the analysis figures — not the feature geometry. Field renaming does not apply."}[n];const e=n==="pdf";A.fieldsWrap&&A.fieldsWrap.classList.toggle("d-none",e),Ir()}function Rt(){return A.fmtInputs.find(n=>n.checked)?.value||"geojson"}async function ol(){const n=Rt(),e=(A.filename.value||"export").replace(/[^A-Za-z0-9_\-]+/g,"_");A.btnGo.disabled=!0;try{if(n==="pdf"){const t=Se.pdfRows||[];if(t.length===0)throw new Error("No summary figures available for a PDF report.");const{exportAnalysisPDF:r}=await Le(async()=>{const{exportAnalysisPDF:o}=await import("./pdf-export-BONdNTHD.js");return{exportAnalysisPDF:o}},__vite__mapDeps([4,5,3,0,1,2]));await r({title:Se.title||"Analysis",rows:t})}else await Vs({features:Se.features,rename:Se.rename,format:n,filenameBase:e});bo.hide()}catch(t){console.error("[ExportGIS] failed:",t),alert("Export failed: "+t.message)}finally{A.btnGo.disabled=!1}}function nl(n){if(Qs(),!A.root){console.warn("[ExportGIS] Modal missing from DOM");return}const e=el(n);if(e.length===0){alert("No intersecting features to export.");return}const t=tl(e),r=Object.fromEntries(t.map(a=>[a,a]));Se={features:e,keys:t,rename:r,pdfRows:n.pdfRows||[],title:n.title},A.summary.textContent=`${e.length} feature${e.length===1?"":"s"} intersecting the ${n.kind==="circle"?"circle":"area"}`,A.filename.value=n.kind==="circle"?"circle_analysis":"area_analysis";const o=document.getElementById("export-gis-fmt-geojson");o&&(o.checked=!0),Pr(),bo=ht.getOrCreateInstance(A.root),bo.show()}function wo(n){return String(n).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function Cn(n){return wo(n)}let io=null;async function An(){if(!io){const n=await Le(()=>import("./shpjs-Doisr2ug.js").then(e=>e.i),__vite__mapDeps([6,3]));io=n.default||n}return io}function Fo(n){const e=document.getElementById("import-spinner-overlay"),t=document.getElementById("import-spinner-filename");e&&(t&&(t.textContent=n||""),e.classList.remove("d-none"),e.classList.add("d-flex"))}function de(){const n=document.getElementById("import-spinner-overlay");n&&(n.classList.add("d-none"),n.classList.remove("d-flex"))}let E=null,ie=null,se=null,so=null;const _o=typeof window<"u"&&window.LUPMIS_EMBED||null,qe=!!(_o&&_o.mode==="permit");let q=qe?"embed-permit":"none";function rl(){const n=typeof window<"u"?window.LUPMIS_SESSION:null;if(!n||typeof n!="object")return!1;const e=n.district_id;if(e!=null&&String(e).length>0)return!1;console.warn("[App] Authenticated user has no district assigned; halting init.");const t=document.createElement("div");t.id="no-district-overlay",t.setAttribute("role","alertdialog"),t.setAttribute("aria-modal","true"),t.style.cssText="position:fixed;inset:0;z-index:99999;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,0.98);padding:24px;";const r=n.full_name||n.username||"You";return t.innerHTML=` + `}).join(""),A.tbody.querySelectorAll(".export-field-rename").forEach(e=>{e.addEventListener("input",t=>{const r=t.target.dataset.src;Se.rename[r]=t.target.value,Rt()==="shp"&&Ir()})})}function Pr(){const n=Rt();A.fmtHint.innerHTML={geojson:"GeoJSON keeps all attributes as-is and is the safest default.",shp:"Shapefile attribute names are limited to 10 characters and alphanumeric/underscore only. Over-length names will be truncated (collisions are auto-numbered). One file per geometry type is written, all zipped into one download.",kml:"KML preserves attribute names; the first non-empty renamed field is used as each feature's in Google Earth.",pdf:"A one-page summary report of the analysis figures — not the feature geometry. Field renaming does not apply."}[n];const e=n==="pdf";A.fieldsWrap&&A.fieldsWrap.classList.toggle("d-none",e),Ir()}function Rt(){return A.fmtInputs.find(n=>n.checked)?.value||"geojson"}async function ol(){const n=Rt(),e=(A.filename.value||"export").replace(/[^A-Za-z0-9_\-]+/g,"_");A.btnGo.disabled=!0;try{if(n==="pdf"){const t=Se.pdfRows||[];if(t.length===0)throw new Error("No summary figures available for a PDF report.");const{exportAnalysisPDF:r}=await Le(async()=>{const{exportAnalysisPDF:o}=await import("./pdf-export-BONdNTHD.js");return{exportAnalysisPDF:o}},__vite__mapDeps([4,5,3,0,1,2]));await r({title:Se.title||"Analysis",rows:t})}else await Ks({features:Se.features,rename:Se.rename,format:n,filenameBase:e});bo.hide()}catch(t){console.error("[ExportGIS] failed:",t),alert("Export failed: "+t.message)}finally{A.btnGo.disabled=!1}}function nl(n){if(Qs(),!A.root){console.warn("[ExportGIS] Modal missing from DOM");return}const e=el(n);if(e.length===0){alert("No intersecting features to export.");return}const t=tl(e),r=Object.fromEntries(t.map(a=>[a,a]));Se={features:e,keys:t,rename:r,pdfRows:n.pdfRows||[],title:n.title},A.summary.textContent=`${e.length} feature${e.length===1?"":"s"} intersecting the ${n.kind==="circle"?"circle":"area"}`,A.filename.value=n.kind==="circle"?"circle_analysis":"area_analysis";const o=document.getElementById("export-gis-fmt-geojson");o&&(o.checked=!0),Pr(),bo=ht.getOrCreateInstance(A.root),bo.show()}function wo(n){return String(n).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function Cn(n){return wo(n)}let io=null;async function An(){if(!io){const n=await Le(()=>import("./shpjs-Doisr2ug.js").then(e=>e.i),__vite__mapDeps([6,3]));io=n.default||n}return io}function Fo(n){const e=document.getElementById("import-spinner-overlay"),t=document.getElementById("import-spinner-filename");e&&(t&&(t.textContent=n||""),e.classList.remove("d-none"),e.classList.add("d-flex"))}function de(){const n=document.getElementById("import-spinner-overlay");n&&(n.classList.add("d-none"),n.classList.remove("d-flex"))}let E=null,ie=null,se=null,so=null;const _o=typeof window<"u"&&window.LUPMIS_EMBED||null,qe=!!(_o&&_o.mode==="permit");let q=qe?"embed-permit":"none";function rl(){const n=typeof window<"u"?window.LUPMIS_SESSION:null;if(!n||typeof n!="object")return!1;const e=n.district_id;if(e!=null&&String(e).length>0)return!1;console.warn("[App] Authenticated user has no district assigned; halting init.");const t=document.createElement("div");t.id="no-district-overlay",t.setAttribute("role","alertdialog"),t.setAttribute("aria-modal","true"),t.style.cssText="position:fixed;inset:0;z-index:99999;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,0.98);padding:24px;";const r=n.full_name||n.username||"You";return t.innerHTML=`
@@ -795,7 +795,7 @@ This only affects the current session — built-in layers cannot be removed.`))r border-radius:8px;padding:10px 18px;font-weight:600;cursor:pointer;"> Return to LUSPA portal -
`,document.body.appendChild(t),t.querySelector("#no-district-portal-btn")?.addEventListener("click",()=>{window.location.href="https://lupmis4luspa.org/"}),!0}async function Fn(){if(console.log("[App] Initializing..."),rl())return;await zi({installButton:"#install-btn",offlineIndicator:"#offline-indicator",autoRegisterSW:!0});const n=localStorage.getItem("default-basemap")||"topo";E=new Mi("map",{center:[-1.5,7.5],zoom:7,basemap:n}),ie=new Ci(E.getMap()),Gl(),ie.onMeasureComplete(t=>{console.log("[MapTools] Measurement complete:",t),t.type==="polygon"&&t.coordinate&&t.feature?.get("_layerType")!=="measure_area"&&E?.showDrawnPolygonPopup(t.feature,t.coordinate)}),qe&&(so=Ls({mapView:E,embedConfig:_o})),E.onClick((t,r,o,a)=>{if(qe||(console.log("[MapClick] Clicked at:",t.toFixed(4),r.toFixed(4)),console.log("[MapClick] currentMode =",q),q==="draw"||q.startsWith("measure")))return;let i=null;if(E.getMap().forEachFeatureAtPixel(a.pixel,l=>{if(l.get("_layerType")==="parcel")return i=l,!0}),i){console.log("[MapClick] Clicked on parcel → Edit Attributes"),E.showParcelEditPopup(i,a.coordinate);return}let s=null;if(E.getMap().forEachFeatureAtPixel(a.pixel,l=>{if(l.get("_layerType")==="upn_grid")return s=l,!0}),s){console.log("[MapClick] Clicked on UPN-grid cell → Info popup"),E.showInfoPopup(s,a.coordinate,{title:"UPN Grid Cell",color:"#7c3aed"});return}q==="none"&&(o?(console.log("[MapClick] Clicked on marker:",o.getId()),E.selectMarker(o),il(o)):E.clearSelection())}),E.onDblClick((t,r,o,a)=>{if(qe||!o)return;const i=o.get("_layerType");if(console.log("[App] Double-click on feature, _layerType:",i||"none"),i==="measure_circle")E.showCircleIntersectionPopup(o,a.coordinate);else{if(i==="measure_circle_radius")return;i==="measure_area"?E.showAreaIntersectionPopup(o,a.coordinate):i==="collector_zone"?E.showInfoPopup(o,a.coordinate,{title:"Zone Info",color:"#7c3aed"}):i==="parcel"?E.showInfoPopup(o,a.coordinate,{title:"Parcel Info",color:"#0ea5e9"}):E.showInfoPopup(o,a.coordinate,{title:"Feature Info",color:"#e11d48"})}}),E.onDrawnPoint(t=>{qe||(console.log("[App] Point drawn → Add Location popup"),E.clearSelection(),E.showAddLocationPopup(t))}),E.onAddLocation(async t=>{console.log("[App] Add location from map popup:",t);try{const r=await wa(t.name,t.lon,t.lat,{description:t.description||null,category:t.category||"default"});console.log("[App] Location added:",t.name,"id:",r.id),await lo(),E?.zoomTo(t.lon,t.lat,14),r.id&&E?.selectMarker(r.id),ge("Location added successfully")}catch(r){console.error("[App] Failed to add location:",r),O("Failed to add location: "+r.message)}}),E.onParcelEdit(async(t,r)=>{const o=r.id||r.parcelid||r.parcel_id;if(console.log("[App] Parcel edit saved:",o,r),!o){console.warn("[App] No parcel ID found in updated properties — skipping local save");return}try{await Oa(o,r),ge("Parcel updated locally")}catch(a){console.error("[App] Failed to save parcel update:",a),O("Failed to save parcel: "+a.message)}});const e=new So;E.onDrawnPolygonSave(async(t,r)=>{console.log("[App] Drawn polygon attributes saved:",r);try{const o=e.writeGeometry(t.getGeometry(),{dataProjection:"EPSG:4326",featureProjection:"EPSG:3857"}),a=await Da(o,r);console.log("[App] New parcel inserted with id:",a.id),ge("New parcel saved (pending verification)")}catch(o){console.error("[App] Failed to save new parcel:",o),O("Failed to save parcel: "+o.message)}}),E.onFeatureModified(async t=>{const r=t.get("_externalImportId"),o=t.get("_clientUuid");if(!(r==null||!o))try{const a=e.writeGeometry(t.getGeometry(),{dataProjection:"EPSG:4326",featureProjection:"EPSG:3857"});await Ma(o,a),console.log("[App] Imported feature geometry updated in staging:",o)}catch(a){console.warn("[App] Failed to persist imported-feature edit:",a),O("Could not save the edit locally: "+a.message)}});try{console.log("[App] Initializing database..."),await ba(),console.log("[App] Database ready"),await yl();const t=await ko();console.log("[App] Database status:",t),V()&&(await ns()||(console.warn("[App] API server unreachable — using local data only"),Ro("Server not responding — loading cached data."))),await Tl(),E?.initEditBar(),bl(),wl(),_l(),vl(),qe&&so&&se&&(se.setVisible(!0),so.attachParcelsLayer(se)),El(),xl(),Sl(),Ll()}catch(t){console.error("[App] Database initialization failed:",t),O("Failed to initialize database. Please refresh the page.");return}al(),await lo(),ya(t=>{if(console.log("[App] Database change:",t),t.table==="locations"&&!t.local&&lo(),t.table==="parcels"){const r=document.getElementById("local-data-stats");r&&!r.classList.contains("d-none")&&$t()}}),pr(t=>{t?console.log("[App] Working offline - data will sync when back online"):(console.log("[App] Back online - syncing data..."),kl())}),jl(),ql(),zl(),Ul(),Hl(),Wl(),Kl(),Vl(),console.log("[App] Initialized successfully")}function al(){console.log("[initUI] Starting UI initialization..."),Bl();const n=document.getElementById("export-btn");n&&n.addEventListener("click",dl);const e=document.getElementById("local-data-btn");e&&e.addEventListener("click",()=>$t());const t=document.getElementById("import-shp-btn"),r=document.getElementById("shp-file-input");t&&r&&(t.addEventListener("click",()=>r.click()),r.addEventListener("change",Or));const o=document.getElementById("import-geojson-btn"),a=document.getElementById("geojson-file-input");o&&a&&(o.addEventListener("click",()=>a.click()),a.addEventListener("change",Dr));const i=document.getElementById("import-kml-btn"),s=document.getElementById("kml-file-input");i&&s&&(i.addEventListener("click",()=>s.click()),s.addEventListener("change",Rr)),Nl();const l=document.getElementById("exportGeoJSON-btn");l&&l.addEventListener("click",ul);const c=document.getElementById("status-btn");c&&c.addEventListener("click",pl);const d=document.getElementById("fit-btn");d&&d.addEventListener("click",()=>E?.fitToMarkers());const u=document.getElementById("dock-btn-measure-circle"),f=document.getElementById("dock-btn-measure-line"),p=document.getElementById("dock-btn-measure-area"),h=document.getElementById("dock-btn-draw"),g=document.getElementById("dock-btn-clear"),m=document.getElementById("dock-btn-analysis");m&&m.addEventListener("click",async()=>{if(E)try{const{openAnalysisModal:_}=await Le(async()=>{const{openAnalysisModal:S}=await import("./analysis-modal-Bt1CVsnO.js");return{openAnalysisModal:S}},__vite__mapDeps([7,0,1,2,3,8,9,10,11,12,6]));_(E)}catch(_){console.error("[App] Could not load the Analysis panel:",_),O("Analysis tools could not be loaded — check your connection and try again.")}}),console.log("[initUI] Buttons found:",{measureCircle:!!u,measureLine:!!f,measureArea:!!p,draw:!!h,clear:!!g});const y=[u,f,p,h],b=(_,S)=>{switch(console.log("[setMode] Changing mode from",q,"to",_),q=_,console.log("[setMode] currentMode is now:",q),y.forEach(L=>{L&&L.classList.toggle("active",L===S)}),ie?.deactivate(),_!=="draw"&&E?.setEditMode(!1),_!=="draw"&&E?.hideAddLocationPopup(),_){case"measureCircle":ie?.startCircleMeasure();break;case"measureLine":ie?.startLineMeasure();break;case"measureArea":ie?.startAreaMeasure();break;case"draw":E?.setEditMode(!0);break}};u&&u.addEventListener("click",()=>{console.log("[Button] Circle clicked, currentMode is:",q),q==="measureCircle"?b("none",null):b("measureCircle",u)}),f&&f.addEventListener("click",()=>{console.log("[Button] Line clicked, currentMode is:",q),q==="measureLine"?b("none",null):b("measureLine",f)}),p&&p.addEventListener("click",()=>{console.log("[Button] Area clicked, currentMode is:",q),q==="measureArea"?b("none",null):b("measureArea",p)}),h&&h.addEventListener("click",()=>{console.log("[Button] Draw clicked, currentMode is:",q),q==="draw"?b("none",null):b("draw",h)}),g&&g.addEventListener("click",()=>{if(ie?.clearMeasurements(),q.startsWith("measure"))switch(ie?.deactivate(),q){case"measureCircle":ie?.startCircleMeasure();break;case"measureLine":ie?.startLineMeasure();break;case"measureArea":ie?.startAreaMeasure();break}})}async function lo(){try{console.log("[App] Loading locations...");const n=await Qn();console.log("[App] Locations loaded:",n),sl(n),E&&(E.clearMarkers(),n.length>0&&(E.addMarkers(n),console.log("[App] Added",n.length,"markers to map")));const e=document.getElementById("location-count");e&&(e.textContent=n.length)}catch(n){console.error("[App] Failed to load locations:",n)}}function il(n){const e=n.get("name"),t=n.get("description"),r=n.get("category"),o=n.get("lon")||n.get("longitude"),a=n.get("lat")||n.get("latitude");console.log("[App] Selected location:",{name:e,description:t,category:r,lon:o,lat:a})}function sl(n){const e=document.getElementById("locations-list");if(!e)return;const t=document.getElementById("location-count-mobile");if(t&&(t.textContent=n.length),n.length===0){e.innerHTML=` +
`,document.body.appendChild(t),t.querySelector("#no-district-portal-btn")?.addEventListener("click",()=>{window.location.href="https://lupmis4luspa.org/"}),!0}async function Fn(){if(console.log("[App] Initializing..."),rl())return;await zi({installButton:"#install-btn",offlineIndicator:"#offline-indicator",autoRegisterSW:!0});const n=localStorage.getItem("default-basemap")||"topo";E=new Mi("map",{center:[-1.5,7.5],zoom:7,basemap:n}),ie=new Ci(E.getMap()),Gl(),ie.onMeasureComplete(t=>{console.log("[MapTools] Measurement complete:",t),t.type==="polygon"&&t.coordinate&&t.feature?.get("_layerType")!=="measure_area"&&E?.showDrawnPolygonPopup(t.feature,t.coordinate)}),qe&&(so=Ls({mapView:E,embedConfig:_o})),E.onClick((t,r,o,a)=>{if(qe||(console.log("[MapClick] Clicked at:",t.toFixed(4),r.toFixed(4)),console.log("[MapClick] currentMode =",q),q==="draw"||q.startsWith("measure")))return;let i=null;if(E.getMap().forEachFeatureAtPixel(a.pixel,l=>{if(l.get("_layerType")==="parcel")return i=l,!0}),i){console.log("[MapClick] Clicked on parcel → Edit Attributes"),E.showParcelEditPopup(i,a.coordinate);return}let s=null;if(E.getMap().forEachFeatureAtPixel(a.pixel,l=>{if(l.get("_layerType")==="upn_grid")return s=l,!0}),s){console.log("[MapClick] Clicked on UPN-grid cell → Info popup"),E.showInfoPopup(s,a.coordinate,{title:"UPN Grid Cell",color:"#7c3aed"});return}q==="none"&&(o?(console.log("[MapClick] Clicked on marker:",o.getId()),E.selectMarker(o),il(o)):E.clearSelection())}),E.onDblClick((t,r,o,a)=>{if(qe||!o)return;const i=o.get("_layerType");if(console.log("[App] Double-click on feature, _layerType:",i||"none"),i==="measure_circle")E.showCircleIntersectionPopup(o,a.coordinate);else{if(i==="measure_circle_radius")return;i==="measure_area"?E.showAreaIntersectionPopup(o,a.coordinate):i==="collector_zone"?E.showInfoPopup(o,a.coordinate,{title:"Zone Info",color:"#7c3aed"}):i==="parcel"?E.showInfoPopup(o,a.coordinate,{title:"Parcel Info",color:"#0ea5e9"}):E.showInfoPopup(o,a.coordinate,{title:"Feature Info",color:"#e11d48"})}}),E.onDrawnPoint(t=>{qe||(console.log("[App] Point drawn → Add Location popup"),E.clearSelection(),E.showAddLocationPopup(t))}),E.onAddLocation(async t=>{console.log("[App] Add location from map popup:",t);try{const r=await wa(t.name,t.lon,t.lat,{description:t.description||null,category:t.category||"default"});console.log("[App] Location added:",t.name,"id:",r.id),await lo(),E?.showMarkers(),E?.zoomTo(t.lon,t.lat,14),r.id&&E?.selectMarker(r.id),ge("Location added successfully")}catch(r){console.error("[App] Failed to add location:",r),O("Failed to add location: "+r.message)}}),E.onParcelEdit(async(t,r)=>{const o=r.id||r.parcelid||r.parcel_id;if(console.log("[App] Parcel edit saved:",o,r),!o){console.warn("[App] No parcel ID found in updated properties — skipping local save");return}try{await Oa(o,r),ge("Parcel updated locally")}catch(a){console.error("[App] Failed to save parcel update:",a),O("Failed to save parcel: "+a.message)}});const e=new So;E.onDrawnPolygonSave(async(t,r)=>{console.log("[App] Drawn polygon attributes saved:",r);try{const o=e.writeGeometry(t.getGeometry(),{dataProjection:"EPSG:4326",featureProjection:"EPSG:3857"}),a=await Da(o,r);console.log("[App] New parcel inserted with id:",a.id),ge("New parcel saved (pending verification)")}catch(o){console.error("[App] Failed to save new parcel:",o),O("Failed to save parcel: "+o.message)}}),E.onFeatureModified(async t=>{const r=t.get("_externalImportId"),o=t.get("_clientUuid");if(!(r==null||!o))try{const a=e.writeGeometry(t.getGeometry(),{dataProjection:"EPSG:4326",featureProjection:"EPSG:3857"});await Ma(o,a),console.log("[App] Imported feature geometry updated in staging:",o)}catch(a){console.warn("[App] Failed to persist imported-feature edit:",a),O("Could not save the edit locally: "+a.message)}});try{console.log("[App] Initializing database..."),await ba(),console.log("[App] Database ready"),await yl();const t=await ko();console.log("[App] Database status:",t),K()&&(await ns()||(console.warn("[App] API server unreachable — using local data only"),Ro("Server not responding — loading cached data."))),await Tl(),E?.initEditBar(),bl(),wl(),_l(),vl(),qe&&so&&se&&(se.setVisible(!0),so.attachParcelsLayer(se)),El(),xl(),Sl(),Ll()}catch(t){console.error("[App] Database initialization failed:",t),O("Failed to initialize database. Please refresh the page.");return}al(),await lo(),ya(t=>{if(console.log("[App] Database change:",t),t.table==="locations"&&!t.local&&lo(),t.table==="parcels"){const r=document.getElementById("local-data-stats");r&&!r.classList.contains("d-none")&&$t()}}),pr(t=>{t?console.log("[App] Working offline - data will sync when back online"):(console.log("[App] Back online - syncing data..."),kl())}),jl(),ql(),zl(),Ul(),Hl(),Wl(),Vl(),Kl(),console.log("[App] Initialized successfully")}function al(){console.log("[initUI] Starting UI initialization..."),Bl();const n=document.getElementById("export-btn");n&&n.addEventListener("click",dl);const e=document.getElementById("local-data-btn");e&&e.addEventListener("click",()=>$t());const t=document.getElementById("import-shp-btn"),r=document.getElementById("shp-file-input");t&&r&&(t.addEventListener("click",()=>r.click()),r.addEventListener("change",Or));const o=document.getElementById("import-geojson-btn"),a=document.getElementById("geojson-file-input");o&&a&&(o.addEventListener("click",()=>a.click()),a.addEventListener("change",Dr));const i=document.getElementById("import-kml-btn"),s=document.getElementById("kml-file-input");i&&s&&(i.addEventListener("click",()=>s.click()),s.addEventListener("change",Rr)),Nl();const l=document.getElementById("exportGeoJSON-btn");l&&l.addEventListener("click",ul);const c=document.getElementById("status-btn");c&&c.addEventListener("click",pl);const d=document.getElementById("fit-btn");d&&d.addEventListener("click",()=>E?.fitToMarkers());const u=document.getElementById("dock-btn-measure-circle"),f=document.getElementById("dock-btn-measure-line"),p=document.getElementById("dock-btn-measure-area"),h=document.getElementById("dock-btn-draw"),g=document.getElementById("dock-btn-clear"),m=document.getElementById("dock-btn-analysis");m&&m.addEventListener("click",async()=>{if(E)try{const{openAnalysisModal:_}=await Le(async()=>{const{openAnalysisModal:S}=await import("./analysis-modal-_rBzLxQd.js");return{openAnalysisModal:S}},__vite__mapDeps([7,0,1,2,3,8,9,10,11,12,6]));_(E)}catch(_){console.error("[App] Could not load the Analysis panel:",_),O("Analysis tools could not be loaded — check your connection and try again.")}}),console.log("[initUI] Buttons found:",{measureCircle:!!u,measureLine:!!f,measureArea:!!p,draw:!!h,clear:!!g});const y=[u,f,p,h],b=(_,S)=>{switch(console.log("[setMode] Changing mode from",q,"to",_),q=_,console.log("[setMode] currentMode is now:",q),y.forEach(L=>{L&&L.classList.toggle("active",L===S)}),ie?.deactivate(),_!=="draw"&&E?.setEditMode(!1),_!=="draw"&&E?.hideAddLocationPopup(),_){case"measureCircle":ie?.startCircleMeasure();break;case"measureLine":ie?.startLineMeasure();break;case"measureArea":ie?.startAreaMeasure();break;case"draw":E?.setEditMode(!0);break}};u&&u.addEventListener("click",()=>{console.log("[Button] Circle clicked, currentMode is:",q),q==="measureCircle"?b("none",null):b("measureCircle",u)}),f&&f.addEventListener("click",()=>{console.log("[Button] Line clicked, currentMode is:",q),q==="measureLine"?b("none",null):b("measureLine",f)}),p&&p.addEventListener("click",()=>{console.log("[Button] Area clicked, currentMode is:",q),q==="measureArea"?b("none",null):b("measureArea",p)}),h&&h.addEventListener("click",()=>{console.log("[Button] Draw clicked, currentMode is:",q),q==="draw"?b("none",null):b("draw",h)}),g&&g.addEventListener("click",()=>{if(ie?.clearMeasurements(),q.startsWith("measure"))switch(ie?.deactivate(),q){case"measureCircle":ie?.startCircleMeasure();break;case"measureLine":ie?.startLineMeasure();break;case"measureArea":ie?.startAreaMeasure();break}})}async function lo(){try{console.log("[App] Loading locations...");const n=await Qn();console.log("[App] Locations loaded:",n),sl(n),E&&(E.clearMarkers(),n.length>0&&(E.addMarkers(n),console.log("[App] Added",n.length,"markers to map")));const e=document.getElementById("location-count");e&&(e.textContent=n.length)}catch(n){console.error("[App] Failed to load locations:",n)}}function il(n){const e=n.get("name"),t=n.get("description"),r=n.get("category"),o=n.get("lon")||n.get("longitude"),a=n.get("lat")||n.get("latitude");console.log("[App] Selected location:",{name:e,description:t,category:r,lon:o,lat:a})}function sl(n){const e=document.getElementById("locations-list");if(!e)return;const t=document.getElementById("location-count-mobile");if(t&&(t.textContent=n.length),n.length===0){e.innerHTML=`

No locations yet.

Click the map or fill the form above! @@ -812,7 +812,7 @@ This only affects the current session — built-in layers cannot be removed.`))r
${o.description?`${j(o.description)}`:""} - `}).join(""),e.querySelectorAll(".location-item").forEach(o=>{o.addEventListener("click",a=>{a.preventDefault();const i=parseFloat(o.dataset.lon),s=parseFloat(o.dataset.lat),l=parseInt(o.dataset.id);E?.zoomTo(i,s,14),E?.selectMarker(l)})})}async function $t(){const n=document.getElementById("local-data-stats"),e=document.getElementById("local-data-tbody"),t=document.getElementById("clear-all-cached-btn");if(!(!n||!e)){try{const r=await qa();e.innerHTML=r.map(o=>{const i=rr(o.name)?` `,e.appendChild(o)}),n.classList.remove("d-none"),n.querySelectorAll("[data-remove-idx]").forEach(t=>{t.addEventListener("click",()=>{Dl(Number(t.dataset.removeIdx))})}),n.querySelector("#remove-imported-layers")?.addEventListener("click",()=>{Rl()})}function Dl(n){if(n<0||n>=ue.length)return;const e=ue[n],t=E?.getOverlayGroup();t&&t.getLayers().remove(e),ue.splice(n,1),Do(),console.log("[FileImport] Removed layer:",e.get("title"))}function Rl(){const n=E?.getOverlayGroup();if(n)for(const e of ue)n.getLayers().remove(e);ue.length=0,Do(),console.log("[FileImport] All imported layers removed")}function Fr(n){const e={};for(const t of n){const r=t.name.split(".").pop().toLowerCase();e[r]=t}return e}async function Or(n){const e=n.target.files;if(!e||e.length===0)return;const t=200*1024*1024,r=Array.from(e).reduce((o,a)=>o+a.size,0);if(r>t){const o=(r/1048576).toFixed(0);oe(`Files too large (${o} MB total). Maximum supported size is 200 MB.`),n.target.value="";return}Fo(e[0]?.name||"shapefile");try{let o,a;const i=Fr(e);if(i.zip){const s=i.zip;a=s.name.replace(/\.zip$/i,""),console.log("[ShpImport] Parsing zip",s.name,"("+(s.size/1024).toFixed(1)+" KB)"),o=await(await An())(await s.arrayBuffer())}else if(i.shp){a=i.shp.name.replace(/\.shp$/i,"");const l=["dbf","shx","prj"].filter(u=>!i[u]);if(l.length>0){de(),oe("Missing required file(s): "+l.map(u=>"."+u).join(", ")+". Please select .shp, .dbf, .shx and .prj together."),n.target.value="";return}const c={};c.shp=await i.shp.arrayBuffer(),c.dbf=await i.dbf.arrayBuffer(),c.prj=await new Response(i.prj).text(),i.cpg&&(c.cpg=await new Response(i.cpg).text()),console.log("[ShpImport] Parsing loose files:",Object.keys(i).map(u=>"."+u).join(", "),"("+(i.shp.size/1024).toFixed(1)+" KB .shp)"),o=await(await An())(c)}else{de(),oe("Please select a .zip or at least a .shp file."),n.target.value="";return}Oo(o,a,"ShpImport")}catch(o){de(),console.error("[ShpImport] Failed:",o),oe("Failed to parse shapefile: "+o.message)}n.target.value=""}async function Dr(n){const e=n.target.files?.[0];if(!e)return;const t=200*1024*1024;if(e.size>t){const r=(e.size/1048576).toFixed(0);oe(`File too large (${r} MB). Maximum supported size is 200 MB. Consider splitting the file into smaller tiles with ogr2ogr or QGIS.`),n.target.value="";return}Fo(e.name);try{const r=await e.text();console.log("[GeoJSONImport] Parsing",e.name,"("+(e.size/1024).toFixed(1)+" KB)");const o=JSON.parse(r);let a;if(o.type==="FeatureCollection")a=o;else if(o.type==="Feature")a={type:"FeatureCollection",features:[o]};else if(o.type&&o.coordinates)a={type:"FeatureCollection",features:[{type:"Feature",geometry:o,properties:{}}]};else{de(),oe("The file does not contain valid GeoJSON."),n.target.value="";return}const i=e.name.replace(/\.(geo)?json$/i,"");Oo(a,i,"GeoJSONImport")}catch(r){de(),console.error("[GeoJSONImport] Failed:",r);const o=(e.size/(1024*1024)).toFixed(1);oe(`Failed to import "${e.name}" (${o} MB): ${r.message}`)}n.target.value=""}async function Rr(n){const e=n.target.files?.[0];if(!e)return;const t=200*1024*1024;if(e.size>t){const r=(e.size/1048576).toFixed(0);oe(`File too large (${r} MB). Maximum supported size is 200 MB.`),n.target.value="";return}Fo(e.name);try{const r=await e.text();console.log("[KMLImport] Parsing",e.name,"("+(e.size/1024).toFixed(1)+" KB)");const a=new Gn({extractStyles:!1}).readFeatures(r,{featureProjection:"EPSG:3857"});if(!a||a.length===0){de(),oe("No features found in the KML file."),n.target.value="";return}const i=new le,s=JSON.parse(i.writeFeatures(a,{featureProjection:"EPSG:3857",dataProjection:"EPSG:4326"})),l=e.name.replace(/\.kml$/i,"");Oo(s,l,"KMLImport")}catch(r){de(),console.error("[KMLImport] Failed:",r);const o=(e.size/(1024*1024)).toFixed(1);oe(`Failed to import "${e.name}" (${o} MB): ${r.message}`)}n.target.value=""}function Nl(){const n=document.querySelector(".map-container");if(!n)return;let e=0;n.addEventListener("dragenter",t=>{t.preventDefault(),e++,n.classList.add("drag-over")}),n.addEventListener("dragover",t=>{t.preventDefault()}),n.addEventListener("dragleave",t=>{t.preventDefault(),e--,e<=0&&(e=0,n.classList.remove("drag-over"))}),n.addEventListener("drop",t=>{t.preventDefault(),e=0,n.classList.remove("drag-over");const r=t.dataTransfer?.files;if(!r||r.length===0)return;const o=Fr(r),a=Object.keys(o);if(o.zip||o.shp){const i={target:{files:r,value:""}};Object.defineProperty(i.target,"value",{writable:!0}),Or(i)}else if(o.geojson||o.json){const s={target:{files:[o.geojson||o.json],value:""}};Object.defineProperty(s.target,"value",{writable:!0}),Dr(s)}else if(o.kml){const i={target:{files:[o.kml],value:""}};Object.defineProperty(i.target,"value",{writable:!0}),Rr(i)}else oe("Unsupported file type(s): "+a.map(i=>"."+i).join(", ")+". Drop .zip, .shp, .geojson, .json, or .kml files.")}),console.log("[FileImport] Map drop zone initialised")}function j(n){const e=document.createElement("div");return e.textContent=n,e.innerHTML}const $l=50,Bn={error:{icon:"bi-x-circle-fill",color:"var(--destructive, #dc3545)"},warning:{icon:"bi-exclamation-triangle-fill",color:"var(--warning, #ffc107)"},success:{icon:"bi-check-circle-fill",color:"var(--success, #198754)"},info:{icon:"bi-info-circle-fill",color:"var(--primary, #0d6efd)"}};function Bt(n,e){const t=Bn[n]||Bn.info;(n==="error"?console.error:n==="warning"?console.warn:console.log)("[App]",e);const o=document.getElementById("message-log");if(!o)return;const a=o.querySelector(".text-muted");a&&a.remove();const i=document.createElement("div");i.className="list-group-item message-log-entry py-2 px-3";const l=new Date().toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"});for(i.innerHTML=`
${j(e)}
${l}
`,o.prepend(i);o.children.length>$l;)o.lastElementChild.remove()}function Bl(){const n=document.getElementById("clear-message-log");n&&n.addEventListener("click",()=>{const e=document.getElementById("message-log");e&&(e.innerHTML='
No messages yet.
')})}let Eo=null;function Nr(){const n=localStorage.getItem("coord-format");return n==="utm"||n==="both"?n:"latlon"}function $r(){const n=document.getElementById("gps-coords"),e=document.getElementById("gps-utm");if(!n||!Eo)return;const{lat:t,lon:r}=Eo,o=`${ln(t)}, ${ln(r)}`,a=ys(t,r),i=Nr();i==="utm"?(n.textContent=a,e&&e.classList.add("d-none")):i==="both"?(n.textContent=o,e&&(e.textContent=a,e.classList.remove("d-none"))):(n.textContent=o,e&&e.classList.add("d-none"));const s=document.getElementById("gps-readout");s&&(s.title=`Lat/Lon: ${o} -UTM: ${a}`)}function Gl(){const n=document.getElementById("gps-readout"),e=document.getElementById("gps-coords"),t=document.getElementById("gps-accuracy"),r=document.getElementById("gps-sats");if(!ve.isSupported){e&&(e.textContent="No GPS");return}ve.on("position",a=>{Eo=a,$r(),t&&(t.textContent=ws(a.accuracy)),r&&(r.textContent=`${a.satellites!=null?a.satellites:"—"} sat`),n&&(n.classList.add("active"),n.classList.remove("quality-good","quality-fair","quality-poor"),n.classList.add("quality-"+_s(a.accuracy))),E?.showCurrentPosition(a.lon,a.lat,a.accuracy)}),ve.on("point",a=>{E?.appendTrailPoint(a.point.lon,a.point.lat)}),ve.on("error",a=>{console.warn("[GPS]",a?.message||a),a&&a.code===1&&O("Location permission denied. Enable location access to use GPS.")}),E.onLocateMe(async()=>{try{const a=await ve.getCurrentPosition();E.centerOn(a.lon,a.lat,16)}catch(a){O("Could not get your location: "+(a?.message||a))}}),E.onToggleRecording(async a=>{if(a)try{await Xo,E.startTrailRender(),E.setRecordingState(!0),n?.classList.add("recording"),await ve.startRecording({name:`Trail ${new Date().toLocaleString()}`}),ge("GPS trail recording started")}catch(i){E.setRecordingState(!1),n?.classList.remove("recording"),O("Could not start recording: "+(i?.message||i))}else try{const i=await ve.stopRecording();if(E.setRecordingState(!1),n?.classList.remove("recording"),i){const s=`Trail saved: ${i.pointCount} points, ${bs(i.distanceM)}`+(i.synced?" — synced":" — will sync when online");ge(s)}}catch(i){O("Error stopping recording: "+(i?.message||i))}});const o=async()=>{if(V())try{await Xo;const a=await ve.syncPending();a.pushed&&console.log(`[GPS] Synced ${a.pushed} pending trail(s)`)}catch(a){console.warn("[GPS] pending-sync error",a)}};o(),pr(a=>{a||o()})}function O(n){Bt("error",n);const e=document.getElementById("error-message");e&&(e.querySelector(".message-text").textContent=n,e.classList.remove("d-none"),setTimeout(()=>e.classList.add("d-none"),5e3))}function ge(n){Bt("success",n);const e=document.getElementById("success-message");e&&(e.querySelector(".message-text").textContent=n,e.classList.remove("d-none"),setTimeout(()=>e.classList.add("d-none"),3e3))}function Ro(n){Bt("warning",n);const e=document.getElementById("warning-message");e&&(e.querySelector(".message-text").textContent=n,e.classList.remove("d-none"),setTimeout(()=>e.classList.add("d-none"),5e3))}function jl(){const n=document.getElementById("fieldwork-mode-toggle");if(!n)return;localStorage.getItem("fieldwork-mode")==="true"&&(document.documentElement.classList.add("fieldwork-mode"),n.checked=!0),n.addEventListener("change",()=>{document.documentElement.classList.toggle("fieldwork-mode",n.checked),localStorage.setItem("fieldwork-mode",n.checked),console.log("[Settings] Fieldwork mode",n.checked?"ON":"OFF")})}function Ul(){const n=document.getElementById("dark-mode-toggle");if(!n)return;function e(r){document.documentElement.classList.toggle("dark-mode",r),document.documentElement.setAttribute("data-bs-theme",r?"dark":"light")}localStorage.getItem("dark-mode")==="true"&&(n.checked=!0,e(!0)),n.addEventListener("change",()=>{e(n.checked),localStorage.setItem("dark-mode",n.checked),console.log("[Settings] Dark mode",n.checked?"ON":"OFF")})}function ql(){const n=document.getElementById("measurement-system-toggle"),e=document.getElementById("measurement-system-label");if(!n)return;function t(){e&&(e.textContent=n.checked?"Imperial":"Metric")}const r=localStorage.getItem("measurement-system");r==="imperial"&&(n.checked=!0),t(),E?.setScaleBarUnits(r||"metric"),n.addEventListener("change",()=>{const o=n.checked?"imperial":"metric";localStorage.setItem("measurement-system",o),t(),E?.setScaleBarUnits(o),console.log("[Settings] Measurement system:",o)})}function zl(){const n=document.getElementById("coord-format-select");n&&(n.value=Nr(),n.addEventListener("change",()=>{const e=n.value==="utm"||n.value==="both"?n.value:"latlon";localStorage.setItem("coord-format",e),$r(),console.log("[Settings] GPS coordinate format:",e)}))}function Hl(){const n=document.getElementById("default-basemap-select");if(!n)return;const e=localStorage.getItem("default-basemap")||"topo";n.value=e,n.addEventListener("change",()=>{const t=n.value;localStorage.setItem("default-basemap",t),E?.setBaseMap(t),console.log("[Settings] Default base map:",t)}),E?.getMap()?.on("basemapchange",t=>{if(t?.key&&n.value!==t.key){n.value=t.key;try{localStorage.setItem("default-basemap",t.key)}catch{}}})}function Wl(){const n=document.getElementById("tile-cache-stats"),e=document.getElementById("clear-tiles-btn"),t=document.getElementById("offcanvasBottom");if(!n||!e||!t)return;function r(i){return i?i<1024*1024?(i/1024).toFixed(0)+" KB":i<1024*1024*1024?(i/(1024*1024)).toFixed(1)+" MB":(i/(1024*1024*1024)).toFixed(2)+" GB":"0 KB"}let o=null;async function a(){if(o)return o;const i=!!navigator.serviceWorker?.controller;return n.innerHTML=i?'
Loading…
':'
Initialising service worker…
',o=(async()=>{try{const s=await Gi();if(!s){n.innerHTML=` +UTM: ${a}`)}function Gl(){const n=document.getElementById("gps-readout"),e=document.getElementById("gps-coords"),t=document.getElementById("gps-accuracy"),r=document.getElementById("gps-sats");if(!ve.isSupported){e&&(e.textContent="No GPS");return}ve.on("position",a=>{Eo=a,$r(),t&&(t.textContent=ws(a.accuracy)),r&&(r.textContent=`${a.satellites!=null?a.satellites:"—"} sat`),n&&(n.classList.add("active"),n.classList.remove("quality-good","quality-fair","quality-poor"),n.classList.add("quality-"+_s(a.accuracy))),E?.showCurrentPosition(a.lon,a.lat,a.accuracy)}),ve.on("point",a=>{E?.appendTrailPoint(a.point.lon,a.point.lat)}),ve.on("error",a=>{console.warn("[GPS]",a?.message||a),a&&a.code===1&&O("Location permission denied. Enable location access to use GPS.")}),E.onLocateMe(async()=>{try{const a=await ve.getCurrentPosition();E.centerOn(a.lon,a.lat,16)}catch(a){O("Could not get your location: "+(a?.message||a))}}),E.onToggleRecording(async a=>{if(a)try{await Xo,E.startTrailRender(),E.setRecordingState(!0),n?.classList.add("recording"),await ve.startRecording({name:`Trail ${new Date().toLocaleString()}`}),ge("GPS trail recording started")}catch(i){E.setRecordingState(!1),n?.classList.remove("recording"),O("Could not start recording: "+(i?.message||i))}else try{const i=await ve.stopRecording();if(E.setRecordingState(!1),n?.classList.remove("recording"),i){const s=`Trail saved: ${i.pointCount} points, ${bs(i.distanceM)}`+(i.synced?" — synced":" — will sync when online");ge(s)}}catch(i){O("Error stopping recording: "+(i?.message||i))}});const o=async()=>{if(K())try{await Xo;const a=await ve.syncPending();a.pushed&&console.log(`[GPS] Synced ${a.pushed} pending trail(s)`)}catch(a){console.warn("[GPS] pending-sync error",a)}};o(),pr(a=>{a||o()})}function O(n){Bt("error",n);const e=document.getElementById("error-message");e&&(e.querySelector(".message-text").textContent=n,e.classList.remove("d-none"),setTimeout(()=>e.classList.add("d-none"),5e3))}function ge(n){Bt("success",n);const e=document.getElementById("success-message");e&&(e.querySelector(".message-text").textContent=n,e.classList.remove("d-none"),setTimeout(()=>e.classList.add("d-none"),3e3))}function Ro(n){Bt("warning",n);const e=document.getElementById("warning-message");e&&(e.querySelector(".message-text").textContent=n,e.classList.remove("d-none"),setTimeout(()=>e.classList.add("d-none"),5e3))}function jl(){const n=document.getElementById("fieldwork-mode-toggle");if(!n)return;localStorage.getItem("fieldwork-mode")==="true"&&(document.documentElement.classList.add("fieldwork-mode"),n.checked=!0),n.addEventListener("change",()=>{document.documentElement.classList.toggle("fieldwork-mode",n.checked),localStorage.setItem("fieldwork-mode",n.checked),console.log("[Settings] Fieldwork mode",n.checked?"ON":"OFF")})}function Ul(){const n=document.getElementById("dark-mode-toggle");if(!n)return;function e(r){document.documentElement.classList.toggle("dark-mode",r),document.documentElement.setAttribute("data-bs-theme",r?"dark":"light")}localStorage.getItem("dark-mode")==="true"&&(n.checked=!0,e(!0)),n.addEventListener("change",()=>{e(n.checked),localStorage.setItem("dark-mode",n.checked),console.log("[Settings] Dark mode",n.checked?"ON":"OFF")})}function ql(){const n=document.getElementById("measurement-system-toggle"),e=document.getElementById("measurement-system-label");if(!n)return;function t(){e&&(e.textContent=n.checked?"Imperial":"Metric")}const r=localStorage.getItem("measurement-system");r==="imperial"&&(n.checked=!0),t(),E?.setScaleBarUnits(r||"metric"),n.addEventListener("change",()=>{const o=n.checked?"imperial":"metric";localStorage.setItem("measurement-system",o),t(),E?.setScaleBarUnits(o),console.log("[Settings] Measurement system:",o)})}function zl(){const n=document.getElementById("coord-format-select");n&&(n.value=Nr(),n.addEventListener("change",()=>{const e=n.value==="utm"||n.value==="both"?n.value:"latlon";localStorage.setItem("coord-format",e),$r(),console.log("[Settings] GPS coordinate format:",e)}))}function Hl(){const n=document.getElementById("default-basemap-select");if(!n)return;const e=localStorage.getItem("default-basemap")||"topo";n.value=e,n.addEventListener("change",()=>{const t=n.value;localStorage.setItem("default-basemap",t),E?.setBaseMap(t),console.log("[Settings] Default base map:",t)}),E?.getMap()?.on("basemapchange",t=>{if(t?.key&&n.value!==t.key){n.value=t.key;try{localStorage.setItem("default-basemap",t.key)}catch{}}})}function Wl(){const n=document.getElementById("tile-cache-stats"),e=document.getElementById("clear-tiles-btn"),t=document.getElementById("offcanvasBottom");if(!n||!e||!t)return;function r(i){return i?i<1024*1024?(i/1024).toFixed(0)+" KB":i<1024*1024*1024?(i/(1024*1024)).toFixed(1)+" MB":(i/(1024*1024*1024)).toFixed(2)+" GB":"0 KB"}let o=null;async function a(){if(o)return o;const i=!!navigator.serviceWorker?.controller;return n.innerHTML=i?'
Loading…
':'
Initialising service worker…
',o=(async()=>{try{const s=await Gi();if(!s){n.innerHTML=`
Tile cache stats unavailable. Try reloading the page if this persists.
`;return}const l=s.totals,c=s.byProvider.filter(f=>f.count>0).map(f=>` @@ -919,9 +919,9 @@ UTM: ${a}`)}function Gl(){const n=document.getElementById("gps-readout"),e=docum ${c} ${d}`,e.disabled=!1,n.querySelectorAll(".provider-clear-btn").forEach(f=>{f.addEventListener("click",async p=>{p.preventDefault();const h=f.dataset.cache,g=f.dataset.label||h;if(!confirm(`Clear cached "${g}" tiles? -Other providers are not affected. The tiles will re-download as you browse online.`))return;f.disabled=!0,await Ui(h)?console.log(`[Settings] Cleared tile cache for ${g}`):console.warn(`[Settings] Could not clear tile cache for ${g}`),await a()})})}finally{o=null}})(),o}e.addEventListener("click",async()=>{if(!confirm("Clear all cached map tiles from this device? You will need to be online to view them again."))return;e.disabled=!0,await ji()?console.log("[Settings] Tile caches cleared"):console.warn("[Settings] Tile-cache clear failed"),await a()}),t.addEventListener("show.bs.offcanvas",a),Bi(()=>{console.log("[Settings] SW controller changed → refreshing tile-cache stats"),a()}),a()}function Kl(){const n=document.getElementById("download-tiles-btn"),e=document.getElementById("offline-download-modal");if(!n||!e)return;const t=ht.getOrCreateInstance(e),r=document.getElementById("offline-download-form-view"),o=document.getElementById("offline-download-progress-view"),a=document.getElementById("offline-download-done-view"),i=document.getElementById("offline-download-cancel-btn"),s=document.getElementById("offline-download-start-btn"),l=document.getElementById("offline-download-close-done-btn"),c=document.getElementById("offline-download-close-btn"),d=document.getElementById("offline-basemap-select"),u=document.getElementById("offline-min-zoom"),f=document.getElementById("offline-max-zoom"),p=document.getElementById("offline-ack-check"),h=document.getElementById("offline-estimate-detail"),g=document.getElementById("offline-estimate"),m=document.getElementById("offline-area-view"),y=document.getElementById("offline-area-district"),b=document.getElementById("offline-area-ghana"),_=document.getElementById("offline-area-view-info"),S=document.getElementById("offline-area-district-info"),L=document.getElementById("offline-progress-bar"),v=document.getElementById("offline-progress-percent"),x=document.getElementById("offline-progress-counts"),P=document.getElementById("offline-progress-ok"),k=document.getElementById("offline-progress-failed"),U=document.getElementById("offline-progress-eta"),z=document.getElementById("offline-done-title"),Z=document.getElementById("offline-done-detail");let $=null;function ce(B){return B?B<1024*1024?(B/1024).toFixed(0)+" KB":B<1024*1024*1024?(B/(1024*1024)).toFixed(1)+" MB":(B/(1024*1024*1024)).toFixed(2)+" GB":"0 KB"}function Q(B){if(!B||B<1e3)return"< 1 s";const J=Math.round(B/1e3);if(J<60)return J+" s";const K=Math.floor(J/60),ne=J%60;return K<60?`${K} min ${ne} s`:`${Math.floor(K/60)} h ${K%60} min`}function ye(){return m.checked?E?.getCurrentViewExtent()||null:y.checked?E?.getDistrictBoundaryExtent()?.extent||null:b.checked?Yi:null}function H(){const B=d.value,J=parseInt(u.value,10),K=parseInt(f.value,10);if(Number.isNaN(J)||Number.isNaN(K)||J>K){h.textContent="Invalid zoom range",g.classList.replace("alert-info","alert-warning"),s.disabled=!0;return}const ne=ye();if(!ne){h.textContent="Selected area is not available.",g.classList.replace("alert-info","alert-warning"),s.disabled=!0;return}const R=fr[B]?.maxZoom??19,W=Math.min(K,R),re=Wi(ne,J,W),Gt=Ji(re);let we="";WZoom ${K} is above this provider's max (${R}); will clamp to ${R}.`),re>8e3&&(we+='
More than 8 000 tiles — exceeds the per-provider cache limit. Earlier tiles will be evicted as new ones arrive.'),h.innerHTML=`${re.toLocaleString()} tiles · ~${ce(Gt)}`+we,g.classList.toggle("alert-warning",!!we),g.classList.toggle("alert-info",!we),s.disabled=!p.checked||re===0}function be(){E?.getCurrentViewExtent()?_.textContent=" · ready":_.textContent="",E?.getDistrictBoundaryExtent()?(S.textContent="",y.disabled=!1):(S.textContent=" (not loaded — connect online to fetch)",y.disabled=!0,y.checked&&(m.checked=!0))}function Ye(){r.classList.remove("d-none"),o.classList.add("d-none"),a.classList.add("d-none"),s.classList.remove("d-none"),i.classList.remove("d-none"),i.textContent="Cancel",l.classList.add("d-none"),c.disabled=!1,p.checked=!1,s.disabled=!0,$=null}n.addEventListener("click",()=>{Ye(),be(),H(),t.show()}),d.addEventListener("change",H),u.addEventListener("input",H),f.addEventListener("input",H),m.addEventListener("change",H),y.addEventListener("change",H),b.addEventListener("change",H),p.addEventListener("change",H),s.addEventListener("click",async()=>{const B=d.value,J=parseInt(u.value,10),K=parseInt(f.value,10),ne=ye();if(!ne)return;r.classList.add("d-none"),o.classList.remove("d-none"),s.classList.add("d-none"),i.textContent="Cancel download",c.disabled=!0,L.style.width="0%",L.setAttribute("aria-valuenow","0"),v.textContent="0%",x.textContent="0 of 0 tiles",P.textContent="0",k.textContent="0",U.textContent="—",$=new Xi({baseMap:B,extent3857:ne,minZoom:J,maxZoom:K,onProgress:W=>{if(W.total>0){const re=Math.min(100,Math.round(W.done/W.total*100));L.style.width=re+"%",L.setAttribute("aria-valuenow",String(re)),v.textContent=re+"%",x.textContent=`${W.done.toLocaleString()} of ${W.total.toLocaleString()} tiles`}P.textContent=W.ok.toLocaleString(),k.textContent=W.failed.toLocaleString(),U.textContent=W.etaMs!=null?Q(W.etaMs):"—"}});let R;try{R=await $.start()}catch(W){console.error("[OfflineDownload] failed:",W),R={phase:"error",done:0,total:0,ok:0,failed:0}}o.classList.add("d-none"),a.classList.remove("d-none"),i.classList.add("d-none"),l.classList.remove("d-none"),c.disabled=!1,R.phase==="cancelled"?(z.textContent="Download cancelled",Z.innerHTML=`Stopped after ${R.done.toLocaleString()} of ${R.total.toLocaleString()} tiles.
${R.ok.toLocaleString()} fetched · ${R.failed.toLocaleString()} failed.`):R.phase==="error"?(z.textContent="Download failed",Z.textContent="See console for details."):(z.textContent="Download complete",Z.innerHTML=`${R.ok.toLocaleString()} tiles cached`+(R.failed>0?`, ${R.failed.toLocaleString()} failed`:"")+`.
Took ${Q(R.elapsedMs)}.`)}),i.addEventListener("click",()=>{$&&$.cancel()}),e.addEventListener("hidden.bs.modal",()=>{$&&$.cancel(),Ye()})}function Vl(){const n=Xe(),e=document.getElementById("menu-btn"),t=document.getElementById("menu-user-avatar"),r=document.getElementById("menu-user-name"),o=document.getElementById("menu-user-email"),a=document.getElementById("menu-user-detail"),i=document.getElementById("menu-signout-btn"),s=document.getElementById("menu-landing-link"),l=document.getElementById("menu-signin-link"),c=document.getElementById("menu-no-session-note");if(!e||!t||!r||!o||!a||!i){console.warn("[AccountMenu] One or more elements missing — shell may be stale. Hard-refresh.");return}if(!!n&&!!n.user_id){const u=[n.title,n.full_name].filter(Boolean).join(" ").trim()||n.username||"Authenticated user",f=(n.full_name||n.username||"?").trim().charAt(0).toUpperCase();t.textContent=f,t.style.background="var(--brand-navy, #1e1a4b)",r.textContent=u,o.textContent=n.email||"";const p=[];n.district_id!=null&&p.push(`District ${j(String(n.district_id))}`),n.region_id!=null&&p.push(`Region ${j(String(n.region_id))}`),n.ua_position&&p.push(j(n.ua_position)),a.innerHTML=p.join(" · ")||"No district info",i.classList.remove("d-none"),i.addEventListener("click",()=>Xl(n),{once:!1}),s?.classList.remove("d-none"),l?.classList.add("d-none"),c?.classList.add("d-none"),e.removeAttribute("data-state"),e.setAttribute("title",`Menu — ${u}`)}else typeof window.LUPMIS_SESSION>"u"?(t.innerHTML='',t.style.background="var(--brand-orange-warm, #ff9e1b)",r.textContent="No session injected",o.textContent="",a.textContent="",i.classList.add("d-none"),s?.classList.add("d-none"),l?.classList.add("d-none"),c?.classList.remove("d-none"),e.dataset.state="no-session",e.setAttribute("title","Menu (no session — dev mode)")):(t.innerHTML='',t.style.background="var(--brand-gray-medium, #7a7a7a)",r.textContent="Not signed in",o.textContent="",a.textContent="",i.classList.add("d-none"),s?.classList.add("d-none"),l?.classList.remove("d-none"),c?.classList.add("d-none"),e.dataset.state="unauthenticated",e.setAttribute("title","Menu (not signed in)"))}async function Xl(n){if(!V()){alert(`You appear to be offline. +Other providers are not affected. The tiles will re-download as you browse online.`))return;f.disabled=!0,await Ui(h)?console.log(`[Settings] Cleared tile cache for ${g}`):console.warn(`[Settings] Could not clear tile cache for ${g}`),await a()})})}finally{o=null}})(),o}e.addEventListener("click",async()=>{if(!confirm("Clear all cached map tiles from this device? You will need to be online to view them again."))return;e.disabled=!0,await ji()?console.log("[Settings] Tile caches cleared"):console.warn("[Settings] Tile-cache clear failed"),await a()}),t.addEventListener("show.bs.offcanvas",a),Bi(()=>{console.log("[Settings] SW controller changed → refreshing tile-cache stats"),a()}),a()}function Vl(){const n=document.getElementById("download-tiles-btn"),e=document.getElementById("offline-download-modal");if(!n||!e)return;const t=ht.getOrCreateInstance(e),r=document.getElementById("offline-download-form-view"),o=document.getElementById("offline-download-progress-view"),a=document.getElementById("offline-download-done-view"),i=document.getElementById("offline-download-cancel-btn"),s=document.getElementById("offline-download-start-btn"),l=document.getElementById("offline-download-close-done-btn"),c=document.getElementById("offline-download-close-btn"),d=document.getElementById("offline-basemap-select"),u=document.getElementById("offline-min-zoom"),f=document.getElementById("offline-max-zoom"),p=document.getElementById("offline-ack-check"),h=document.getElementById("offline-estimate-detail"),g=document.getElementById("offline-estimate"),m=document.getElementById("offline-area-view"),y=document.getElementById("offline-area-district"),b=document.getElementById("offline-area-ghana"),_=document.getElementById("offline-area-view-info"),S=document.getElementById("offline-area-district-info"),L=document.getElementById("offline-progress-bar"),v=document.getElementById("offline-progress-percent"),x=document.getElementById("offline-progress-counts"),P=document.getElementById("offline-progress-ok"),k=document.getElementById("offline-progress-failed"),U=document.getElementById("offline-progress-eta"),z=document.getElementById("offline-done-title"),Z=document.getElementById("offline-done-detail");let $=null;function ce(B){return B?B<1024*1024?(B/1024).toFixed(0)+" KB":B<1024*1024*1024?(B/(1024*1024)).toFixed(1)+" MB":(B/(1024*1024*1024)).toFixed(2)+" GB":"0 KB"}function Q(B){if(!B||B<1e3)return"< 1 s";const J=Math.round(B/1e3);if(J<60)return J+" s";const V=Math.floor(J/60),ne=J%60;return V<60?`${V} min ${ne} s`:`${Math.floor(V/60)} h ${V%60} min`}function ye(){return m.checked?E?.getCurrentViewExtent()||null:y.checked?E?.getDistrictBoundaryExtent()?.extent||null:b.checked?Yi:null}function H(){const B=d.value,J=parseInt(u.value,10),V=parseInt(f.value,10);if(Number.isNaN(J)||Number.isNaN(V)||J>V){h.textContent="Invalid zoom range",g.classList.replace("alert-info","alert-warning"),s.disabled=!0;return}const ne=ye();if(!ne){h.textContent="Selected area is not available.",g.classList.replace("alert-info","alert-warning"),s.disabled=!0;return}const R=fr[B]?.maxZoom??19,W=Math.min(V,R),re=Wi(ne,J,W),Gt=Ji(re);let we="";WZoom ${V} is above this provider's max (${R}); will clamp to ${R}.`),re>8e3&&(we+='
More than 8 000 tiles — exceeds the per-provider cache limit. Earlier tiles will be evicted as new ones arrive.'),h.innerHTML=`${re.toLocaleString()} tiles · ~${ce(Gt)}`+we,g.classList.toggle("alert-warning",!!we),g.classList.toggle("alert-info",!we),s.disabled=!p.checked||re===0}function be(){E?.getCurrentViewExtent()?_.textContent=" · ready":_.textContent="",E?.getDistrictBoundaryExtent()?(S.textContent="",y.disabled=!1):(S.textContent=" (not loaded — connect online to fetch)",y.disabled=!0,y.checked&&(m.checked=!0))}function Ye(){r.classList.remove("d-none"),o.classList.add("d-none"),a.classList.add("d-none"),s.classList.remove("d-none"),i.classList.remove("d-none"),i.textContent="Cancel",l.classList.add("d-none"),c.disabled=!1,p.checked=!1,s.disabled=!0,$=null}n.addEventListener("click",()=>{Ye(),be(),H(),t.show()}),d.addEventListener("change",H),u.addEventListener("input",H),f.addEventListener("input",H),m.addEventListener("change",H),y.addEventListener("change",H),b.addEventListener("change",H),p.addEventListener("change",H),s.addEventListener("click",async()=>{const B=d.value,J=parseInt(u.value,10),V=parseInt(f.value,10),ne=ye();if(!ne)return;r.classList.add("d-none"),o.classList.remove("d-none"),s.classList.add("d-none"),i.textContent="Cancel download",c.disabled=!0,L.style.width="0%",L.setAttribute("aria-valuenow","0"),v.textContent="0%",x.textContent="0 of 0 tiles",P.textContent="0",k.textContent="0",U.textContent="—",$=new Xi({baseMap:B,extent3857:ne,minZoom:J,maxZoom:V,onProgress:W=>{if(W.total>0){const re=Math.min(100,Math.round(W.done/W.total*100));L.style.width=re+"%",L.setAttribute("aria-valuenow",String(re)),v.textContent=re+"%",x.textContent=`${W.done.toLocaleString()} of ${W.total.toLocaleString()} tiles`}P.textContent=W.ok.toLocaleString(),k.textContent=W.failed.toLocaleString(),U.textContent=W.etaMs!=null?Q(W.etaMs):"—"}});let R;try{R=await $.start()}catch(W){console.error("[OfflineDownload] failed:",W),R={phase:"error",done:0,total:0,ok:0,failed:0}}o.classList.add("d-none"),a.classList.remove("d-none"),i.classList.add("d-none"),l.classList.remove("d-none"),c.disabled=!1,R.phase==="cancelled"?(z.textContent="Download cancelled",Z.innerHTML=`Stopped after ${R.done.toLocaleString()} of ${R.total.toLocaleString()} tiles.
${R.ok.toLocaleString()} fetched · ${R.failed.toLocaleString()} failed.`):R.phase==="error"?(z.textContent="Download failed",Z.textContent="See console for details."):(z.textContent="Download complete",Z.innerHTML=`${R.ok.toLocaleString()} tiles cached`+(R.failed>0?`, ${R.failed.toLocaleString()} failed`:"")+`.
Took ${Q(R.elapsedMs)}.`)}),i.addEventListener("click",()=>{$&&$.cancel()}),e.addEventListener("hidden.bs.modal",()=>{$&&$.cancel(),Ye()})}function Kl(){const n=Xe(),e=document.getElementById("menu-btn"),t=document.getElementById("menu-user-avatar"),r=document.getElementById("menu-user-name"),o=document.getElementById("menu-user-email"),a=document.getElementById("menu-user-detail"),i=document.getElementById("menu-signout-btn"),s=document.getElementById("menu-landing-link"),l=document.getElementById("menu-signin-link"),c=document.getElementById("menu-no-session-note");if(!e||!t||!r||!o||!a||!i){console.warn("[AccountMenu] One or more elements missing — shell may be stale. Hard-refresh.");return}if(!!n&&!!n.user_id){const u=[n.title,n.full_name].filter(Boolean).join(" ").trim()||n.username||"Authenticated user",f=(n.full_name||n.username||"?").trim().charAt(0).toUpperCase();t.textContent=f,t.style.background="var(--brand-navy, #1e1a4b)",r.textContent=u,o.textContent=n.email||"";const p=[];n.district_id!=null&&p.push(`District ${j(String(n.district_id))}`),n.region_id!=null&&p.push(`Region ${j(String(n.region_id))}`),n.ua_position&&p.push(j(n.ua_position)),a.innerHTML=p.join(" · ")||"No district info",i.classList.remove("d-none"),i.addEventListener("click",()=>Xl(n),{once:!1}),s?.classList.remove("d-none"),l?.classList.add("d-none"),c?.classList.add("d-none"),e.removeAttribute("data-state"),e.setAttribute("title",`Menu — ${u}`)}else typeof window.LUPMIS_SESSION>"u"?(t.innerHTML='',t.style.background="var(--brand-orange-warm, #ff9e1b)",r.textContent="No session injected",o.textContent="",a.textContent="",i.classList.add("d-none"),s?.classList.add("d-none"),l?.classList.add("d-none"),c?.classList.remove("d-none"),e.dataset.state="no-session",e.setAttribute("title","Menu (no session — dev mode)")):(t.innerHTML='',t.style.background="var(--brand-gray-medium, #7a7a7a)",r.textContent="Not signed in",o.textContent="",a.textContent="",i.classList.add("d-none"),s?.classList.add("d-none"),l?.classList.remove("d-none"),c?.classList.add("d-none"),e.dataset.state="unauthenticated",e.setAttribute("title","Menu (not signed in)"))}async function Xl(n){if(!K()){alert(`You appear to be offline. Logging out needs a connection to the sign-in server, and you would not be able to sign back in until you are online again. Please try again once you have a connection. You can keep working — the app stays signed in while offline.`);return}if(confirm(`Log out, ${n?.full_name||n?.username||"user"}?`)){try{await Io(),localStorage.removeItem(vo)}catch(e){console.warn("[Logout] Cache clear failed (continuing):",e)}window.location.href="/?logout=1"}}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Fn):Fn();export{F as s}; -//# sourceMappingURL=index-CF9fisV-.js.map +//# sourceMappingURL=index-BQfxXCp7.js.map diff --git a/dist/assets/index-BQfxXCp7.js.map b/dist/assets/index-BQfxXCp7.js.map new file mode 100644 index 0000000..b0f90ab --- /dev/null +++ b/dist/assets/index-BQfxXCp7.js.map @@ -0,0 +1 @@ +{"version":3,"mappings":"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","names":["FUNCTION","CHANNEL","GET","HAS","SET","isArray","SharedArrayBuffer","window","notify","wait","waitAsync","postPatched","buffer","onmessage","w","ids","resolvers","postMessage","listener","event","details","id","sb","data","rest","resolve","buff","i","as","Int32Array","Map","Uint16Array","I32_BYTES","UI16_BYTES","waitInterrupt","delay","handler","buffers","context","syncResult","fn","uid","coincident","self","parse","stringify","transform","interrupt","sendMessage","post","transfer","args","decoder","waitFor","isAsync","seppuku","_","action","deadlock","length","bytes","actions","callback","type","results","error","result","serialized","ui16a","createMutex","promise","res","normalizeDatabaseFile","dbFile","convertStreamTo","bufferOrStream","reader","chunks","streamDone","chunk","arrayLength","offset","SQLiteMemoryDriver","sqlite3InitModule","config","databasePath","flags","__vitePreload","statement","statements","tx","prepared","stmt","newStmt","columns","rows","size","database","dataPointer","resultCode","pointer","readOnly","verbose","db","statementData","opMap","_ctx","opId","_db","table","rowid","cb","debounce","func","options","lastArgs","lastThis","maxWait","timerId","lastCallTime","lastInvokeTime","leading","maxing","trailing","invokeFunc","time","thisArg","leadingEdge","timerExpired","remainingWait","timeSinceLastCall","timeSinceLastInvoke","timeWaiting","shouldInvoke","trailingEdge","cancel","flush","debounced","isInvoking","getQueryKey","getDatabaseKey","clientKey","sessionKey","SQLocalProcessor","driver","reason","dbKey","message","change","_transfer","response","partOfTransaction","sql","name","queryKey","errored","proxy","sqlTag","queryTemplate","params","isArrayOfArrays","row","convertRowsToObjects","checkedRows","rowObj","column","columnIndex","isDrizzleStatement","isStatement","normalizeStatement","drizzleStatement","exec","normalizeSql","maybeQueryTemplate","mutationLock","mode","bypass","mutation","SQLiteKvvfsDriver","storageType","memdb","_a","_b","SQLocal","queries","reject","userCallback","method","transactionKey","resultIndex","passStatements","query","passStatement","transaction","err","value","gotFirstValue","isListening","updateCount","watchedTables","subObservers","errObservers","runStatement","updateOrder","usedTables","readTables","writtenTables","observer","onEffect","onData","onError","funcName","key","attachFunction","databaseFile","beforeUnlock","clientConfig","onInit","onConnect","processor","commonConfig","DATABASE_PATH","BROADCAST_CHANNEL","channel","isReady","readyResolve","readyReject","dbReady","changeListeners","onDatabaseChange","payload","e","broadcastChange","initSchema","testResult","tablesAfterLocations","cols","c","allTables","t","addLocation","longitude","latitude","description","category","tableCheck","newId","verifyResult","getLocations","limit","getLocationCount","saveRemoteData","json","getRemoteData","parsed","saveCollectorZones","zones","z","props","getLocalCollectorZones","r","saveUpnGrid","districtId","saved","wkt","numOrNull","getLocalUpnGrid","uuidv4","createExternalImport","opts","filename","targetType","featureCount","clientImportId","addExternalImportFeatures","importId","features","inserted","f","propsJson","cuuid","updateExternalImport","patch","cur","mappingJson","status","errorCount","lastUploadedAt","getExternalImport","getExternalImportFeatures","remapImportedFeatureProperties","remap","n","before","after","updateExternalImportFeatureGeometry","clientUuid","geometryWkt","properties","deleteExternalImportFeature","v","saveParcels","parcels","p","getLocalParcels","updateParcel","parcelId","insertNewParcel","saveBuildingFootprints","footprints","first","types","k","rawWkt","rawId","getLocalBuildingFootprints","saveOSMRoads","roads","getLocalOSMRoads","exportDatabase","downloadDatabase","blob","url","a","exportToGeoJSON","loc","getDatabaseStatus","tables","locationCount","CACHED_LAYER_TABLES","isCachedLayerTable","tableName","clearTable","count","clearAllCachedLayers","existing","existingNames","total","s","getTableStats","getTableContent","testDatabase","version","createGpsTrail","meta","uuid","startedAt","addGpsTrailPoint","trailId","point","seq","lon","lat","altitude","accuracy","altitudeAccuracy","heading","speed","satellites","timestamp","recordedAt","finishGpsTrail","summary","endedAt","pointCount","distanceM","getUnsyncedGpsTrails","getGpsTrailPoints","markGpsTrailSynced","remoteId","M_TO_FT","M_TO_MI","SQM_TO_SQFT","SQM_TO_ACRE","SQM_TO_SQMI","getSystem","formatLength","metres","ft","formatLengthFull","mi","formatArea","sqMetres","acres","formatAreaFull","sqft","sqmi","formatCircleExtent","radiusMetres","segmentIntersection","p1","p2","p3","p4","eps","dx1","dy1","dx2","dy2","denom","dx3","dy3","u","signedArea","ring","area","pointInRing","pt","inside","j","xi","yi","xj","yj","dist2","b","findIntersections","line","hits","li","ri","ix","isDup","h","insertPointsIntoRing","sorted","expanded","indices","insertIdx","snapDist","ringSlice","i0","i1","start","end","idx","cuttingLineSlice","hit0","hit1","startSeg","endSeg","ensureWinding","ccw","closeRing","coords","last","extendLineOutsideRing","minX","minY","maxX","maxY","diag","p0","dx","dy","len","scale","pN","pN1","splitPolygonByLine","polygonCoords","lineCoords","exteriorRing","holes","extendedLine","expandedRing","idx0","idx1","iA","iB","cutForward","cutReverse","sliceAB","ringA","sliceBA","ringB","originalCCW","finalA","finalB","polyA","polyB","hole","centroid","holeCentroid","cx","cy","THEMES","container","ensureContainer","showToast","duration","parent","theme","el","dismiss","SPLIT_COLORS","HIGHLIGHT_STYLE","Style","Stroke","Fill","SKETCH_STYLE","CircleStyle","PolygonSplitInteraction","ol_interaction_Interaction","VectorSource","VectorLayer","map","active","sources","collect","layers","layer","hit","clone","best","bestDist","source","feat","geom","closest","distPx","LineString","ol_interaction_Draw","evt","cuttingLine","cuttingLineCoords","feature","coordsA","coordsB","featureA","PolygonGeom","featureB","splitFeatures","splitMap","distToSegmentSq","segA","segB","lenSq","projX","projY","findClosestEdge","clickCoord","bestIdx","d","coordsEqual","tolSq","isVertexNearRing","findSharedBoundary","seedIdxA","seedIdxB","tolerance","nA","nB","a0","a1","b0","b1","a0NearB","a1NearB","b0NearA","b1NearA","reversed","startA","endA","startB","endB","safety","nextA","nextB","prevA","prevB","walkRing","fromIdx","toIdx","mergePolygons","polygonCoordsA","polygonCoordsB","clickCoordA","clickCoordB","holesA","holesB","seedA","seedB","shared","partA","partB","merged","mergedRing","areaA","areaB","areaMerged","expectedArea","finalRing","validHoles","HIGHLIGHT_A","HIGHLIGHT_B","LABEL_A","Text","LABEL_B","EDGE_STYLE","MERGE_STYLE","PolygonMergeInteraction","skipFeature","style","edge","edgeFeat","Feature","resolution","bestSeg","sourceA","sourceB","geomA","geomB","mergedFeature","evtData","mergeMap","afterEvt","isParcelA","isParcelB","toRemove","cloneA","cloneB","polygonArea","longestEdge","bestLen","bestI","along","perp","makeCuttingLine","origin","extent","centroidT","sx","sy","dividePolygon","edgeCoords","nVerts","perpMin","perpMax","pieces","remaining","remainingCount","remainingArea","targetArea","remRing","remN","rMin","rMax","lo","hi","bestPiece","bestRemaining","bestError","iter","mid","nudge","lineA","resultA","halfA","halfB","tA","tB","nearPiece","farPiece","nearArea","lineB","resultB","pieceColors","colors","hue","PolygonDivideInteraction","ext","center","newFeatures","divideMap","TOOLBAR_ICONS","TOOLBAR_GROUPS","MapView","targetId","cat","emoji","label","fontSize","baseLayers","LayerGroup","View","fromLonLat","layerSwitcher","LayerSwitcher","btn","baseUrl","_lsChromeScheduled","ScaleLine","searchNominatim","SearchNominatim","searchResult","lonLat","coordinate","Circle","mapLayers","overlayIdx","Select","clickCondition","shiftKeyOnly","DragBox","platformModifierKeyOnly","selected","ModifyFeature","UndoRedo","EditBar","drawPoint","undoRedo","wasRecording","extraBar","Bar","Button","Split","idFields","field","splitLineToggle","Toggle","splitPolyToggle","splitDivideToggle","splitSubBar","splitParentToggle","mergeToggle","SnapGuides","VectorImageLayer","drawToolNames","interaction","snapToggleBtn","visible","TouchCursor","tag","del","l","listeners","wasActive","hasFinePointer","canHover","wantCursor","q","onChange","mq","bar","className","icon","ctrl","button","ordered","group","members","member","grip","STORAGE_KEY","phone","clamp","min","max","place","left","top","maxLeft","maxTop","clearInline","restore","startX","startY","originLeft","originTop","onMove","onUp","rect","parentRect","ur","prev","feats","Point","out","isCoord","visitRing","isPolygonRing","poly","walk","sub","system","Overlay","currentFeature","html","catColor","title","color","geometry","geomType","skipKeys","areaSqm","getArea","areaFormatted","lengthM","getLength","lengthFormatted","toLonLat","parcelFeatures","zoneFeatures","otherByLayer","dataRows","names","tableRows","labelColor","border","exportContext","pdfRows","analyseBtn","totalFeatures","arr","gisBtn","circleFeature","circleGeom","circlePoly","fromCircle","circleExtent","radius","intersectsCircle","fExtent","scanGroup","groupTitle","layerTitle","candidates","fType","radiusFormatted","polygonFeature","polyGeom","polyExtent","perimeterM","perimeterFormatted","intersectsPoly","typeB","flatB","stride","flatA","strideA","fieldsHtml","displayVal","escapedKey","escapedVal","form","formData","updatedProps","propsA","propsB","getLabel","labelA","labelB","close","chosenProps","labels","radios","updateHighlight","lbl","radio","input","keys","attributeKeys","clickedFeature","text","div","coordsEl","defaultBasemap","topoLayer","TileLayer","XYZ","cartoLightLayer","cartoDarkLayer","osmCycleLayer","OSM","satelliteLayer","googleLayer","osmLayer","baseGroup","matched","on","OPTIONS","target","panel","opt","syncSelection","open","resAtLat","halo","dot","radiusMeters","segments","zoom","toggle","next","customStyle","styles","locations","featureOrId","padding","hasOverlayFeature","hasParcelFeature","markerFeature","index","hoveredFeature","geojson","styleOptions","targetGroup","strokeColor","strokeWidth","strokeDash","fillColor","lineCasingColor","lineCasingWidth","pointRadius","pointFillColor","pointStrokeColor","pointStrokeWidth","GeoJSON","fillStyle","pointStyle","mainStroke","layerStyle","casingW","describeFromGeom","initial","once","ev","desc","wmsSource","TileWMS","wmsLayer","xyzSource","xyzLayer","problem","WebGLTileLayer","GeoTIFFSource","cogLayer","sourceOpts","visibleOnly","nonEmptyOnly","card","nameRow","nameHint","urlInput","typeHint","layerName","dlg","typeHintEl","inp","wmsSrc","wfsUrl","wfsSource","labelSpan","chip","btnBar","chevronEl","content","ensureSubtitle","removeBtn","chipSpec","fresh","addBtn","visit","removed","child","panelContainer","ul","badge","footer","counts","totalOverlays","activeOverlays","HIDDEN_INTERNAL","refresh","hookLayer","added","legendUrl","wrapper","update","children","str","found","view","src","ex","MapTools","drawCircle","Draw","output","radiusLine","unByKey","drawLine","drawPolygon","mainBar","measureBar","circleBtn","lineBtn","areaBtn","clearBtn","swRegistration","registerServiceWorker","newWorker","showUpdateNotification","deferredPrompt","installButton","initInstallPrompt","buttonSelector","showManualInstallInstructions","outcome","isIOS","isSafari","offlineIndicator","offlineListeners","initOfflineDetection","indicatorSelector","updateOfflineUI","notifyOfflineListeners","isOffline","onOfflineChange","isOnline","applyUpdate","getActiveServiceWorker","timeoutMs","ready","timeout","registration","sw","onServiceWorkerControllerChange","requestFromServiceWorker","requestType","responseType","extra","readyTimeoutMs","timer","getTileCacheStats","clearTileCaches","clearTileCacheForProvider","cacheName","getStorageEstimate","usage","quota","initPWA","autoRegisterSW","BASEMAP_TEMPLATES","AVG_TILE_BYTES","ORIGIN_SHIFT","metersToLonLat","x","y","lonLatToTile","latRad","tileRangeForExtent","extent3857","minLon","minLat","maxLon","maxLat","tl","br","minTileX","maxTileX","minTileY","maxTileY","countTiles","minZ","maxZ","enumerateTiles","formatTileUrl","template","OfflineTileDownloader","baseMap","minZoom","maxZoom","concurrency","interBatchDelayMs","onProgress","tpl","tiles","done","ok","failed","cached","emit","phase","elapsedMs","etaMs","batch","GHANA_EXTENT_3857","lonLatToMeters","ne","estimatedSizeBytes","tileCount","API_ROOT","API_BASE","resolveEndpoint","endpoint","FALLBACK_DISTRICT_ID","API_TOKEN","resolveDistrictId","session","API_CREDENTIALS","getSession","raw","REQUEST_TIMEOUT","PING_TIMEOUT","_serverReachable","checkServerReachable","force","controller","isServerReachable","withTimeout","ms","remotePost","body","getDistrictBoundary","getLayers","getCollectorZones","getDistrictParcels","getBuildingFootprints","getContoursHillshade","getOSMRoads","getUpnGrid","pushGpsTrail","trail","points","EARTH_RADIUS_M","DEG2RAD","haversineMeters","lon1","lat1","lon2","lat2","dLat","dLon","formatCoord","decimals","UTM_A","UTM_F","UTM_K0","UTM_E2","UTM_EP2","latLonToUTM","lonNorm","zone","lon0","phi","lambda","lambda0","sinPhi","cosPhi","tanPhi","N","T","C","A","M","easting","northing","hemisphere","formatUTM","formatDistance","meters","formatAccuracy","accuracyQuality","DEFAULTS","GeoTracker","set","pos","fix","trailMeta","synced","pushed","trails","trailRow","minIntervalMs","minDistanceM","heartbeatMs","maxAccuracyM","now","keep","stepM","elapsed","num","ch","sqlocalStorage","remote","remoteSync","geoTracker","KNOWN_COMMANDS","createEmbedBridge","mapView","embedConfig","highlightSource","highlightLayer","parcelsLayer","pendingSelectUpn","readyEmitted","send","emitError","code","emitReady","parcelPayload","outLon","outLat","getCenter","highlightFeature","_markerFeature","parcelFeature","msg","selectByUpn","upn","attachParcelsLayer","drain","scheduled","TARGET_TYPES","TARGET_FIELDS","dominantGeometryFamily","fc","polys","lines","attributeFingerprint","sampleSize","has","fp","detectTargetType","family","FIELD_CANDIDATES","normaliseKey","autoMapFields","sourceByNorm","mapping","col","cand","applyFieldMapping","sourceProps","targetCol","sourceKey","listSourceFields","seen","sampleSourceValues","maxLen","samples","els","modal","state","cacheEls","onTargetChange","finish","renderFieldsTable","sampleSuffix","escapeHtml","optionsHtml","escapeAttr","toBoldUnicode","current","select","sel","newType","onResult","openImportMappingModal","Modal","cp","fieldsize","lib","char","fieldLength","fieldSize","require$$0","fields","multi","bytesPer","obj","inherit","isDef","o","memo","require$$1","require$$2","structure","field_meta","fieldDescLength","bytesPerRecord","headerLength","val","dbf","prj","coordinates","shpView","shxView","contentLength","fileLength","shpI","shxI","geometries","TYPE","shxOffset","writePolyLine","flattened","justCoords","noParts","parts","featureExtent","onlyParts","no","rings","require$$3","require$$4","pointWriter","require$$6","polyWriter","require$$7","writers","write_1","write","geometry_type","writer","shpLength","shxLength","shpBuffer","shxBuffer","writeHeader","writeExtent","dbfBuf","justType","gj","oftype","isType","justProps","JSZip","zip","files","fileName","generateOptions","download","shpWrite","PROJ_MAP","PROJ_WGS84","exportFeaturesToGis","rename","format","filenameBase","buildRenamedGeoJSON","downloadBlob","writeKml","writeShp","fmt","GeoJSONFormat","geomGeoJson","stripGeometryFromProps","outProps","clones","labelKey","KMLFormat","sanitised","sanitiseShpFieldNames","shpwrite.download","allKeys","used","base","candidate","suffix","newProps","onFormatChange","onExportClick","collectFeatures","ctx","tagWithSource","sourceLabel","unionAttributeKeys","skip","currentFormat","warn","isPdf","exportAnalysisPDF","openExportGisModal","def","_shpModule","getShp","mod","showImportSpinner","overlay","nameEl","hideImportSpinner","mapTools","embedBridge","EMBED_CONFIG","IS_EMBED_PERMIT","currentMode","showNoDistrictBlockerIfNeeded","initApp","savedBasemap","initGpsTracking","upnGridFeature","showLocationDetails","layerType","loadLocations","showSuccess","showError","wktFormat","WKT","wktString","enforceDistrictConsistency","showWarning","loadLayers","loadDistrictBoundary","loadUpnGrid","loadCollectorZones","loadParcels","loadBuildingFootprints","loadContoursHillshade","loadOSMRoads","loadExternalWMSLayers","initUI","statsContainer","refreshLocalDataStats","offline","syncData","initFieldworkMode","initMeasurementSystem","initCoordinateFormat","initDarkMode","initDefaultBasemap","initOfflineTileCache","initOfflineDownloadDialog","initAccountCard","initMessageLog","exportBtn","handleExport","localDataBtn","importShpBtn","shpFileInput","handleShapefileImport","importGeoJSONBtn","geojsonFileInput","handleGeoJSONImport","importKMLBtn","kmlFileInput","handleKMLImport","initMapDropZone","exportGeoJSONBtn","handleExportGeoJSON","statusBtn","handleShowStatus","fitBtn","measureCircleBtn","measureLineBtn","measureAreaBtn","drawBtn","analysisBtn","openAnalysisModal","modeButtons","setMode","activeBtn","renderLocations","countEl","mobileCount","categoryEmojis","item","tbody","clearAllBtn","stats","link","showTableContent","handleClearAllCachedLayers","modalTitle","modalBody","modalInfo","headerCells","bodyRows","display","statusContent","parseCoordRing","ringStr","pair","parseWKTPolygon","parseWKTMultiPolygon","polyStr","parseWKT","trimmed","apiResponseToGeoJSON","apiResponse","boundary","districtid","district_name","zonesToGeoJSON","LAST_DISTRICT_KEY","districtBoundaryCacheKey","currentStr","previous","CACHE_KEY","ADMIN_GROUP_ID","boundaryStyle","adminGroup","removeBoundaryLayer","zoomToBoundary","willFetchFresh","upnGridToGeoJSON","upnGridStyle","emptyGeoJSON","upnGridLayer","UPN_LABEL_MAX_RESOLUTION","prefix","OlText","setFeatures","zoneStyle","zonesLayer","setZoneFeatures","parcelsToGeoJSON","parcel","parcelStyle","landUseGroup","setParcelFeatures","footprintsToGeoJSON","geomKeys","footprintStyle","physInfraGroup","footprintsLayer","setFootprintFeatures","wktRowsToGeoJSON","geojsonFormat","olGeom","flattenProps","contoursStyle","biophysGroup","contoursLayer","CONTOUR_LABEL_MAX_RESOLUTION","elev","roadsStyle","roadsLayer","setRoadFeatures","createLayerGroupsOnMap","overlayLayers","importedFileLayers","IMPORT_STYLE","showFileImportError","logMessage","addImportedGeoJSON","geojsonInput","fallbackName","collections","stageImport","lastLayer","refreshImportedLayersCard","wktFormat4326","geometryToWkt4326","displayName","stagedRows","newClientUuid","impId","handleImportModalResult","refreshLayerSwitcherChip","runUpload","imp","scopeFeatures","switcher","infoEl","listEl","removeImportedLayer","removeImportedLayers","overlayGroup","indexFilesByExtension","MAX_FILE_SIZE","totalSize","sizeMB","byExt","file","missing","shpObj","KML","dragCounter","exts","fakeEvt","MESSAGE_LOG_MAX","MSG_CONFIG","cfg","log","placeholder","entry","_lastGpsFix","getCoordFormat","renderGpsCoords","utmEl","latlon","utm","readout","accEl","satsEl","trySync","applyDark","updateLabel","statsEl","offcanvas","fmtBytes","refreshInFlight","swActive","storageNote","est","pct","triggerBtn","modalEl","formView","progressView","doneView","cancelBtn","startBtn","closeDoneBtn","headerCloseBtn","basemapSelect","minZoomInput","maxZoomInput","ackCheck","estimateEl","estimateBox","areaViewRadio","areaDistrictRadio","areaGhanaRadio","areaViewInfo","areaDistrictInfo","progressBar","progressPercent","progressCounts","progressOk","progressFailed","progressEta","doneTitle","doneDetail","currentDownloader","fmtDuration","m","getSelectedExtent","updateEstimate","tplMaxZoom","effMaxZ","warningHTML","updateAreaInfos","resetModal","menuBtn","avatarEl","emailEl","detailEl","signoutBtn","landingLink","signinLink","noSessNote","bits","handleSignOut"],"ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54],"sources":["../../node_modules/proxy-target/esm/types.js","../../node_modules/coincident/esm/channel.js","../../node_modules/proxy-target/esm/traps.js","../../node_modules/coincident/esm/bridge.js","../../node_modules/coincident/esm/index.js","../../node_modules/sqlocal/dist/lib/create-mutex.js","../../node_modules/sqlocal/dist/lib/normalize-database-file.js","../../node_modules/sqlocal/dist/drivers/sqlite-memory-driver.js","../../node_modules/sqlocal/dist/lib/debounce.js","../../node_modules/sqlocal/dist/lib/get-query-key.js","../../node_modules/sqlocal/dist/lib/get-database-key.js","../../node_modules/sqlocal/dist/processor.js","../../node_modules/sqlocal/dist/lib/sql-tag.js","../../node_modules/sqlocal/dist/lib/convert-rows-to-objects.js","../../node_modules/sqlocal/dist/lib/normalize-statement.js","../../node_modules/sqlocal/dist/lib/normalize-sql.js","../../node_modules/sqlocal/dist/lib/mutation-lock.js","../../node_modules/sqlocal/dist/drivers/sqlite-kvvfs-driver.js","../../node_modules/sqlocal/dist/client.js","../../src/database.js","../../src/units.js","../../src/geom/polygonSplit.js","../../src/toast.js","../../src/interactions/PolygonSplitInteraction.js","../../src/geom/polygonMerge.js","../../src/interactions/PolygonMergeInteraction.js","../../src/geom/polygonDivide.js","../../src/interactions/PolygonDivideInteraction.js","../../src/components/MapView.js","../../src/components/MapTools.js","../../src/pwa.js","../../src/offlineTiles.js","../../src/remotedb.js","../../src/geotracker/geo-utils.js","../../src/geotracker/GeoTracker.js","../../src/geotracker-lupmis.js","../../src/embed-bridge.js","../../src/import-detect.js","../../src/import-modal.js","../../node_modules/shp-write/src/types.js","../../node_modules/dbf/src/fieldsize.js","../../node_modules/dbf/src/lib.js","../../node_modules/dbf/src/fields.js","../../node_modules/dbf/src/structure.js","../../node_modules/dbf/index.js","../../node_modules/shp-write/src/prj.js","../../node_modules/shp-write/src/extent.js","../../node_modules/shp-write/src/fields.js","../../node_modules/shp-write/src/points.js","../../node_modules/shp-write/src/poly.js","../../node_modules/shp-write/src/write.js","../../node_modules/shp-write/src/geojson.js","../../node_modules/shp-write/src/zip.js","../../node_modules/shp-write/src/download.js","../../node_modules/shp-write/index.js","../../src/gis-export.js","../../src/export-gis-modal.js","../../main.js"],"sourcesContent":["export const ARRAY = 'array';\nexport const BIGINT = 'bigint';\nexport const BOOLEAN = 'boolean';\nexport const FUNCTION = 'function';\nexport const NULL = 'null';\nexport const NUMBER = 'number';\nexport const OBJECT = 'object';\nexport const STRING = 'string';\nexport const SYMBOL = 'symbol';\nexport const UNDEFINED = 'undefined';\n","// ⚠️ AUTOMATICALLY GENERATED - DO NOT CHANGE\nexport const CHANNEL = '64e10b34-2bf7-4616-9668-f99de5aa046e';\n\nexport const MAIN = 'M' + CHANNEL;\nexport const THREAD = 'T' + CHANNEL;\n","export const APPLY = 'apply';\nexport const CONSTRUCT = 'construct';\nexport const DEFINE_PROPERTY = 'defineProperty';\nexport const DELETE_PROPERTY = 'deleteProperty';\nexport const GET = 'get';\nexport const GET_OWN_PROPERTY_DESCRIPTOR = 'getOwnPropertyDescriptor';\nexport const GET_PROTOTYPE_OF = 'getPrototypeOf';\nexport const HAS = 'has';\nexport const IS_EXTENSIBLE = 'isExtensible';\nexport const OWN_KEYS = 'ownKeys';\nexport const PREVENT_EXTENSION = 'preventExtensions';\nexport const SET = 'set';\nexport const SET_PROTOTYPE_OF = 'setPrototypeOf';\n","// The goal of this file is to normalize SAB\n// at least in main -> worker() use cases.\n// This still cannot possibly solve the sync\n// worker -> main() use case if SharedArrayBuffer\n// is not available or usable.\n\nimport {CHANNEL} from './channel.js';\n\nconst {isArray} = Array;\n\nlet {SharedArrayBuffer, window} = globalThis;\nlet {notify, wait, waitAsync} = Atomics;\nlet postPatched = null;\n\n// This is needed for some version of Firefox\nif (!waitAsync) {\n waitAsync = buffer => ({\n value: new Promise(onmessage => {\n // encodeURIComponent('onmessage=({data:b})=>(Atomics.wait(b,0),postMessage(0))')\n let w = new Worker('data:application/javascript,onmessage%3D(%7Bdata%3Ab%7D)%3D%3E(Atomics.wait(b%2C0)%2CpostMessage(0))');\n w.onmessage = onmessage;\n w.postMessage(buffer);\n })\n });\n}\n\n// Monkey-patch SharedArrayBuffer if needed\ntry {\n new SharedArrayBuffer(4);\n}\ncatch (_) {\n SharedArrayBuffer = ArrayBuffer;\n\n const ids = new WeakMap;\n // patch only main -> worker():async use case\n if (window) {\n const resolvers = new Map;\n const {prototype: {postMessage}} = Worker;\n\n const listener = event => {\n const details = event.data?.[CHANNEL];\n if (!isArray(details)) {\n event.stopImmediatePropagation();\n const { id, sb } = details;\n resolvers.get(id)(sb);\n }\n };\n\n postPatched = function (data, ...rest) {\n const details = data?.[CHANNEL];\n if (isArray(details)) {\n const [id, sb] = details;\n ids.set(sb, id);\n this.addEventListener('message', listener);\n }\n return postMessage.call(this, data, ...rest);\n };\n\n waitAsync = sb => ({\n value: new Promise(resolve => {\n resolvers.set(ids.get(sb), resolve);\n }).then(buff => {\n resolvers.delete(ids.get(sb));\n ids.delete(sb);\n for (let i = 0; i < buff.length; i++) sb[i] = buff[i];\n return 'ok';\n })\n });\n }\n else {\n const as = (id, sb) => ({[CHANNEL]: { id, sb }});\n\n notify = sb => {\n postMessage(as(ids.get(sb), sb));\n };\n\n addEventListener('message', event => {\n const details = event.data?.[CHANNEL];\n if (isArray(details)) {\n const [id, sb] = details;\n ids.set(sb, id);\n }\n });\n }\n}\n\nexport {SharedArrayBuffer, isArray, notify, postPatched, wait, waitAsync};\n","/*! (c) Andrea Giammarchi - ISC */\n\nimport {FUNCTION} from 'proxy-target/types';\n\nimport {CHANNEL} from './channel.js';\nimport {GET, HAS, SET} from './shared/traps.js';\n\nimport {SharedArrayBuffer, isArray, notify, postPatched, wait, waitAsync} from './bridge.js';\n\n// just minifier friendly for Blob Workers' cases\nconst {Int32Array, Map, Uint16Array} = globalThis;\n\n// common constants / utilities for repeated operations\nconst {BYTES_PER_ELEMENT: I32_BYTES} = Int32Array;\nconst {BYTES_PER_ELEMENT: UI16_BYTES} = Uint16Array;\n\nconst waitInterrupt = (sb, delay, handler) => {\n while (wait(sb, 0, 0, delay) === 'timed-out')\n handler();\n};\n\n// retain buffers to transfer\nconst buffers = new WeakSet;\n\n// retain either main threads or workers global context\nconst context = new WeakMap;\n\nconst syncResult = {value: {then: fn => fn()}};\n\n// used to generate a unique `id` per each worker `postMessage` \"transaction\"\nlet uid = 0;\n\n/**\n * @typedef {Object} Interrupt used to sanity-check interrupts while waiting synchronously.\n * @prop {function} [handler] a callback invoked every `delay` milliseconds.\n * @prop {number} [delay=42] define `handler` invokes in terms of milliseconds.\n */\n\n/**\n * Create once a `Proxy` able to orchestrate synchronous `postMessage` out of the box.\n * @param {globalThis | Worker} self the context in which code should run\n * @param {{parse: (serialized: string) => any, stringify: (serializable: any) => string, transform?: (value:any) => any, interrupt?: () => void | Interrupt}} [JSON] an optional `JSON` like interface to `parse` or `stringify` content with extra `transform` ability.\n * @returns {ProxyHandler | ProxyHandler}\n */\nconst coincident = (self, {parse = JSON.parse, stringify = JSON.stringify, transform, interrupt} = JSON) => {\n // create a Proxy once for the given context (globalThis or Worker instance)\n if (!context.has(self)) {\n // ensure no SAB gets a chance to pass through this call\n const sendMessage = postPatched || self.postMessage;\n // ensure the CHANNEL and data are posted correctly\n const post = (transfer, ...args) => sendMessage.call(self, {[CHANNEL]: args}, {transfer});\n\n const handler = typeof interrupt === FUNCTION ? interrupt : interrupt?.handler;\n const delay = interrupt?.delay || 42;\n const decoder = new TextDecoder('utf-16');\n\n // automatically uses sync wait (worker -> main)\n // or fallback to async wait (main -> worker)\n const waitFor = (isAsync, sb) => isAsync ?\n waitAsync(sb, 0) :\n ((handler ? waitInterrupt(sb, delay, handler) : wait(sb, 0)), syncResult);\n\n // prevent Harakiri https://github.com/WebReflection/coincident/issues/18\n let seppuku = false;\n\n context.set(self, new Proxy(new Map, {\n // there is very little point in checking prop in proxy for this very specific case\n // and I don't want to orchestrate a whole roundtrip neither, as stuff would fail\n // regardless if from Worker we access non existent Main callback, and vice-versa.\n // This is here mostly to guarantee that if such check is performed, at least the\n // get trap goes through and then it's up to developers guarantee they are accessing\n // stuff that actually exists elsewhere.\n [HAS]: (_, action) => typeof action === 'string' && !action.startsWith('_'),\n\n // worker related: get any utility that should be available on the main thread\n [GET]: (_, action) => action === 'then' ? null : ((...args) => {\n // transaction id\n const id = uid++;\n\n // first contact: just ask for how big the buffer should be\n // the value would be stored at index [1] while [0] is just control\n let sb = new Int32Array(new SharedArrayBuffer(I32_BYTES * 2));\n\n // if a transfer list has been passed, drop it from args\n let transfer = [];\n if (buffers.has(args.at(-1) || transfer))\n buffers.delete(transfer = args.pop());\n\n // ask for invoke with arguments and wait for it\n post(transfer, id, sb, action, transform ? args.map(transform) : args);\n\n // helps deciding how to wait for results\n const isAsync = self !== globalThis;\n\n // warn users about possible deadlock still allowing them\n // to explicitly `proxy.invoke().then(...)` without blocking\n let deadlock = 0;\n if (seppuku && isAsync)\n deadlock = setTimeout(console.warn, 1000, `💀🔒 - Possible deadlock if proxy.${action}(...args) is awaited`);\n\n return waitFor(isAsync, sb).value.then(() => {\n clearTimeout(deadlock);\n\n // commit transaction using the returned / needed buffer length\n const length = sb[1];\n\n // filter undefined results\n if (!length) return;\n\n // calculate the needed ui16 bytes length to store the result string\n const bytes = UI16_BYTES * length;\n\n // round up to the next amount of bytes divided by 4 to allow i32 operations\n sb = new Int32Array(new SharedArrayBuffer(bytes + (bytes % I32_BYTES)));\n\n // ask for results and wait for it\n post([], id, sb);\n return waitFor(isAsync, sb).value.then(() => parse(\n decoder.decode(new Uint16Array(sb.buffer).slice(0, length)))\n );\n });\n }),\n\n // main thread related: react to any utility a worker is asking for\n [SET](actions, action, callback) {\n const type = typeof callback;\n if (type !== FUNCTION)\n throw new Error(`Unable to assign ${action} as ${type}`);\n // lazy event listener and logic handling, triggered once by setters actions\n if (!actions.size) {\n // maps results by `id` as they are asked for\n const results = new Map;\n // add the event listener once (first defined setter, all others work the same)\n self.addEventListener('message', async (event) => {\n // grub the very same library CHANNEL; ignore otherwise\n const details = event.data?.[CHANNEL];\n if (isArray(details)) {\n // if early enough, avoid leaking data to other listeners\n event.stopImmediatePropagation();\n const [id, sb, ...rest] = details;\n let error;\n // action available: it must be defined/known on the main thread\n if (rest.length) {\n const [action, args] = rest;\n if (actions.has(action)) {\n seppuku = true;\n try {\n // await for result either sync or async and serialize it\n const result = await actions.get(action)(...args);\n if (result !== void 0) {\n const serialized = stringify(transform ? transform(result) : result);\n // store the result for \"the very next\" event listener call\n results.set(id, serialized);\n // communicate the required SharedArrayBuffer length out of the\n // resulting serialized string\n sb[1] = serialized.length;\n }\n }\n catch (_) {\n error = _;\n }\n finally {\n seppuku = false;\n }\n }\n // unknown action should be notified as missing on the main thread\n else {\n error = new Error(`Unsupported action: ${action}`);\n }\n // unlock the wait lock later on\n sb[0] = 1;\n }\n // no action means: get results out of the well known `id`\n // wait lock automatically unlocked here as no `0` value would\n // possibly ever land at index `0`\n else {\n const result = results.get(id);\n results.delete(id);\n // populate the SharedArrayBuffer with utf-16 chars code\n for (let ui16a = new Uint16Array(sb.buffer), i = 0; i < result.length; i++)\n ui16a[i] = result.charCodeAt(i);\n }\n // release te worker waiting either the length or the result\n notify(sb, 0);\n if (error) throw error;\n }\n });\n }\n // store this action callback allowing the setter in the process\n return !!actions.set(action, callback);\n }\n }));\n }\n return context.get(self);\n};\n\ncoincident.transfer = (...args) => (buffers.add(args), args);\n\nexport default coincident;\n","export function createMutex() {\n let promise;\n let resolve;\n const lock = async () => {\n while (promise) {\n await promise;\n }\n promise = new Promise((res) => {\n resolve = res;\n });\n };\n const unlock = async () => {\n const res = resolve;\n promise = undefined;\n resolve = undefined;\n res?.();\n };\n return { lock, unlock };\n}\n//# sourceMappingURL=create-mutex.js.map","export async function normalizeDatabaseFile(dbFile, convertStreamTo) {\n let bufferOrStream;\n if (dbFile instanceof Blob) {\n bufferOrStream = dbFile.stream();\n }\n else {\n bufferOrStream = dbFile;\n }\n if (bufferOrStream instanceof ReadableStream && convertStreamTo) {\n const stream = bufferOrStream;\n const reader = stream.getReader();\n switch (convertStreamTo) {\n case 'callback':\n return async () => {\n const chunk = await reader.read();\n return chunk.value;\n };\n case 'buffer':\n const chunks = [];\n let streamDone = false;\n while (!streamDone) {\n const chunk = await reader.read();\n if (chunk.value)\n chunks.push(chunk.value);\n streamDone = chunk.done;\n }\n const arrayLength = chunks.reduce((length, chunk) => {\n return length + chunk.length;\n }, 0);\n const buffer = new Uint8Array(arrayLength);\n let offset = 0;\n chunks.forEach((chunk) => {\n buffer.set(chunk, offset);\n offset += chunk.length;\n });\n return buffer.buffer;\n }\n }\n else {\n return bufferOrStream;\n }\n}\n//# sourceMappingURL=normalize-database-file.js.map","import { normalizeDatabaseFile } from '../lib/normalize-database-file.js';\nexport class SQLiteMemoryDriver {\n constructor(sqlite3InitModule) {\n Object.defineProperty(this, \"sqlite3InitModule\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: sqlite3InitModule\n });\n Object.defineProperty(this, \"sqlite3\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"db\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"config\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"pointers\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: []\n });\n Object.defineProperty(this, \"writeCallbacks\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: new Set()\n });\n Object.defineProperty(this, \"storageType\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: 'memory'\n });\n }\n async init(config) {\n const { databasePath } = config;\n const flags = this.getFlags(config);\n if (!this.sqlite3InitModule) {\n const { default: sqlite3InitModule } = await import('@sqlite.org/sqlite-wasm');\n this.sqlite3InitModule = sqlite3InitModule;\n }\n if (!this.sqlite3) {\n this.sqlite3 = await this.sqlite3InitModule();\n }\n if (this.db) {\n await this.destroy();\n }\n this.db = new this.sqlite3.oo1.DB(databasePath, flags);\n this.config = config;\n this.initWriteHook();\n }\n onWrite(callback) {\n this.writeCallbacks.add(callback);\n return () => {\n this.writeCallbacks.delete(callback);\n };\n }\n async exec(statement) {\n if (!this.db)\n throw new Error('Driver not initialized');\n return this.execOnDb(this.db, statement);\n }\n async execBatch(statements) {\n if (!this.db)\n throw new Error('Driver not initialized');\n const results = [];\n this.db.transaction((tx) => {\n const prepared = new Map();\n try {\n for (let statement of statements) {\n let stmt = prepared.get(statement.sql);\n if (!stmt) {\n const newStmt = tx.prepare(statement.sql);\n prepared.set(statement.sql, newStmt);\n stmt = newStmt;\n }\n if (statement.params?.length) {\n stmt.bind(statement.params);\n }\n let columns = [];\n let rows = [];\n while (stmt.step()) {\n columns = stmt.getColumnNames([]);\n rows.push(stmt.get([]));\n }\n results.push({ columns, rows });\n stmt.reset();\n }\n }\n finally {\n prepared.forEach((stmt) => {\n stmt.finalize();\n });\n }\n });\n return results;\n }\n async isDatabasePersisted() {\n return false;\n }\n async getDatabaseSizeBytes() {\n const sizeResult = await this.exec({\n sql: `SELECT page_count * page_size AS size \n\t\t\t\tFROM pragma_page_count(), pragma_page_size()`,\n method: 'get',\n });\n const size = sizeResult?.rows?.[0];\n if (typeof size !== 'number') {\n throw new Error('Failed to query database size');\n }\n return size;\n }\n async createFunction(fn) {\n if (!this.db)\n throw new Error('Driver not initialized');\n switch (fn.type) {\n case 'callback':\n case 'scalar':\n this.db.createFunction({\n name: fn.name,\n xFunc: (_, ...args) => fn.func(...args),\n arity: -1,\n });\n break;\n case 'aggregate':\n this.db.createFunction({\n name: fn.name,\n xStep: (_, ...args) => fn.func.step(...args),\n xFinal: (_, ...args) => fn.func.final(...args),\n arity: -1,\n });\n break;\n }\n }\n async import(database) {\n if (!this.sqlite3 || !this.db || !this.config) {\n throw new Error('Driver not initialized');\n }\n const data = await normalizeDatabaseFile(database, 'buffer');\n const dataPointer = this.sqlite3.wasm.allocFromTypedArray(data);\n this.pointers.push(dataPointer);\n const resultCode = this.sqlite3.capi.sqlite3_deserialize(this.db, 'main', dataPointer, data.byteLength, data.byteLength, this.config.readOnly\n ? this.sqlite3.capi.SQLITE_DESERIALIZE_READONLY\n : this.sqlite3.capi.SQLITE_DESERIALIZE_RESIZEABLE);\n this.db.checkRc(resultCode);\n }\n async export() {\n if (!this.sqlite3 || !this.db) {\n throw new Error('Driver not initialized');\n }\n return {\n name: 'database.sqlite3',\n data: this.sqlite3.capi.sqlite3_js_db_export(this.db),\n };\n }\n async clear() { }\n async destroy() {\n this.closeDb();\n this.pointers.forEach((pointer) => this.sqlite3?.wasm.dealloc(pointer));\n this.pointers = [];\n this.writeCallbacks.clear();\n }\n getFlags(config) {\n const { readOnly, verbose } = config;\n const parts = [readOnly === true ? 'r' : 'cw', verbose === true ? 't' : ''];\n return parts.join('');\n }\n execOnDb(db, statement) {\n const statementData = {\n rows: [],\n columns: [],\n };\n const rows = db.exec({\n sql: statement.sql,\n bind: statement.params,\n returnValue: 'resultRows',\n rowMode: 'array',\n columnNames: statementData.columns,\n });\n switch (statement.method) {\n case 'run':\n break;\n case 'get':\n statementData.rows = rows[0] ?? [];\n break;\n case 'all':\n default:\n statementData.rows = rows;\n break;\n }\n return statementData;\n }\n initWriteHook() {\n if (!this.config?.reactive)\n return;\n if (!this.sqlite3 || !this.db) {\n throw new Error('Driver not initialized');\n }\n const opMap = {\n [this.sqlite3.capi.SQLITE_INSERT]: 'insert',\n [this.sqlite3.capi.SQLITE_UPDATE]: 'update',\n [this.sqlite3.capi.SQLITE_DELETE]: 'delete',\n };\n this.sqlite3.capi.sqlite3_update_hook(this.db, (_ctx, opId, _db, table, rowid) => {\n this.writeCallbacks.forEach((cb) => {\n cb({ table, rowid, operation: opMap[opId] });\n });\n }, 0);\n }\n closeDb() {\n if (this.db) {\n this.db.close();\n this.db = undefined;\n }\n }\n}\n//# sourceMappingURL=sqlite-memory-driver.js.map","/**\n * Lodash (Custom Build) \n * Build: `lodash modularize exports=\"es\" include=\"debounce\" -p -o ./`\n * Copyright JS Foundation and other contributors \n * Released under MIT license \n * Based on Underscore.js 1.8.3 \n * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n */\nexport function debounce(func, wait, options) {\n let lastArgs;\n let lastThis;\n let maxWait;\n let result;\n let timerId;\n let lastCallTime;\n let lastInvokeTime = 0;\n let leading = false;\n let maxing = false;\n let trailing = true;\n if (typeof func !== 'function') {\n throw new TypeError('Expected a function');\n }\n wait = Number(wait) || 0;\n if (typeof options === 'object' && options !== null) {\n leading = !!options.leading;\n maxing = 'maxWait' in options;\n maxWait = maxing ? Math.max(Number(options.maxWait) || 0, wait) : 0;\n trailing = 'trailing' in options ? !!options.trailing : trailing;\n }\n function invokeFunc(time) {\n const args = lastArgs;\n const thisArg = lastThis;\n lastArgs = lastThis = undefined;\n lastInvokeTime = time;\n result = func.apply(thisArg, args);\n return result;\n }\n function leadingEdge(time) {\n lastInvokeTime = time;\n timerId = setTimeout(timerExpired, wait);\n return leading ? invokeFunc(time) : result;\n }\n function remainingWait(time) {\n const timeSinceLastCall = time - (lastCallTime ?? 0);\n const timeSinceLastInvoke = time - lastInvokeTime;\n const timeWaiting = wait - timeSinceLastCall;\n return maxing\n ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)\n : timeWaiting;\n }\n function shouldInvoke(time) {\n const timeSinceLastCall = time - (lastCallTime ?? 0);\n const timeSinceLastInvoke = time - lastInvokeTime;\n return (lastCallTime === undefined ||\n timeSinceLastCall >= wait ||\n timeSinceLastCall < 0 ||\n (maxing && timeSinceLastInvoke >= maxWait));\n }\n function timerExpired() {\n const time = Date.now();\n if (shouldInvoke(time)) {\n return trailingEdge(time);\n }\n timerId = setTimeout(timerExpired, remainingWait(time));\n }\n function trailingEdge(time) {\n timerId = undefined;\n if (trailing && lastArgs) {\n return invokeFunc(time);\n }\n lastArgs = lastThis = undefined;\n return result;\n }\n function cancel() {\n if (timerId !== undefined) {\n clearTimeout(timerId);\n }\n lastInvokeTime = 0;\n lastArgs = lastCallTime = lastThis = timerId = undefined;\n }\n function flush() {\n return timerId === undefined ? result : trailingEdge(Date.now());\n }\n function debounced() {\n const time = Date.now();\n const isInvoking = shouldInvoke(time);\n // @ts-ignore\n lastArgs = arguments;\n // @ts-ignore\n lastThis = this;\n lastCallTime = time;\n if (isInvoking) {\n if (timerId === undefined) {\n return leadingEdge(lastCallTime);\n }\n if (maxing) {\n timerId = setTimeout(timerExpired, wait);\n return invokeFunc(lastCallTime);\n }\n }\n if (timerId === undefined) {\n timerId = setTimeout(timerExpired, wait);\n }\n return result;\n }\n debounced.cancel = cancel;\n debounced.flush = flush;\n return debounced;\n}\n//# sourceMappingURL=debounce.js.map","export function getQueryKey() {\n return crypto.randomUUID();\n}\n//# sourceMappingURL=get-query-key.js.map","import { getQueryKey } from './get-query-key.js';\nexport function getDatabaseKey(databasePath, clientKey) {\n switch (databasePath) {\n case 'session':\n case ':sessionStorage:':\n // The sessionStorage DB can be shared between clients in the same tab\n let sessionKey = sessionStorage._sqlocal_session_key;\n if (!sessionKey) {\n sessionKey = getQueryKey();\n sessionStorage._sqlocal_session_key = sessionKey;\n }\n return `session:${sessionKey}`;\n case 'local':\n case ':localStorage:':\n // There's only one localStorage DB per origin\n return 'local';\n case ':memory:':\n // Each memory DB is unique to a client\n return `memory:${clientKey}`;\n default:\n // OPFS DBs are shared by path across same-origin tabs\n return `path:${databasePath}`;\n }\n}\n//# sourceMappingURL=get-database-key.js.map","import coincident from 'coincident';\nimport { createMutex } from './lib/create-mutex.js';\nimport { SQLiteMemoryDriver } from './drivers/sqlite-memory-driver.js';\nimport { debounce } from './lib/debounce.js';\nimport { getDatabaseKey } from './lib/get-database-key.js';\nexport class SQLocalProcessor {\n constructor(driver) {\n Object.defineProperty(this, \"driver\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"config\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: {}\n });\n Object.defineProperty(this, \"userFunctions\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: new Map()\n });\n Object.defineProperty(this, \"initMutex\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: createMutex()\n });\n Object.defineProperty(this, \"transactionMutex\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: createMutex()\n });\n Object.defineProperty(this, \"transactionKey\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: null\n });\n Object.defineProperty(this, \"proxy\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"dirtyTables\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: new Set()\n });\n Object.defineProperty(this, \"effectsChannel\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"reinitChannel\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"onmessage\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"init\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (reason) => {\n if (!this.config.databasePath || !this.config.clientKey)\n return;\n await this.initMutex.lock();\n try {\n try {\n await this.driver.init(this.config);\n }\n catch {\n console.warn(`Persistence failed, so ${this.config.databasePath} will not be saved. For origin private file system persistence, make sure your web server is configured to use the correct HTTP response headers (See https://sqlocal.dev/guide/setup#cross-origin-isolation).`);\n this.config.databasePath = ':memory:';\n this.driver = new SQLiteMemoryDriver();\n await this.driver.init(this.config);\n }\n const dbKey = getDatabaseKey(this.config.databasePath, this.config.clientKey);\n this.reinitChannel = new BroadcastChannel(`_sqlocal_reinit_(${dbKey})`);\n this.reinitChannel.onmessage = (event) => {\n const message = event.data;\n if (this.config.clientKey === message.clientKey)\n return;\n switch (message.type) {\n case 'reinit':\n this.init(message.reason);\n break;\n case 'close':\n this.driver.destroy();\n break;\n }\n };\n if (this.config.reactive) {\n this.effectsChannel = new BroadcastChannel(`_sqlocal_effects_(${dbKey})`);\n this.driver.onWrite(async (change) => {\n this.dirtyTables.add(change.table);\n await this.transactionMutex.lock();\n this.emitEffectsDebounced();\n await this.transactionMutex.unlock();\n });\n }\n await Promise.all(Array.from(this.userFunctions.values()).map((fn) => {\n return this.initUserFunction(fn);\n }));\n await this.execInitStatements();\n this.emitMessage({ type: 'event', event: 'connect', reason });\n }\n catch (error) {\n this.emitMessage({\n type: 'error',\n error,\n queryKey: null,\n });\n await this.destroy();\n }\n finally {\n await this.initMutex.unlock();\n }\n }\n });\n Object.defineProperty(this, \"postMessage\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (event, _transfer) => {\n const message = event instanceof MessageEvent ? event.data : event;\n await this.initMutex.lock();\n switch (message.type) {\n case 'config':\n this.editConfig(message);\n break;\n case 'query':\n case 'batch':\n case 'transaction':\n this.exec(message);\n break;\n case 'function':\n this.createUserFunction(message);\n break;\n case 'getinfo':\n this.getDatabaseInfo(message);\n break;\n case 'import':\n this.importDb(message);\n break;\n case 'export':\n this.exportDb(message);\n break;\n case 'delete':\n this.deleteDb(message);\n break;\n case 'destroy':\n this.destroy(message);\n break;\n }\n await this.initMutex.unlock();\n }\n });\n Object.defineProperty(this, \"emitMessage\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: (message, transfer = []) => {\n if (this.onmessage) {\n this.onmessage(message, transfer);\n }\n }\n });\n Object.defineProperty(this, \"emitEffects\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: () => {\n if (!this.effectsChannel || this.dirtyTables.size === 0)\n return;\n this.effectsChannel.postMessage({\n type: 'effects',\n tables: [...this.dirtyTables],\n });\n this.dirtyTables.clear();\n }\n });\n Object.defineProperty(this, \"emitEffectsDebounced\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: debounce(() => this.emitEffects(), 32, {\n maxWait: 180,\n })\n });\n Object.defineProperty(this, \"editConfig\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: (message) => {\n this.config = message.config;\n this.init('initial');\n }\n });\n Object.defineProperty(this, \"exec\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (message) => {\n try {\n const response = {\n type: 'data',\n queryKey: message.queryKey,\n data: [],\n };\n switch (message.type) {\n case 'query':\n const partOfTransaction = this.transactionKey !== null &&\n this.transactionKey === message.transactionKey;\n try {\n if (!partOfTransaction) {\n await this.transactionMutex.lock();\n }\n const statementData = await this.driver.exec(message);\n response.data.push(statementData);\n }\n finally {\n if (!partOfTransaction) {\n await this.transactionMutex.unlock();\n }\n }\n break;\n case 'batch':\n try {\n await this.transactionMutex.lock();\n const results = await this.driver.execBatch(message.statements);\n response.data.push(...results);\n }\n finally {\n await this.transactionMutex.unlock();\n }\n break;\n case 'transaction':\n if (message.action === 'begin') {\n await this.transactionMutex.lock();\n this.transactionKey = message.transactionKey;\n await this.driver.exec({ sql: 'BEGIN' });\n }\n if ((message.action === 'commit' || message.action === 'rollback') &&\n this.transactionKey !== null &&\n this.transactionKey === message.transactionKey) {\n const sql = message.action === 'commit' ? 'COMMIT' : 'ROLLBACK';\n await this.driver.exec({ sql });\n this.transactionKey = null;\n await this.transactionMutex.unlock();\n }\n break;\n }\n this.emitMessage(response);\n }\n catch (error) {\n this.emitMessage({\n type: 'error',\n error,\n queryKey: message.queryKey,\n });\n }\n }\n });\n Object.defineProperty(this, \"execInitStatements\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async () => {\n if (this.config.onInitStatements) {\n for (let statement of this.config.onInitStatements) {\n await this.driver.exec(statement);\n }\n }\n }\n });\n Object.defineProperty(this, \"getDatabaseInfo\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (message) => {\n try {\n this.emitMessage({\n type: 'info',\n queryKey: message.queryKey,\n info: {\n databasePath: this.config.databasePath,\n storageType: this.driver.storageType,\n databaseSizeBytes: await this.driver.getDatabaseSizeBytes(),\n persisted: await this.driver.isDatabasePersisted(),\n },\n });\n }\n catch (error) {\n this.emitMessage({\n type: 'error',\n queryKey: message.queryKey,\n error,\n });\n }\n }\n });\n Object.defineProperty(this, \"createUserFunction\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (message) => {\n const { functionName: name, functionType: type, queryKey } = message;\n let fn;\n if (this.userFunctions.has(name)) {\n this.emitMessage({\n type: 'error',\n error: new Error(`A user-defined function with the name \"${name}\" has already been created for this SQLocal instance.`),\n queryKey,\n });\n return;\n }\n switch (type) {\n case 'callback':\n fn = {\n type,\n name,\n func: (...args) => {\n this.emitMessage({ type: 'callback', name, args });\n },\n };\n break;\n case 'scalar':\n fn = {\n type,\n name,\n func: this.proxy[`_sqlocal_func_${name}`],\n };\n break;\n case 'aggregate':\n fn = {\n type,\n name,\n func: {\n step: this.proxy[`_sqlocal_func_${name}_step`],\n final: this.proxy[`_sqlocal_func_${name}_final`],\n },\n };\n break;\n }\n try {\n await this.initUserFunction(fn);\n this.emitMessage({\n type: 'success',\n queryKey,\n });\n }\n catch (error) {\n this.emitMessage({\n type: 'error',\n error,\n queryKey,\n });\n }\n }\n });\n Object.defineProperty(this, \"initUserFunction\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (fn) => {\n await this.driver.createFunction(fn);\n this.userFunctions.set(fn.name, fn);\n }\n });\n Object.defineProperty(this, \"importDb\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (message) => {\n const { queryKey, database } = message;\n let errored = false;\n try {\n await this.driver.import(database);\n if (this.driver.storageType === 'memory') {\n await this.execInitStatements();\n }\n }\n catch (error) {\n this.emitMessage({\n type: 'error',\n error,\n queryKey,\n });\n errored = true;\n }\n finally {\n if (this.driver.storageType !== 'memory') {\n await this.init('overwrite');\n }\n }\n if (!errored) {\n this.emitMessage({\n type: 'success',\n queryKey,\n });\n }\n }\n });\n Object.defineProperty(this, \"exportDb\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (message) => {\n const { queryKey } = message;\n try {\n const { name, data } = await this.driver.export();\n this.emitMessage({\n type: 'buffer',\n queryKey,\n bufferName: name,\n buffer: data,\n }, [data]);\n }\n catch (error) {\n this.emitMessage({\n type: 'error',\n error,\n queryKey,\n });\n }\n }\n });\n Object.defineProperty(this, \"deleteDb\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (message) => {\n const { queryKey } = message;\n let errored = false;\n try {\n await this.driver.clear();\n }\n catch (error) {\n this.emitMessage({\n type: 'error',\n error,\n queryKey,\n });\n errored = true;\n }\n finally {\n await this.init('delete');\n }\n if (!errored) {\n this.emitMessage({\n type: 'success',\n queryKey,\n });\n }\n }\n });\n Object.defineProperty(this, \"destroy\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (message) => {\n await this.driver.exec({ sql: 'PRAGMA optimize' });\n await this.driver.destroy();\n if (this.effectsChannel) {\n this.emitEffectsDebounced.flush();\n this.effectsChannel.close();\n this.effectsChannel = undefined;\n }\n if (this.reinitChannel) {\n this.reinitChannel.close();\n this.reinitChannel = undefined;\n }\n if (message) {\n this.emitMessage({\n type: 'success',\n queryKey: message.queryKey,\n });\n }\n }\n });\n const isInWorker = typeof WorkerGlobalScope !== 'undefined' &&\n globalThis instanceof WorkerGlobalScope;\n const proxy = isInWorker ? coincident(globalThis) : globalThis;\n this.proxy = proxy;\n this.driver = driver;\n }\n}\n//# sourceMappingURL=processor.js.map","export function sqlTag(queryTemplate, ...params) {\n return {\n sql: queryTemplate.join('?'),\n params,\n };\n}\n//# sourceMappingURL=sql-tag.js.map","function isArrayOfArrays(rows) {\n return !rows.some((row) => !Array.isArray(row));\n}\nexport function convertRowsToObjects(rows, columns) {\n let checkedRows;\n if (isArrayOfArrays(rows)) {\n checkedRows = rows;\n }\n else {\n checkedRows = [rows];\n }\n return checkedRows.map((row) => {\n const rowObj = {};\n columns.forEach((column, columnIndex) => {\n rowObj[column] = row[columnIndex];\n });\n return rowObj;\n });\n}\n//# sourceMappingURL=convert-rows-to-objects.js.map","import { sqlTag } from './sql-tag.js';\nfunction isDrizzleStatement(statement) {\n return (typeof statement === 'object' &&\n statement !== null &&\n 'getSQL' in statement &&\n typeof statement.getSQL === 'function');\n}\nfunction isStatement(statement) {\n return (typeof statement === 'object' &&\n statement !== null &&\n 'sql' in statement === true &&\n typeof statement.sql === 'string' &&\n 'params' in statement === true);\n}\nexport function normalizeStatement(statement) {\n if (typeof statement === 'function') {\n statement = statement(sqlTag);\n }\n if (isDrizzleStatement(statement)) {\n try {\n if (!('toSQL' in statement && typeof statement.toSQL === 'function')) {\n throw 1;\n }\n const drizzleStatement = statement.toSQL();\n if (!isStatement(drizzleStatement)) {\n throw 2;\n }\n const exec = 'all' in statement && typeof statement.all === 'function'\n ? statement.all\n : undefined;\n return {\n ...drizzleStatement,\n exec: exec ? () => exec() : undefined,\n };\n }\n catch {\n throw new Error('The passed statement could not be parsed.');\n }\n }\n const sql = statement.sql;\n let params = [];\n if ('params' in statement) {\n params = statement.params;\n }\n else if ('parameters' in statement) {\n params = statement.parameters;\n }\n return { sql, params };\n}\n//# sourceMappingURL=normalize-statement.js.map","import { sqlTag } from './sql-tag.js';\nexport function normalizeSql(maybeQueryTemplate, params) {\n let statement;\n if (typeof maybeQueryTemplate === 'string') {\n statement = { sql: maybeQueryTemplate, params };\n }\n else {\n statement = sqlTag(maybeQueryTemplate, ...params);\n }\n return statement;\n}\n//# sourceMappingURL=normalize-sql.js.map","export async function mutationLock(mode, bypass, config, mutation) {\n if (!bypass && 'locks' in navigator) {\n return navigator.locks.request(`_sqlocal_mutation_(${config.databasePath})`, { mode }, mutation);\n }\n else {\n return mutation();\n }\n}\n//# sourceMappingURL=mutation-lock.js.map","import { SQLiteMemoryDriver } from './sqlite-memory-driver.js';\nexport class SQLiteKvvfsDriver extends SQLiteMemoryDriver {\n constructor(storageType, sqlite3InitModule) {\n super(sqlite3InitModule);\n Object.defineProperty(this, \"storageType\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: storageType\n });\n }\n async init(config) {\n const flags = this.getFlags(config);\n if (config.readOnly) {\n throw new Error(`SQLite storage type \"${this.storageType}\" does not support read-only mode.`);\n }\n if (!this.sqlite3InitModule) {\n const { default: sqlite3InitModule } = await import('@sqlite.org/sqlite-wasm');\n this.sqlite3InitModule = sqlite3InitModule;\n }\n if (!this.sqlite3) {\n this.sqlite3 = await this.sqlite3InitModule();\n }\n if (this.db) {\n await this.destroy();\n }\n this.db = new this.sqlite3.oo1.JsStorageDb({\n filename: this.storageType,\n flags,\n });\n this.config = config;\n this.initWriteHook();\n }\n async isDatabasePersisted() {\n return navigator.storage?.persisted();\n }\n async getDatabaseSizeBytes() {\n if (!this.db)\n throw new Error('Driver not initialized');\n return this.db.storageSize();\n }\n async import(database) {\n const memdb = new SQLiteMemoryDriver();\n await memdb.init({});\n await memdb.import(database);\n await this.clear();\n await memdb.exec({\n sql: `VACUUM INTO 'file:${this.storageType}?vfs=kvvfs'`,\n });\n await memdb.destroy();\n }\n async clear() {\n if (!this.db)\n throw new Error('Driver not initialized');\n this.db.clearStorage();\n }\n async destroy() {\n this.closeDb();\n this.writeCallbacks.clear();\n }\n}\n//# sourceMappingURL=sqlite-kvvfs-driver.js.map","var _a, _b;\nimport coincident from 'coincident';\nimport { SQLocalProcessor } from './processor.js';\nimport { sqlTag } from './lib/sql-tag.js';\nimport { convertRowsToObjects } from './lib/convert-rows-to-objects.js';\nimport { normalizeStatement } from './lib/normalize-statement.js';\nimport { getQueryKey } from './lib/get-query-key.js';\nimport { normalizeSql } from './lib/normalize-sql.js';\nimport { mutationLock } from './lib/mutation-lock.js';\nimport { normalizeDatabaseFile } from './lib/normalize-database-file.js';\nimport { SQLiteMemoryDriver } from './drivers/sqlite-memory-driver.js';\nimport { SQLiteKvvfsDriver } from './drivers/sqlite-kvvfs-driver.js';\nimport { getDatabaseKey } from './lib/get-database-key.js';\nexport class SQLocal {\n constructor(config) {\n Object.defineProperty(this, \"config\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"clientKey\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"processor\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"isDestroyed\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: false\n });\n Object.defineProperty(this, \"bypassMutationLock\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: false\n });\n Object.defineProperty(this, \"transactionQueryKeyQueue\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: []\n });\n Object.defineProperty(this, \"userCallbacks\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: new Map()\n });\n Object.defineProperty(this, \"queriesInProgress\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: new Map()\n });\n Object.defineProperty(this, \"proxy\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"reinitChannel\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"effectsChannel\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: void 0\n });\n Object.defineProperty(this, \"processMessageEvent\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: (event) => {\n const message = event instanceof MessageEvent ? event.data : event;\n const queries = this.queriesInProgress;\n switch (message.type) {\n case 'success':\n case 'data':\n case 'buffer':\n case 'info':\n case 'error':\n if (message.queryKey && queries.has(message.queryKey)) {\n const [resolve, reject] = queries.get(message.queryKey);\n if (message.type === 'error') {\n reject(message.error);\n }\n else {\n resolve(message);\n }\n queries.delete(message.queryKey);\n }\n else if (message.type === 'error') {\n throw message.error;\n }\n break;\n case 'callback':\n const userCallback = this.userCallbacks.get(message.name);\n if (userCallback) {\n userCallback(...(message.args ?? []));\n }\n break;\n case 'event':\n this.config.onConnect?.(message.reason);\n break;\n }\n }\n });\n Object.defineProperty(this, \"createQuery\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (message) => {\n return mutationLock('shared', this.bypassMutationLock ||\n message.type === 'import' ||\n message.type === 'delete', this.config, async () => {\n if (this.isDestroyed === true) {\n throw new Error('This SQLocal client has been destroyed. You will need to initialize a new client in order to make further queries.');\n }\n const queryKey = getQueryKey();\n switch (message.type) {\n case 'import':\n this.processor.postMessage({\n ...message,\n queryKey,\n }, [message.database]);\n break;\n default:\n this.processor.postMessage({\n ...message,\n queryKey,\n });\n break;\n }\n return new Promise((resolve, reject) => {\n this.queriesInProgress.set(queryKey, [resolve, reject]);\n });\n });\n }\n });\n Object.defineProperty(this, \"broadcast\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: (message) => {\n this.reinitChannel.postMessage(message);\n }\n });\n Object.defineProperty(this, \"exec\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (sql, params, method = 'all', transactionKey) => {\n const message = await this.createQuery({\n type: 'query',\n transactionKey,\n sql,\n params,\n method,\n });\n const data = {\n rows: [],\n columns: [],\n };\n if (message.type === 'data') {\n data.rows = message.data[0]?.rows ?? [];\n data.columns = message.data[0]?.columns ?? [];\n }\n return data;\n }\n });\n Object.defineProperty(this, \"execBatch\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (statements) => {\n const message = await this.createQuery({\n type: 'batch',\n statements,\n });\n const data = new Array(statements.length).fill({\n rows: [],\n columns: [],\n });\n if (message.type === 'data') {\n message.data.forEach((result, resultIndex) => {\n data[resultIndex] = result;\n });\n }\n return data;\n }\n });\n Object.defineProperty(this, \"sql\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (queryTemplate, ...params) => {\n const statement = normalizeSql(queryTemplate, params);\n const { rows, columns } = await this.exec(statement.sql, statement.params, 'all');\n const resultRecords = convertRowsToObjects(rows, columns);\n return resultRecords;\n }\n });\n Object.defineProperty(this, \"batch\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (passStatements) => {\n const statements = passStatements(sqlTag);\n const data = await this.execBatch(statements);\n return data.map(({ rows, columns }) => {\n const resultRecords = convertRowsToObjects(rows, columns);\n return resultRecords;\n });\n }\n });\n Object.defineProperty(this, \"beginTransaction\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async () => {\n const transactionKey = getQueryKey();\n await this.createQuery({\n type: 'transaction',\n transactionKey,\n action: 'begin',\n });\n const query = async (passStatement) => {\n const statement = normalizeStatement(passStatement);\n if (statement.exec) {\n this.transactionQueryKeyQueue.push(transactionKey);\n return statement.exec();\n }\n const { rows, columns } = await this.exec(statement.sql, statement.params, 'all', transactionKey);\n const resultRecords = convertRowsToObjects(rows, columns);\n return resultRecords;\n };\n const sql = async (queryTemplate, ...params) => {\n const statement = normalizeSql(queryTemplate, params);\n const resultRecords = await query(statement);\n return resultRecords;\n };\n const commit = async () => {\n await this.createQuery({\n type: 'transaction',\n transactionKey,\n action: 'commit',\n });\n };\n const rollback = async () => {\n await this.createQuery({\n type: 'transaction',\n transactionKey,\n action: 'rollback',\n });\n };\n return {\n query,\n sql,\n commit,\n rollback,\n };\n }\n });\n Object.defineProperty(this, \"transaction\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (transaction) => {\n return mutationLock('exclusive', false, this.config, async () => {\n let tx;\n this.bypassMutationLock = true;\n try {\n tx = await this.beginTransaction();\n const result = await transaction({\n sql: tx.sql,\n query: tx.query,\n });\n await tx.commit();\n return result;\n }\n catch (err) {\n await tx?.rollback();\n throw err;\n }\n finally {\n this.bypassMutationLock = false;\n }\n });\n }\n });\n Object.defineProperty(this, \"reactiveQuery\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: (passStatement) => {\n let value = [];\n let gotFirstValue = false;\n let isListening = false;\n let updateCount = 0;\n const statement = normalizeStatement(passStatement);\n const watchedTables = new Set();\n const subObservers = new Set();\n const errObservers = new Set();\n const runStatement = async () => {\n try {\n const updateOrder = ++updateCount;\n if (watchedTables.size === 0) {\n const usedTables = await this.sql(\"SELECT name, wr FROM tables_used(?) WHERE type = 'table'\", statement.sql);\n const readTables = new Set();\n const writtenTables = new Set();\n usedTables.forEach((table) => {\n if (typeof table.name !== 'string')\n return;\n table.wr\n ? writtenTables.add(table.name)\n : readTables.add(table.name);\n });\n if (readTables.size === 0) {\n throw new Error('The passed SQL does not read any tables.');\n }\n if (Array.from(writtenTables).some((table) => readTables.has(table))) {\n throw new Error('The passed SQL would mutate one or more of the tables that it reads. Doing this in a reactive query would create an infinite loop.');\n }\n readTables.forEach((name) => watchedTables.add(name));\n }\n const results = statement.exec\n ? await statement.exec()\n : await this.sql(statement.sql, ...statement.params);\n if (updateOrder === updateCount) {\n value = results;\n gotFirstValue = true;\n subObservers.forEach((observer) => observer(value));\n }\n }\n catch (err) {\n errObservers.forEach((observer) => {\n observer(err instanceof Error ? err : new Error(String(err)));\n });\n }\n };\n const onEffect = (message) => {\n if (message.data.tables.some((table) => watchedTables.has(table))) {\n runStatement();\n }\n };\n return {\n get value() {\n return value;\n },\n subscribe: (onData, onError) => {\n if (!this.effectsChannel) {\n throw new Error('This SQLocal instance is not configured for reactive queries. Set the \"reactive\" option to enable them.');\n }\n if (!onError) {\n onError = (err) => {\n throw err;\n };\n }\n subObservers.add(onData);\n errObservers.add(onError);\n if (!isListening) {\n this.effectsChannel.addEventListener('message', onEffect);\n isListening = true;\n runStatement();\n }\n else if (gotFirstValue) {\n onData(value);\n }\n return {\n unsubscribe: () => {\n subObservers.delete(onData);\n errObservers.delete(onError);\n if (subObservers.size !== 0)\n return;\n this.effectsChannel?.removeEventListener('message', onEffect);\n isListening = false;\n },\n };\n },\n };\n }\n });\n Object.defineProperty(this, \"createCallbackFunction\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (funcName, func) => {\n await this.createQuery({\n type: 'function',\n functionName: funcName,\n functionType: 'callback',\n });\n this.userCallbacks.set(funcName, func);\n }\n });\n Object.defineProperty(this, \"createScalarFunction\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (funcName, func) => {\n const key = `_sqlocal_func_${funcName}`;\n const attachFunction = () => {\n this.proxy[key] = func;\n };\n if (this.proxy === globalThis) {\n attachFunction();\n }\n await this.createQuery({\n type: 'function',\n functionName: funcName,\n functionType: 'scalar',\n });\n if (this.proxy !== globalThis) {\n attachFunction();\n }\n }\n });\n Object.defineProperty(this, \"createAggregateFunction\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (funcName, func) => {\n const key = `_sqlocal_func_${funcName}`;\n const attachFunction = () => {\n this.proxy[`${key}_step`] = func.step;\n this.proxy[`${key}_final`] = func.final;\n };\n if (this.proxy === globalThis) {\n attachFunction();\n }\n await this.createQuery({\n type: 'function',\n functionName: funcName,\n functionType: 'aggregate',\n });\n if (this.proxy !== globalThis) {\n attachFunction();\n }\n }\n });\n Object.defineProperty(this, \"getDatabaseInfo\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async () => {\n const message = await this.createQuery({ type: 'getinfo' });\n if (message.type === 'info') {\n return message.info;\n }\n else {\n throw new Error('The database failed to return valid information.');\n }\n }\n });\n Object.defineProperty(this, \"getDatabaseFile\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async () => {\n const message = await this.createQuery({ type: 'export' });\n if (message.type === 'buffer') {\n return new File([message.buffer], message.bufferName, {\n type: 'application/x-sqlite3',\n });\n }\n else {\n throw new Error('The database failed to export.');\n }\n }\n });\n Object.defineProperty(this, \"overwriteDatabaseFile\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (databaseFile, beforeUnlock) => {\n await mutationLock('exclusive', false, this.config, async () => {\n try {\n this.broadcast({\n type: 'close',\n clientKey: this.clientKey,\n });\n const database = await normalizeDatabaseFile(databaseFile, 'buffer');\n await this.createQuery({\n type: 'import',\n database,\n });\n if (typeof beforeUnlock === 'function') {\n this.bypassMutationLock = true;\n await beforeUnlock();\n }\n this.broadcast({\n type: 'reinit',\n clientKey: this.clientKey,\n reason: 'overwrite',\n });\n }\n finally {\n this.bypassMutationLock = false;\n }\n });\n }\n });\n Object.defineProperty(this, \"deleteDatabaseFile\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async (beforeUnlock) => {\n await mutationLock('exclusive', false, this.config, async () => {\n try {\n this.broadcast({\n type: 'close',\n clientKey: this.clientKey,\n });\n await this.createQuery({\n type: 'delete',\n });\n if (typeof beforeUnlock === 'function') {\n this.bypassMutationLock = true;\n await beforeUnlock();\n }\n this.broadcast({\n type: 'reinit',\n clientKey: this.clientKey,\n reason: 'delete',\n });\n }\n finally {\n this.bypassMutationLock = false;\n }\n });\n }\n });\n Object.defineProperty(this, \"destroy\", {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async () => {\n await this.createQuery({ type: 'destroy' });\n if (typeof globalThis.Worker !== 'undefined' &&\n this.processor instanceof Worker) {\n this.processor.removeEventListener('message', this.processMessageEvent);\n this.processor.terminate();\n }\n this.queriesInProgress.clear();\n this.userCallbacks.clear();\n this.reinitChannel.close();\n this.effectsChannel?.close();\n this.isDestroyed = true;\n }\n });\n Object.defineProperty(this, _a, {\n enumerable: true,\n configurable: true,\n writable: true,\n value: () => {\n this.destroy();\n }\n });\n Object.defineProperty(this, _b, {\n enumerable: true,\n configurable: true,\n writable: true,\n value: async () => {\n await this.destroy();\n }\n });\n const clientConfig = typeof config === 'string' ? { databasePath: config } : config;\n const { onInit, onConnect, processor, ...commonConfig } = clientConfig;\n const { databasePath } = commonConfig;\n this.config = clientConfig;\n this.clientKey = getQueryKey();\n const dbKey = getDatabaseKey(databasePath, this.clientKey);\n this.reinitChannel = new BroadcastChannel(`_sqlocal_reinit_(${dbKey})`);\n if (commonConfig.reactive) {\n this.effectsChannel = new BroadcastChannel(`_sqlocal_effects_(${dbKey})`);\n }\n if (typeof processor !== 'undefined') {\n this.processor = processor;\n }\n else if (databasePath === 'local' || databasePath === ':localStorage:') {\n const driver = new SQLiteKvvfsDriver('local');\n this.processor = new SQLocalProcessor(driver);\n }\n else if (databasePath === 'session' ||\n databasePath === ':sessionStorage:') {\n const driver = new SQLiteKvvfsDriver('session');\n this.processor = new SQLocalProcessor(driver);\n }\n else if (typeof globalThis.Worker !== 'undefined' &&\n databasePath !== ':memory:') {\n this.processor = new Worker(new URL('./worker', import.meta.url), {\n type: 'module',\n });\n }\n else {\n const driver = new SQLiteMemoryDriver();\n this.processor = new SQLocalProcessor(driver);\n }\n if (this.processor instanceof SQLocalProcessor) {\n this.processor.onmessage = (message) => this.processMessageEvent(message);\n this.proxy = globalThis;\n }\n else {\n this.processor.addEventListener('message', this.processMessageEvent);\n this.proxy = coincident(this.processor);\n }\n this.processor.postMessage({\n type: 'config',\n config: {\n ...commonConfig,\n clientKey: this.clientKey,\n onInitStatements: onInit?.(sqlTag) ?? [],\n },\n });\n }\n}\n_a = Symbol.dispose, _b = Symbol.asyncDispose;\n//# sourceMappingURL=client.js.map","/**\n * Database Module\n *\n * Uses SQLocal directly with BroadcastChannel for cross-tab coordination.\n *\n * Why this approach instead of SharedWorker?\n * - SQLocal already uses its own internal worker for OPFS access\n * - Wrapping it in another SharedWorker adds complexity and causes issues\n * - BroadcastChannel provides simple cross-tab communication\n * - Each tab has its own SQLocal instance but they share the same OPFS database file\n *\n * Usage:\n * import { sql, dbReady, addLocation, getLocations } from './database.js';\n *\n * await dbReady;\n * await addLocation('Point A', -1.5, 7.5);\n * const locations = await getLocations();\n */\n\nimport { SQLocal } from 'sqlocal';\n\n// Database configuration\nconst DATABASE_PATH = 'lupmis2.db';\nconst BROADCAST_CHANNEL = 'lupmis-db-sync';\n\n// Create SQLocal instance\nconst db = new SQLocal(DATABASE_PATH);\n\n// Get the sql tagged template function\nconst { sql } = db;\n\nconsole.log('[Database] SQLocal instance created for:', DATABASE_PATH);\n\n// Export sql for direct queries\nexport { sql };\n\n// Create broadcast channel for cross-tab coordination\nconst channel = new BroadcastChannel(BROADCAST_CHANNEL);\n\n// Track if database is ready\nlet isReady = false;\nlet readyResolve;\nlet readyReject;\n\nexport const dbReady = new Promise((resolve, reject) => {\n readyResolve = resolve;\n readyReject = reject;\n});\n\n// Database change listeners\nconst changeListeners = new Set();\n\n/**\n * Subscribe to database changes (from any tab)\n * @param {Function} listener - Called with { table, action, id }\n * @returns {Function} Unsubscribe function\n */\nexport function onDatabaseChange(listener) {\n changeListeners.add(listener);\n return () => changeListeners.delete(listener);\n}\n\n// Handle messages from other tabs\nchannel.onmessage = (event) => {\n const { type, payload } = event.data;\n if (type === 'DB_CHANGE') {\n // Notify local listeners about changes from other tabs\n for (const listener of changeListeners) {\n try {\n listener(payload);\n } catch (e) {\n console.error('[Database] Change listener error:', e);\n }\n }\n }\n};\n\n/**\n * Broadcast a database change to other tabs\n */\nfunction broadcastChange(table, action, id = null) {\n channel.postMessage({\n type: 'DB_CHANGE',\n payload: { table, action, id, timestamp: Date.now() }\n });\n\n // Also notify local listeners\n for (const listener of changeListeners) {\n try {\n listener({ table, action, id, timestamp: Date.now(), local: true });\n } catch (e) {\n console.error('[Database] Change listener error:', e);\n }\n }\n}\n\n// ============================================================================\n// Database Initialization\n// ============================================================================\n\n/**\n * Initialize the database schema\n */\nexport async function initSchema() {\n try {\n console.log('[Database] Initializing schema...');\n\n // Test connection\n const testResult = await sql`SELECT sqlite_version() as version`;\n console.log('[Database] SQLite version:', testResult[0]?.version);\n\n // Create locations table\n console.log('[Database] Creating locations table...');\n await sql`\n CREATE TABLE IF NOT EXISTS locations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL,\n longitude REAL NOT NULL,\n latitude REAL NOT NULL,\n description TEXT,\n category TEXT DEFAULT 'default',\n created_at TEXT DEFAULT CURRENT_TIMESTAMP,\n updated_at TEXT DEFAULT CURRENT_TIMESTAMP,\n synced INTEGER DEFAULT 0\n )\n `;\n\n // Verify table exists\n const tablesAfterLocations = await sql`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;\n console.log('[Database] Locations table exists:', tablesAfterLocations.length > 0);\n\n // Create sync_log table\n console.log('[Database] Creating sync_log table...');\n await sql`\n CREATE TABLE IF NOT EXISTS sync_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n table_name TEXT NOT NULL,\n record_id INTEGER NOT NULL,\n action TEXT NOT NULL,\n timestamp TEXT DEFAULT CURRENT_TIMESTAMP,\n synced INTEGER DEFAULT 0\n )\n `;\n\n // Create remote_data cache table\n console.log('[Database] Creating remote_data table...');\n await sql`\n CREATE TABLE IF NOT EXISTS remote_data (\n key TEXT PRIMARY KEY,\n data TEXT NOT NULL,\n fetched_at TEXT DEFAULT CURRENT_TIMESTAMP\n )\n `;\n\n // Create collector_zones table for caching zone data\n console.log('[Database] Creating collector_zones table...');\n await sql`\n CREATE TABLE IF NOT EXISTS collector_zones (\n id INTEGER PRIMARY KEY,\n zone_name TEXT,\n geometry_wkt TEXT,\n properties TEXT,\n fetched_at TEXT DEFAULT CURRENT_TIMESTAMP\n )\n `;\n\n // Create parcels table — mirrors the server's spatial.lu_parcels structure\n // (land-use parcels). Attribute columns match the server 1:1 so the local\n // data viewer shows real fields rather than a JSON blob.\n // status — local-only flag: 'verified' = downloaded from the API,\n // 'new' = drawn locally and pending server verification.\n // fetched_at — local-only cache timestamp.\n // Migrate older databases that used the previous JSON-blob schema\n // (id, geometry_wkt, properties, status, fetched_at): if the new `upn`\n // column is missing, drop the cached table so it is recreated with the\n // lu_parcels columns. Cached server parcels re-download on next load.\n console.log('[Database] Creating parcels table...');\n try {\n const cols = await sql`PRAGMA table_info(parcels)`;\n if (cols.length > 0 && !cols.some((c) => c.name === 'upn')) {\n console.log('[Database] Migrating parcels table to lu_parcels structure (dropping old cache)...');\n await sql`DROP TABLE parcels`;\n }\n } catch (e) {\n console.warn('[Database] parcels migration check failed:', e);\n }\n await sql`\n CREATE TABLE IF NOT EXISTS parcels (\n id INTEGER PRIMARY KEY,\n upn TEXT,\n style INTEGER,\n landuse TEXT,\n zone_code TEXT,\n zone_name TEXT,\n sector TEXT,\n block TEXT,\n parcel_no TEXT,\n prop_no TEXT,\n st_name TEXT,\n prop_add TEXT,\n fac_name TEXT,\n min_height INTEGER,\n max_height INTEGER,\n eff_date TEXT,\n lp_name TEXT,\n locality TEXT,\n mmda TEXT,\n last_update TEXT,\n remarks TEXT,\n geometry_wkt TEXT,\n created_at TEXT,\n updated_at TEXT,\n districtid INTEGER,\n status TEXT DEFAULT 'verified',\n fetched_at TEXT DEFAULT CURRENT_TIMESTAMP\n )\n `;\n\n // Create building_footprints table for caching footprint data\n console.log('[Database] Creating building_footprints table...');\n await sql`\n CREATE TABLE IF NOT EXISTS building_footprints (\n id INTEGER PRIMARY KEY,\n geometry_wkt TEXT,\n properties TEXT,\n fetched_at TEXT DEFAULT CURRENT_TIMESTAMP\n )\n `;\n\n // Create osm_roads table for caching the OSM road network\n console.log('[Database] Creating osm_roads table...');\n await sql`\n CREATE TABLE IF NOT EXISTS osm_roads (\n osm_id INTEGER PRIMARY KEY,\n geometry_wkt TEXT,\n properties TEXT,\n fetched_at TEXT DEFAULT CURRENT_TIMESTAMP\n )\n `;\n\n // UPN-grid table — district sub-division grid (one cell per UPN prefix).\n // The grid is static per district, so the cache is populated once and only\n // refreshed when the user is associated with a different district. The\n // districtid column lets getLocalUpnGrid() detect that mismatch.\n console.log('[Database] Creating upn_grid table...');\n await sql`\n CREATE TABLE IF NOT EXISTS upn_grid (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n districtid INTEGER,\n upn_prefix TEXT,\n geometry_wkt TEXT,\n fetched_at TEXT DEFAULT CURRENT_TIMESTAMP\n )\n `;\n\n // ── GPS trails ──────────────────────────────────────────────────────\n // Recorded field-movement tracks. These are the local store for the\n // reusable GeoTracker module (src/geotracker/). `client_uuid` lets the\n // server de-duplicate re-synced trails; `satellites` is nullable because\n // the web Geolocation API does not expose it (only native builds do).\n console.log('[Database] Creating gps_trails table...');\n await sql`\n CREATE TABLE IF NOT EXISTS gps_trails (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n client_uuid TEXT UNIQUE,\n name TEXT,\n district_id TEXT,\n started_at TEXT NOT NULL,\n ended_at TEXT,\n status TEXT NOT NULL DEFAULT 'recording',\n point_count INTEGER NOT NULL DEFAULT 0,\n distance_m REAL NOT NULL DEFAULT 0,\n synced INTEGER NOT NULL DEFAULT 0,\n remote_id TEXT,\n created_at TEXT DEFAULT CURRENT_TIMESTAMP\n )\n `;\n\n console.log('[Database] Creating gps_trail_points table...');\n await sql`\n CREATE TABLE IF NOT EXISTS gps_trail_points (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n trail_id INTEGER NOT NULL,\n seq INTEGER NOT NULL,\n longitude REAL NOT NULL,\n latitude REAL NOT NULL,\n altitude REAL,\n accuracy REAL,\n altitude_accuracy REAL,\n heading REAL,\n speed REAL,\n satellites INTEGER,\n recorded_at TEXT NOT NULL\n )\n `;\n\n // ── External imports ────────────────────────────────────────────────\n // Staging for shapefile / GeoJSON / KML imports before they are uploaded\n // to the server. See LUPMIS2_Import_Upload_Design.docx §4.\n // external_imports — one row per imported file (the dataset)\n // external_import_features — staged features (rows pending upload)\n //\n // Status state machine (aligns with the database team's staged-upload\n // model — uploads land in spatial.lu_parcels_upload_tmp first, then a\n // supervisor promotes them to the live spatial.lu_parcels via the\n // migrated flag):\n //\n // 'imported' → 'mapped' → 'uploading' → 'submitted' → 'migrated'\n // ↘ 'failed'\n //\n // 'other' is a terminal display-only state (user chose \"Other / view only\").\n // 'submitted' = row exists in upload_tmp, awaiting supervisor review.\n // 'migrated' = supervisor approved & promoted to the live table.\n console.log('[Database] Creating external_imports table...');\n await sql`\n CREATE TABLE IF NOT EXISTS external_imports (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n filename TEXT NOT NULL,\n target_type TEXT NOT NULL DEFAULT 'other',\n mapping_json TEXT,\n status TEXT NOT NULL DEFAULT 'imported',\n feature_count INTEGER NOT NULL DEFAULT 0,\n error_count INTEGER NOT NULL DEFAULT 0,\n client_import_id TEXT UNIQUE,\n created_at TEXT DEFAULT CURRENT_TIMESTAMP,\n last_uploaded_at TEXT\n )\n `;\n\n console.log('[Database] Creating external_import_features table...');\n await sql`\n CREATE TABLE IF NOT EXISTS external_import_features (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n import_id INTEGER NOT NULL REFERENCES external_imports(id) ON DELETE CASCADE,\n client_uuid TEXT UNIQUE,\n geometry_wkt TEXT NOT NULL,\n properties_json TEXT,\n upload_status TEXT NOT NULL DEFAULT 'pending',\n server_id INTEGER,\n error_message TEXT\n )\n `;\n\n // Create indexes\n await sql`CREATE INDEX IF NOT EXISTS idx_locations_category ON locations(category)`;\n await sql`CREATE INDEX IF NOT EXISTS idx_locations_synced ON locations(synced)`;\n await sql`CREATE INDEX IF NOT EXISTS idx_gps_trails_synced ON gps_trails(synced, status)`;\n await sql`CREATE INDEX IF NOT EXISTS idx_gps_trail_points_trail ON gps_trail_points(trail_id, seq)`;\n await sql`CREATE INDEX IF NOT EXISTS idx_external_imports_status ON external_imports(status)`;\n await sql`CREATE INDEX IF NOT EXISTS idx_external_import_features_import ON external_import_features(import_id, upload_status)`;\n\n // Final verification\n const allTables = await sql`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;\n console.log('[Database] All tables:', allTables.map(t => t.name));\n\n isReady = true;\n readyResolve(true);\n console.log('[Database] ✓ Schema initialized');\n\n } catch (error) {\n console.error('[Database] ✗ Schema init failed:', error);\n readyReject(error);\n throw error;\n }\n}\n\n// ============================================================================\n// Location Operations\n// ============================================================================\n\n/**\n * Add a new location\n */\nexport async function addLocation(name, longitude, latitude, options = {}) {\n const { description = null, category = 'default' } = options;\n\n console.log('[Database] Adding location:', name, longitude, latitude, category);\n\n try {\n // Check table exists first\n const tableCheck = await sql`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;\n console.log('[Database] Table check before insert:', tableCheck);\n\n if (tableCheck.length === 0) {\n console.error('[Database] ✗ locations table does not exist!');\n throw new Error('locations table does not exist');\n }\n\n // Insert - using explicit values\n console.log('[Database] Executing INSERT...');\n await sql`\n INSERT INTO locations (name, longitude, latitude, description, category)\n VALUES (${name}, ${longitude}, ${latitude}, ${description}, ${category})\n `;\n console.log('[Database] INSERT completed');\n\n // Get the ID\n const idResult = await sql`SELECT last_insert_rowid() as id`;\n const newId = idResult[0]?.id;\n console.log('[Database] New ID:', newId);\n\n // Verify it was actually inserted\n const verifyResult = await sql`SELECT * FROM locations WHERE id = ${newId}`;\n console.log('[Database] Verify insert:', verifyResult);\n\n if (verifyResult.length === 0) {\n console.error('[Database] ✗ Insert verification failed - row not found!');\n throw new Error('Insert verification failed');\n }\n\n // Log for sync\n await sql`\n INSERT INTO sync_log (table_name, record_id, action)\n VALUES ('locations', ${newId}, 'INSERT')\n `;\n\n // Broadcast to other tabs\n broadcastChange('locations', 'INSERT', newId);\n\n console.log('[Database] ✓ Location added:', newId);\n return { id: newId };\n\n } catch (error) {\n console.error('[Database] ✗ Failed to add location:', error);\n throw error;\n}\n}\n\n/**\n * Get all locations\n */\nexport async function getLocations(options = {}) {\n const { category = null, limit = 1000 } = options;\n\n try {\n // First check if table exists\n const tableCheck = await sql`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;\n console.log('[Database] getLocations - table exists:', tableCheck.length > 0);\n\n if (tableCheck.length === 0) {\n console.warn('[Database] locations table does not exist yet');\n return [];\n }\n\n let results;\n if (category) {\n results = await sql`\n SELECT * FROM locations\n WHERE category = ${category}\n ORDER BY created_at DESC\n LIMIT ${limit}\n `;\n } else {\n results = await sql`\n SELECT * FROM locations\n ORDER BY created_at DESC\n LIMIT ${limit}\n `;\n}\n\n console.log('[Database] getLocations returned', results.length, 'rows');\n return results;\n\n } catch (error) {\n console.error('[Database] getLocations error:', error);\n return [];\n }\n}\n\nexport async function getLocation(id) {\n try {\n const results = await sql`SELECT * FROM locations WHERE id = ${id}`;\n return results[0] || null;\n } catch (error) {\n console.error('[Database] getLocation error:', error);\n return null;\n}\n}\n\n/**\n * Update a location\n */\nexport async function updateLocation(id, updates) {\n const { name, longitude, latitude, description, category } = updates;\n\n try {\n const location = await getLocation(id);\n if (!location) {\n throw new Error(`Location ${id} not found`);\n }\n\n await sql`\n UPDATE locations\n SET\n name = ${name ?? location.name},\n longitude = ${longitude ?? location.longitude},\n latitude = ${latitude ?? location.latitude},\n description = ${description ?? location.description},\n category = ${category ?? location.category},\n updated_at = CURRENT_TIMESTAMP,\n synced = 0\n WHERE id = ${id}\n `;\n\n // Log for sync\n await sql`\n INSERT INTO sync_log (table_name, record_id, action)\n VALUES ('locations', ${id}, 'UPDATE')\n `;\n\n // Broadcast to other tabs\n broadcastChange('locations', 'UPDATE', id);\n console.log('[Database] ✓ Location updated:', id);\n\n } catch (error) {\n console.error('[Database] ✗ updateLocation error:', error);\n throw error;\n}\n}\n\n/**\n * Delete a location\n */\nexport async function deleteLocation(id) {\n try {\n await sql`\n INSERT INTO sync_log (table_name, record_id, action)\n VALUES ('locations', ${id}, 'DELETE')\n `;\n\n await sql`DELETE FROM locations WHERE id = ${id}`;\n\n // Broadcast to other tabs\n broadcastChange('locations', 'DELETE', id);\n console.log('[Database] ✓ Location deleted:', id);\n\n } catch (error) {\n console.error('[Database] ✗ deleteLocation error:', error);\n throw error;\n}\n}\n\n/**\n * Get location count\n */\nexport async function getLocationCount() {\n try {\n const result = await sql`SELECT COUNT(*) as count FROM locations`;\n return result[0]?.count ?? 0;\n } catch (error) {\n console.error('[Database] getLocationCount error:', error);\n return 0;\n}\n}\n\n// ============================================================================\n// Sync Operations\n// ============================================================================\n\n/**\n * Get unsynced changes\n */\nexport async function getUnsyncedChanges() {\n return sql`SELECT * FROM sync_log WHERE synced = 0 ORDER BY timestamp ASC`;\n}\n\n/**\n * Mark changes as synced\n */\nexport async function markSynced(syncLogIds) {\n if (!syncLogIds.length) return;\n for (const id of syncLogIds) {\n await sql`UPDATE sync_log SET synced = 1 WHERE id = ${id}`;\n}\n}\n\n/**\n * Get locations that need syncing\n */\nexport async function getUnsyncedLocations() {\n return sql`SELECT * FROM locations WHERE synced = 0`;\n}\n\n/**\n * Mark locations as synced\n */\nexport async function markLocationsSynced(ids) {\n if (!ids.length) return;\n for (const id of ids) {\n await sql`UPDATE locations SET synced = 1 WHERE id = ${id}`;\n}\n}\n\n// ============================================================================\n// Remote Data Cache\n// ============================================================================\n\n/**\n * Save remote API data to the local cache.\n * Uses INSERT OR REPLACE so the same key is always overwritten.\n *\n * @param {string} key - Unique identifier (e.g. 'district_boundary')\n * @param {Object|Array} data - JSON-serialisable data to cache\n */\nexport async function saveRemoteData(key, data) {\n try {\n const json = JSON.stringify(data);\n await sql`\n INSERT OR REPLACE INTO remote_data (key, data, fetched_at)\n VALUES (${key}, ${json}, CURRENT_TIMESTAMP)\n `;\n console.log('[Database] ✓ Remote data cached:', key);\n } catch (error) {\n console.error('[Database] ✗ Failed to cache remote data:', key, error);\n throw error;\n }\n}\n\n/**\n * Retrieve cached remote data by key.\n *\n * @param {string} key - Unique identifier (e.g. 'district_boundary')\n * @returns {Promise} Parsed data, or null if not cached\n */\nexport async function getRemoteData(key) {\n try {\n const rows = await sql`SELECT data, fetched_at FROM remote_data WHERE key = ${key}`;\n if (rows.length === 0) return null;\n const parsed = JSON.parse(rows[0].data);\n console.log('[Database] ✓ Remote data loaded from cache:', key, '(fetched', rows[0].fetched_at + ')');\n return parsed;\n } catch (error) {\n console.error('[Database] ✗ Failed to read cached remote data:', key, error);\n return null;\n }\n}\n\n// ============================================================================\n// Collector Zones\n// ============================================================================\n\n/**\n * Save collector zones to the local table.\n * Replaces all existing rows.\n *\n * @param {Array} zones - Array of zone objects from the API\n */\nexport async function saveCollectorZones(zones) {\n try {\n await sql`DELETE FROM collector_zones`;\n for (const z of zones) {\n const props = JSON.stringify(z);\n await sql`\n INSERT INTO collector_zones (id, zone_name, geometry_wkt, properties, fetched_at)\n VALUES (${z.colzonenr || z.id}, ${z.colzonename || z.zone_name || ''}, ${z.polygon || z.boundary || ''}, ${props}, CURRENT_TIMESTAMP)\n `;\n }\n console.log('[Database] ✓ Saved', zones.length, 'collector zones');\n } catch (error) {\n console.error('[Database] ✗ Failed to save collector zones:', error);\n throw error;\n }\n}\n\n/**\n * Load all cached collector zones from the local table.\n * @returns {Promise} Array of zone objects, or null if empty\n */\nexport async function getLocalCollectorZones() {\n try {\n const rows = await sql`SELECT properties FROM collector_zones ORDER BY id`;\n if (rows.length === 0) return null;\n return rows.map(r => JSON.parse(r.properties));\n } catch (error) {\n console.error('[Database] ✗ Failed to read local collector zones:', error);\n return null;\n }\n}\n\n// ============================================================================\n// UPN Grid\n// ============================================================================\n\n/**\n * Save the UPN-grid for one district to the local table. Replaces any\n * previously cached grid (even if it was for a different district) — the\n * grid is static per district, so we only ever keep the current district's\n * cells locally.\n *\n * @param {Array} rows - Grid rows from the API\n * (each: { polygon, districtid, upn_prefix })\n * @param {number|string} districtId - The current district id\n */\nexport async function saveUpnGrid(rows, districtId) {\n try {\n await sql`BEGIN`;\n await sql`DELETE FROM upn_grid`;\n let saved = 0;\n for (const r of rows) {\n const wkt = r.polygon || r.geometry_wkt || r.geom || '';\n await sql`\n INSERT INTO upn_grid (districtid, upn_prefix, geometry_wkt, fetched_at)\n VALUES (${numOrNull(districtId)}, ${r.upn_prefix ?? null}, ${wkt}, CURRENT_TIMESTAMP)\n `;\n saved++;\n }\n await sql`COMMIT`;\n console.log('[Database] ✓ Saved', saved, 'UPN-grid cells (district', districtId, ')');\n } catch (error) {\n try { await sql`ROLLBACK`; } catch { /* no active txn */ }\n console.error('[Database] ✗ Failed to save UPN grid:', error);\n throw error;\n }\n}\n\n/**\n * Load the UPN-grid rows that belong to the given district. Returns null\n * if no cells are cached for that district — the caller should then fetch\n * from the API and call saveUpnGrid().\n *\n * @param {number|string} districtId\n * @returns {Promise}\n */\nexport async function getLocalUpnGrid(districtId) {\n try {\n const rows = await sql`\n SELECT id, districtid, upn_prefix, geometry_wkt\n FROM upn_grid\n WHERE districtid = ${numOrNull(districtId)}\n ORDER BY id\n `;\n if (rows.length === 0) return null;\n return rows;\n } catch (error) {\n console.error('[Database] ✗ Failed to read UPN grid:', error);\n return null;\n }\n}\n\n// ============================================================================\n// External imports (staging for shapefile / GeoJSON / KML uploads)\n// See LUPMIS2_Import_Upload_Design.docx §3 & §4.\n// ============================================================================\n\n/** Tiny RFC-4122-ish UUIDv4 (uses crypto.randomUUID when available). */\nfunction uuidv4() {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n // Fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\n/**\n * Create a new staging row for an imported dataset. Returns the import_id.\n *\n * @param {Object} opts\n * @param {string} opts.filename — original filename\n * @param {string} [opts.targetType] — initial target ('other' by default)\n * @param {number} [opts.featureCount] — number of features being staged\n * @returns {Promise<{ id: number, client_import_id: string }>}\n */\nexport async function createExternalImport(opts) {\n const { filename, targetType = 'other', featureCount = 0 } = opts;\n const clientImportId = uuidv4();\n try {\n await sql`\n INSERT INTO external_imports\n (filename, target_type, status, feature_count, client_import_id)\n VALUES\n (${filename}, ${targetType},\n ${targetType === 'other' ? 'other' : 'imported'},\n ${featureCount}, ${clientImportId})\n `;\n const idRow = await sql`SELECT last_insert_rowid() AS id`;\n const id = idRow[0]?.id;\n broadcastChange('external_imports', 'INSERT', id);\n return { id, client_import_id: clientImportId };\n } catch (error) {\n console.error('[Database] ✗ Failed to create external import:', error);\n throw error;\n }\n}\n\n/**\n * Bulk-insert staging features for an import. Wraps the inserts in a single\n * transaction (the upload payload is typically dozens to thousands of rows).\n *\n * @param {number} importId\n * @param {Array<{ geometry_wkt: string, properties: Object }>} features\n */\nexport async function addExternalImportFeatures(importId, features) {\n if (!Array.isArray(features) || features.length === 0) return 0;\n try {\n await sql`BEGIN`;\n let inserted = 0;\n for (const f of features) {\n const wkt = f.geometry_wkt || '';\n if (!wkt) continue;\n const propsJson = JSON.stringify(f.properties ?? {});\n const cuuid = f.client_uuid || uuidv4();\n await sql`\n INSERT INTO external_import_features\n (import_id, client_uuid, geometry_wkt, properties_json, upload_status)\n VALUES\n (${importId}, ${cuuid}, ${wkt}, ${propsJson}, 'pending')\n `;\n inserted++;\n }\n await sql`COMMIT`;\n broadcastChange('external_import_features', 'INSERT', importId);\n return inserted;\n } catch (error) {\n try { await sql`ROLLBACK`; } catch { /* no active txn */ }\n console.error('[Database] ✗ Failed to add import features:', error);\n throw error;\n }\n}\n\n/**\n * Update target type + mapping + status for a staged import. Pass any\n * subset of fields. Useful targets:\n * updateExternalImport(id, { targetType: 'parcels', mapping: {...},\n * status: 'mapped' })\n */\nexport async function updateExternalImport(importId, patch = {}) {\n try {\n const cur = await sql`SELECT * FROM external_imports WHERE id = ${importId}`;\n if (cur.length === 0) throw new Error(`Import ${importId} not found`);\n const row = cur[0];\n\n const targetType = patch.targetType ?? row.target_type;\n const mappingJson = patch.mapping !== undefined\n ? (patch.mapping ? JSON.stringify(patch.mapping) : null)\n : row.mapping_json;\n const status = patch.status ?? row.status;\n const errorCount = patch.errorCount ?? row.error_count;\n const lastUploadedAt = patch.lastUploadedAt ?? row.last_uploaded_at;\n\n await sql`\n UPDATE external_imports SET\n target_type = ${targetType},\n mapping_json = ${mappingJson},\n status = ${status},\n error_count = ${errorCount},\n last_uploaded_at = ${lastUploadedAt}\n WHERE id = ${importId}\n `;\n broadcastChange('external_imports', 'UPDATE', importId);\n } catch (error) {\n console.error('[Database] ✗ Failed to update external import:', error);\n throw error;\n }\n}\n\n/** Get an external_imports row by id (parsed mapping_json). */\nexport async function getExternalImport(importId) {\n try {\n const rows = await sql`SELECT * FROM external_imports WHERE id = ${importId}`;\n if (rows.length === 0) return null;\n const row = rows[0];\n return { ...row, mapping: row.mapping_json ? JSON.parse(row.mapping_json) : null };\n } catch (error) {\n console.error('[Database] ✗ Failed to read external import:', error);\n return null;\n }\n}\n\n/** List every external import, newest first. */\nexport async function listExternalImports() {\n try {\n return await sql`SELECT * FROM external_imports ORDER BY id DESC`;\n } catch (error) {\n console.error('[Database] ✗ Failed to list external imports:', error);\n return [];\n }\n}\n\n/** Get the staging features for an import (parsed properties_json). */\nexport async function getExternalImportFeatures(importId) {\n try {\n const rows = await sql`\n SELECT id, client_uuid, geometry_wkt, properties_json,\n upload_status, server_id, error_message\n FROM external_import_features\n WHERE import_id = ${importId}\n ORDER BY id\n `;\n return rows.map((r) => ({\n ...r,\n properties: r.properties_json ? JSON.parse(r.properties_json) : {},\n }));\n } catch (error) {\n console.error('[Database] ✗ Failed to read import features:', error);\n return [];\n }\n}\n\n/**\n * Bulk-rewrite the properties_json of every staged feature for an import.\n * Used after the user confirms a field-mapping: the caller passes a function\n * that receives each row's parsed properties and returns the remapped ones.\n * All updates run in a single transaction.\n *\n * @param {number} importId\n * @param {(props: Object) => Object} remap\n */\nexport async function remapImportedFeatureProperties(importId, remap) {\n try {\n const rows = await sql`\n SELECT id, properties_json\n FROM external_import_features\n WHERE import_id = ${importId}\n `;\n if (rows.length === 0) return 0;\n\n await sql`BEGIN`;\n let n = 0;\n for (const r of rows) {\n const before = r.properties_json ? JSON.parse(r.properties_json) : {};\n const after = remap(before) ?? {};\n await sql`\n UPDATE external_import_features\n SET properties_json = ${JSON.stringify(after)}\n WHERE id = ${r.id}\n `;\n n++;\n }\n await sql`COMMIT`;\n broadcastChange('external_import_features', 'UPDATE', importId);\n return n;\n } catch (error) {\n try { await sql`ROLLBACK`; } catch { /* */ }\n console.error('[Database] ✗ Failed to remap import features:', error);\n throw error;\n }\n}\n\n/**\n * Update a single staged feature's geometry (and properties, optionally).\n * Used when the user modifies an imported feature on the map (the EditBar\n * Modify interaction fires `modifyend`) — we write the new WKT back so the\n * next upload re-sends the corrected geometry.\n *\n * @param {string} clientUuid - the per-feature idempotency key on the OL feature\n * @param {string} geometryWkt - new geometry in EPSG:4326 WKT\n * @param {Object} [properties] - optional new properties (if omitted, leave as-is)\n */\nexport async function updateExternalImportFeatureGeometry(clientUuid, geometryWkt, properties) {\n try {\n if (properties !== undefined) {\n const propsJson = JSON.stringify(properties);\n await sql`\n UPDATE external_import_features\n SET geometry_wkt = ${geometryWkt}, properties_json = ${propsJson}\n WHERE client_uuid = ${clientUuid}\n `;\n } else {\n await sql`\n UPDATE external_import_features\n SET geometry_wkt = ${geometryWkt}\n WHERE client_uuid = ${clientUuid}\n `;\n }\n broadcastChange('external_import_features', 'UPDATE', clientUuid);\n } catch (error) {\n console.error('[Database] ✗ Failed to update import feature geometry:', error);\n throw error;\n }\n}\n\n/**\n * Delete a single staged feature by client_uuid, and decrement the parent\n * import's feature_count. Used when the user deletes an imported feature on\n * the map (EditBar Delete).\n *\n * @param {string} clientUuid\n */\nexport async function deleteExternalImportFeature(clientUuid) {\n try {\n // Look up parent import_id first so we can decrement its count atomically.\n const row = await sql`\n SELECT import_id FROM external_import_features WHERE client_uuid = ${clientUuid}\n `;\n if (row.length === 0) return; // already gone\n const importId = row[0].import_id;\n\n await sql`BEGIN`;\n try {\n await sql`DELETE FROM external_import_features WHERE client_uuid = ${clientUuid}`;\n await sql`\n UPDATE external_imports\n SET feature_count = MAX(0, feature_count - 1)\n WHERE id = ${importId}\n `;\n await sql`COMMIT`;\n } catch (e) {\n try { await sql`ROLLBACK`; } catch { /* */ }\n throw e;\n }\n broadcastChange('external_import_features', 'DELETE', clientUuid);\n broadcastChange('external_imports', 'UPDATE', importId);\n } catch (error) {\n console.error('[Database] ✗ Failed to delete import feature:', error);\n throw error;\n }\n}\n\n/** Delete a staged import (and its features via CASCADE). */\nexport async function deleteExternalImport(importId) {\n try {\n await sql`DELETE FROM external_imports WHERE id = ${importId}`;\n broadcastChange('external_imports', 'DELETE', importId);\n } catch (error) {\n console.error('[Database] ✗ Failed to delete external import:', error);\n throw error;\n }\n}\n\n// ============================================================================\n// Parcels\n// ============================================================================\n\n/** Coerce a value for an INTEGER column; '' / null / undefined / NaN → null. */\nfunction numOrNull(v) {\n if (v === '' || v === null || v === undefined) return null;\n const n = Number(v);\n return Number.isNaN(n) ? null : n;\n}\n\n/**\n * Save parcels to the local table (mirrors spatial.lu_parcels).\n * Replaces all server-cached rows. Each API field maps to its own column.\n * Geometry is taken from the server `geom` field (WKT, EPSG:4326).\n *\n * @param {Array} parcels - Array of parcel objects from the API\n */\nexport async function saveParcels(parcels) {\n try {\n // Wrap the bulk load in a single transaction — with ~25k parcels, per-row\n // auto-commits on OPFS would be prohibitively slow.\n await sql`BEGIN`;\n await sql`DELETE FROM parcels`;\n let saved = 0;\n for (const p of parcels) {\n const id = p.id ?? p.parcelid ?? p.parcel_id ?? null;\n if (id == null) continue; // skip rows without a usable ID\n // Geometry must be a WKT *string* for the geometry_wkt TEXT column.\n // The API sends WKT in `boundary` and a GeoJSON *object* in `geom`, so\n // prefer the string fields and only accept `geom` when it is a string.\n const wkt = p.boundary || p.geometry_wkt || p.polygon || p.wkt\n || (typeof p.geom === 'string' ? p.geom : '');\n await sql`\n INSERT OR REPLACE INTO parcels (\n id, upn, style, landuse, zone_code, zone_name, sector, block, parcel_no,\n prop_no, st_name, prop_add, fac_name, min_height, max_height, eff_date,\n lp_name, locality, mmda, last_update, remarks, geometry_wkt,\n created_at, updated_at, districtid, status, fetched_at\n ) VALUES (\n ${id}, ${p.upn ?? null}, ${numOrNull(p.style)}, ${p.landuse ?? null},\n ${p.zone_code ?? null}, ${p.zone_name ?? null}, ${p.sector ?? null},\n ${p.block ?? null}, ${p.parcel_no ?? null}, ${p.prop_no ?? null},\n ${p.st_name ?? null}, ${p.prop_add ?? null}, ${p.fac_name ?? null},\n ${numOrNull(p.min_height)}, ${numOrNull(p.max_height)}, ${p.eff_date ?? null},\n ${p.lp_name ?? null}, ${p.locality ?? null}, ${p.mmda ?? null},\n ${p.last_update ?? null}, ${p.remarks ?? null}, ${wkt},\n ${p.created_at ?? null}, ${p.updated_at ?? null}, ${numOrNull(p.districtid)},\n 'verified', CURRENT_TIMESTAMP\n )\n `;\n saved++;\n }\n await sql`COMMIT`;\n console.log('[Database] ✓ Saved', saved, 'parcels (from', parcels.length, 'rows,', parcels.length - saved, 'skipped/replaced)');\n } catch (error) {\n try { await sql`ROLLBACK`; } catch { /* no active txn */ }\n console.error('[Database] ✗ Failed to save parcels:', error);\n throw error;\n }\n}\n\n/**\n * Load all cached parcels from the local table.\n * Each row is a plain object keyed by column name (incl. geometry_wkt).\n * @returns {Promise} Array of parcel rows, or null if empty\n */\nexport async function getLocalParcels() {\n try {\n const rows = await sql`SELECT * FROM parcels ORDER BY id`;\n if (rows.length === 0) return null;\n return rows;\n } catch (error) {\n console.error('[Database] ✗ Failed to read local parcels:', error);\n return null;\n }\n}\n\n/**\n * Update a single parcel's attribute columns in the local table.\n * Geometry, id, created_at and status are left unchanged; updated_at is bumped.\n *\n * @param {number|string} parcelId - The parcel id\n * @param {Object} p - Updated attribute values (keys = lu_parcels columns)\n */\nexport async function updateParcel(parcelId, p) {\n try {\n await sql`\n UPDATE parcels SET\n upn = ${p.upn ?? null},\n style = ${numOrNull(p.style)},\n landuse = ${p.landuse ?? null},\n zone_code = ${p.zone_code ?? null},\n zone_name = ${p.zone_name ?? null},\n sector = ${p.sector ?? null},\n block = ${p.block ?? null},\n parcel_no = ${p.parcel_no ?? null},\n prop_no = ${p.prop_no ?? null},\n st_name = ${p.st_name ?? null},\n prop_add = ${p.prop_add ?? null},\n fac_name = ${p.fac_name ?? null},\n min_height = ${numOrNull(p.min_height)},\n max_height = ${numOrNull(p.max_height)},\n eff_date = ${p.eff_date ?? null},\n lp_name = ${p.lp_name ?? null},\n locality = ${p.locality ?? null},\n mmda = ${p.mmda ?? null},\n last_update = ${p.last_update ?? null},\n remarks = ${p.remarks ?? null},\n districtid = ${numOrNull(p.districtid)},\n updated_at = CURRENT_TIMESTAMP\n WHERE id = ${parcelId}\n `;\n console.log('[Database] ✓ Parcel updated:', parcelId);\n broadcastChange('parcels', 'UPDATE', parcelId);\n } catch (error) {\n console.error('[Database] ✗ Failed to update parcel:', parcelId, error);\n throw error;\n }\n}\n\n/**\n * Insert a newly drawn parcel into the local table.\n * The parcel is tagged with status='new' to indicate it needs verification.\n *\n * @param {string} geometryWkt - WKT geometry string (EPSG:4326)\n * @param {Object} p - Attribute values from the form (keys = lu_parcels columns)\n * @returns {Promise<{id: number}>} The new row id\n */\nexport async function insertNewParcel(geometryWkt, p = {}) {\n try {\n await sql`\n INSERT INTO parcels (\n id, upn, style, landuse, zone_code, zone_name, sector, block, parcel_no,\n prop_no, st_name, prop_add, fac_name, min_height, max_height, eff_date,\n lp_name, locality, mmda, last_update, remarks, geometry_wkt,\n created_at, updated_at, districtid, status, fetched_at\n ) VALUES (\n NULL, ${p.upn ?? null}, ${numOrNull(p.style)}, ${p.landuse ?? null},\n ${p.zone_code ?? null}, ${p.zone_name ?? null}, ${p.sector ?? null},\n ${p.block ?? null}, ${p.parcel_no ?? null}, ${p.prop_no ?? null},\n ${p.st_name ?? null}, ${p.prop_add ?? null}, ${p.fac_name ?? null},\n ${numOrNull(p.min_height)}, ${numOrNull(p.max_height)}, ${p.eff_date ?? null},\n ${p.lp_name ?? null}, ${p.locality ?? null}, ${p.mmda ?? null},\n ${p.last_update ?? null}, ${p.remarks ?? null}, ${geometryWkt},\n CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ${numOrNull(p.districtid)},\n 'new', CURRENT_TIMESTAMP\n )\n `;\n const idResult = await sql`SELECT last_insert_rowid() as id`;\n const newId = idResult[0]?.id;\n console.log('[Database] ✓ New parcel inserted:', newId, '(status: new)');\n broadcastChange('parcels', 'INSERT', newId);\n return { id: newId };\n } catch (error) {\n console.error('[Database] ✗ Failed to insert new parcel:', error);\n throw error;\n }\n}\n\n// ============================================================================\n// Building Footprints\n// ============================================================================\n\n/**\n * Save building footprints to the local table.\n * Replaces all existing rows.\n *\n * @param {Array} footprints - Array of footprint objects from the API\n */\nexport async function saveBuildingFootprints(footprints) {\n try {\n // Log first entry's keys and value types to help debug field names\n if (footprints.length > 0) {\n const first = footprints[0];\n const types = {};\n for (const [k, v] of Object.entries(first)) {\n types[k] = v === null ? 'null' : typeof v;\n }\n console.log('[Database] First footprint field types:', types);\n }\n\n await sql`DELETE FROM building_footprints`;\n for (const f of footprints) {\n const props = JSON.stringify(f);\n\n // Geometry may arrive as a string (WKT) or an object (GeoJSON) —\n // coerce to a string so SQLocal can bind it.\n let rawWkt = f.polygon || f.boundary || f.geom || f.wkt || f.footprint || '';\n const wkt = typeof rawWkt === 'object' ? JSON.stringify(rawWkt) : String(rawWkt);\n\n // ID must be a primitive (number or null)\n let rawId = f.id || f.footprint_id || f.building_id || null;\n const id = (rawId !== null && typeof rawId === 'object') ? null : rawId;\n\n await sql`\n INSERT INTO building_footprints (id, geometry_wkt, properties, fetched_at)\n VALUES (${id}, ${wkt}, ${props}, CURRENT_TIMESTAMP)\n `;\n }\n console.log('[Database] ✓ Saved', footprints.length, 'building footprints');\n } catch (error) {\n console.error('[Database] ✗ Failed to save building footprints:', error);\n throw error;\n }\n}\n\n/**\n * Load all cached building footprints from the local table.\n * @returns {Promise} Array of footprint objects, or null if empty\n */\nexport async function getLocalBuildingFootprints() {\n try {\n const rows = await sql`SELECT properties FROM building_footprints ORDER BY id`;\n if (rows.length === 0) return null;\n return rows.map(r => JSON.parse(r.properties));\n } catch (error) {\n console.error('[Database] ✗ Failed to read local building footprints:', error);\n return null;\n }\n}\n\n/**\n * Save OSM roads to the local SQLite table.\n * Replaces all existing rows.\n *\n * @param {Array} roads - Array of road objects from the API\n */\nexport async function saveOSMRoads(roads) {\n try {\n if (roads.length > 0) {\n const first = roads[0];\n const types = {};\n for (const [k, v] of Object.entries(first)) {\n types[k] = v === null ? 'null' : typeof v;\n }\n console.log('[Database] First road field types:', types);\n }\n\n await sql`DELETE FROM osm_roads`;\n for (const r of roads) {\n const props = JSON.stringify(r);\n\n // Geometry — may arrive as WKT string or GeoJSON object\n let rawWkt = r.geom || r.geometry || r.wkt || r.road || r.line || '';\n const wkt = typeof rawWkt === 'object' ? JSON.stringify(rawWkt) : String(rawWkt);\n\n // osm_id must be a primitive — fall back to null if missing or malformed\n let rawId = r.osm_id ?? r.osmid ?? r.id ?? null;\n const osmId = (rawId !== null && typeof rawId === 'object') ? null : rawId;\n\n await sql`\n INSERT OR REPLACE INTO osm_roads (osm_id, geometry_wkt, properties, fetched_at)\n VALUES (${osmId}, ${wkt}, ${props}, CURRENT_TIMESTAMP)\n `;\n }\n console.log('[Database] ✓ Saved', roads.length, 'OSM roads');\n } catch (error) {\n console.error('[Database] ✗ Failed to save OSM roads:', error);\n throw error;\n }\n}\n\n/**\n * Load all cached OSM roads from the local table.\n * @returns {Promise} Array of road objects, or null if empty\n */\nexport async function getLocalOSMRoads() {\n try {\n const rows = await sql`SELECT properties FROM osm_roads ORDER BY osm_id`;\n if (rows.length === 0) return null;\n return rows.map(r => JSON.parse(r.properties));\n } catch (error) {\n console.error('[Database] ✗ Failed to read local OSM roads:', error);\n return null;\n }\n}\n\n// ============================================================================\n// Export / Import\n// ============================================================================\n\n/**\n * Export database for backup\n */\nexport async function exportDatabase() {\n return db.getDatabaseFile();\n}\n\n/**\n * Import database from backup\n */\nexport async function importDatabase(data) {\n await db.overwriteDatabaseFile(data);\n broadcastChange('*', 'IMPORT', null);\n}\n\n/**\n * Download database as file\n */\nexport async function downloadDatabase(filename = 'lupmis-backup.sqlite3') {\n const data = await exportDatabase();\n const blob = new Blob([data], { type: 'application/x-sqlite3' });\n const url = URL.createObjectURL(blob);\n\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n\n URL.revokeObjectURL(url);\n}\n\n// Export to GeoJSON\nexport async function exportToGeoJSON() {\n const locations = await getLocations();\n\n return {\n type: 'FeatureCollection',\n features: locations.map((loc) => ({\n type: 'Feature',\n properties: {\n id: loc.id,\n name: loc.name,\n category: loc.category,\n notes: loc.notes,\n created_at: loc.created_at,\n },\n geometry: {\n type: 'Point',\n coordinates: [loc.lon, loc.lat],\n },\n })),\n };\n}\n\n// ============================================================================\n// Utility & Debug\n// ============================================================================\n\n/**\n * Get database status\n */\nexport async function getDatabaseStatus() {\n try {\n const tables = await sql`\n SELECT name FROM sqlite_master\n WHERE type='table' AND name NOT LIKE 'sqlite_%'\n ORDER BY name\n `;\n\n const locationCount = await getLocationCount();\n\n return {\n ready: isReady,\n databasePath: DATABASE_PATH,\n tables: tables.map(t => t.name),\n locationCount\n };\n } catch (error) {\n return {\n ready: false,\n error: error.message\n };\n}\n}\n\n// ============================================================================\n// Cached-Layer Management\n// ============================================================================\n\n/**\n * Tables that hold data fetched from the server.\n *\n * Clearing these is safe — the corresponding loaders (loadParcels,\n * loadBuildingFootprints, loadOSMRoads, loadCollectorZones, …) will re-fetch\n * the data from the API on the next app start.\n *\n * NOT included: user-created tables (`locations`, `pending_changes`) — those\n * hold local work that must not be auto-deleted.\n */\nexport const CACHED_LAYER_TABLES = Object.freeze([\n 'parcels',\n 'building_footprints',\n 'osm_roads',\n 'collector_zones',\n 'upn_grid',\n 'remote_data',\n]);\n\n/**\n * Check whether a table name is in the cleared-layer allow-list.\n * @param {string} tableName\n * @returns {boolean}\n */\nexport function isCachedLayerTable(tableName) {\n return CACHED_LAYER_TABLES.includes(tableName);\n}\n\n/**\n * Delete all rows from a single cached-layer table.\n * Rejects unknown table names so this can't be abused to drop user data.\n *\n * @param {string} tableName - One of CACHED_LAYER_TABLES\n * @returns {Promise} Number of rows that were in the table before deletion\n */\nexport async function clearTable(tableName) {\n if (!isCachedLayerTable(tableName)) {\n throw new Error(`Refusing to clear \"${tableName}\" — not a known cached-layer table`);\n }\n\n const before = await sql(`SELECT COUNT(*) AS n FROM \"${tableName}\"`);\n const count = before[0]?.n ?? 0;\n\n await sql(`DELETE FROM \"${tableName}\"`);\n console.log(`[Database] ✓ Cleared \"${tableName}\" (${count} rows)`);\n broadcastChange(tableName, 'CLEAR', null);\n return count;\n}\n\n/**\n * Clear every cached-layer table (whatever exists in this database).\n * Tables that don't exist yet are skipped silently.\n *\n * @returns {Promise<{ table: string, count: number }[]>} per-table report\n */\nexport async function clearAllCachedLayers() {\n const existing = await sql`\n SELECT name FROM sqlite_master\n WHERE type='table' AND name IN (\n 'parcels', 'building_footprints', 'osm_roads', 'collector_zones', 'upn_grid', 'remote_data'\n )\n `;\n const existingNames = new Set(existing.map((r) => r.name));\n\n const results = [];\n for (const tableName of CACHED_LAYER_TABLES) {\n if (!existingNames.has(tableName)) continue;\n try {\n const count = await clearTable(tableName);\n results.push({ table: tableName, count });\n } catch (err) {\n console.error(`[Database] Failed to clear ${tableName}:`, err);\n results.push({ table: tableName, count: 0, error: err.message });\n }\n }\n\n const total = results.reduce((s, r) => s + r.count, 0);\n console.log(`[Database] ✓ Cleared all cached layers: ${total} rows across ${results.length} tables`);\n return results;\n}\n\n/**\n * Get a list of all tables with their row counts.\n * @returns {Promise>}\n */\nexport async function getTableStats() {\n const tables = await sql`\n SELECT name FROM sqlite_master\n WHERE type='table' AND name NOT LIKE 'sqlite_%'\n ORDER BY name\n `;\n\n if (tables.length === 0) return [];\n\n // Build a UNION ALL query — table names come from sqlite_master so safe to embed.\n // sql() accepts a plain string as first argument (not only tagged template).\n const query = tables\n .map(t => `SELECT '${t.name}' AS name, COUNT(*) AS count FROM \"${t.name}\"`)\n .join(' UNION ALL ');\n\n return sql(query);\n}\n\n/**\n * Get all rows from a given table (max 200 rows for safety).\n * Table name is validated against sqlite_master before querying.\n * @param {string} tableName - Name of the table to read\n * @param {number} [limit=200] - Max rows to return\n * @returns {Promise<{columns: string[], rows: Object[]}>}\n */\nexport async function getTableContent(tableName, limit = 200) {\n // Validate that the table actually exists (prevent injection)\n const valid = await sql`\n SELECT name FROM sqlite_master\n WHERE type='table' AND name = ${tableName}\n `;\n if (valid.length === 0) {\n throw new Error(`Table \"${tableName}\" does not exist`);\n }\n\n const rows = await sql(`SELECT * FROM \"${tableName}\" LIMIT ${limit}`);\n\n // Extract column names from the first row (or return empty)\n const columns = rows.length > 0 ? Object.keys(rows[0]) : [];\n\n return { columns, rows };\n}\n\n// Debug function - call from console to test\nexport async function testDatabase() {\n console.log('=== DATABASE TEST ===');\n\n try {\n // 1. Check connection\n const version = await sql`SELECT sqlite_version() as v`;\n console.log('1. SQLite version:', version[0].v);\n\n // 2. Check tables\n const tables = await sql`SELECT name FROM sqlite_master WHERE type='table'`;\n console.log('2. Tables:', tables.map(t => t.name));\n\n // 3. Try to insert a test row\n console.log('3. Inserting test row...');\n await sql`INSERT INTO locations (name, longitude, latitude, category) VALUES ('TEST', -1.0, 7.0, 'test')`;\n\n // 4. Read it back\n const rows = await sql`SELECT * FROM locations WHERE name = 'TEST'`;\n console.log('4. Test row:', rows);\n\n // 5. Count all rows\n const count = await sql`SELECT COUNT(*) as c FROM locations`;\n console.log('5. Total rows:', count[0].c);\n\n // 6. Delete test row\n await sql`DELETE FROM locations WHERE name = 'TEST'`;\n console.log('6. Test row deleted');\n\n console.log('=== TEST PASSED ===');\n return true;\n } catch (error) {\n console.error('=== TEST FAILED ===', error);\n return false;\n}\n}\n\n// Expose to window for debugging\nif (typeof window !== 'undefined') {\n window.testDatabase = testDatabase;\n window.dbStatus = getDatabaseStatus;\n}\n\nexport async function closeDatabase() {\n channel.close();\n if (db.destroy) {\n await db.destroy();\n}\n}\n\n// ============================================================================\n// GPS Trails (storage adapter for the reusable GeoTracker module)\n// ============================================================================\n//\n// These functions implement the GeoTracker \"storage adapter\" contract using\n// SQLocal. They are intentionally generic (no map / UI coupling) so the same\n// schema + helpers can be lifted into another app alongside src/geotracker/.\n\n/**\n * Create a new trail row (status='recording') and return its local id.\n * @param {object} meta { uuid, name, startedAt, districtId }\n * @returns {Promise} local trail id\n */\nexport async function createGpsTrail(meta) {\n const { uuid, name = null, startedAt, districtId = null } = meta;\n await sql`\n INSERT INTO gps_trails (client_uuid, name, district_id, started_at, status)\n VALUES (${uuid}, ${name}, ${districtId}, ${startedAt}, 'recording')\n `;\n const idResult = await sql`SELECT last_insert_rowid() as id`;\n const id = idResult[0]?.id;\n broadcastChange('gps_trails', 'insert', id);\n return id;\n}\n\n/**\n * Append one recorded point to a trail.\n * @param {number} trailId\n * @param {object} point normalized fix + { seq }\n */\nexport async function addGpsTrailPoint(trailId, point) {\n const {\n seq, lon, lat,\n altitude = null, accuracy = null, altitudeAccuracy = null,\n heading = null, speed = null, satellites = null, timestamp,\n } = point;\n const recordedAt = typeof timestamp === 'number' ? new Date(timestamp).toISOString() : (timestamp || new Date().toISOString());\n await sql`\n INSERT INTO gps_trail_points\n (trail_id, seq, longitude, latitude, altitude, accuracy, altitude_accuracy, heading, speed, satellites, recorded_at)\n VALUES\n (${trailId}, ${seq}, ${lon}, ${lat}, ${altitude}, ${accuracy}, ${altitudeAccuracy}, ${heading}, ${speed}, ${satellites}, ${recordedAt})\n `;\n}\n\n/**\n * Finalise a trail: mark completed and store the summary.\n * @param {number} trailId\n * @param {object} summary { endedAt, pointCount, distanceM }\n */\nexport async function finishGpsTrail(trailId, summary) {\n const { endedAt, pointCount = 0, distanceM = 0 } = summary;\n await sql`\n UPDATE gps_trails\n SET ended_at = ${endedAt}, point_count = ${pointCount}, distance_m = ${distanceM}, status = 'completed'\n WHERE id = ${trailId}\n `;\n broadcastChange('gps_trails', 'update', trailId);\n}\n\n/**\n * Trails that are completed but not yet pushed to the server.\n * @returns {Promise}\n */\nexport async function getUnsyncedGpsTrails() {\n return sql`SELECT * FROM gps_trails WHERE synced = 0 AND status = 'completed' ORDER BY started_at ASC`;\n}\n\n/**\n * All points of a trail, in recorded order.\n * @param {number} trailId\n * @returns {Promise}\n */\nexport async function getGpsTrailPoints(trailId) {\n return sql`SELECT * FROM gps_trail_points WHERE trail_id = ${trailId} ORDER BY seq ASC`;\n}\n\n/**\n * Mark a trail as synced and record the server-assigned id.\n * @param {number} trailId\n * @param {string|number|null} remoteId\n */\nexport async function markGpsTrailSynced(trailId, remoteId = null) {\n await sql`UPDATE gps_trails SET synced = 1, remote_id = ${remoteId} WHERE id = ${trailId}`;\n broadcastChange('gps_trails', 'update', trailId);\n}\n\n/**\n * List trails (most recent first) for a history/list UI.\n * @returns {Promise}\n */\nexport async function getGpsTrails() {\n return sql`SELECT * FROM gps_trails ORDER BY started_at DESC`;\n}\n\n/**\n * Delete a trail and all its points.\n * @param {number} trailId\n */\nexport async function deleteGpsTrail(trailId) {\n await sql`DELETE FROM gps_trail_points WHERE trail_id = ${trailId}`;\n await sql`DELETE FROM gps_trails WHERE id = ${trailId}`;\n broadcastChange('gps_trails', 'delete', trailId);\n}\n\nexport default {\n sql,\n dbReady,\n initSchema,\n addLocation,\n getLocations,\n getLocation,\n updateLocation,\n deleteLocation,\n getLocationCount,\n getUnsyncedChanges,\n getUnsyncedLocations,\n markSynced,\n markLocationsSynced,\n saveRemoteData,\n getRemoteData,\n saveCollectorZones,\n getLocalCollectorZones,\n saveUpnGrid,\n getLocalUpnGrid,\n createExternalImport,\n addExternalImportFeatures,\n updateExternalImport,\n getExternalImport,\n listExternalImports,\n getExternalImportFeatures,\n remapImportedFeatureProperties,\n updateExternalImportFeatureGeometry,\n deleteExternalImportFeature,\n deleteExternalImport,\n saveParcels,\n getLocalParcels,\n updateParcel,\n insertNewParcel,\n saveBuildingFootprints,\n getLocalBuildingFootprints,\n saveOSMRoads,\n getLocalOSMRoads,\n createGpsTrail,\n addGpsTrailPoint,\n finishGpsTrail,\n getUnsyncedGpsTrails,\n getGpsTrailPoints,\n markGpsTrailSynced,\n getGpsTrails,\n deleteGpsTrail,\n CACHED_LAYER_TABLES,\n isCachedLayerTable,\n clearTable,\n clearAllCachedLayers,\n exportDatabase,\n exportToGeoJSON,\n importDatabase,\n downloadDatabase,\n getDatabaseStatus,\n getTableStats,\n getTableContent,\n testDatabase,\n onDatabaseChange,\n closeDatabase\n};\n","/**\n * Measurement unit formatting — Metric / Imperial.\n *\n * The active system is persisted in localStorage('measurement-system').\n * Every formatter reads the current setting so the UI updates immediately\n * after the user flips the toggle.\n *\n * All input values are in metres (length) or square metres (area).\n */\n\n// ── Conversion constants ────────────────────────────────────────────────────\nconst M_TO_FT = 3.28084;\nconst M_TO_MI = 0.000621371;\nconst SQM_TO_SQFT = 10.7639;\nconst SQM_TO_ACRE = 0.000247105;\nconst SQM_TO_SQMI = 3.861e-7;\n\n// ── System accessor ─────────────────────────────────────────────────────────\n\n/** @returns {'metric'|'imperial'} */\nexport function getSystem() {\n return localStorage.getItem('measurement-system') || 'metric';\n}\n\n// ── Length / distance ───────────────────────────────────────────────────────\n\n/**\n * Format a length value (in metres) for display.\n * Metric: m / km\n * Imperial: ft / mi\n */\nexport function formatLength(metres) {\n if (getSystem() === 'imperial') {\n const ft = metres * M_TO_FT;\n if (ft >= 5280) {\n return (Math.round(metres * M_TO_MI * 100) / 100) + ' mi';\n }\n return Math.round(ft) + ' ft';\n }\n // metric\n if (metres > 1000) {\n return (Math.round(metres / 1000 * 100) / 100) + ' km';\n }\n return (Math.round(metres * 100) / 100) + ' m';\n}\n\n/**\n * Format a length with both large and small units (for info popups).\n * Metric: \"1.23 km (1,230 m)\" or \"456 m\"\n * Imperial: \"1.23 mi (6,494 ft)\" or \"456 ft\"\n */\nexport function formatLengthFull(metres) {\n if (getSystem() === 'imperial') {\n const ft = metres * M_TO_FT;\n const mi = metres * M_TO_MI;\n if (ft >= 5280) {\n return `${mi.toFixed(2)} mi (${ft.toLocaleString('en', { maximumFractionDigits: 0 })} ft)`;\n }\n return `${ft.toLocaleString('en', { maximumFractionDigits: 1 })} ft`;\n }\n if (metres >= 1000) {\n return `${(metres / 1000).toFixed(2)} km (${metres.toLocaleString('en', { maximumFractionDigits: 0 })} m)`;\n }\n return `${metres.toLocaleString('en', { maximumFractionDigits: 1 })} m`;\n}\n\n// ── Area ────────────────────────────────────────────────────────────────────\n\n/**\n * Format an area value (in square metres) for display.\n * Metric: m² / km²\n * Imperial: ft² / acres / mi²\n */\nexport function formatArea(sqMetres) {\n if (getSystem() === 'imperial') {\n const acres = sqMetres * SQM_TO_ACRE;\n if (acres >= 640) {\n return (Math.round(sqMetres * SQM_TO_SQMI * 100) / 100) + ' mi²';\n }\n if (acres >= 1) {\n return (Math.round(acres * 100) / 100) + ' acres';\n }\n return Math.round(sqMetres * SQM_TO_SQFT).toLocaleString('en') + ' ft²';\n }\n // metric\n if (sqMetres > 1000000) {\n return (Math.round(sqMetres / 1000000 * 100) / 100) + ' km²';\n }\n return (Math.round(sqMetres * 100) / 100) + ' m²';\n}\n\n/**\n * Format an area with both large and small units (for info popups).\n * Metric: \"1.23 km² (1,230,000 m²)\" or \"456 m²\"\n * Imperial: \"1.23 mi² (787 acres)\" or \"2.5 acres\" or \"456 ft²\"\n */\nexport function formatAreaFull(sqMetres) {\n if (getSystem() === 'imperial') {\n const sqft = sqMetres * SQM_TO_SQFT;\n const acres = sqMetres * SQM_TO_ACRE;\n const sqmi = sqMetres * SQM_TO_SQMI;\n if (acres >= 640) {\n return `${sqmi.toFixed(2)} mi² (${acres.toLocaleString('en', { maximumFractionDigits: 0 })} acres)`;\n }\n if (acres >= 1) {\n return `${acres.toLocaleString('en', { maximumFractionDigits: 1 })} acres (${sqft.toLocaleString('en', { maximumFractionDigits: 0 })} ft²)`;\n }\n return `${sqft.toLocaleString('en', { maximumFractionDigits: 0 })} ft²`;\n }\n if (sqMetres > 1000000) {\n return `${(sqMetres / 1000000).toFixed(2)} km² (${sqMetres.toLocaleString('en', { maximumFractionDigits: 0 })} m²)`;\n }\n return `${sqMetres.toLocaleString('en', { maximumFractionDigits: 0 })} m²`;\n}\n\n// ── Circle helper ───────────────────────────────────────────────────────────\n\n/**\n * Format the area of a circle given its radius (in metres).\n */\nexport function formatCircleExtent(radiusMetres) {\n return formatArea(Math.PI * radiusMetres * radiusMetres);\n}\n","/**\n * Pure geometry functions for splitting a polygon by a line.\n *\n * No OpenLayers dependency — operates on raw coordinate arrays.\n */\n\n/**\n * Compute the intersection point of two 2D line segments.\n * Segment A: p1→p2, Segment B: p3→p4.\n *\n * @param {number[]} p1\n * @param {number[]} p2\n * @param {number[]} p3\n * @param {number[]} p4\n * @param {number} [eps=1e-10] tolerance for parallel check\n * @returns {{ point: number[], t: number, u: number } | null}\n * t = parametric position on segment A (0–1),\n * u = parametric position on segment B (0–1)\n */\nfunction segmentIntersection(p1, p2, p3, p4, eps = 1e-10) {\n const dx1 = p2[0] - p1[0];\n const dy1 = p2[1] - p1[1];\n const dx2 = p4[0] - p3[0];\n const dy2 = p4[1] - p3[1];\n\n const denom = dx1 * dy2 - dy1 * dx2;\n if (Math.abs(denom) < eps) return null; // parallel / collinear\n\n const dx3 = p3[0] - p1[0];\n const dy3 = p3[1] - p1[1];\n\n const t = (dx3 * dy2 - dy3 * dx2) / denom;\n const u = (dx3 * dy1 - dy3 * dx1) / denom;\n\n if (t < -eps || t > 1 + eps || u < -eps || u > 1 + eps) return null;\n\n return {\n point: [p1[0] + t * dx1, p1[1] + t * dy1],\n t: Math.max(0, Math.min(1, t)),\n u: Math.max(0, Math.min(1, u)),\n };\n}\n\n/**\n * Signed area of a ring (shoelace formula).\n * Positive = counter-clockwise, negative = clockwise.\n */\nfunction signedArea(ring) {\n let area = 0;\n for (let i = 0, n = ring.length; i < n - 1; i++) {\n area += (ring[i][0] * ring[i + 1][1]) - (ring[i + 1][0] * ring[i][1]);\n }\n return area / 2;\n}\n\n/**\n * Test whether a point is inside a ring (ray-casting algorithm).\n */\nfunction pointInRing(pt, ring) {\n let inside = false;\n for (let i = 0, j = ring.length - 2; i < ring.length - 1; j = i++) {\n const xi = ring[i][0], yi = ring[i][1];\n const xj = ring[j][0], yj = ring[j][1];\n if (((yi > pt[1]) !== (yj > pt[1])) &&\n (pt[0] < (xj - xi) * (pt[1] - yi) / (yj - yi) + xi)) {\n inside = !inside;\n }\n }\n return inside;\n}\n\n/**\n * Squared distance between two points.\n */\nfunction dist2(a, b) {\n return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2;\n}\n\n/**\n * Find all intersection points between a cutting line and a polygon ring.\n *\n * @param {number[][]} ring Closed ring coordinates (first === last)\n * @param {number[][]} line LineString coordinates (2+ points)\n * @returns {Array<{ point: number[], ringSegIdx: number, ringT: number, lineSegIdx: number, lineT: number }>}\n */\nfunction findIntersections(ring, line) {\n const hits = [];\n const eps = 1e-10;\n\n for (let li = 0; li < line.length - 1; li++) {\n for (let ri = 0; ri < ring.length - 1; ri++) {\n const ix = segmentIntersection(ring[ri], ring[ri + 1], line[li], line[li + 1], eps);\n if (!ix) continue;\n\n // Skip if intersection is at the very start of the ring segment\n // but was already caught as the end of the previous segment\n const pt = ix.point;\n\n // Avoid duplicate hits at shared vertices\n let isDup = false;\n for (const h of hits) {\n if (dist2(h.point, pt) < 1e-6) {\n isDup = true;\n break;\n }\n }\n if (isDup) continue;\n\n hits.push({\n point: pt,\n ringSegIdx: ri,\n ringT: ix.t,\n lineSegIdx: li,\n lineT: ix.u,\n });\n }\n }\n\n // Sort by position along the cutting line\n hits.sort((a, b) => {\n if (a.lineSegIdx !== b.lineSegIdx) return a.lineSegIdx - b.lineSegIdx;\n return a.lineT - b.lineT;\n });\n\n return hits;\n}\n\n/**\n * Insert intersection points into a ring, returning the expanded ring\n * and the new indices of the inserted points.\n *\n * @param {number[][]} ring Closed ring (first === last)\n * @param {Array<{ point: number[], ringSegIdx: number, ringT: number }>} hits\n * Sorted by ringSegIdx then ringT.\n * @returns {{ ring: number[][], indices: number[] }}\n */\nfunction insertPointsIntoRing(ring, hits) {\n // Sort hits by ring position (segment index, then parametric t) so\n // we can insert from back to front without shifting earlier indices.\n const sorted = hits.map((h, i) => ({ ...h, origOrder: i }));\n sorted.sort((a, b) => {\n if (a.ringSegIdx !== b.ringSegIdx) return a.ringSegIdx - b.ringSegIdx;\n return a.ringT - b.ringT;\n });\n\n const expanded = ring.slice(); // copy\n const indices = new Array(sorted.length);\n\n // Insert from the end so that earlier insertions don't shift later indices.\n for (let k = sorted.length - 1; k >= 0; k--) {\n const h = sorted[k];\n const insertIdx = h.ringSegIdx + 1;\n\n // Check if this point is essentially identical to an existing vertex\n const snapDist = 1e-6;\n if (dist2(h.point, expanded[h.ringSegIdx]) < snapDist) {\n indices[h.origOrder] = h.ringSegIdx;\n continue;\n }\n if (dist2(h.point, expanded[h.ringSegIdx + 1]) < snapDist) {\n indices[h.origOrder] = h.ringSegIdx + 1;\n continue;\n }\n\n // Insert the new point\n expanded.splice(insertIdx, 0, h.point);\n indices[h.origOrder] = insertIdx;\n\n // Adjust indices for all previously recorded insertions\n // that reference a position >= insertIdx\n for (let j = k + 1; j < sorted.length; j++) {\n if (indices[sorted[j].origOrder] >= insertIdx) {\n indices[sorted[j].origOrder]++;\n }\n }\n }\n\n return { ring: expanded, indices };\n}\n\n/**\n * Extract a slice of a ring from index i0 to i1 (going forward, wrapping).\n * Both endpoints are included.\n *\n * @param {number[][]} ring Closed ring (first === last); length includes closing vertex\n * @param {number} i0 Start index (inclusive)\n * @param {number} i1 End index (inclusive)\n * @returns {number[][]}\n */\nfunction ringSlice(ring, i0, i1) {\n const n = ring.length - 1; // number of unique vertices (ring is closed)\n // Normalise indices into the [0, n-1] range\n const start = ((i0 % n) + n) % n;\n const end = ((i1 % n) + n) % n;\n const result = [];\n let idx = start;\n while (true) {\n result.push(ring[idx]);\n if (idx === end) break;\n idx = (idx + 1) % n;\n }\n return result;\n}\n\n/**\n * Extract the cutting line segment between two intersection points.\n *\n * @param {number[][]} line Full cutting line coordinates\n * @param {{ point: number[], lineSegIdx: number, lineT: number }} hit0\n * @param {{ point: number[], lineSegIdx: number, lineT: number }} hit1\n * @returns {number[][]} Coordinates from hit0.point to hit1.point along the line\n */\nfunction cuttingLineSlice(line, hit0, hit1) {\n const result = [hit0.point];\n\n // Include all intermediate line vertices between the two hit segments\n const startSeg = hit0.lineSegIdx;\n const endSeg = hit1.lineSegIdx;\n\n for (let i = startSeg + 1; i <= endSeg; i++) {\n result.push(line[i]);\n }\n\n // Add the end intersection point if it's not the same as the last vertex\n if (dist2(result[result.length - 1], hit1.point) > 1e-10) {\n result.push(hit1.point);\n }\n\n return result;\n}\n\n/**\n * Ensure a ring has the desired winding order.\n * @param {number[][]} ring Closed ring\n * @param {boolean} ccw true for counter-clockwise\n * @returns {number[][]}\n */\nfunction ensureWinding(ring, ccw) {\n const area = signedArea(ring);\n if ((ccw && area < 0) || (!ccw && area > 0)) {\n return ring.slice().reverse();\n }\n return ring;\n}\n\n/**\n * Close a ring (ensure first === last).\n */\nfunction closeRing(coords) {\n if (coords.length < 2) return coords;\n const first = coords[0];\n const last = coords[coords.length - 1];\n if (dist2(first, last) > 1e-10) {\n return [...coords, first.slice()];\n }\n return coords;\n}\n\n/**\n * Extend a cutting line so that both endpoints lie outside the polygon ring.\n * If an endpoint is inside, we extend the first/last segment outward past the\n * bounding box diagonal so it definitely exits.\n *\n * @param {number[][]} line Cutting line coordinates\n * @param {number[][]} ring Closed polygon ring\n * @returns {number[][]} Extended line (may be the original if already outside)\n */\nfunction extendLineOutsideRing(line, ring) {\n // Compute bounding-box diagonal for a generous extension distance\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const pt of ring) {\n if (pt[0] < minX) minX = pt[0];\n if (pt[1] < minY) minY = pt[1];\n if (pt[0] > maxX) maxX = pt[0];\n if (pt[1] > maxY) maxY = pt[1];\n }\n const diag = Math.sqrt((maxX - minX) ** 2 + (maxY - minY) ** 2) || 1;\n\n const result = line.slice();\n\n // Extend start if inside\n if (pointInRing(result[0], ring)) {\n const p0 = result[0];\n const p1 = result[1];\n const dx = p0[0] - p1[0];\n const dy = p0[1] - p1[1];\n const len = Math.sqrt(dx * dx + dy * dy) || 1;\n const scale = diag * 2 / len;\n result[0] = [p0[0] + dx * scale, p0[1] + dy * scale];\n }\n\n // Extend end if inside\n const last = result.length - 1;\n if (pointInRing(result[last], ring)) {\n const pN = result[last];\n const pN1 = result[last - 1];\n const dx = pN[0] - pN1[0];\n const dy = pN[1] - pN1[1];\n const len = Math.sqrt(dx * dx + dy * dy) || 1;\n const scale = diag * 2 / len;\n result[last] = [pN[0] + dx * scale, pN[1] + dy * scale];\n }\n\n return result;\n}\n\n/**\n * Split a polygon by a cutting line.\n *\n * The cutting line can start or end inside the polygon — the algorithm will\n * automatically extend it outward so it crosses the boundary at exactly 2\n * points. Multi-vertex cutting lines (with corners or approximated arcs)\n * are fully supported.\n *\n * @param {number[][][]} polygonCoords Polygon coordinates:\n * [exteriorRing, ...holeRings] where each ring is closed (first === last)\n * @param {number[][]} lineCoords Cutting line coordinates (2+ points)\n * @returns {number[][][][] | null} Two polygon coordinate arrays, or null if split failed\n */\nexport function splitPolygonByLine(polygonCoords, lineCoords) {\n const exteriorRing = polygonCoords[0];\n const holes = polygonCoords.slice(1);\n\n // Extend the cutting line if its endpoints are inside the polygon\n const extendedLine = extendLineOutsideRing(lineCoords, exteriorRing);\n\n // 1. Find intersections between cutting line and exterior ring\n const hits = findIntersections(exteriorRing, extendedLine);\n\n // We need exactly 2 intersection points for a simple split\n if (hits.length !== 2) {\n console.warn(`[polygonSplit] Expected 2 intersections, got ${hits.length}`);\n return null;\n }\n\n const [hit0, hit1] = hits;\n\n // 2. Insert intersection points into the ring\n const { ring: expandedRing, indices } = insertPointsIntoRing(exteriorRing, hits);\n const idx0 = indices[0];\n const idx1 = indices[1];\n\n // Ensure idx0 < idx1 for consistent traversal\n const [iA, iB] = idx0 < idx1 ? [idx0, idx1] : [idx1, idx0];\n const [hitA, hitB] = idx0 < idx1 ? [hit0, hit1] : [hit1, hit0];\n\n // 3. Get the cutting line segment between the two intersection points\n const cutForward = idx0 < idx1\n ? cuttingLineSlice(extendedLine, hit0, hit1)\n : cuttingLineSlice(extendedLine, hit1, hit0);\n const cutReverse = cutForward.slice().reverse();\n\n // 4. Build two polygon rings\n // Ring A: walk ring from iA to iB (forward), then cutting line reversed back to iA\n const sliceAB = ringSlice(expandedRing, iA, iB);\n const ringA = closeRing([...sliceAB, ...cutReverse.slice(1)]);\n\n // Ring B: walk ring from iB to iA (wrapping), then cutting line forward back to iB\n const sliceBA = ringSlice(expandedRing, iB, iA);\n const ringB = closeRing([...sliceBA, ...cutForward.slice(1)]);\n\n // 5. Match winding order to original\n const originalCCW = signedArea(exteriorRing) > 0;\n const finalA = ensureWinding(ringA, originalCCW);\n const finalB = ensureWinding(ringB, originalCCW);\n\n // 6. Build polygon coordinate arrays, assigning holes to the correct piece\n const polyA = [finalA];\n const polyB = [finalB];\n\n for (const hole of holes) {\n // Use the centroid of the hole to determine containment\n const centroid = holeCentroid(hole);\n if (pointInRing(centroid, finalA)) {\n polyA.push(hole);\n } else {\n polyB.push(hole);\n }\n }\n\n return [polyA, polyB];\n}\n\n/**\n * Compute the centroid of a closed ring.\n */\nfunction holeCentroid(ring) {\n let cx = 0, cy = 0;\n const n = ring.length - 1; // exclude closing vertex\n for (let i = 0; i < n; i++) {\n cx += ring[i][0];\n cy += ring[i][1];\n }\n return [cx / n, cy / n];\n}\n","/**\n * Lightweight toast notification system.\n *\n * Usage:\n * import { showToast } from '../toast.js';\n *\n * showToast('Something went wrong', 'error');\n * showToast('Merge successful!', 'success');\n * showToast('Select two adjacent polygons', 'info');\n */\n\n// ── Palette ──────────────────────────────────────────────────────────────────\n\nconst THEMES = {\n success: { bg: '#10b981', icon: '\\u2705' }, // green\n error: { bg: '#ef4444', icon: '\\u274c' }, // red\n warning: { bg: '#f59e0b', icon: '\\u26a0\\ufe0f' }, // amber\n info: { bg: '#0ea5e9', icon: '\\u2139\\ufe0f' }, // cyan\n};\n\n// ── Container (created once, appended to ) ────────────────────────────\n\nlet container = null;\n\nfunction ensureContainer() {\n if (container) return container;\n container = document.createElement('div');\n container.style.cssText = `\n position: fixed;\n top: 16px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10000;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n pointer-events: none;\n `;\n document.body.appendChild(container);\n return container;\n}\n\n// ── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Display a toast notification.\n *\n * @param {string} message Plain-text message to show.\n * @param {'success'|'error'|'warning'|'info'} [type='info']\n * @param {number} [duration=4000] Auto-dismiss time in ms.\n */\nexport function showToast(message, type = 'info', duration = 4000) {\n const parent = ensureContainer();\n const theme = THEMES[type] || THEMES.info;\n\n const el = document.createElement('div');\n el.style.cssText = `\n background: ${theme.bg};\n color: #fff;\n padding: 10px 18px;\n border-radius: 8px;\n font-family: var(--font-body, 'Exo', sans-serif);\n font-size: 13px;\n font-weight: 600;\n box-shadow: 0 4px 12px rgba(0,0,0,0.25);\n pointer-events: auto;\n cursor: pointer;\n opacity: 0;\n transition: opacity 0.25s ease, transform 0.25s ease;\n transform: translateY(-8px);\n max-width: 420px;\n text-align: center;\n line-height: 1.4;\n `;\n el.textContent = `${theme.icon} ${message}`;\n\n parent.appendChild(el);\n\n // Animate in\n requestAnimationFrame(() => {\n el.style.opacity = '1';\n el.style.transform = 'translateY(0)';\n });\n\n // Dismiss helper\n const dismiss = () => {\n el.style.opacity = '0';\n el.style.transform = 'translateY(-8px)';\n setTimeout(() => el.remove(), 300);\n };\n\n // Click to dismiss early\n el.addEventListener('click', dismiss);\n\n // Auto-dismiss\n setTimeout(dismiss, duration);\n}\n","/**\n * PolygonSplitInteraction\n *\n * A two-phase OpenLayers interaction for splitting polygons:\n * Phase 1 – SELECT: hover to highlight, click to select a polygon\n * Phase 2 – DRAW: draw a cutting line, double-click to finish\n *\n * After a successful split the original feature is removed and two new\n * coloured features are added. The interaction fires `beforesplit` and\n * `aftersplit` events compatible with ol-ext's UndoRedo.\n */\n\nimport ol_interaction_Interaction from 'ol/interaction/Interaction';\nimport ol_interaction_Draw from 'ol/interaction/Draw';\nimport VectorSource from 'ol/source/Vector';\nimport VectorLayer from 'ol/layer/Vector';\nimport Feature from 'ol/Feature';\nimport { Style, Stroke, Fill, Circle as CircleStyle } from 'ol/style';\nimport { LineString } from 'ol/geom';\nimport { Polygon as PolygonGeom } from 'ol/geom';\nimport { splitPolygonByLine } from '../geom/polygonSplit.js';\nimport { showToast } from '../toast.js';\n\n// Marker colours for the two split pieces\nconst SPLIT_COLORS = [\n { stroke: '#ef4444', fill: 'rgba(239,68,68,0.25)' }, // red\n { stroke: '#3b82f6', fill: 'rgba(59,130,246,0.25)' }, // blue\n];\n\n// Highlight style for the selected polygon (phase 1)\nconst HIGHLIGHT_STYLE = new Style({\n stroke: new Stroke({ color: '#0ea5e9', width: 3 }),\n fill: new Fill({ color: 'rgba(14,165,233,0.15)' }),\n});\n\n// Style for the cutting-line sketch (phase 2)\nconst SKETCH_STYLE = new Style({\n stroke: new Stroke({ color: '#f43f5e', width: 2, lineDash: [8, 6] }),\n image: new CircleStyle({\n radius: 5,\n fill: new Fill({ color: '#f43f5e' }),\n stroke: new Stroke({ color: '#fff', width: 1.5 }),\n }),\n});\n\nexport class PolygonSplitInteraction extends ol_interaction_Interaction {\n /**\n * @param {Object} options\n * @param {VectorSource|VectorSource[]} [options.sources] Sources containing\n * polygons to split. If omitted the interaction searches all visible\n * vector layers on the map.\n * @param {number} [options.snapDistance=25] Pixel distance for hover highlight.\n */\n constructor(options = {}) {\n super({\n handleEvent: (e) => this._handleEvent(e),\n });\n\n this.snapDistance_ = options.snapDistance || 25;\n this._sources = options.sources\n ? (Array.isArray(options.sources) ? options.sources : [options.sources])\n : null;\n\n // Phase: 'select' | 'draw' | 'pick'\n this._phase = 'select';\n this._selectedFeature = null;\n this._selectedSource = null;\n this._drawInteraction = null;\n this._splitFeatures = null; // the two pieces (for pick phase)\n\n // Overlay layer for highlighting the polygon under the cursor / selected\n this._overlaySource = new VectorSource({ useSpatialIndex: false });\n this._overlayLayer = new VectorLayer({\n source: this._overlaySource,\n displayInLayerSwitcher: false,\n style: HIGHLIGHT_STYLE,\n });\n }\n\n /* ------------------------------------------------------------------ */\n /* Map lifecycle */\n /* ------------------------------------------------------------------ */\n\n setMap(map) {\n if (this.getMap()) {\n this.getMap().removeLayer(this._overlayLayer);\n this._removeDrawInteraction();\n }\n super.setMap(map);\n if (map) {\n this._overlayLayer.setMap(map);\n }\n }\n\n setActive(active) {\n super.setActive(active);\n if (!active) {\n this._reset();\n }\n }\n\n /* ------------------------------------------------------------------ */\n /* Source helpers */\n /* ------------------------------------------------------------------ */\n\n _getSources() {\n if (this._sources) return this._sources;\n if (!this.getMap()) return [];\n const sources = [];\n const collect = (layers) => {\n layers.forEach((layer) => {\n if (layer.getVisible()) {\n if (layer.getSource && layer.getSource() instanceof VectorSource) {\n sources.push(layer.getSource());\n } else if (layer.getLayers) {\n collect(layer.getLayers());\n }\n }\n });\n };\n collect(this.getMap().getLayers());\n return sources;\n }\n\n /* ------------------------------------------------------------------ */\n /* Event router */\n /* ------------------------------------------------------------------ */\n\n _handleEvent(e) {\n if (!this.getActive()) return true;\n\n if (this._phase === 'select') {\n if (e.type === 'pointermove') return this._onSelectMove(e);\n if (e.type === 'singleclick') return this._onSelectClick(e);\n }\n // In 'draw' phase the Draw interaction handles events directly;\n // we only intercept Escape to cancel.\n if (this._phase === 'draw') {\n if (e.type === 'keydown' && e.originalEvent?.key === 'Escape') {\n this._cancelDraw();\n return false;\n }\n }\n\n // In 'pick' phase the user selects which split piece keeps the UPN\n if (this._phase === 'pick') {\n if (e.type === 'pointermove') return this._onPickMove(e);\n if (e.type === 'singleclick') return this._onPickClick(e);\n if (e.type === 'keydown' && e.originalEvent?.key === 'Escape') {\n this._reset();\n return false;\n }\n }\n\n return true;\n }\n\n /* ------------------------------------------------------------------ */\n /* Phase 1: SELECT */\n /* ------------------------------------------------------------------ */\n\n _onSelectMove(e) {\n const map = this.getMap();\n if (!map) return true;\n\n this._overlaySource.clear();\n\n const hit = this._closestPolygon(e);\n if (hit) {\n // Show highlight copy\n const clone = hit.feature.clone();\n this._overlaySource.addFeature(clone);\n map.getTargetElement().style.cursor = 'pointer';\n } else {\n map.getTargetElement().style.cursor = '';\n }\n return true;\n }\n\n _onSelectClick(e) {\n const hit = this._closestPolygon(e);\n if (!hit) return true;\n\n this._selectedFeature = hit.feature;\n this._selectedSource = hit.source;\n\n // Keep highlight visible during draw phase\n this._overlaySource.clear();\n const clone = hit.feature.clone();\n this._overlaySource.addFeature(clone);\n\n this._startDrawPhase();\n return false; // consume the click\n }\n\n /**\n * Find the closest polygon feature within snap distance.\n */\n _closestPolygon(e) {\n let best = null;\n let bestDist = this.snapDistance_ + 1;\n\n for (const source of this._getSources()) {\n const feat = source.getClosestFeatureToCoordinate(e.coordinate);\n if (!feat) continue;\n const geom = feat.getGeometry();\n if (!geom) continue;\n const type = geom.getType();\n if (type !== 'Polygon' && type !== 'MultiPolygon') continue;\n\n const closest = geom.getClosestPoint(e.coordinate);\n const line = new LineString([e.coordinate, closest]);\n const distPx = line.getLength() / e.frameState.viewState.resolution;\n\n if (distPx < bestDist) {\n bestDist = distPx;\n best = { feature: feat, source, coord: closest };\n }\n }\n return best;\n }\n\n /* ------------------------------------------------------------------ */\n /* Phase 2: DRAW cutting line */\n /* ------------------------------------------------------------------ */\n\n _startDrawPhase() {\n this._phase = 'draw';\n const map = this.getMap();\n if (!map) return;\n\n map.getTargetElement().style.cursor = 'crosshair';\n\n this._drawInteraction = new ol_interaction_Draw({\n type: 'LineString',\n style: SKETCH_STYLE,\n });\n\n this._drawInteraction.on('drawend', (evt) => {\n const cuttingLine = evt.feature.getGeometry().getCoordinates();\n this._performSplit(cuttingLine);\n });\n\n map.addInteraction(this._drawInteraction);\n }\n\n _removeDrawInteraction() {\n if (this._drawInteraction && this.getMap()) {\n this.getMap().removeInteraction(this._drawInteraction);\n }\n this._drawInteraction = null;\n }\n\n _cancelDraw() {\n this._removeDrawInteraction();\n this._reset();\n }\n\n /* ------------------------------------------------------------------ */\n /* Split logic */\n /* ------------------------------------------------------------------ */\n\n _performSplit(cuttingLineCoords) {\n const feature = this._selectedFeature;\n const source = this._selectedSource;\n const geom = feature.getGeometry();\n\n let polygonCoords;\n if (geom.getType() === 'Polygon') {\n polygonCoords = geom.getCoordinates();\n } else if (geom.getType() === 'MultiPolygon') {\n // For MultiPolygon, try to split each sub-polygon and use the\n // first one that produces a valid result.\n // For now, use the first polygon ring.\n polygonCoords = geom.getCoordinates()[0];\n }\n\n const result = splitPolygonByLine(polygonCoords, cuttingLineCoords);\n\n if (!result) {\n console.warn('[PolygonSplit] Split failed — line must cross the polygon boundary at exactly 2 points.');\n // Stay in draw phase so user can retry\n this._removeDrawInteraction();\n this._startDrawPhase();\n return;\n }\n\n const [coordsA, coordsB] = result;\n\n // Create two new features from the split result\n const featureA = feature.clone();\n featureA.setGeometry(new PolygonGeom(coordsA));\n featureA.setStyle(new Style({\n stroke: new Stroke({ color: SPLIT_COLORS[0].stroke, width: 2.5 }),\n fill: new Fill({ color: SPLIT_COLORS[0].fill }),\n }));\n\n const featureB = feature.clone();\n featureB.setGeometry(new PolygonGeom(coordsB));\n featureB.setStyle(new Style({\n stroke: new Stroke({ color: SPLIT_COLORS[1].stroke, width: 2.5 }),\n fill: new Fill({ color: SPLIT_COLORS[1].fill }),\n }));\n\n // Dispatch beforesplit (compatible with ol-ext UndoRedo)\n const splitFeatures = [featureA, featureB];\n this.dispatchEvent({\n type: 'beforesplit',\n original: feature,\n features: splitFeatures,\n });\n source.dispatchEvent({\n type: 'beforesplit',\n original: feature,\n features: splitFeatures,\n });\n\n // Replace the original feature. Group the remove + two adds into a single\n // UndoRedo block (via the map-level undoblockstart/undoblockend events the\n // ol-ext UndoRedo interaction listens for) so the whole split is reversed\n // by ONE undo press instead of three.\n const splitMap = this.getMap();\n splitMap?.dispatchEvent('undoblockstart');\n source.removeFeature(feature);\n source.addFeature(featureA);\n source.addFeature(featureB);\n splitMap?.dispatchEvent('undoblockend');\n\n // Dispatch aftersplit\n this.dispatchEvent({\n type: 'aftersplit',\n original: feature,\n features: splitFeatures,\n });\n source.dispatchEvent({\n type: 'aftersplit',\n original: feature,\n features: splitFeatures,\n });\n\n // Clean up draw interaction\n this._removeDrawInteraction();\n\n // If the original was a parcel, enter pick phase for UPN assignment\n const isParcel = feature.get('_layerType') === 'parcel';\n if (isParcel) {\n this._splitFeatures = splitFeatures;\n this._phase = 'pick';\n this._overlaySource.clear();\n const map = this.getMap();\n if (map) map.getTargetElement().style.cursor = '';\n showToast('Click the polygon that should keep the original identifier.', 'info', 5000);\n\n this.dispatchEvent({\n type: 'splitparcel',\n features: splitFeatures,\n originalProps: feature.getProperties(),\n source,\n });\n } else {\n this._reset();\n }\n }\n\n /* ------------------------------------------------------------------ */\n /* Phase 3: PICK — select which split piece keeps the UPN */\n /* ------------------------------------------------------------------ */\n\n _onPickMove(e) {\n const map = this.getMap();\n if (!map) return true;\n\n this._overlaySource.clear();\n\n const hit = this._closestSplitPiece(e);\n if (hit) {\n const clone = hit.clone();\n this._overlaySource.addFeature(clone);\n map.getTargetElement().style.cursor = 'pointer';\n } else {\n map.getTargetElement().style.cursor = '';\n }\n return true;\n }\n\n _onPickClick(e) {\n const hit = this._closestSplitPiece(e);\n if (!hit) return true;\n\n this.dispatchEvent({\n type: 'splitpick',\n picked: hit,\n features: this._splitFeatures,\n });\n\n this._reset();\n return false;\n }\n\n /**\n * Find the closest split piece to the cursor.\n */\n _closestSplitPiece(e) {\n if (!this._splitFeatures) return null;\n let best = null;\n let bestDist = this.snapDistance_ + 1;\n\n for (const feat of this._splitFeatures) {\n const geom = feat.getGeometry();\n if (!geom) continue;\n const closest = geom.getClosestPoint(e.coordinate);\n const line = new LineString([e.coordinate, closest]);\n const distPx = line.getLength() / e.frameState.viewState.resolution;\n if (distPx < bestDist) {\n bestDist = distPx;\n best = feat;\n }\n }\n return best;\n }\n\n /* ------------------------------------------------------------------ */\n /* Reset */\n /* ------------------------------------------------------------------ */\n\n _reset() {\n this._phase = 'select';\n this._selectedFeature = null;\n this._selectedSource = null;\n this._splitFeatures = null;\n this._overlaySource.clear();\n this._removeDrawInteraction();\n\n const map = this.getMap();\n if (map) {\n map.getTargetElement().style.cursor = '';\n }\n }\n}\n","/**\n * Pure geometry functions for merging two adjacent polygons.\n *\n * No OpenLayers dependency — operates on raw coordinate arrays.\n */\n\n/**\n * Squared distance between two points.\n */\nfunction dist2(a, b) {\n return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2;\n}\n\n/**\n * Signed area of a ring (shoelace formula).\n * Positive = counter-clockwise, negative = clockwise.\n */\nfunction signedArea(ring) {\n let area = 0;\n for (let i = 0, n = ring.length; i < n - 1; i++) {\n area += (ring[i][0] * ring[i + 1][1]) - (ring[i + 1][0] * ring[i][1]);\n }\n return area / 2;\n}\n\n/**\n * Test whether a point is inside a ring (ray-casting algorithm).\n */\nfunction pointInRing(pt, ring) {\n let inside = false;\n for (let i = 0, j = ring.length - 2; i < ring.length - 1; j = i++) {\n const xi = ring[i][0], yi = ring[i][1];\n const xj = ring[j][0], yj = ring[j][1];\n if (((yi > pt[1]) !== (yj > pt[1])) &&\n (pt[0] < (xj - xi) * (pt[1] - yi) / (yj - yi) + xi)) {\n inside = !inside;\n }\n }\n return inside;\n}\n\n/**\n * Ensure a ring has the desired winding order.\n */\nfunction ensureWinding(ring, ccw) {\n const area = signedArea(ring);\n if ((ccw && area < 0) || (!ccw && area > 0)) {\n return ring.slice().reverse();\n }\n return ring;\n}\n\n/**\n * Close a ring (ensure first === last).\n */\nfunction closeRing(coords) {\n if (coords.length < 2) return coords;\n if (dist2(coords[0], coords[coords.length - 1]) > 1e-10) {\n return [...coords, coords[0].slice()];\n }\n return coords;\n}\n\n/**\n * Perpendicular distance from a point to a segment.\n *\n * @param {number[]} pt\n * @param {number[]} segA Segment start\n * @param {number[]} segB Segment end\n * @returns {number} Squared distance\n */\nfunction distToSegmentSq(pt, segA, segB) {\n const dx = segB[0] - segA[0];\n const dy = segB[1] - segA[1];\n const lenSq = dx * dx + dy * dy;\n\n if (lenSq < 1e-20) return dist2(pt, segA); // degenerate segment\n\n // Parametric position of the projection\n let t = ((pt[0] - segA[0]) * dx + (pt[1] - segA[1]) * dy) / lenSq;\n t = Math.max(0, Math.min(1, t));\n\n const projX = segA[0] + t * dx;\n const projY = segA[1] + t * dy;\n return (pt[0] - projX) ** 2 + (pt[1] - projY) ** 2;\n}\n\n/**\n * Find the ring edge closest to a click coordinate.\n *\n * @param {number[][]} ring Closed ring\n * @param {number[]} clickCoord [x, y]\n * @returns {{ segIdx: number, distSq: number }}\n */\nfunction findClosestEdge(ring, clickCoord) {\n let bestIdx = 0;\n let bestDist = Infinity;\n const n = ring.length - 1; // unique vertices\n\n for (let i = 0; i < n; i++) {\n const d = distToSegmentSq(clickCoord, ring[i], ring[(i + 1) % n === 0 ? n : i + 1]);\n if (d < bestDist) {\n bestDist = d;\n bestIdx = i;\n }\n }\n return { segIdx: bestIdx, distSq: bestDist };\n}\n\n/**\n * Check if two coordinates are equal within tolerance.\n */\nfunction coordsEqual(a, b, tolSq) {\n return dist2(a, b) < tolSq;\n}\n\n/**\n * Test whether a point lies within tolerance of any edge of a ring.\n *\n * Unlike coordsEqual (vertex-to-vertex), this checks whether the point is\n * close to the ring's *boundary* — it projects onto segments, so it works\n * even when the two polygons have different vertex density along the shared\n * edge or when vertices are slightly offset from separate digitisation.\n *\n * @param {number[]} pt Point to test\n * @param {number[][]} ring Closed ring\n * @param {number} tolSq Squared distance tolerance\n * @returns {boolean}\n */\nfunction isVertexNearRing(pt, ring, tolSq) {\n const n = ring.length - 1;\n for (let i = 0; i < n; i++) {\n if (distToSegmentSq(pt, ring[i], ring[i + 1]) < tolSq) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Find the shared boundary between two polygon rings.\n *\n * Adjacent polygons share edges in reverse winding direction:\n * if A walks P→Q along the shared boundary, B walks Q→P.\n *\n * The algorithm has two stages:\n *\n * 1. **Seed validation** — uses `isVertexNearRing` (vertex-to-edge proximity)\n * to confirm the user-clicked edges actually lie on a common boundary.\n * This is the forgiving check that handles offset vertices from separate\n * digitisation.\n *\n * 2. **Lockstep extension** — walks both rings together (A forward, B in\n * the opposite direction) and extends the shared boundary one vertex at\n * a time. Three cases are tried at each step:\n * a) Both rings advance: vertex-to-vertex match (classic case).\n * b) Only A advances: A has an extra vertex that projects onto B's\n * frontier edge (different vertex density).\n * c) Only B advances: vice-versa.\n * Because extension is coupled to the *frontier edge* of the other ring\n * (not the entire ring), it cannot overshoot into non-shared territory,\n * even for small or closely-spaced polygons.\n *\n * @param {number[][]} ringA Closed ring\n * @param {number[][]} ringB Closed ring\n * @param {number} seedIdxA Seed edge index on ring A\n * @param {number} seedIdxB Seed edge index on ring B\n * @param {number} tolerance Distance tolerance (in map units)\n * @returns {{ startA: number, endA: number, startB: number, endB: number, reversed: boolean } | null}\n */\nfunction findSharedBoundary(ringA, ringB, seedIdxA, seedIdxB, tolerance) {\n const nA = ringA.length - 1; // unique vertices\n const nB = ringB.length - 1;\n const tolSq = tolerance * tolerance;\n\n // ── Validate seed edges ─────────────────────────────────────────────\n // Both vertices of the seed edge on A must be near ring B's boundary,\n // or both vertices of the seed edge on B must be near ring A's boundary.\n // This uses vertex-to-edge proximity so it handles offset digitisation.\n const a0 = ringA[seedIdxA];\n const a1 = ringA[(seedIdxA + 1) % nA];\n const b0 = ringB[seedIdxB];\n const b1 = ringB[(seedIdxB + 1) % nB];\n\n const a0NearB = isVertexNearRing(a0, ringB, tolSq);\n const a1NearB = isVertexNearRing(a1, ringB, tolSq);\n const b0NearA = isVertexNearRing(b0, ringA, tolSq);\n const b1NearA = isVertexNearRing(b1, ringA, tolSq);\n\n if (!(a0NearB && a1NearB) && !(b0NearA && b1NearA)) {\n console.warn('[polygonMerge] Seed edges are not on the shared boundary');\n return null;\n }\n\n // ── Determine winding direction ─────────────────────────────────────\n // Reversed (the normal case): A's a0 ≈ B's b1 and A's a1 ≈ B's b0.\n let reversed;\n if (coordsEqual(a0, b1, tolSq) && coordsEqual(a1, b0, tolSq)) {\n reversed = true;\n } else if (coordsEqual(a0, b0, tolSq) && coordsEqual(a1, b1, tolSq)) {\n reversed = false;\n } else {\n // Vertices don't match exactly — use proximity to decide direction\n reversed = dist2(a0, b1) < dist2(a0, b0);\n }\n\n // ── Initialise shared boundary ──────────────────────────────────────\n let startA = seedIdxA;\n let endA = (seedIdxA + 1) % nA;\n let startB, endB;\n\n if (reversed) {\n // A walks startA → endA, B walks startB ← endB (reversed ring order)\n startB = (seedIdxB + 1) % nB;\n endB = seedIdxB;\n } else {\n startB = seedIdxB;\n endB = (seedIdxB + 1) % nB;\n }\n\n // ── Extend forward (endA++, endB-- if reversed) ─────────────────────\n // Walk both rings in lockstep. At each step try three strategies:\n // 1. Both advance — vertices match (vertex-to-vertex).\n // 2. Only A advances — A's next vertex projects onto B's frontier edge.\n // 3. Only B advances — B's next vertex projects onto A's frontier edge.\n let safety = nA + nB;\n while (safety-- > 0) {\n const nextA = (endA + 1) % nA;\n const nextB = reversed ? (endB - 1 + nB) % nB : (endB + 1) % nB;\n if (nextA === startA || nextB === startB) break; // wrapped around\n\n // Case 1: vertex-to-vertex match\n if (coordsEqual(ringA[nextA], ringB[nextB], tolSq)) {\n endA = nextA;\n endB = nextB;\n continue;\n }\n\n // Case 2: A has extra vertex — project onto B's frontier edge\n if (distToSegmentSq(ringA[nextA], ringB[endB], ringB[nextB]) < tolSq) {\n endA = nextA;\n continue;\n }\n\n // Case 3: B has extra vertex — project onto A's frontier edge\n if (distToSegmentSq(ringB[nextB], ringA[endA], ringA[nextA]) < tolSq) {\n endB = nextB;\n continue;\n }\n\n break; // no match — end of shared boundary\n }\n\n // ── Extend backward (startA--, startB++ if reversed) ────────────────\n safety = nA + nB;\n while (safety-- > 0) {\n const prevA = (startA - 1 + nA) % nA;\n const prevB = reversed ? (startB + 1) % nB : (startB - 1 + nB) % nB;\n if (prevA === endA || prevB === endB) break;\n\n // Case 1: vertex-to-vertex match\n if (coordsEqual(ringA[prevA], ringB[prevB], tolSq)) {\n startA = prevA;\n startB = prevB;\n continue;\n }\n\n // Case 2: A has extra vertex — project onto B's frontier edge\n if (distToSegmentSq(ringA[prevA], ringB[startB], ringB[prevB]) < tolSq) {\n startA = prevA;\n continue;\n }\n\n // Case 3: B has extra vertex — project onto A's frontier edge\n if (distToSegmentSq(ringB[prevB], ringA[startA], ringA[prevA]) < tolSq) {\n startB = prevB;\n continue;\n }\n\n break;\n }\n\n return { startA, endA, startB, endB, reversed };\n}\n\n/**\n * Walk a ring from startIdx to endIdx (exclusive), going forward and wrapping.\n * Skips startIdx and stops before reaching endIdx.\n * Returns the vertices of the non-shared portion.\n *\n * @param {number[][]} ring Closed ring\n * @param {number} fromIdx Start walking from this index (inclusive)\n * @param {number} toIdx Stop at this index (inclusive)\n * @returns {number[][]}\n */\nfunction walkRing(ring, fromIdx, toIdx) {\n const n = ring.length - 1;\n const result = [];\n let idx = fromIdx;\n while (true) {\n result.push(ring[idx]);\n if (idx === toIdx) break;\n idx = (idx + 1) % n;\n // Safety: prevent infinite loops\n if (result.length > n + 1) break;\n }\n return result;\n}\n\n/**\n * Merge two adjacent polygons along their shared boundary.\n *\n * @param {number[][][]} polygonCoordsA Polygon A coordinates [exteriorRing, ...holes]\n * @param {number[][][]} polygonCoordsB Polygon B coordinates [exteriorRing, ...holes]\n * @param {number[]} clickCoordA Click coordinate on the shared edge of polygon A\n * @param {number[]} clickCoordB Click coordinate on the shared edge of polygon B\n * @param {number} [tolerance=5] Distance tolerance in map units (default 5 metres in EPSG:3857).\n * A larger tolerance handles polygons that were digitised separately and\n * whose shared vertices don't coincide exactly.\n * @returns {{ coords: number[][][], error?: undefined } | { coords: null, error: string }}\n * On success: `{ coords: [...] }`. On failure: `{ coords: null, error: 'reason' }`.\n */\nexport function mergePolygons(polygonCoordsA, polygonCoordsB, clickCoordA, clickCoordB, tolerance = 5) {\n const ringA = polygonCoordsA[0];\n const ringB = polygonCoordsB[0];\n const holesA = polygonCoordsA.slice(1);\n const holesB = polygonCoordsB.slice(1);\n\n // 1. Find seed edges (closest to user clicks)\n const seedA = findClosestEdge(ringA, clickCoordA);\n const seedB = findClosestEdge(ringB, clickCoordB);\n\n // 2. Find shared boundary\n const shared = findSharedBoundary(ringA, ringB, seedA.segIdx, seedB.segIdx, tolerance);\n if (!shared) {\n console.warn('[polygonMerge] Could not find shared boundary between polygons — seed edges are not near the other ring');\n return { coords: null, error: 'The selected edges are not on a shared boundary. Click edges that lie on the common border between the two polygons.' };\n }\n\n const { startA, endA, startB, endB, reversed } = shared;\n const nA = ringA.length - 1;\n const nB = ringB.length - 1;\n\n // 3. Stitch the non-shared portions together\n // A's shared goes from startA → endA. Non-shared: endA → startA (forward, wrapping).\n // B's shared (reversed case) goes from startB backward to endB.\n // Non-shared: startB → endB (forward).\n // B's shared (same-dir case) goes from startB → endB.\n // Non-shared: endB → startB (forward, wrapping).\n //\n // The last vertex of partA (startA) must coincide with the first vertex\n // of partB for a clean join.\n\n const partA = walkRing(ringA, endA, startA);\n\n let partB;\n if (reversed) {\n // B's non-shared goes from startB forward to endB.\n // startB vertex ≈ startA vertex (they meet at one end of the shared boundary).\n partB = walkRing(ringB, startB, endB);\n } else {\n // B's non-shared goes from endB forward to startB.\n partB = walkRing(ringB, endB, startB);\n }\n\n // partA ends at startA, partB starts at a vertex that should coincide.\n // Skip the first vertex of partB to avoid the duplicate junction point.\n const merged = [...partA, ...partB.slice(1)];\n\n // Snap the closing junction. With non-coincident vertices (separate\n // digitisation) the last vertex of partB may be a few metres from the\n // first vertex of partA (ringA[endA]). Replace it to avoid a tiny\n // sliver edge that closeRing would otherwise create.\n const tolSq = tolerance * tolerance;\n if (merged.length > 2 && dist2(merged[merged.length - 1], merged[0]) < tolSq) {\n merged[merged.length - 1] = merged[0].slice();\n }\n\n const mergedRing = closeRing(merged);\n\n // 4. Validate: the merged ring should have a reasonable area\n const areaA = Math.abs(signedArea(ringA));\n const areaB = Math.abs(signedArea(ringB));\n const areaMerged = Math.abs(signedArea(mergedRing));\n const expectedArea = areaA + areaB;\n\n // Allow 10% tolerance for area mismatch (shared edges can cause slight differences)\n if (areaMerged < expectedArea * 0.5 || areaMerged > expectedArea * 1.5) {\n console.warn(`[polygonMerge] Area mismatch: A=${areaA.toFixed(1)}, B=${areaB.toFixed(1)}, merged=${areaMerged.toFixed(1)}, expected≈${expectedArea.toFixed(1)}`);\n return { coords: null, error: 'Merge produced an invalid polygon (area mismatch). The polygons may not be truly adjacent — try clicking closer to the shared boundary.' };\n }\n\n // 5. Match winding order to original\n const originalCCW = signedArea(ringA) > 0;\n const finalRing = ensureWinding(mergedRing, originalCCW);\n\n // 6. Collect holes from both polygons\n const allHoles = [...holesA, ...holesB];\n // Filter: only include holes that actually fall inside the merged ring\n const validHoles = allHoles.filter(hole => {\n const cx = hole.reduce((s, p) => s + p[0], 0) / (hole.length - 1);\n const cy = hole.reduce((s, p) => s + p[1], 0) / (hole.length - 1);\n return pointInRing([cx, cy], finalRing);\n });\n\n return { coords: [finalRing, ...validHoles] };\n}\n","/**\n * PolygonMergeInteraction\n *\n * A four-phase OpenLayers interaction for merging two adjacent polygons:\n * Phase 1 – SELECT_A: hover to highlight, click to select polygon A\n * Phase 2 – SELECT_B: hover to highlight, click to select polygon B\n * Phase 3 – CLICK_EDGE_A: hover highlights edge, click to pick shared edge on A\n * Phase 4 – CLICK_EDGE_B: hover highlights edge, click to pick shared edge on B → merge\n *\n * After a successful merge the two original features are removed and one\n * merged feature (coloured green) is added. If both originals were parcels,\n * a `mergedparcel` event is fired so external code can present a UPN chooser.\n */\n\nimport ol_interaction_Interaction from 'ol/interaction/Interaction';\nimport VectorSource from 'ol/source/Vector';\nimport VectorLayer from 'ol/layer/Vector';\nimport Feature from 'ol/Feature';\nimport { Style, Stroke, Fill, Text } from 'ol/style';\nimport { LineString, Polygon as PolygonGeom } from 'ol/geom';\nimport { mergePolygons } from '../geom/polygonMerge.js';\nimport { showToast } from '../toast.js';\n\n// ── Styles ───────────────────────────────────────────────────────────────────\n\nconst HIGHLIGHT_A = new Style({\n stroke: new Stroke({ color: '#0ea5e9', width: 3 }),\n fill: new Fill({ color: 'rgba(14,165,233,0.15)' }),\n});\n\nconst HIGHLIGHT_B = new Style({\n stroke: new Stroke({ color: '#f59e0b', width: 3 }),\n fill: new Fill({ color: 'rgba(245,158,11,0.15)' }),\n});\n\n// Labelled versions for permanent highlights (shown after selection)\nconst LABEL_A = new Style({\n stroke: new Stroke({ color: '#0ea5e9', width: 3 }),\n fill: new Fill({ color: 'rgba(14,165,233,0.15)' }),\n text: new Text({\n text: 'A',\n font: 'bold 22px Exo, sans-serif',\n fill: new Fill({ color: '#0ea5e9' }),\n stroke: new Stroke({ color: '#fff', width: 4 }),\n overflow: true,\n }),\n});\n\nconst LABEL_B = new Style({\n stroke: new Stroke({ color: '#f59e0b', width: 3 }),\n fill: new Fill({ color: 'rgba(245,158,11,0.15)' }),\n text: new Text({\n text: 'B',\n font: 'bold 22px Exo, sans-serif',\n fill: new Fill({ color: '#f59e0b' }),\n stroke: new Stroke({ color: '#fff', width: 4 }),\n overflow: true,\n }),\n});\n\nconst EDGE_STYLE = new Style({\n stroke: new Stroke({ color: '#ec4899', width: 4, lineDash: [10, 6] }),\n});\n\nconst MERGE_STYLE = new Style({\n stroke: new Stroke({ color: '#10b981', width: 2.5 }),\n fill: new Fill({ color: 'rgba(16,185,129,0.3)' }),\n});\n\n// ── Interaction ──────────────────────────────────────────────────────────────\n\nexport class PolygonMergeInteraction extends ol_interaction_Interaction {\n /**\n * @param {Object} [options]\n * @param {number} [options.snapDistance=25] Pixel distance for hover detection.\n * @param {number} [options.tolerance=5] Map-unit tolerance for shared-edge matching.\n */\n constructor(options = {}) {\n super({\n handleEvent: (e) => this._handleEvent(e),\n });\n\n this.snapDistance_ = options.snapDistance || 25;\n this.tolerance_ = options.tolerance || 5;\n\n // Phase: 'select_a' | 'select_b' | 'click_edge_a' | 'click_edge_b'\n this._phase = 'select_a';\n\n // Selected features and their sources\n this._featureA = null;\n this._sourceA = null;\n this._featureB = null;\n this._sourceB = null;\n\n // Clicked edge coordinates (map coords)\n this._edgeClickA = null;\n this._edgeClickB = null;\n\n // Overlay for polygon highlights\n this._highlightSource = new VectorSource({ useSpatialIndex: false });\n this._highlightLayer = new VectorLayer({\n source: this._highlightSource,\n displayInLayerSwitcher: false,\n style: (f) => f.get('_highlightStyle') || HIGHLIGHT_A,\n });\n\n // Overlay for edge highlights\n this._edgeSource = new VectorSource({ useSpatialIndex: false });\n this._edgeLayer = new VectorLayer({\n source: this._edgeSource,\n displayInLayerSwitcher: false,\n style: EDGE_STYLE,\n });\n }\n\n /* ------------------------------------------------------------------ */\n /* Map lifecycle */\n /* ------------------------------------------------------------------ */\n\n setMap(map) {\n if (this.getMap()) {\n this.getMap().removeLayer(this._highlightLayer);\n this.getMap().removeLayer(this._edgeLayer);\n }\n super.setMap(map);\n if (map) {\n this._highlightLayer.setMap(map);\n this._edgeLayer.setMap(map);\n }\n }\n\n setActive(active) {\n super.setActive(active);\n if (!active) this._reset();\n }\n\n /* ------------------------------------------------------------------ */\n /* Source helpers */\n /* ------------------------------------------------------------------ */\n\n _getSources() {\n if (!this.getMap()) return [];\n const sources = [];\n const collect = (layers) => {\n layers.forEach((layer) => {\n if (layer.getVisible()) {\n if (layer.getSource && layer.getSource() instanceof VectorSource) {\n sources.push(layer.getSource());\n } else if (layer.getLayers) {\n collect(layer.getLayers());\n }\n }\n });\n };\n collect(this.getMap().getLayers());\n return sources;\n }\n\n /* ------------------------------------------------------------------ */\n /* Event router */\n /* ------------------------------------------------------------------ */\n\n _handleEvent(e) {\n if (!this.getActive()) return true;\n\n // Escape cancels at any phase\n if (e.type === 'keydown' && e.originalEvent?.key === 'Escape') {\n this._reset();\n return false;\n }\n\n switch (this._phase) {\n case 'select_a':\n if (e.type === 'pointermove') return this._onSelectMove(e, null);\n if (e.type === 'singleclick') return this._onSelectAClick(e);\n break;\n case 'select_b':\n if (e.type === 'pointermove') return this._onSelectMove(e, this._featureA);\n if (e.type === 'singleclick') return this._onSelectBClick(e);\n break;\n case 'click_edge_a':\n if (e.type === 'pointermove') return this._onEdgeMove(e, this._featureA);\n if (e.type === 'singleclick') return this._onEdgeAClick(e);\n break;\n case 'click_edge_b':\n if (e.type === 'pointermove') return this._onEdgeMove(e, this._featureB);\n if (e.type === 'singleclick') return this._onEdgeBClick(e);\n break;\n }\n return true;\n }\n\n /* ------------------------------------------------------------------ */\n /* Phase 1 & 2: SELECT polygons */\n /* ------------------------------------------------------------------ */\n\n _onSelectMove(e, skipFeature) {\n const map = this.getMap();\n if (!map) return true;\n\n // Keep existing highlights for already-selected polygons\n this._highlightSource.clear();\n this._edgeSource.clear();\n this._rebuildHighlights();\n\n const hit = this._closestPolygon(e, skipFeature);\n if (hit) {\n const style = this._phase === 'select_a' ? HIGHLIGHT_A : HIGHLIGHT_B;\n const clone = hit.feature.clone();\n clone.set('_highlightStyle', style);\n this._highlightSource.addFeature(clone);\n map.getTargetElement().style.cursor = 'pointer';\n } else {\n map.getTargetElement().style.cursor = '';\n }\n return true;\n }\n\n _onSelectAClick(e) {\n const hit = this._closestPolygon(e, null);\n if (!hit) return true;\n\n this._featureA = hit.feature;\n this._sourceA = hit.source;\n this._phase = 'select_b';\n\n this._rebuildHighlights();\n return false;\n }\n\n _onSelectBClick(e) {\n const hit = this._closestPolygon(e, this._featureA);\n if (!hit) return true;\n\n this._featureB = hit.feature;\n this._sourceB = hit.source;\n this._phase = 'click_edge_a';\n\n this._rebuildHighlights();\n this.getMap().getTargetElement().style.cursor = 'crosshair';\n return false;\n }\n\n /**\n * Find the closest polygon feature within snap distance.\n * Optionally skip a feature (used in phase 2 to avoid re-selecting A).\n */\n _closestPolygon(e, skipFeature) {\n let best = null;\n let bestDist = this.snapDistance_ + 1;\n\n for (const source of this._getSources()) {\n const feat = source.getClosestFeatureToCoordinate(e.coordinate);\n if (!feat) continue;\n if (skipFeature && feat === skipFeature) continue;\n const geom = feat.getGeometry();\n if (!geom) continue;\n const type = geom.getType();\n if (type !== 'Polygon' && type !== 'MultiPolygon') continue;\n\n const closest = geom.getClosestPoint(e.coordinate);\n const line = new LineString([e.coordinate, closest]);\n const distPx = line.getLength() / e.frameState.viewState.resolution;\n\n if (distPx < bestDist) {\n bestDist = distPx;\n best = { feature: feat, source, coord: closest };\n }\n }\n return best;\n }\n\n /* ------------------------------------------------------------------ */\n /* Phase 3 & 4: CLICK edges */\n /* ------------------------------------------------------------------ */\n\n _onEdgeMove(e, feature) {\n const map = this.getMap();\n if (!map) return true;\n\n this._edgeSource.clear();\n\n const edge = this._closestEdgeSegment(feature, e);\n if (edge) {\n const edgeFeat = new Feature(new LineString([edge.segStart, edge.segEnd]));\n this._edgeSource.addFeature(edgeFeat);\n map.getTargetElement().style.cursor = 'crosshair';\n }\n return true;\n }\n\n _onEdgeAClick(e) {\n this._edgeClickA = e.coordinate;\n this._phase = 'click_edge_b';\n this._edgeSource.clear();\n return false;\n }\n\n _onEdgeBClick(e) {\n this._edgeClickB = e.coordinate;\n this._performMerge();\n return false;\n }\n\n /**\n * Find the closest edge segment of a polygon feature to the cursor.\n */\n _closestEdgeSegment(feature, e) {\n const geom = feature.getGeometry();\n let ring;\n if (geom.getType() === 'Polygon') {\n ring = geom.getCoordinates()[0];\n } else if (geom.getType() === 'MultiPolygon') {\n ring = geom.getCoordinates()[0][0];\n } else {\n return null;\n }\n\n const resolution = e.frameState.viewState.resolution;\n let bestDist = Infinity;\n let bestSeg = null;\n const n = ring.length - 1;\n\n for (let i = 0; i < n; i++) {\n const a = ring[i];\n const b = ring[i + 1];\n const dx = b[0] - a[0], dy = b[1] - a[1];\n const lenSq = dx * dx + dy * dy;\n if (lenSq < 1e-20) continue;\n\n let t = ((e.coordinate[0] - a[0]) * dx + (e.coordinate[1] - a[1]) * dy) / lenSq;\n t = Math.max(0, Math.min(1, t));\n const projX = a[0] + t * dx, projY = a[1] + t * dy;\n const distPx = Math.sqrt((e.coordinate[0] - projX) ** 2 + (e.coordinate[1] - projY) ** 2) / resolution;\n\n if (distPx < bestDist) {\n bestDist = distPx;\n bestSeg = { segStart: a, segEnd: b };\n }\n }\n return bestDist <= this.snapDistance_ ? bestSeg : null;\n }\n\n /* ------------------------------------------------------------------ */\n /* Merge logic */\n /* ------------------------------------------------------------------ */\n\n _performMerge() {\n const featureA = this._featureA;\n const featureB = this._featureB;\n const sourceA = this._sourceA;\n const sourceB = this._sourceB;\n\n // Extract polygon coordinates\n const geomA = featureA.getGeometry();\n const geomB = featureB.getGeometry();\n const coordsA = geomA.getType() === 'Polygon' ? geomA.getCoordinates() : geomA.getCoordinates()[0];\n const coordsB = geomB.getType() === 'Polygon' ? geomB.getCoordinates() : geomB.getCoordinates()[0];\n\n const result = mergePolygons(coordsA, coordsB, this._edgeClickA, this._edgeClickB, this.tolerance_);\n\n if (!result.coords) {\n showToast(result.error || 'Merge failed — try clicking on the shared boundary.', 'error', 5000);\n // Return to edge click phase for retry\n this._edgeClickA = null;\n this._edgeClickB = null;\n this._phase = 'click_edge_a';\n this._edgeSource.clear();\n return;\n }\n\n // Create merged feature (clone A for default properties)\n const mergedFeature = featureA.clone();\n mergedFeature.setGeometry(new PolygonGeom(result.coords));\n mergedFeature.setStyle(MERGE_STYLE);\n\n // Dispatch beforemerge events\n const evtData = {\n type: 'beforemerge',\n original: [featureA, featureB],\n merged: mergedFeature,\n };\n this.dispatchEvent(evtData);\n sourceA.dispatchEvent({ ...evtData });\n if (sourceB !== sourceA) {\n sourceB.dispatchEvent({ ...evtData });\n }\n\n // Replace originals with merged. Group the two removes + one add into a\n // single UndoRedo block (map-level undoblockstart/undoblockend) so the\n // whole merge — across both source layers — is reversed by ONE undo press.\n const mergeMap = this.getMap();\n mergeMap?.dispatchEvent('undoblockstart');\n sourceA.removeFeature(featureA);\n sourceB.removeFeature(featureB);\n sourceA.addFeature(mergedFeature);\n mergeMap?.dispatchEvent('undoblockend');\n\n // Dispatch aftermerge events\n const afterEvt = {\n type: 'aftermerge',\n original: [featureA, featureB],\n merged: mergedFeature,\n };\n this.dispatchEvent(afterEvt);\n sourceA.dispatchEvent({ ...afterEvt });\n if (sourceB !== sourceA) {\n sourceB.dispatchEvent({ ...afterEvt });\n }\n\n // If both features were parcels, fire mergedparcel so MapView can show the UPN chooser\n const isParcelA = featureA.get('_layerType') === 'parcel';\n const isParcelB = featureB.get('_layerType') === 'parcel';\n if (isParcelA && isParcelB) {\n this.dispatchEvent({\n type: 'mergedparcel',\n merged: mergedFeature,\n propsA: featureA.getProperties(),\n propsB: featureB.getProperties(),\n coordinate: this._edgeClickA,\n });\n showToast('Polygons merged — choose which identifier to keep.', 'success');\n } else {\n showToast('Polygons merged successfully.', 'success');\n }\n\n // Clean up\n this._reset();\n }\n\n /* ------------------------------------------------------------------ */\n /* Highlight management */\n /* ------------------------------------------------------------------ */\n\n /**\n * Rebuild the permanent highlights for already-selected polygons.\n */\n _rebuildHighlights() {\n // Remove previous non-hover highlights\n const toRemove = [];\n this._highlightSource.getFeatures().forEach((f) => {\n if (f.get('_permanent')) toRemove.push(f);\n });\n toRemove.forEach((f) => this._highlightSource.removeFeature(f));\n\n if (this._featureA) {\n const cloneA = this._featureA.clone();\n cloneA.set('_highlightStyle', LABEL_A);\n cloneA.set('_permanent', true);\n this._highlightSource.addFeature(cloneA);\n }\n if (this._featureB) {\n const cloneB = this._featureB.clone();\n cloneB.set('_highlightStyle', LABEL_B);\n cloneB.set('_permanent', true);\n this._highlightSource.addFeature(cloneB);\n }\n }\n\n /* ------------------------------------------------------------------ */\n /* Reset */\n /* ------------------------------------------------------------------ */\n\n _reset() {\n this._phase = 'select_a';\n this._featureA = null;\n this._sourceA = null;\n this._featureB = null;\n this._sourceB = null;\n this._edgeClickA = null;\n this._edgeClickB = null;\n this._highlightSource.clear();\n this._edgeSource.clear();\n\n const map = this.getMap();\n if (map) {\n map.getTargetElement().style.cursor = '';\n }\n }\n}\n","/**\n * Pure geometry functions for dividing a polygon into N equal-area pieces.\n *\n * No OpenLayers dependency — operates on raw coordinate arrays.\n *\n * The algorithm finds the polygon's longest edge, then places N-1 cutting\n * lines perpendicular to that edge. Each cutting-line position is found\n * via binary search so that the piece it cuts off has exactly 1/N of the\n * remaining area. The actual cut is delegated to `splitPolygonByLine()`.\n */\n\nimport { splitPolygonByLine } from './polygonSplit.js';\n\n// ── Utility helpers (self-contained) ─────────────────────────────────────────\n\nfunction dist2(a, b) {\n return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2;\n}\n\n/**\n * Signed area of a ring (shoelace formula).\n */\nfunction signedArea(ring) {\n let area = 0;\n for (let i = 0, n = ring.length; i < n - 1; i++) {\n area += ring[i][0] * ring[i + 1][1] - ring[i + 1][0] * ring[i][1];\n }\n return area / 2;\n}\n\n/**\n * Absolute polygon area, accounting for holes.\n */\nfunction polygonArea(coords) {\n let area = Math.abs(signedArea(coords[0]));\n for (let i = 1; i < coords.length; i++) {\n area -= Math.abs(signedArea(coords[i]));\n }\n return area;\n}\n\n/**\n * Find the longest edge of a ring and return direction vectors.\n *\n * @param {number[][]} ring Closed ring\n * @returns {{ p0: number[], p1: number[], along: number[], perp: number[] }}\n * `along` = unit vector along the longest edge,\n * `perp` = unit vector perpendicular to `along` (rotated 90° CCW)\n */\nfunction longestEdge(ring) {\n const n = ring.length - 1; // unique vertices\n let bestLen = -1;\n let bestI = 0;\n\n for (let i = 0; i < n; i++) {\n const d = dist2(ring[i], ring[i + 1]);\n if (d > bestLen) {\n bestLen = d;\n bestI = i;\n }\n }\n\n const p0 = ring[bestI];\n const p1 = ring[bestI + 1];\n const len = Math.sqrt(bestLen);\n const along = [(p1[0] - p0[0]) / len, (p1[1] - p0[1]) / len];\n // Perpendicular: rotate 90° CCW\n const perp = [-along[1], along[0]];\n\n return { p0, p1, along, perp };\n}\n\n/**\n * Build a cutting line perpendicular to `along` at parameter `t`.\n *\n * The line passes through `origin + t * along` and extends `extent` units\n * in both `perp` directions — long enough to fully cross the polygon.\n */\nfunction makeCuttingLine(origin, along, perp, t, extent) {\n const cx = origin[0] + t * along[0];\n const cy = origin[1] + t * along[1];\n return [\n [cx - extent * perp[0], cy - extent * perp[1]],\n [cx + extent * perp[0], cy + extent * perp[1]],\n ];\n}\n\n/**\n * Project the centroid of a polygon's exterior ring onto the `along` axis.\n * Returns the scalar parameter `t` relative to `origin`.\n */\nfunction centroidT(coords, origin, along) {\n const ring = coords[0];\n const n = ring.length - 1;\n let sx = 0, sy = 0;\n for (let i = 0; i < n; i++) {\n sx += ring[i][0];\n sy += ring[i][1];\n }\n const cx = sx / n - origin[0];\n const cy = sy / n - origin[1];\n return cx * along[0] + cy * along[1];\n}\n\n// ── Main export ──────────────────────────────────────────────────────────────\n\n/**\n * Divide a polygon into N equal-area pieces by parallel cuts perpendicular\n * to a user-selected edge.\n *\n * @param {number[][][]} polygonCoords Polygon coordinates [ring, ...holes]\n * @param {number} n Number of pieces (must be >= 1)\n * @param {number[][]} edgeCoords The selected edge `[p0, p1]` — cuts will be\n * perpendicular to this edge direction.\n * @returns {{ pieces: number[][][][], error?: undefined } | { pieces: null, error: string }}\n */\nexport function dividePolygon(polygonCoords, n, edgeCoords) {\n if (!Number.isInteger(n) || n < 1) {\n return { pieces: null, error: 'Number of divisions must be a positive integer.' };\n }\n if (n === 1) {\n return { pieces: [polygonCoords] };\n }\n\n const ring = polygonCoords[0];\n const totalArea = polygonArea(polygonCoords);\n\n if (totalArea < 1e-6) {\n return { pieces: null, error: 'Polygon has no measurable area.' };\n }\n\n // 1. Determine cutting direction from the selected edge\n let p0, along, perp;\n if (edgeCoords && edgeCoords.length === 2) {\n p0 = edgeCoords[0];\n const dx = edgeCoords[1][0] - edgeCoords[0][0];\n const dy = edgeCoords[1][1] - edgeCoords[0][1];\n const len = Math.sqrt(dx * dx + dy * dy);\n if (len < 1e-10) {\n return { pieces: null, error: 'Selected edge has zero length.' };\n }\n along = [dx / len, dy / len];\n perp = [-along[1], along[0]];\n } else {\n // Fallback: use longest edge\n const edge = longestEdge(ring);\n p0 = edge.p0;\n along = edge.along;\n perp = edge.perp;\n }\n const origin = p0;\n\n // 2. Project all vertices onto the `along` axis to find extent\n const nVerts = ring.length - 1;\n let tMin = Infinity, tMax = -Infinity;\n for (let i = 0; i < nVerts; i++) {\n const dx = ring[i][0] - origin[0];\n const dy = ring[i][1] - origin[1];\n const t = dx * along[0] + dy * along[1];\n if (t < tMin) tMin = t;\n if (t > tMax) tMax = t;\n }\n\n // Cutting line extent: enough to cross the polygon in the `perp` direction\n let perpMin = Infinity, perpMax = -Infinity;\n for (let i = 0; i < nVerts; i++) {\n const dx = ring[i][0] - origin[0];\n const dy = ring[i][1] - origin[1];\n const p = dx * perp[0] + dy * perp[1];\n if (p < perpMin) perpMin = p;\n if (p > perpMax) perpMax = p;\n }\n const extent = (perpMax - perpMin) * 1.5; // generous overshoot\n\n // 3. Iteratively cut pieces\n const pieces = [];\n let remaining = polygonCoords;\n let remainingCount = n;\n\n for (let i = 0; i < n - 1; i++) {\n const remainingArea = polygonArea(remaining);\n const targetArea = remainingArea / remainingCount;\n\n // Re-project the remaining polygon to get its current t-range\n const remRing = remaining[0];\n const remN = remRing.length - 1;\n let rMin = Infinity, rMax = -Infinity;\n for (let j = 0; j < remN; j++) {\n const dx = remRing[j][0] - origin[0];\n const dy = remRing[j][1] - origin[1];\n const t = dx * along[0] + dy * along[1];\n if (t < rMin) rMin = t;\n if (t > rMax) rMax = t;\n }\n\n // Binary search for the cutting position\n let lo = rMin;\n let hi = rMax;\n let bestT = (lo + hi) / 2;\n let bestPiece = null;\n let bestRemaining = null;\n let bestError = Infinity;\n\n for (let iter = 0; iter < 40; iter++) {\n const mid = (lo + hi) / 2;\n const line = makeCuttingLine(origin, along, perp, mid, extent);\n const result = splitPolygonByLine(remaining, line);\n\n if (!result) {\n // Cutting line didn't produce a valid split — nudge and retry\n // Try slightly shifted positions\n const nudge = (hi - lo) * 0.01;\n const lineA = makeCuttingLine(origin, along, perp, mid + nudge, extent);\n const resultA = splitPolygonByLine(remaining, lineA);\n if (resultA) {\n const [halfA, halfB] = resultA;\n const tA = centroidT(halfA, origin, along);\n const tB = centroidT(halfB, origin, along);\n const nearPiece = tA < tB ? halfA : halfB;\n const farPiece = tA < tB ? halfB : halfA;\n const nearArea = polygonArea(nearPiece);\n const err = Math.abs(nearArea - targetArea);\n if (err < bestError) {\n bestError = err;\n bestT = mid + nudge;\n bestPiece = nearPiece;\n bestRemaining = farPiece;\n }\n }\n // Try the other direction\n const lineB = makeCuttingLine(origin, along, perp, mid - nudge, extent);\n const resultB = splitPolygonByLine(remaining, lineB);\n if (resultB) {\n const [halfA, halfB] = resultB;\n const tA = centroidT(halfA, origin, along);\n const tB = centroidT(halfB, origin, along);\n const nearPiece = tA < tB ? halfA : halfB;\n const farPiece = tA < tB ? halfB : halfA;\n const nearArea = polygonArea(nearPiece);\n const err = Math.abs(nearArea - targetArea);\n if (err < bestError) {\n bestError = err;\n bestT = mid - nudge;\n bestPiece = nearPiece;\n bestRemaining = farPiece;\n }\n }\n // Bisect anyway to keep converging\n lo = mid;\n continue;\n }\n\n const [halfA, halfB] = result;\n const tA = centroidT(halfA, origin, along);\n const tB = centroidT(halfB, origin, along);\n const nearPiece = tA < tB ? halfA : halfB;\n const farPiece = tA < tB ? halfB : halfA;\n const nearArea = polygonArea(nearPiece);\n\n const err = Math.abs(nearArea - targetArea);\n if (err < bestError) {\n bestError = err;\n bestT = mid;\n bestPiece = nearPiece;\n bestRemaining = farPiece;\n }\n\n // Converged?\n if (err / remainingArea < 0.001) break;\n\n // Adjust search range\n if (nearArea < targetArea) {\n lo = mid; // need to cut farther out\n } else {\n hi = mid; // need to cut closer\n }\n }\n\n if (!bestPiece || !bestRemaining) {\n return {\n pieces: null,\n error: `Could not find a valid cut for piece ${i + 1} of ${n}. The polygon shape may be too irregular for equal division.`,\n };\n }\n\n pieces.push(bestPiece);\n remaining = bestRemaining;\n remainingCount--;\n }\n\n // The last remaining piece is the Nth piece\n pieces.push(remaining);\n\n return { pieces };\n}\n","/**\n * PolygonDivideInteraction\n *\n * A three-phase OpenLayers interaction for dividing a polygon into N\n * equal-area pieces:\n * Phase 1 – SELECT: hover to highlight, click to select a polygon\n * Phase 2 – EDGE: hover to highlight edges, click to pick the divide\n * direction (cuts will be perpendicular to this edge)\n * Phase 3 – FORM: wait for the popup form to call performDivide(n)\n *\n * After a successful divide the original feature is removed and N new\n * coloured features are added. The interaction fires `beforedivide` and\n * `afterdivide` events compatible with ol-ext's UndoRedo.\n */\n\nimport ol_interaction_Interaction from 'ol/interaction/Interaction';\nimport VectorSource from 'ol/source/Vector';\nimport VectorLayer from 'ol/layer/Vector';\nimport Feature from 'ol/Feature';\nimport { Style, Stroke, Fill } from 'ol/style';\nimport { LineString, Polygon as PolygonGeom } from 'ol/geom';\nimport { dividePolygon } from '../geom/polygonDivide.js';\nimport { showToast } from '../toast.js';\n\n// Highlight style for the selected polygon (phase 1)\nconst HIGHLIGHT_STYLE = new Style({\n stroke: new Stroke({ color: '#0ea5e9', width: 3 }),\n fill: new Fill({ color: 'rgba(14,165,233,0.15)' }),\n});\n\n// Style for the hovered edge (phase 2)\nconst EDGE_STYLE = new Style({\n stroke: new Stroke({ color: '#8b5cf6', width: 4, lineDash: [10, 6] }),\n});\n\n/**\n * Generate N visually distinct colours using evenly-spaced HSL hues.\n */\nfunction pieceColors(n) {\n const colors = [];\n for (let i = 0; i < n; i++) {\n const hue = Math.round((i * 360) / n);\n colors.push({\n stroke: `hsl(${hue}, 70%, 45%)`,\n fill: `hsla(${hue}, 70%, 55%, 0.25)`,\n });\n }\n return colors;\n}\n\nexport class PolygonDivideInteraction extends ol_interaction_Interaction {\n /**\n * @param {Object} options\n * @param {VectorSource|VectorSource[]} [options.sources] Specific sources\n * to search. If omitted the interaction searches all visible vector layers.\n * @param {number} [options.snapDistance=25] Pixel distance for hover.\n */\n constructor(options = {}) {\n super({\n handleEvent: (e) => this._handleEvent(e),\n });\n\n this.snapDistance_ = options.snapDistance || 25;\n this._sources = options.sources\n ? (Array.isArray(options.sources) ? options.sources : [options.sources])\n : null;\n\n // Phase: 'select' | 'edge' | 'form' | 'pick'\n this._phase = 'select';\n this._selectedFeature = null;\n this._selectedSource = null;\n this._selectedEdge = null; // [p0, p1] — the edge the user clicked\n this._dividedFeatures = null; // features created after divide (for pick phase)\n\n // Overlay layer for polygon highlight\n this._overlaySource = new VectorSource({ useSpatialIndex: false });\n this._overlayLayer = new VectorLayer({\n source: this._overlaySource,\n displayInLayerSwitcher: false,\n style: HIGHLIGHT_STYLE,\n });\n\n // Overlay layer for edge highlight\n this._edgeSource = new VectorSource({ useSpatialIndex: false });\n this._edgeLayer = new VectorLayer({\n source: this._edgeSource,\n displayInLayerSwitcher: false,\n style: EDGE_STYLE,\n });\n }\n\n /* ------------------------------------------------------------------ */\n /* Map lifecycle */\n /* ------------------------------------------------------------------ */\n\n setMap(map) {\n if (this.getMap()) {\n this.getMap().removeLayer(this._overlayLayer);\n this.getMap().removeLayer(this._edgeLayer);\n }\n super.setMap(map);\n if (map) {\n this._overlayLayer.setMap(map);\n this._edgeLayer.setMap(map);\n }\n }\n\n setActive(active) {\n super.setActive(active);\n if (!active) {\n this._reset();\n }\n }\n\n /* ------------------------------------------------------------------ */\n /* Source helpers */\n /* ------------------------------------------------------------------ */\n\n _getSources() {\n if (this._sources) return this._sources;\n if (!this.getMap()) return [];\n const sources = [];\n const collect = (layers) => {\n layers.forEach((layer) => {\n if (layer.getVisible()) {\n if (layer.getSource && layer.getSource() instanceof VectorSource) {\n sources.push(layer.getSource());\n } else if (layer.getLayers) {\n collect(layer.getLayers());\n }\n }\n });\n };\n collect(this.getMap().getLayers());\n return sources;\n }\n\n /* ------------------------------------------------------------------ */\n /* Event router */\n /* ------------------------------------------------------------------ */\n\n _handleEvent(e) {\n if (!this.getActive()) return true;\n\n // Escape cancels at any phase\n if (e.type === 'keydown' && e.originalEvent?.key === 'Escape') {\n if (this._phase === 'form') {\n this.cancelDivide();\n } else {\n this._reset();\n }\n return false;\n }\n\n if (this._phase === 'select') {\n if (e.type === 'pointermove') return this._onSelectMove(e);\n if (e.type === 'singleclick') return this._onSelectClick(e);\n }\n\n if (this._phase === 'edge') {\n if (e.type === 'pointermove') return this._onEdgeMove(e);\n if (e.type === 'singleclick') return this._onEdgeClick(e);\n }\n\n if (this._phase === 'pick') {\n if (e.type === 'pointermove') return this._onPickMove(e);\n if (e.type === 'singleclick') return this._onPickClick(e);\n }\n\n return true;\n }\n\n /* ------------------------------------------------------------------ */\n /* Phase 1: SELECT polygon */\n /* ------------------------------------------------------------------ */\n\n _onSelectMove(e) {\n const map = this.getMap();\n if (!map) return true;\n\n this._overlaySource.clear();\n\n const hit = this._closestPolygon(e);\n if (hit) {\n const clone = hit.feature.clone();\n this._overlaySource.addFeature(clone);\n map.getTargetElement().style.cursor = 'pointer';\n } else {\n map.getTargetElement().style.cursor = '';\n }\n return true;\n }\n\n _onSelectClick(e) {\n const hit = this._closestPolygon(e);\n if (!hit) return true;\n\n this._selectedFeature = hit.feature;\n this._selectedSource = hit.source;\n\n // Keep polygon highlight visible during edge phase\n this._overlaySource.clear();\n const clone = hit.feature.clone();\n clone.set('_permanent', true);\n this._overlaySource.addFeature(clone);\n\n this._phase = 'edge';\n showToast('Click the edge to divide along.', 'info', 3000);\n return false;\n }\n\n _closestPolygon(e) {\n let best = null;\n let bestDist = this.snapDistance_ + 1;\n\n for (const source of this._getSources()) {\n const feat = source.getClosestFeatureToCoordinate(e.coordinate);\n if (!feat) continue;\n const geom = feat.getGeometry();\n if (!geom) continue;\n const type = geom.getType();\n if (type !== 'Polygon' && type !== 'MultiPolygon') continue;\n\n const closest = geom.getClosestPoint(e.coordinate);\n const line = new LineString([e.coordinate, closest]);\n const distPx = line.getLength() / e.frameState.viewState.resolution;\n\n if (distPx < bestDist) {\n bestDist = distPx;\n best = { feature: feat, source };\n }\n }\n return best;\n }\n\n /* ------------------------------------------------------------------ */\n /* Phase 2: EDGE selection */\n /* ------------------------------------------------------------------ */\n\n _onEdgeMove(e) {\n const map = this.getMap();\n if (!map) return true;\n\n this._edgeSource.clear();\n\n const edge = this._closestEdgeSegment(this._selectedFeature, e);\n if (edge) {\n const edgeFeat = new Feature(new LineString([edge.segStart, edge.segEnd]));\n this._edgeSource.addFeature(edgeFeat);\n map.getTargetElement().style.cursor = 'crosshair';\n } else {\n map.getTargetElement().style.cursor = '';\n }\n return true;\n }\n\n _onEdgeClick(e) {\n const edge = this._closestEdgeSegment(this._selectedFeature, e);\n if (!edge) return true;\n\n this._selectedEdge = [edge.segStart, edge.segEnd];\n this._edgeSource.clear();\n\n this._phase = 'form';\n\n // Dispatch divideform so MapView can show the popup\n const geom = this._selectedFeature.getGeometry();\n const ext = geom.getExtent();\n const center = [(ext[0] + ext[2]) / 2, (ext[1] + ext[3]) / 2];\n\n this.dispatchEvent({\n type: 'divideform',\n feature: this._selectedFeature,\n source: this._selectedSource,\n coordinate: center,\n });\n\n return false;\n }\n\n /**\n * Find the closest edge segment of a polygon feature to the cursor.\n */\n _closestEdgeSegment(feature, e) {\n const geom = feature.getGeometry();\n let ring;\n if (geom.getType() === 'Polygon') {\n ring = geom.getCoordinates()[0];\n } else if (geom.getType() === 'MultiPolygon') {\n ring = geom.getCoordinates()[0][0];\n } else {\n return null;\n }\n\n const resolution = e.frameState.viewState.resolution;\n let bestDist = Infinity;\n let bestSeg = null;\n const n = ring.length - 1;\n\n for (let i = 0; i < n; i++) {\n const a = ring[i];\n const b = ring[i + 1];\n const dx = b[0] - a[0], dy = b[1] - a[1];\n const lenSq = dx * dx + dy * dy;\n if (lenSq < 1e-20) continue;\n\n let t = ((e.coordinate[0] - a[0]) * dx + (e.coordinate[1] - a[1]) * dy) / lenSq;\n t = Math.max(0, Math.min(1, t));\n const projX = a[0] + t * dx, projY = a[1] + t * dy;\n const distPx = Math.sqrt((e.coordinate[0] - projX) ** 2 + (e.coordinate[1] - projY) ** 2) / resolution;\n\n if (distPx < bestDist) {\n bestDist = distPx;\n bestSeg = { segStart: a, segEnd: b };\n }\n }\n return bestDist <= this.snapDistance_ ? bestSeg : null;\n }\n\n /* ------------------------------------------------------------------ */\n /* Phase 3: FORM — called externally by the popup */\n /* ------------------------------------------------------------------ */\n\n /**\n * Divide the selected polygon into `n` equal-area pieces.\n * Called by the MapView popup's Confirm handler.\n *\n * @param {number} n Number of pieces (>= 2)\n */\n performDivide(n) {\n if (this._phase !== 'form' || !this._selectedFeature) return;\n\n const feature = this._selectedFeature;\n const source = this._selectedSource;\n const geom = feature.getGeometry();\n\n let polygonCoords;\n if (geom.getType() === 'Polygon') {\n polygonCoords = geom.getCoordinates();\n } else if (geom.getType() === 'MultiPolygon') {\n polygonCoords = geom.getCoordinates()[0];\n }\n\n const result = dividePolygon(polygonCoords, n, this._selectedEdge);\n\n if (!result.pieces) {\n showToast(result.error || 'Division failed.', 'error', 5000);\n this._reset();\n return;\n }\n\n // Create N new coloured features\n const colors = pieceColors(n);\n const newFeatures = result.pieces.map((coords, i) => {\n const f = feature.clone();\n f.setGeometry(new PolygonGeom(coords));\n f.setStyle(new Style({\n stroke: new Stroke({ color: colors[i].stroke, width: 2.5 }),\n fill: new Fill({ color: colors[i].fill }),\n }));\n return f;\n });\n\n // Dispatch beforedivide (UndoRedo compatible)\n const evtData = {\n type: 'beforedivide',\n original: feature,\n features: newFeatures,\n };\n this.dispatchEvent(evtData);\n source.dispatchEvent({ ...evtData });\n\n // Replace original with pieces. Group the remove + all the adds into a\n // single UndoRedo block (map-level undoblockstart/undoblockend) so the\n // whole divide is reversed by ONE undo press regardless of piece count.\n const divideMap = this.getMap();\n divideMap?.dispatchEvent('undoblockstart');\n source.removeFeature(feature);\n for (const f of newFeatures) {\n source.addFeature(f);\n }\n divideMap?.dispatchEvent('undoblockend');\n\n // Dispatch afterdivide\n const afterEvt = {\n type: 'afterdivide',\n original: feature,\n features: newFeatures,\n };\n this.dispatchEvent(afterEvt);\n source.dispatchEvent({ ...afterEvt });\n\n // If original was a parcel, enter pick phase for UPN assignment\n const isParcel = feature.get('_layerType') === 'parcel';\n if (isParcel) {\n this._dividedFeatures = newFeatures;\n this._phase = 'pick';\n showToast('Click the polygon that should keep the original identifier.', 'info', 5000);\n\n this.dispatchEvent({\n type: 'dividedparcel',\n features: newFeatures,\n originalProps: feature.getProperties(),\n source,\n });\n } else {\n showToast(`Polygon divided into ${n} equal pieces.`, 'success');\n this._reset();\n }\n }\n\n /* ------------------------------------------------------------------ */\n /* Phase 4: PICK — select which piece keeps the UPN */\n /* ------------------------------------------------------------------ */\n\n _onPickMove(e) {\n const map = this.getMap();\n if (!map) return true;\n\n this._overlaySource.clear();\n\n // Highlight whichever divided piece is under the cursor\n const hit = this._closestDividedPiece(e);\n if (hit) {\n const clone = hit.clone();\n this._overlaySource.addFeature(clone);\n map.getTargetElement().style.cursor = 'pointer';\n } else {\n map.getTargetElement().style.cursor = '';\n }\n return true;\n }\n\n _onPickClick(e) {\n const hit = this._closestDividedPiece(e);\n if (!hit) return true;\n\n this.dispatchEvent({\n type: 'dividepick',\n picked: hit,\n features: this._dividedFeatures,\n });\n\n this._reset();\n return false;\n }\n\n /**\n * Find the closest divided piece to the cursor.\n */\n _closestDividedPiece(e) {\n if (!this._dividedFeatures) return null;\n let best = null;\n let bestDist = this.snapDistance_ + 1;\n\n for (const feat of this._dividedFeatures) {\n const geom = feat.getGeometry();\n if (!geom) continue;\n const closest = geom.getClosestPoint(e.coordinate);\n const line = new LineString([e.coordinate, closest]);\n const distPx = line.getLength() / e.frameState.viewState.resolution;\n if (distPx < bestDist) {\n bestDist = distPx;\n best = feat;\n }\n }\n return best;\n }\n\n /**\n * Cancel the divide operation and return to select phase.\n * Called by the MapView popup's Cancel handler.\n */\n cancelDivide() {\n this.dispatchEvent({ type: 'dividecancel' });\n this._reset();\n }\n\n /* ------------------------------------------------------------------ */\n /* Reset */\n /* ------------------------------------------------------------------ */\n\n _reset() {\n this._phase = 'select';\n this._selectedFeature = null;\n this._selectedSource = null;\n this._selectedEdge = null;\n this._dividedFeatures = null;\n this._overlaySource.clear();\n this._edgeSource.clear();\n\n const map = this.getMap();\n if (map) {\n map.getTargetElement().style.cursor = '';\n }\n }\n}\n","/**\n * MapView Component\n *\n * OpenLayers map with ol-ext LayerSwitcher for base map selection.\n *\n * Usage:\n * import { MapView } from './components/MapView.js';\n *\n * const map = new MapView('map', {\n * center: [-1.5, 7.5], // Ghana\n * zoom: 7,\n * basemap: 'osm'\n * });\n *\n * map.onClick((lon, lat) => console.log('Clicked:', lon, lat));\n * map.addMarker(lon, lat, { name: 'Point A' });\n */\n\nimport Map from 'ol/Map';\nimport View from 'ol/View';\nimport Overlay from 'ol/Overlay';\nimport TileLayer from 'ol/layer/Tile';\nimport ImageLayer from 'ol/layer/Image';\nimport LayerGroup from 'ol/layer/Group';\nimport VectorLayer from 'ol/layer/Vector';\nimport VectorImageLayer from 'ol/layer/VectorImage';\nimport VectorSource from 'ol/source/Vector';\nimport ImageWMS from 'ol/source/ImageWMS';\nimport TileWMS from 'ol/source/TileWMS';\n// NOTE: Cloud-Optimized GeoTIFF support (ol/source/GeoTIFF + ol/layer/WebGLTile)\n// is imported lazily inside addCOGLayer(). The underlying geotiff.js library adds\n// ~100 kB to the bundle, which shouldn't be paid by field users who never open a\n// raster on a metered connection.\nimport OSM from 'ol/source/OSM';\nimport XYZ from 'ol/source/XYZ';\nimport { fromLonLat, toLonLat } from 'ol/proj';\nimport { Point, LineString, Polygon as PolygonGeom } from 'ol/geom';\nimport Feature from 'ol/Feature';\nimport { Style, Circle, Fill, Stroke, Text } from 'ol/style';\nimport GeoJSON from 'ol/format/GeoJSON';\nimport { getArea, getLength } from 'ol/sphere';\nimport { fromCircle } from 'ol/geom/Polygon';\nimport ScaleLine from 'ol/control/ScaleLine';\nimport { formatLength, formatLengthFull, formatArea, formatAreaFull } from '../units.js';\n\n// ol-ext LayerSwitcher\nimport LayerSwitcher from 'ol-ext/control/LayerSwitcher';\n\n// ol-ext SearchNominatim\nimport SearchNominatim from 'ol-ext/control/SearchNominatim';\n\n// ol-ext EditBar for drawing/editing features\nimport EditBar from 'ol-ext/control/EditBar';\nimport Bar from 'ol-ext/control/Bar';\nimport Button from 'ol-ext/control/Button';\n\n// ol-ext TouchCursor for touch-enabled devices\nimport TouchCursor from 'ol-ext/interaction/TouchCursor';\n\n// ol-ext ModifyFeature for cross-layer modification\nimport ModifyFeature from 'ol-ext/interaction/ModifyFeature';\n\n// ol-ext UndoRedo interaction\nimport UndoRedo from 'ol-ext/interaction/UndoRedo';\n\n// ol-ext SnapGuides — snaps drawing vertices to alignment guides\nimport SnapGuides from 'ol-ext/interaction/SnapGuides';\n\n// ol Select interaction (for custom multi-layer Select)\nimport Select from 'ol/interaction/Select';\nimport DragBox from 'ol/interaction/DragBox';\nimport { click as clickCondition, platformModifierKeyOnly, shiftKeyOnly } from 'ol/events/condition';\n\n// ol-ext Split interaction (for line splitting) and Toggle control\nimport Split from 'ol-ext/interaction/Split';\nimport Toggle from 'ol-ext/control/Toggle';\nimport TextButton from 'ol-ext/control/TextButton';\n\n// Custom polygon split interaction\nimport { PolygonSplitInteraction } from '../interactions/PolygonSplitInteraction.js';\n\n// Custom polygon merge interaction\nimport { PolygonMergeInteraction } from '../interactions/PolygonMergeInteraction.js';\n\n// Custom polygon divide interaction\nimport { PolygonDivideInteraction } from '../interactions/PolygonDivideInteraction.js';\n\n// Toast notifications\nimport { showToast } from '../toast.js';\n\n// CSS imports\nimport 'ol/ol.css';\nimport 'ol-ext/dist/ol-ext.css';\nimport '../styles/layerswitcher.css';\n\n/**\n * Bootstrap Icon for every button in the drawing toolbar, keyed by the control's\n * class name.\n *\n * ol-ext ships no icon font or sprite sheet — it draws each built-in tool as CSS\n * ::before / ::after shapes made from borders and box-shadows. Our own tools were\n * always Bootstrap Icons, so the bar showed two visual languages at once. The\n * generated shapes are suppressed in CSS (`.ol-editbar-rail … { content: none }`)\n * and every button is given an icon from here instead, which makes this the single\n * place the toolbar's iconography is defined.\n */\nconst TOOLBAR_ICONS = {\n // ol-ext built-ins\n 'ol-selection': 'bi-cursor',\n 'ol-drawpoint': 'bi-geo-alt',\n 'ol-drawline': 'bi-vector-pen',\n 'ol-drawpolygon': 'bi-pentagon',\n 'ol-drawregular': 'bi-square',\n 'ol-drawhole': 'bi-record-circle',\n 'ol-transform': 'bi-arrows-move',\n 'ol-offset': 'bi-arrows-angle-expand',\n // ol-ext Select option bar\n 'ol-delete': 'bi-trash3',\n 'ol-info': 'bi-info-circle',\n // ours\n 'ol-split': 'bi-signpost-split',\n 'ol-split-line': 'bi-slash-lg',\n 'ol-split-polygon': 'bi-scissors',\n 'ol-split-divide': 'bi-grid-3x3-gap',\n 'ol-merge': 'bi-union',\n 'ol-undo': 'bi-arrow-counterclockwise',\n 'ol-redo': 'bi-arrow-clockwise',\n 'ol-save': 'bi-floppy',\n 'ol-snap-toggle': 'bi-magnet',\n};\n\n/**\n * Order and grouping of the left rail, top to bottom.\n *\n * ol-ext appends controls in construction order, which interleaves drawing and\n * editing tools. Officers reported the palette was hard to read, so the rail is\n * re-ordered into labelled bands: the eye lands on the band first, then the icon.\n * Controls not named here keep their place at the end of the bar.\n */\nconst TOOLBAR_GROUPS = [\n { label: 'Draw', controls: ['ol-drawpoint', 'ol-drawline', 'ol-drawpolygon', 'ol-drawregular', 'ol-drawhole'] },\n { label: 'Edit', controls: ['ol-selection', 'ol-transform'] },\n // ol-offset is an EditBar default we never explicitly enabled, so it was\n // sitting in the bar unlabelled. It is a genuine geometry tool, so it is\n // grouped here rather than removed.\n { label: 'Shape', controls: ['ol-split', 'ol-merge', 'ol-offset'] },\n { label: 'File', controls: ['ol-editbar-actions'] },\n];\n\nexport class MapView {\n constructor(targetId, options = {}) {\n this.options = options;\n this.markerSource = new VectorSource();\n this.clickCallbacks = [];\n\n // Category emoji and label mapping\n // Add new categories here - they will automatically appear in the dropdown\n this.categoryEmojis = {\n 'default': { emoji: '📍', label: 'Default' },\n 'water': { emoji: '💧', label: 'Water Point' },\n 'school': { emoji: '🏫', label: 'School' },\n 'health': { emoji: '🏥', label: 'Health Facility' },\n 'market': { emoji: '🏪', label: 'Market' },\n 'other': { emoji: '📌', label: 'Other' }\n };\n\n // Helper to get emoji for a category\n this.getEmoji = (category) => {\n const cat = this.categoryEmojis[category];\n return cat ? cat.emoji : '📍';\n };\n\n // Helper to generate category options HTML for select dropdowns\n this.getCategoryOptionsHtml = () => {\n return Object.entries(this.categoryEmojis)\n .map(([key, { emoji, label }]) =>\n ``\n )\n .join('\\n ');\n };\n\n // Create emoji style helper\n this.createEmojiStyle = (emoji, fontSize = 24) => {\n return new Style({\n text: new Text({\n text: emoji,\n font: `${fontSize}px sans-serif`,\n textBaseline: 'bottom',\n textAlign: 'center',\n offsetY: -5,\n }),\n });\n };\n\n // Default marker style (pin emoji)\n this.defaultStyle = this.createEmojiStyle('📍', 32);\n\n // Selected marker style (larger)\n this.selectedStyle = this.createEmojiStyle('📍', 42);\n\n // Initialize category styles with emojis\n this.categoryStyles = {};\n for (const [category, { emoji }] of Object.entries(this.categoryEmojis)) {\n this.categoryStyles[category] = this.createEmojiStyle(emoji, 32);\n }\n\n // Create base layers group\n const baseLayers = this.createBaseLayers(options.basemap || 'topo');\n\n // Markers layer — hidden at startup; the user enables it from the\n // LayerSwitcher when they want to see location markers / category pins.\n this.markersLayer = new VectorLayer({\n title: 'Markers',\n source: this.markerSource,\n style: (feature) => this.getFeatureStyle(feature),\n visible: false,\n });\n\n // Overlay layers group (for remote data like boundaries)\n this.overlayGroup = new LayerGroup({\n title: 'Overlays',\n });\n\n // Create map\n // Layer order (bottom → top): Base Maps, Markers, Overlays\n // MapTools will insert Measurements and Drawings between Markers and Overlays.\n // initEditBar() will insert its Drawings group above those.\n // Final LayerSwitcher order (top → bottom):\n // Overlays, Drawings, Measurements, Markers, Base Maps\n this.map = new Map({\n target: targetId,\n layers: [\n baseLayers,\n this.markersLayer,\n this.overlayGroup,\n ],\n view: new View({\n center: fromLonLat(options.center || [0, 0]),\n zoom: options.zoom || 2,\n minZoom: options.minZoom || 2,\n maxZoom: options.maxZoom || 19,\n })\n });\n\n // Add LayerSwitcher control\n const layerSwitcher = new LayerSwitcher({\n collapsed: true,\n mouseover: true,\n extent: true,\n trash: false,\n oninfo: null,\n });\n this.map.addControl(layerSwitcher);\n\n // Apply the LUSPA branded icon to the LayerSwitcher's collapse button.\n // Done in JS so the URL respects Vite's BASE_URL — survives deployment\n // under any sub-path.\n // NOTE: folder name is `app-icons`, NOT `icons` — Apache aliases `/icons/`\n // by default to its own directory-listing thumbnails, which would\n // intercept this request server-side.\n queueMicrotask(() => {\n const btn = layerSwitcher.element?.querySelector(':scope > button');\n if (btn) {\n const baseUrl = (import.meta.env?.BASE_URL || '/').replace(/\\/?$/, '/');\n btn.style.backgroundImage = `url('${baseUrl}app-icons/luspa-72x72.png')`;\n }\n });\n\n // ------------------------------------------------------------------\n // Decorate each layer's
  • as it's rendered:\n // • inject a type-tag chip (WMS / XYZ / VEC / …) next to the label\n // • add a green \"+\" button to the \"External Source\" group header\n // After each draw cycle, refresh the panel chrome (active count badge\n // + footer reset button). Schedule once per cycle via a microtask.\n // ------------------------------------------------------------------\n let _lsChromeScheduled = false;\n layerSwitcher.on('drawlist', (evt) => {\n this._decorateLayerListItem(evt.layer, evt.li);\n\n if (!_lsChromeScheduled) {\n _lsChromeScheduled = true;\n queueMicrotask(() => {\n _lsChromeScheduled = false;\n this._refreshLayerSwitcherChrome(layerSwitcher);\n });\n }\n });\n\n // Re-render the chrome whenever any layer's visibility changes (so the\n // active-count badge updates even when the user toggles via the panel).\n this.map.getLayers().on('change', () => {\n this._refreshLayerSwitcherChrome(layerSwitcher);\n });\n // Hook visibility events on every layer (recursive into groups).\n this._wireLayerSwitcherVisibilityHooks(layerSwitcher);\n\n // Create the add-layer dialog (hidden by default)\n this._createAddLayerDialog();\n\n // Create the legend panel (shows legends for visible layers that have one)\n this._createLegendPanel();\n\n // Add ScaleBar control\n this.scaleBar = new ScaleLine({\n bar: true,\n steps: 4,\n text: true,\n minWidth: 140,\n });\n this.map.addControl(this.scaleBar);\n\n // GPS rendering layers (current position + recorded trail) and the\n // expandable \"My Location\" control (Locate Me + Record Trail sub-buttons).\n this._initGpsRendering();\n this._createLocationControl();\n\n // Dedicated base-map picker — sits above the My Location button\n this._createBaseMapPicker();\n\n // Add SearchNominatim control\n const searchNominatim = new SearchNominatim({\n placeholder: 'Search location...',\n typing: 300, // Delay before search (ms)\n minLength: 3, // Minimum characters to start search\n maxItems: 10, // Maximum results to show\n collapsed: true, // Start collapsed\n // Limit search to improve relevance (can be adjusted)\n // countrycodes: 'gh', // Uncomment to limit to Ghana\n });\n this.map.addControl(searchNominatim);\n\n // Handle search result selection\n searchNominatim.on('select', (event) => {\n const searchResult = event.search;\n if (searchResult) {\n // SearchNominatim returns a plain object with lon/lat properties (as strings)\n const lon = parseFloat(searchResult.lon);\n const lat = parseFloat(searchResult.lat);\n const lonLat = [lon, lat];\n const coordinate = fromLonLat(lonLat);\n\n // Navigate to the selected location\n this.navigateTo(lon, lat, 14);\n\n // Trigger search select callbacks\n const result = {\n coordinate: coordinate,\n lonLat: lonLat,\n name: searchResult.display_name || searchResult.name || 'Unknown',\n searchResult: searchResult,\n };\n this.searchSelectCallbacks.forEach(cb => cb(result));\n }\n });\n\n // Store reference for external access\n this.searchNominatim = searchNominatim;\n this.searchSelectCallbacks = [];\n\n // Track selected feature\n this.selectedFeature = null;\n\n // Create popup overlay for hover\n this.createPopup();\n\n // Create info popup for double-click feature details\n this.createInfoPopup();\n\n // Create Add Location popup form\n this.createAddLocationPopup();\n\n // Create editable parcel form popup\n this.createParcelEditPopup();\n\n // Create drawn polygon attribute popup\n this.createDrawnPolygonPopup();\n\n // Create merge identifier (UPN) chooser popup\n this.createMergePopup();\n\n // Create divide polygon popup (number input)\n this.createDividePopup();\n\n // Double-click callbacks\n this.dblClickCallbacks = [];\n\n // EditBar is set up lazily via initEditBar() once the Drawings\n // layer/group is available (called from main.js after loadLayers).\n this.editBar = null;\n this.drawingsSource = null;\n this.drawingsLayer = null;\n this.touchCursor = null;\n this._editBarActive = false;\n }\n\n // ============================================================================\n // EditBar + Drawings Layer + TouchCursor\n // ============================================================================\n\n /**\n * Initialise the EditBar with a dedicated \"Drawings\" LayerGroup.\n *\n * A \"Drawings\" LayerGroup is created at the top of the overlay stack\n * containing a \"sketches\" VectorLayer for storing drawn features.\n * The EditBar, Select and Modify interactions are only active while\n * edit mode is on; in all other cases normal click / double-click\n * behaviour is preserved.\n *\n * Call this once from main.js after the layer groups have been created.\n */\n initEditBar() {\n // 1. Create a \"Drawings\" LayerGroup with a \"sketches\" VectorLayer inside\n this.drawingsSource = new VectorSource();\n this.drawingsLayer = new VectorLayer({\n title: 'sketches',\n source: this.drawingsSource,\n style: new Style({\n stroke: new Stroke({ color: '#f59e0b', width: 2.5 }),\n fill: new Fill({ color: 'rgba(245,158,11,0.15)' }),\n image: new Circle({\n radius: 6,\n fill: new Fill({ color: '#f59e0b' }),\n stroke: new Stroke({ color: '#fff', width: 1.5 }),\n }),\n }),\n });\n\n this._drawingsGroup = new LayerGroup({\n title: 'Drawings',\n layers: [this.drawingsLayer],\n });\n // Insert as a top-level map layer just before the Overlays group so the\n // LayerSwitcher order is: Overlays > Drawings > Measurements > Markers > Base Maps.\n // Find Overlays by reference rather than assuming it is the last layer —\n // other layers (e.g. the GPS trail/position layers added by\n // _initGpsRendering) may sit on top of it.\n const mapLayers = this.map.getLayers();\n const overlayIdx = mapLayers.getArray().indexOf(this.overlayGroup);\n mapLayers.insertAt(overlayIdx >= 0 ? overlayIdx : mapLayers.getLength(), this._drawingsGroup);\n\n // 2. Create a Select interaction that works on ALL vector layers.\n // It starts INACTIVE so it doesn't steal clicks from normal handlers.\n // Multi-select: a plain click selects a single feature (replacing the\n // selection); Shift-click toggles a feature in/out of the selection so\n // the user can build up a set to delete or move together. Shift-click is\n // the ol default toggleCondition, but we set it explicitly for clarity.\n this._selectInteraction = new Select({\n condition: clickCondition,\n toggleCondition: shiftKeyOnly,\n filter: (feature, layer) => !!layer,\n // A layer can opt out of selection with selectable:false. The\n // Measurements layer does — measurements are results to read, not\n // geometry to reshape, and editing them would desynchronise the\n // reported radius/area from the shape actually being analysed.\n layers: (layer) => layer instanceof VectorLayer && layer.get('selectable') !== false,\n });\n this._selectInteraction.setActive(false);\n this.map.addInteraction(this._selectInteraction);\n\n // 2b. Box-select — Ctrl/Cmd + drag draws a rectangle and adds every\n // intersecting feature to the selection. This is the discoverable\n // \"select multiple\" gesture (Shift-click also works, one at a time).\n // Plain drag still pans the map; Shift-drag is left free. The box is\n // only live while the Select tool itself is active (see the\n // change:active wiring below) so it never interferes with drawing.\n this._dragBoxSelect = new DragBox({ condition: platformModifierKeyOnly });\n this._dragBoxSelect.setActive(false);\n this.map.addInteraction(this._dragBoxSelect);\n this._dragBoxSelect.on('boxend', () => {\n const extent = this._dragBoxSelect.getGeometry().getExtent();\n const selected = this._selectInteraction.getFeatures();\n // Walk every visible vector layer the Select interaction would accept\n // and add features whose geometry intersects the box.\n this.map.getLayers().forEach((layer) => this._boxAddFromLayer(layer, extent, selected));\n this._refreshVertexOverlay?.();\n });\n // Keep box-select armed only while the Select sub-tool is active.\n this._selectInteraction.on('change:active', () => {\n this._dragBoxSelect.setActive(this._selectInteraction.getActive());\n });\n\n // 3. Create a ModifyFeature interaction bound to the selection.\n // Also starts inactive.\n this._modifyInteraction = new ModifyFeature({\n features: this._selectInteraction.getFeatures(),\n });\n this._modifyInteraction.setActive(false);\n\n // 3b. Fire onFeatureModified callbacks when a modification completes.\n // Consumers attach via onFeatureModified() and decide for themselves\n // whether to react (e.g. the staged-import code persists geometry\n // changes back to external_import_features).\n this._modifyInteraction.on('modifyend', (evt) => {\n if (!this._featureModifiedCallbacks?.length) return;\n const features = evt.features?.getArray?.() || [];\n for (const f of features) {\n for (const cb of this._featureModifiedCallbacks) {\n try { cb(f); } catch (err) { console.warn('[MapView] onFeatureModified callback failed:', err); }\n }\n }\n });\n\n // 4. UndoRedo interaction — watches the drawings source\n this._undoRedo = new UndoRedo();\n this.map.addInteraction(this._undoRedo);\n\n // 5. Build the EditBar — all interactions enabled.\n this.editBar = new EditBar({\n source: this.drawingsSource,\n interactions: {\n Select: this._selectInteraction,\n ModifySelect: this._modifyInteraction,\n DrawPoint: true,\n DrawLine: true,\n DrawPolygon: true,\n DrawRegular: true,\n DrawHole: true,\n Delete: true,\n Info: true,\n Transform: true,\n Split: false,\n },\n });\n this.map.addControl(this.editBar);\n\n // 5a. Draw ▸ Point is the single way to add a Location.\n // There used to be two ways to put a point on the map — the Add Location\n // dock button and this tool — which produced two different kinds of\n // point from the officer's perspective. The tool now raises the same\n // event the dock button did, and the sketch it leaves behind is taken\n // straight back out again: the marker created by the Add Location form\n // is the record, so keeping the sketch as well would show the same place\n // twice, and would linger if the officer cancelled the form.\n const drawPoint = this.editBar.getInteraction('DrawPoint');\n if (drawPoint) {\n drawPoint.on('drawend', (evt) => {\n const coordinate = evt.feature?.getGeometry?.()?.getCoordinates?.();\n if (!coordinate) return;\n\n // ol adds the sketch to the source *after* this handler returns, so the\n // add and its removal straddle a tick. Recording has to be suppressed\n // across both: suppressing only the removal leaves the add on the undo\n // stack, and pressing Undo would then resurrect a point the officer\n // never kept.\n const undoRedo = this._undoRedo;\n const wasRecording = undoRedo?._record;\n if (undoRedo) undoRedo._record = false;\n setTimeout(() => {\n try {\n this.drawingsSource.removeFeature(evt.feature);\n } catch {\n // Already gone (e.g. the layer was cleared) — nothing to undo.\n } finally {\n if (undoRedo) undoRedo._record = wasRecording;\n }\n }, 0);\n\n for (const cb of this._drawnPointCallbacks || []) {\n try { cb(coordinate); } catch (err) {\n console.warn('[MapView] onDrawnPoint callback failed:', err);\n }\n }\n });\n }\n\n // 5b. Persistent vertex overlay — when edit mode is active and the user\n // selects a polygon (or line) for modification, render a small dot\n // at every vertex so the user can see all editable nodes at a glance.\n // ol-ext's ModifyFeature only renders the closest vertex on hover; this\n // overlay complements that without subclassing the interaction.\n this._setupVertexOverlay();\n\n // 6. Add extra buttons (Undo, Redo, Save) as a sub-bar\n // inside the EditBar so they appear inline.\n const extraBar = new Bar({\n group: true,\n // Stable class so CSS can move this group (undo/redo/save/snap) to a\n // second row on small screens — see `.ol-editbar-actions` media query.\n className: 'ol-editbar-actions',\n controls: [\n new Button({\n html: '',\n className: 'ol-undo',\n title: 'Undo',\n handleClick: () => {\n if (this._undoRedo.hasUndo()) this._undoRedo.undo();\n },\n }),\n new Button({\n html: '',\n className: 'ol-redo',\n title: 'Redo',\n handleClick: () => {\n if (this._undoRedo.hasRedo()) this._undoRedo.redo();\n },\n }),\n new Button({\n html: '',\n className: 'ol-save',\n title: 'Save drawings',\n handleClick: () => {\n this.dispatchEditEvent('save');\n },\n }),\n ],\n });\n this.editBar.addControl(extraBar);\n\n // 6a-split. Custom Split tool with Lines / Polygons sub-categories.\n // The default ol-ext Split only handles LineString. We add a parent\n // Toggle with a sub-bar containing two sub-toggles: \"Lines\" (ol-ext\n // Split) and \"Polygons\" (our PolygonSplitInteraction).\n // No explicit sources → both interactions search ALL visible vector layers,\n // so they work on drawn features, parcels, zones, and any other polygon layer.\n this._lineSplitInteraction = new Split();\n this._polygonSplitInteraction = new PolygonSplitInteraction();\n this.map.addInteraction(this._lineSplitInteraction);\n this.map.addInteraction(this._polygonSplitInteraction);\n this._lineSplitInteraction.setActive(false);\n this._polygonSplitInteraction.setActive(false);\n\n // When a parcel is split, the user picks which piece keeps the UPN.\n this._polygonSplitInteraction.on('splitpick', (evt) => {\n const idFields = ['UPN', 'upn', 'id', 'parcelid', 'parcel_id', 'PARCELID', 'PARCEL_ID', 'ID'];\n for (const feat of evt.features) {\n if (feat === evt.picked) continue;\n for (const field of idFields) {\n if (feat.get(field) !== undefined) {\n feat.set(field, '');\n }\n }\n }\n });\n\n // Polygon Divide interaction (parameter-driven equal-area division)\n this._polygonDivideInteraction = new PolygonDivideInteraction();\n this.map.addInteraction(this._polygonDivideInteraction);\n this._polygonDivideInteraction.setActive(false);\n\n const splitLineToggle = new Toggle({\n html: '',\n className: 'ol-split-line',\n title: 'Split Lines',\n name: 'SplitLine',\n interaction: this._lineSplitInteraction,\n autoActivate: true,\n });\n const splitPolyToggle = new Toggle({\n html: '',\n className: 'ol-split-polygon',\n title: 'Split Polygons',\n name: 'SplitPolygon',\n interaction: this._polygonSplitInteraction,\n });\n const splitDivideToggle = new Toggle({\n html: '',\n className: 'ol-split-divide',\n title: 'Divide Polygon',\n name: 'DividePolygon',\n interaction: this._polygonDivideInteraction,\n });\n\n const splitSubBar = new Bar({\n toggleOne: true,\n autoDeactivate: true,\n controls: [splitLineToggle, splitPolyToggle, splitDivideToggle],\n });\n\n const splitParentToggle = new Toggle({\n className: 'ol-split',\n title: 'Split',\n name: 'Split',\n bar: splitSubBar,\n onToggle: (active) => {\n if (!active) {\n this._lineSplitInteraction.setActive(false);\n this._polygonSplitInteraction.setActive(false);\n this._polygonDivideInteraction.setActive(false);\n }\n },\n });\n this.editBar.addControl(splitParentToggle);\n\n // Listen for divide form request → show divide popup\n this._polygonDivideInteraction.on('divideform', (evt) => {\n this.showDividePopup(evt.feature, evt.source, evt.coordinate);\n });\n this._polygonDivideInteraction.on('dividecancel', () => {\n this.hideDividePopup();\n });\n\n // When a parcel is divided, the user picks which piece keeps the UPN.\n // The picked piece gets the original properties; all others get UPN cleared.\n this._polygonDivideInteraction.on('dividepick', (evt) => {\n const idFields = ['UPN', 'upn', 'id', 'parcelid', 'parcel_id', 'PARCELID', 'PARCEL_ID', 'ID'];\n for (const feat of evt.features) {\n if (feat === evt.picked) continue;\n // Clear identifier fields on the non-picked pieces\n for (const field of idFields) {\n if (feat.get(field) !== undefined) {\n feat.set(field, '');\n }\n }\n }\n });\n\n // 6a-merge. Polygon Merge tool — select two adjacent polygons, click shared\n // edges, and merge them into one. For parcels, a UPN chooser popup appears.\n this._polygonMergeInteraction = new PolygonMergeInteraction();\n this.map.addInteraction(this._polygonMergeInteraction);\n this._polygonMergeInteraction.setActive(false);\n\n const mergeToggle = new Toggle({\n html: '',\n className: 'ol-merge',\n title: 'Merge Polygons',\n name: 'Merge',\n interaction: this._polygonMergeInteraction,\n });\n this.editBar.addControl(mergeToggle);\n\n // Listen for merged-parcel event → show UPN chooser\n this._polygonMergeInteraction.on('mergedparcel', (evt) => {\n this.showMergeIdentifierPopup(evt.merged, evt.propsA, evt.propsB, evt.coordinate);\n });\n\n // The rail layout (see _buildToolbarRail) replaces the earlier small-screen\n // workaround, which inserted a flex line-break to push the action group onto\n // a second row. A two-column rail on desktop and a scrolling strip on phones\n // both fit without wrapping, so no break element is needed.\n\n // 6b. SnapGuides — shows alignment guides while drawing.\n // Uses VectorImageLayer for GPU-friendly canvas rendering instead of\n // re-creating individual SVG elements on every guide update.\n this._snapGuidesEnabled = localStorage.getItem('snap-guides-enabled') === '1';\n this._snapGuides = new SnapGuides({\n pixelTolerance: 10,\n vectorClass: VectorImageLayer,\n });\n this.map.addInteraction(this._snapGuides);\n\n // Connect SnapGuides to whichever draw interaction becomes active.\n // setDrawInteraction() only tracks one at a time, so we re-bind\n // whenever a draw tool is activated.\n const drawToolNames = ['DrawPoint', 'DrawLine', 'DrawPolygon', 'DrawHole', 'DrawRegular'];\n for (const name of drawToolNames) {\n const interaction = this.editBar.getInteraction(name);\n if (interaction) {\n interaction.on('change:active', () => {\n if (interaction.getActive()) {\n this._snapGuides.setDrawInteraction(interaction);\n }\n });\n }\n }\n\n // Also connect SnapGuides to the Modify interaction for vertex editing\n if (this._modifyInteraction) {\n this._snapGuides.setModifyInteraction(this._modifyInteraction);\n }\n\n // 6c. Snap-guides toggle button (magnet icon) — persisted in localStorage\n const snapToggleBtn = new Button({\n html: '',\n className: 'ol-snap-toggle' + (this._snapGuidesEnabled ? ' ol-active' : ''),\n title: 'Toggle Snap Guides',\n handleClick: () => {\n this._snapGuidesEnabled = !this._snapGuidesEnabled;\n localStorage.setItem('snap-guides-enabled', this._snapGuidesEnabled ? '1' : '0');\n // Update visual state\n snapToggleBtn.element.classList.toggle('ol-active', this._snapGuidesEnabled);\n // Activate or deactivate the interaction\n if (this._snapGuides) {\n this._snapGuides.setActive(this._snapGuidesEnabled && this._editBarActive);\n }\n console.log('[MapView] Snap guides:', this._snapGuidesEnabled ? 'ON' : 'OFF');\n },\n });\n this._snapToggleBtn = snapToggleBtn;\n extraBar.addControl(snapToggleBtn);\n\n // 7. Re-shape the bar into the docked left rail and swap every icon.\n // Runs last so that every control — including the snap toggle just\n // added to the action group — is already in the DOM.\n this._buildToolbarRail();\n\n // Start hidden — use the full setEditMode(false) so the Select +\n // Modify interactions are deactivated (the EditBar constructor may\n // have re-activated them).\n this.setEditMode(false);\n\n // 7. Link EditBar visibility to the Drawings group's visibility.\n this._drawingsGroup.on('change:visible', () => {\n const visible = this._drawingsGroup.getVisible();\n this.setEditMode(visible);\n });\n\n // 8. Touch-device detection & TouchCursor setup\n //\n // The TouchCursor is only practical on *touch-only* devices (phones,\n // tablets) where the finger is the sole pointing device. On hybrid\n // laptops — a touchscreen plus a touchpad/mouse — the changed cursor\n // gets in the way, because the user is most likely driving the map\n // with the precise pointer, not the screen.\n //\n // The classic `'ontouchstart' in window` / `maxTouchPoints` test can't\n // tell the two apart: it is true for both a tablet and a touchscreen\n // laptop. We additionally consult CSS Media Queries Level 4 pointer\n // features, which DO distinguish them:\n //\n // any-pointer: fine → at least one fine pointer exists\n // (mouse / touchpad / stylus). True on a\n // touch laptop, false on a phone/tablet.\n // any-hover: hover → at least one device can hover. Same split.\n //\n // So we enable the TouchCursor only when the device is touch-capable\n // AND exposes no fine/hovering pointer — i.e. a genuine touch-only\n // device. `_isTouchOnlyDevice()` is also reactive: if the user later\n // plugs in a mouse, `_refreshTouchCursor()` tears the cursor down.\n if (this._isTouchOnlyDevice()) {\n this.touchCursor = new TouchCursor({\n className: 'ol-editbar-cursor',\n });\n this.map.addInteraction(this.touchCursor);\n this.touchCursor.setActive(false);\n console.log('[MapView] Touch-only device detected — TouchCursor added');\n } else {\n console.log('[MapView] Fine pointer available — TouchCursor skipped');\n }\n\n // React to pointer-capability changes (e.g. a Bluetooth mouse paired\n // with a tablet, or a tablet docked to a trackpad keyboard). When a\n // fine pointer appears we drop the TouchCursor; when the last one is\n // removed on a touch device we add it back.\n this._installPointerCapabilityWatcher();\n\n // 9. Listen for polygon features drawn via EditBar's DrawPolygon tool.\n // When a Polygon is added to the drawings source, show the attribute popup.\n this.drawingsSource.on('addfeature', (evt) => {\n const feature = evt.feature;\n const geom = feature.getGeometry();\n if (!geom || geom.getType() !== 'Polygon') return;\n\n const coordinate = geom.getInteriorPoint().getCoordinates();\n this.showDrawnPolygonPopup(feature, coordinate);\n });\n\n // 10. Keyboard Delete — pressing Delete or Backspace while edit mode is\n // active deletes the currently-selected feature(s), mirroring the\n // EditBar's Delete button (same undoable deletestart/deleteend block).\n // Guarded so it never fires while the user is typing in a form field.\n this._editKeyHandler = (e) => {\n if (e.key !== 'Delete' && e.key !== 'Backspace') return;\n if (!this._editBarActive) return;\n const t = e.target;\n const tag = t && t.tagName;\n if (t?.isContentEditable || tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;\n if (this._deleteSelectedFeatures()) {\n e.preventDefault();\n }\n };\n document.addEventListener('keydown', this._editKeyHandler);\n\n console.log('[MapView] EditBar initialised with Drawings group, UndoRedo and SnapGuides (default:', this._snapGuidesEnabled ? 'ON' : 'OFF', ')');\n }\n\n /**\n * Delete the currently-selected feature(s) via the EditBar's Delete\n * interaction, so the removal is recorded as a single undoable block\n * (deletestart/deleteend) exactly like the Delete button. Returns true if\n * anything was deleted.\n * @returns {boolean}\n */\n _deleteSelectedFeatures() {\n if (!this._editBarActive) return false;\n const del = this.editBar?.getInteraction('Delete');\n const features = this._selectInteraction?.getFeatures();\n if (!del || !features || features.getLength() === 0) return false;\n del.delete(features); // removes from all map sources; undoable\n features.clear(); // drop the now-deleted features from selection\n this._refreshVertexOverlay?.();\n return true;\n }\n\n /**\n * Box-select helper: add every feature of a (visible, Select-eligible) vector\n * layer whose geometry intersects `extent` to the `selected` collection,\n * skipping duplicates. Recurses into LayerGroups.\n * @private\n */\n _boxAddFromLayer(layer, extent, selected) {\n if (!layer) return;\n if (typeof layer.getLayers === 'function') {\n layer.getLayers().forEach((l) => this._boxAddFromLayer(l, extent, selected));\n return;\n }\n if (!(layer instanceof VectorLayer)) return;\n if (layer.getVisible && !layer.getVisible()) return;\n // Skip non-selectable helper layers: the vertex-edit overlay, GPS trail /\n // position, and anything explicitly hidden from the layer switcher.\n if (layer === this._vertexOverlayLayer ||\n layer === this._gpsTrailLayer ||\n layer === this._gpsPositionLayer ||\n layer.get('selectable') === false || // e.g. Measurements\n layer.get('displayInLayerSwitcher') === false) return;\n const source = layer.getSource && layer.getSource();\n if (!source || typeof source.forEachFeatureIntersectingExtent !== 'function') return;\n source.forEachFeatureIntersectingExtent(extent, (feature) => {\n if (!selected.getArray().includes(feature)) selected.push(feature);\n });\n }\n\n /**\n * Dispatch a custom edit event (e.g. 'save').\n * External code can listen via mapView.onEditEvent('save', callback).\n * @param {string} type\n */\n dispatchEditEvent(type) {\n if (!this._editEventListeners) return;\n const listeners = this._editEventListeners[type];\n if (listeners) {\n listeners.forEach((fn) => fn());\n }\n }\n\n /**\n * Listen for custom edit events (e.g. 'save').\n * @param {string} type - Event name\n * @param {Function} callback\n */\n onEditEvent(type, callback) {\n if (!this._editEventListeners) this._editEventListeners = {};\n if (!this._editEventListeners[type]) this._editEventListeners[type] = [];\n this._editEventListeners[type].push(callback);\n }\n\n /**\n * Toggle edit mode on or off.\n *\n * When ON: EditBar is visible, Select + Modify interactions are active.\n * When OFF: EditBar is hidden, Select + Modify are deactivated, any\n * current selection is cleared so normal click / double-click\n * events work without interference.\n *\n * @param {boolean} active\n */\n setEditMode(active) {\n const wasActive = this._editBarActive;\n this._editBarActive = !!active;\n\n // On entering edit mode, (1) re-scan vector sources so UndoRedo watches\n // every layer loaded since the EditBar was built — parcels, zones and\n // imports are pushed into sub-groups AFTER init, and ol-ext's UndoRedo only\n // auto-watches TOP-LEVEL map layer changes, so those sources would\n // otherwise be invisible to undo (a delete records an empty block and undo\n // does nothing); and (2) reset the undo history so it covers only this\n // editing session and can't reach back into data-loading operations.\n if (this._editBarActive && !wasActive && this._undoRedo) {\n try {\n this._undoRedo._watchSources();\n this._undoRedo.clear();\n } catch (err) {\n console.warn('[MapView] UndoRedo re-watch/clear failed:', err);\n }\n }\n\n if (this.editBar) {\n this.editBar.setVisible(this._editBarActive);\n\n if (!this._editBarActive) {\n // Deactivate all EditBar controls (DrawPoint, DrawLine, etc.)\n // so no draw interaction stays active in the background.\n this.editBar.deactivateControls();\n }\n }\n\n // Activate / deactivate Select + Modify\n if (this._selectInteraction) {\n if (!this._editBarActive) {\n // Clear any current selection first\n this._selectInteraction.getFeatures().clear();\n }\n this._selectInteraction.setActive(this._editBarActive);\n }\n if (this._modifyInteraction) {\n this._modifyInteraction.setActive(this._editBarActive);\n }\n\n // Toggle SnapGuides — only active when both edit mode AND the user toggle are on\n if (this._snapGuides) {\n this._snapGuides.setActive(this._snapGuidesEnabled && this._editBarActive);\n }\n\n // Toggle TouchCursor\n if (this.touchCursor) {\n this.touchCursor.setActive(this._editBarActive);\n }\n\n // Clear persistent vertex highlights when leaving edit mode (excluded\n // from the undo stack — see _withoutUndoRecording / _silentClearSource).\n if (!this._editBarActive && this._vertexOverlaySource) {\n this._withoutUndoRecording(() => this._silentClearSource(this._vertexOverlaySource));\n }\n\n console.log('[MapView] Edit mode:', this._editBarActive ? 'ON' : 'OFF');\n }\n\n /**\n * Check whether edit mode (select / modify) is currently active.\n * @returns {boolean}\n */\n isEditMode() {\n return this._editBarActive;\n }\n\n // ============================================================================\n // Pointer-capability detection (touch-only vs. hybrid laptop)\n // ============================================================================\n\n /**\n * Decide whether the ol-ext TouchCursor should be used.\n *\n * Returns true ONLY for genuine touch-only devices (phones, tablets) —\n * i.e. devices that are touch-capable but expose no fine pointer and no\n * hover capability. Hybrid laptops (touchscreen + touchpad/mouse) return\n * false, so they keep the normal cursor.\n *\n * @returns {boolean}\n */\n _isTouchOnlyDevice() {\n const hasTouch = ('ontouchstart' in window) ||\n (navigator.maxTouchPoints > 0) ||\n (navigator.msMaxTouchPoints > 0);\n if (!hasTouch) return false;\n\n // Without matchMedia we can't refine the signal — fall back to the\n // historical behaviour (treat any touch device as touch).\n if (typeof window.matchMedia !== 'function') return true;\n\n // `any-pointer: fine` is true when ANY attached pointing device is\n // fine (mouse / touchpad / stylus); `any-hover: hover` when ANY device\n // can hover. A phone/tablet satisfies neither; a touch laptop both.\n const hasFinePointer = window.matchMedia('(any-pointer: fine)').matches;\n const canHover = window.matchMedia('(any-hover: hover)').matches;\n\n return !hasFinePointer && !canHover;\n }\n\n /**\n * Add or remove the TouchCursor to match the current pointer capability,\n * preserving its active state relative to edit mode. Called both at init\n * and whenever the pointer-capability media queries change.\n */\n _refreshTouchCursor() {\n const wantCursor = this._isTouchOnlyDevice();\n\n if (wantCursor && !this.touchCursor) {\n this.touchCursor = new TouchCursor({ className: 'ol-editbar-cursor' });\n this.map.addInteraction(this.touchCursor);\n this.touchCursor.setActive(this._editBarActive);\n console.log('[MapView] Pointer change → TouchCursor added');\n } else if (!wantCursor && this.touchCursor) {\n this.map.removeInteraction(this.touchCursor);\n this.touchCursor = null;\n console.log('[MapView] Pointer change → TouchCursor removed (fine pointer present)');\n }\n }\n\n /**\n * Watch the pointer-capability media queries and re-evaluate the\n * TouchCursor when they change — e.g. a Bluetooth mouse paired with a\n * tablet, or a 2-in-1 docked to / undocked from a keyboard-trackpad.\n */\n _installPointerCapabilityWatcher() {\n if (typeof window.matchMedia !== 'function') return;\n if (this._pointerMediaQueries) return; // already installed\n\n const queries = ['(any-pointer: fine)', '(any-hover: hover)']\n .map((q) => window.matchMedia(q));\n const onChange = () => this._refreshTouchCursor();\n\n for (const mq of queries) {\n // addEventListener is the modern API; addListener is the legacy\n // fallback for older Safari.\n if (typeof mq.addEventListener === 'function') {\n mq.addEventListener('change', onChange);\n } else if (typeof mq.addListener === 'function') {\n mq.addListener(onChange);\n }\n }\n this._pointerMediaQueries = queries;\n this._pointerMediaListener = onChange;\n }\n\n // ============================================================================\n // Drawing toolbar — docked left rail\n // ============================================================================\n\n /**\n * Turn the EditBar into the docked left rail.\n *\n * Two things happen here, both purely presentational — no interaction, tool or\n * event handler is touched:\n *\n * 1. Every button's icon is replaced with a Bootstrap Icon (see TOOLBAR_ICONS),\n * so the built-in tools stop looking like they came from a different program.\n * 2. The controls are re-ordered into the labelled bands in TOOLBAR_GROUPS and\n * the bar is given ol-ext's `ol-left` class, which docks it to the left edge\n * and — usefully — makes option bars (Select's Delete/Info, Split's three\n * sub-tools) fly out to the right instead of dropping downwards.\n *\n * Anything not named in TOOLBAR_GROUPS keeps its position at the end of the bar,\n * so a tool added later still appears rather than silently vanishing.\n */\n _buildToolbarRail() {\n const bar = this.editBar?.element;\n if (!bar) return;\n try {\n this._applyToolbarRail(bar);\n } catch (err) {\n // This is presentation only. If it fails the toolbar must still work, so\n // strip our classes and let ol-ext render its own default bar rather than\n // leaving a half-styled one — or, worse, aborting initEditBar() and\n // taking every tool with it.\n console.warn('[MapView] Could not build the toolbar rail; falling back to the default bar:', err);\n bar.classList.remove('ol-left', 'ol-editbar-rail');\n }\n }\n\n /** @private Does the work for _buildToolbarRail(). */\n _applyToolbarRail(bar) {\n // `ol-left` is ol-ext's own left-dock class; `ol-editbar-rail` scopes our CSS.\n bar.classList.add('ol-left', 'ol-editbar-rail');\n\n // --- 1. icons -----------------------------------------------------------\n for (const [className, icon] of Object.entries(TOOLBAR_ICONS)) {\n for (const ctrl of bar.querySelectorAll(`.${className}`)) {\n // Button controls ARE the button; Toggle controls wrap one.\n const button = ctrl.matches('button') ? ctrl : ctrl.querySelector(':scope > button');\n // A , not an : ol-ext draws the Transform and Offset icons with\n // `.ol-transform > button i::before`, which would out-specify Bootstrap's\n // `.bi::before` and blank the glyph. Bootstrap Icons work on any element,\n // so switching the tag sidesteps the collision entirely.\n if (button) button.innerHTML = ``;\n }\n }\n\n // --- 2. grouping --------------------------------------------------------\n // Appending an element that is already in the DOM moves it, so building the\n // fragment in group order and appending it re-sorts the bar in one reflow.\n const ordered = document.createDocumentFragment();\n for (const group of TOOLBAR_GROUPS) {\n const members = group.controls\n .map((className) => bar.querySelector(`:scope > .${className}`))\n .filter(Boolean);\n if (!members.length) continue;\n\n const label = document.createElement('div');\n label.className = 'ol-rail-label';\n label.textContent = group.label;\n label.setAttribute('aria-hidden', 'true');\n ordered.appendChild(label);\n for (const member of members) ordered.appendChild(member);\n }\n bar.appendChild(ordered);\n\n // --- 3. drag grip -------------------------------------------------------\n const grip = document.createElement('div');\n grip.className = 'ol-rail-grip';\n grip.title = 'Drag to move the toolbar — double-click to reset';\n grip.setAttribute('role', 'button');\n grip.setAttribute('aria-label', 'Move the drawing toolbar');\n bar.insertBefore(grip, bar.firstChild);\n this._makeToolbarDraggable(bar, grip);\n }\n\n /**\n * Let the user drag the rail by its grip, and remember where they put it.\n *\n * A fixed palette always covers something — at the default position it sits\n * over the left off-canvas toggle on short screens, and officers work at\n * different zoom levels and with different hands. Rather than pick a spot that\n * is wrong slightly less often, the rail moves and stays where it is put.\n *\n * Only above the phone breakpoint: below it the bar is a strip docked above\n * the bottom dock, where there is nowhere useful to drag it to.\n */\n _makeToolbarDraggable(bar, grip) {\n const STORAGE_KEY = 'editbar-position';\n // Must stay in step with the media query in index.html that swaps the rail\n // for the docked strip — narrow OR short, because a phone in landscape is\n // wide but has no room for a 395px-tall rail.\n const phone = window.matchMedia('(max-width: 576px), (max-height: 560px)');\n\n const clamp = (value, min, max) => Math.min(Math.max(value, min), max);\n\n /** Apply a position, clamped so the rail can never be dragged off-map. */\n const place = (left, top) => {\n const parent = bar.offsetParent || this.map.getTargetElement();\n if (!parent) return;\n const maxLeft = Math.max(0, parent.clientWidth - bar.offsetWidth - 4);\n const maxTop = Math.max(0, parent.clientHeight - bar.offsetHeight - 4);\n bar.style.left = `${clamp(left, 4, maxLeft)}px`;\n bar.style.top = `${clamp(top, 4, maxTop)}px`;\n // ol-ext centres a left-docked bar with translateY(-50%); once the user\n // has positioned it, top is an absolute value and the offset must go.\n bar.style.transform = 'none';\n bar.style.bottom = 'auto';\n };\n\n const clearInline = () => {\n bar.style.left = '';\n bar.style.top = '';\n bar.style.transform = '';\n bar.style.bottom = '';\n };\n\n /** Restore a saved position, unless we are in the phone strip layout. */\n const restore = () => {\n if (phone.matches) { clearInline(); return; }\n try {\n const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null');\n if (saved && Number.isFinite(saved.left) && Number.isFinite(saved.top)) {\n place(saved.left, saved.top);\n }\n } catch {\n // A corrupt entry is not worth failing the toolbar over.\n }\n };\n\n let startX = 0;\n let startY = 0;\n let originLeft = 0;\n let originTop = 0;\n\n const onMove = (evt) => {\n place(originLeft + (evt.clientX - startX), originTop + (evt.clientY - startY));\n };\n\n const onUp = (evt) => {\n grip.releasePointerCapture?.(evt.pointerId);\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n bar.classList.remove('is-dragging');\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify({\n left: parseFloat(bar.style.left) || 0,\n top: parseFloat(bar.style.top) || 0,\n }));\n } catch {\n // Private browsing or a full quota — the rail still moved this session.\n }\n };\n\n grip.addEventListener('pointerdown', (evt) => {\n if (phone.matches || evt.button !== 0) return;\n evt.preventDefault();\n const rect = bar.getBoundingClientRect();\n const parentRect = (bar.offsetParent || this.map.getTargetElement()).getBoundingClientRect();\n startX = evt.clientX;\n startY = evt.clientY;\n originLeft = rect.left - parentRect.left;\n originTop = rect.top - parentRect.top;\n bar.classList.add('is-dragging');\n grip.setPointerCapture?.(evt.pointerId);\n window.addEventListener('pointermove', onMove);\n window.addEventListener('pointerup', onUp);\n });\n\n // Double-click the grip to put the rail back where it started.\n grip.addEventListener('dblclick', () => {\n try { localStorage.removeItem(STORAGE_KEY); } catch { /* ignore */ }\n clearInline();\n });\n\n // Crossing the phone breakpoint swaps the layout, so drop or re-apply the\n // inline position to match. Safari only gained MediaQueryList.addEventListener\n // in iOS 14; fall back rather than throw, because an exception here would\n // abort initEditBar() and take the whole toolbar with it.\n if (typeof phone.addEventListener === 'function') {\n phone.addEventListener('change', restore);\n } else if (typeof phone.addListener === 'function') {\n phone.addListener(restore);\n }\n restore();\n }\n\n // ============================================================================\n // Persistent Vertex Highlight Overlay\n // ============================================================================\n\n /**\n * Create a vector layer that renders a small dot at every vertex of any\n * currently-selected feature (polygon, multipolygon, line, multiline).\n * Only active while edit mode is on.\n *\n * Hooks:\n * - `select` event from the Select interaction → rebuild dots for the new selection\n * - `change` event on the selected feature → reposition dots when a vertex is dragged\n */\n _setupVertexOverlay() {\n this._vertexOverlaySource = new VectorSource();\n this._vertexOverlayLayer = new VectorLayer({\n title: '__vertex_highlight__',\n source: this._vertexOverlaySource,\n // Render above all other overlays but below ModifyFeature's hover indicator\n zIndex: 990,\n style: new Style({\n image: new Circle({\n radius: 4,\n fill: new Fill({ color: 'rgba(14,165,233,0.85)' }), // brand blue\n stroke: new Stroke({ color: '#fff', width: 1.2 }),\n }),\n }),\n });\n // Hide from LayerSwitcher — purely visual, not user-toggleable\n this._vertexOverlayLayer.set('displayInLayerSwitcher', false);\n this.map.addLayer(this._vertexOverlayLayer);\n\n // Bound handler so we can attach/detach by reference\n this._onSelectedFeatureGeomChange = () => this._refreshVertexOverlay();\n\n // Track which feature(s) we're listening on, so we can unhook cleanly\n this._vertexTrackedFeatures = new Set();\n\n // When the selection changes, swap which features we listen to and rebuild dots\n this._selectInteraction.on('select', () => this._refreshVertexOverlay());\n }\n\n /**\n * Run `fn` with the UndoRedo interaction's recording temporarily paused, so\n * the vector-source mutations it performs are NOT pushed onto the undo stack.\n *\n * The vertex-overlay layer is a plain VectorLayer, and ol-ext's UndoRedo\n * watches every vector source in the map — so without this, each selection's\n * vertex-dot add/clear would land on the undo stack and undo would reverse\n * those instead of the user's actual edit (the \"first undo does nothing,\n * second shows vertices\" symptom). Our overlay source is a plain ol\n * VectorSource (it never emits ol-ext's clearstart/clearend block events),\n * so gating `_record` is sufficient to fully exclude it.\n * @private\n */\n _withoutUndoRecording(fn) {\n const ur = this._undoRedo;\n if (!ur) return fn();\n const prev = ur._record;\n ur._record = false;\n try { return fn(); }\n finally { ur._record = prev; }\n }\n\n /**\n * Clear a vector source WITHOUT firing the clearstart/clearend events that\n * ol-ext patches onto ol/source/Vector.prototype.clear. Those events drive\n * UndoRedo's blockStart/blockEnd, which are NOT gated by the record flag — so\n * a plain `source.clear()` on a helper layer pushes a stray (empty) block\n * onto the undo stack even inside _withoutUndoRecording. Removing features\n * one by one only fires `removefeature`, which IS gated by the record flag,\n * so wrapped in _withoutUndoRecording this leaves the undo stack untouched.\n * @private\n */\n _silentClearSource(source) {\n if (!source) return;\n const feats = source.getFeatures();\n for (let i = feats.length - 1; i >= 0; i--) {\n source.removeFeature(feats[i]);\n }\n }\n\n /**\n * Rebuild the vertex overlay from the current Select interaction's features.\n * No-ops when not in edit mode.\n */\n _refreshVertexOverlay() {\n if (!this._vertexOverlaySource) return;\n // All overlay-source mutations are excluded from the undo stack.\n this._withoutUndoRecording(() => {\n this._silentClearSource(this._vertexOverlaySource);\n\n // Detach change listeners from previously-tracked features\n if (this._vertexTrackedFeatures) {\n for (const f of this._vertexTrackedFeatures) {\n f.un('change', this._onSelectedFeatureGeomChange);\n }\n this._vertexTrackedFeatures.clear();\n }\n\n if (!this._editBarActive || !this._selectInteraction) return;\n\n const selected = this._selectInteraction.getFeatures().getArray();\n for (const feat of selected) {\n const geom = feat.getGeometry();\n if (!geom) continue;\n const type = geom.getType();\n if (!['Polygon', 'MultiPolygon', 'LineString', 'MultiLineString'].includes(type)) {\n continue;\n }\n const coords = this._collectAllVertices(geom);\n for (const c of coords) {\n this._vertexOverlaySource.addFeature(new Feature(new Point(c)));\n }\n // Listen for vertex moves on this feature\n feat.on('change', this._onSelectedFeatureGeomChange);\n this._vertexTrackedFeatures.add(feat);\n }\n });\n }\n\n /**\n * Walk a (Multi)Polygon or (Multi)LineString geometry and return the flat\n * list of vertex coordinates. Polygon rings have a duplicate closing vertex\n * (last == first) which is dropped here so we don't render two dots on top\n * of each other.\n *\n * @param {Geometry} geom\n * @returns {Array>}\n */\n _collectAllVertices(geom) {\n const out = [];\n const isCoord = (v) => Array.isArray(v) && typeof v[0] === 'number';\n\n const visitRing = (ring, isPolygonRing) => {\n const len = isPolygonRing && ring.length > 1 ? ring.length - 1 : ring.length;\n for (let i = 0; i < len; i++) out.push(ring[i]);\n };\n\n const type = geom.getType();\n const coords = geom.getCoordinates();\n\n switch (type) {\n case 'Polygon':\n // coords = [outerRing, hole1, hole2, …]\n for (const ring of coords) visitRing(ring, true);\n break;\n case 'MultiPolygon':\n // coords = [poly1, poly2, …]; each poly = [outerRing, hole1, …]\n for (const poly of coords) for (const ring of poly) visitRing(ring, true);\n break;\n case 'LineString':\n visitRing(coords, false);\n break;\n case 'MultiLineString':\n for (const line of coords) visitRing(line, false);\n break;\n default:\n // Fallback: deep walk to find arrays of [x, y]\n const walk = (v) => {\n if (isCoord(v)) out.push(v);\n else if (Array.isArray(v)) for (const sub of v) walk(sub);\n };\n walk(coords);\n }\n return out;\n }\n\n /**\n * Get the Drawings layer for external access.\n * @returns {VectorLayer}\n */\n getDrawingsLayer() {\n return this.drawingsLayer;\n }\n\n /**\n * Get the Drawings source for external access.\n * @returns {VectorSource}\n */\n getDrawingsSource() {\n return this.drawingsSource;\n }\n\n /**\n * Get the EditBar control for external access.\n * @returns {EditBar}\n */\n getEditBar() {\n return this.editBar;\n }\n\n /**\n * Update the ScaleBar units ('metric' or 'imperial').\n * @param {'metric'|'imperial'} system\n */\n setScaleBarUnits(system) {\n if (this.scaleBar) {\n this.scaleBar.setUnits(system === 'imperial' ? 'imperial' : 'metric');\n }\n }\n\n /**\n * Create the popup overlay element and add to map\n */\n createPopup() {\n // Create popup container element\n this.popupElement = document.createElement('div');\n this.popupElement.className = 'map-popup';\n this.popupElement.style.cssText = `\n position: absolute;\n background: var(--card, #fff);\n color: var(--card-foreground, #1e1a4b);\n border-radius: 8px;\n padding: 10px 14px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.25);\n font-family: var(--font-body, 'Exo', sans-serif);\n font-size: 13px;\n min-width: 150px;\n max-width: 280px;\n pointer-events: none;\n z-index: 1000;\n border: 1px solid var(--border, #1e1a4b1f);\n `;\n\n // Create the overlay\n this.popup = new Overlay({\n element: this.popupElement,\n positioning: 'bottom-center',\n offset: [0, -15],\n stopEvent: false,\n });\n\n this.map.addOverlay(this.popup);\n\n // Set up hover handler\n this.setupHoverPopup();\n }\n\n /**\n * Set up the hover popup behavior\n */\n setupHoverPopup() {\n let currentFeature = null;\n\n this.map.on('pointermove', (evt) => {\n if (evt.dragging) {\n this.hidePopup();\n return;\n }\n\n // Only find features that are location markers (have 'name' property)\n const feature = this.map.forEachFeatureAtPixel(evt.pixel, (f) => {\n // Only return features that have a 'name' property (location markers)\n if (f.get('name')) {\n return f;\n }\n return null;\n });\n\n if (feature && feature !== currentFeature) {\n currentFeature = feature;\n this.showPopup(feature, evt.coordinate);\n } else if (!feature && currentFeature) {\n currentFeature = null;\n this.hidePopup();\n }\n\n // Update cursor - only show pointer for location markers\n this.map.getTargetElement().style.cursor = feature ? 'pointer' : '';\n });\n\n // Hide popup when mouse leaves the map\n this.map.getTargetElement().addEventListener('mouseleave', () => {\n this.hidePopup();\n currentFeature = null;\n });\n }\n\n /**\n * Show popup with feature attributes\n */\n showPopup(feature, coordinate) {\n const name = feature.get('name') || 'Unnamed';\n const category = feature.get('category') || 'default';\n const description = feature.get('description');\n const lon = feature.get('lon');\n const lat = feature.get('lat');\n const emoji = this.getEmoji(category);\n\n // Build popup content\n let html = `\n
    \n ${emoji} ${this.escapeHtml(name)}\n
    \n `;\n\n // Category badge\n const categoryColors = {\n 'water': '#3b82f6',\n 'school': '#f59e0b',\n 'health': '#ef4444',\n 'market': '#8b5cf6',\n 'default': '#2d5016',\n 'other': '#6b7280'\n };\n const catColor = categoryColors[category] || '#6b7280';\n html += `\n
    \n ${category}\n
    \n `;\n\n // Description if available\n if (description) {\n html += `\n
    \n ${this.escapeHtml(description)}\n
    \n `;\n }\n\n // Coordinates\n if (lon !== undefined && lat !== undefined) {\n html += `\n
    \n ${Number(lon).toFixed(5)}, ${Number(lat).toFixed(5)}\n
    \n `;\n }\n\n this.popupElement.innerHTML = html;\n this.popup.setPosition(coordinate);\n }\n\n /**\n * Hide the popup\n */\n hidePopup() {\n this.popup.setPosition(undefined);\n }\n\n /**\n * Create the info popup overlay for double-click feature details\n */\n createInfoPopup() {\n this.infoPopupElement = document.createElement('div');\n this.infoPopupElement.className = 'map-info-popup';\n this.infoPopupElement.style.cssText = `\n position: absolute;\n background: var(--card, #fff);\n color: var(--card-foreground, #1e1a4b);\n border-radius: 10px;\n padding: 0;\n box-shadow: 0 4px 16px rgba(0,0,0,0.3);\n font-family: var(--font-body, 'Exo', sans-serif);\n font-size: 13px;\n min-width: 220px;\n max-width: 320px;\n max-height: 70vh;\n display: flex;\n flex-direction: column;\n z-index: 1001;\n border: 1px solid var(--border, #1e1a4b1f);\n overflow: hidden;\n `;\n\n this.infoPopup = new Overlay({\n element: this.infoPopupElement,\n positioning: 'bottom-center',\n offset: [0, -10],\n stopEvent: true,\n autoPan: true,\n autoPanAnimation: { duration: 250 },\n });\n\n this.map.addOverlay(this.infoPopup);\n }\n\n /**\n * Show the info popup with feature attributes and area\n * @param {Feature} feature - OpenLayers feature\n * @param {Array} coordinate - Map coordinate [x, y]\n * @param {Object} [options] - Display options\n * @param {string} [options.title='Feature Info'] - Popup header title\n * @param {string} [options.color='#e11d48'] - Header background colour\n */\n showInfoPopup(feature, coordinate, options = {}) {\n const { title = 'Feature Info', color = '#e11d48' } = options;\n const properties = feature.getProperties();\n const geometry = feature.getGeometry();\n const geomType = geometry.getType();\n\n // Build attributes table rows (skip geometry and internal keys)\n const skipKeys = ['geometry', '_layerType'];\n let rows = '';\n for (const [key, value] of Object.entries(properties)) {\n if (skipKeys.includes(key) || value === undefined || value === null) continue;\n rows += `\n \n ${this.escapeHtml(key)}\n ${this.escapeHtml(String(value))}\n \n `;\n }\n\n // Add measurement row based on geometry type\n if (geomType === 'Polygon' || geomType === 'MultiPolygon') {\n // Area for polygons\n const areaSqm = getArea(geometry, { projection: 'EPSG:3857' });\n const areaFormatted = formatAreaFull(areaSqm);\n rows += `\n \n area\n ${areaFormatted}\n \n `;\n } else if (geomType === 'LineString' || geomType === 'MultiLineString') {\n // Length for lines\n const lengthM = getLength(geometry, { projection: 'EPSG:3857' });\n const lengthFormatted = formatLengthFull(lengthM);\n rows += `\n \n length\n ${lengthFormatted}\n \n `;\n } else if (geomType === 'Point') {\n // Coordinates for points\n const coords = toLonLat(geometry.getCoordinates());\n const lon = coords[0].toFixed(6);\n const lat = coords[1].toFixed(6);\n rows += `\n \n longitude\n ${lon}\n \n \n latitude\n ${lat}\n \n `;\n }\n\n const html = `\n
    \n ${this.escapeHtml(title)}\n \n
    \n
    \n \n ${rows}\n
    \n
    \n `;\n\n this.infoPopupElement.innerHTML = html;\n this.infoPopup.setPosition(coordinate);\n\n // Close button handler\n this.infoPopupElement.querySelector('#info-popup-close').addEventListener('click', () => {\n this.hideInfoPopup();\n });\n }\n\n /**\n * Hide the info popup\n */\n hideInfoPopup() {\n this.infoPopup.setPosition(undefined);\n }\n\n // ============================================================================\n // Circle Intersection Analysis\n // ============================================================================\n\n /**\n * Analyse which features from overlay layers intersect a measurement circle\n * and show the results in the info popup.\n *\n * @param {Feature} circleFeature - The measurement circle feature (Circle geometry)\n * @param {Array} coordinate - Map coordinate for popup placement [x, y]\n */\n /**\n * Collect intersection results (parcels, zones, other) into a\n * structured { label, value } array for both HTML and PDF rendering.\n */\n _collectIntersectionRows(parcelFeatures, zoneFeatures, otherByLayer) {\n const dataRows = [];\n\n if (parcelFeatures.length > 0) {\n dataRows.push({ label: 'Parcels', value: String(parcelFeatures.length), color: '#0ea5e9' });\n }\n\n if (zoneFeatures.length > 0) {\n const names = zoneFeatures.map(f =>\n f.get('colzonename') || f.get('zone_name') || f.get('name') || 'unnamed'\n );\n dataRows.push({ label: 'Zones', value: String(zoneFeatures.length), color: '#7c3aed' });\n dataRows.push({ label: 'Zone Names', value: names.map(n => this.escapeHtml(n)).join(', '), color: '#7c3aed' });\n }\n\n for (const [title, features] of Object.entries(otherByLayer)) {\n dataRows.push({ label: this.escapeHtml(title), value: `${features.length} feature(s)` });\n }\n\n if (dataRows.length === 0) {\n dataRows.push({ label: '', value: 'No intersecting features found', empty: true });\n }\n\n return dataRows;\n }\n\n /**\n * Build the full popup HTML for an analysis popup (circle or area).\n *\n * @param {string} emoji - Header emoji\n * @param {string} title - e.g. \"Circle Analysis\"\n * @param {Array<{label:string, value:string, color?:string, empty?:boolean}>} dataRows\n * @returns {string} HTML\n */\n _buildAnalysisPopupHtml(emoji, title, dataRows) {\n let tableRows = '';\n for (const row of dataRows) {\n if (row.empty) {\n tableRows += `\n \n ${row.value}\n `;\n continue;\n }\n const labelColor = row.color || 'var(--muted-foreground, #7a7a7a)';\n const border = row._first ? '' : 'border-top:1px solid var(--border, #1e1a4b1f);';\n tableRows += `\n \n ${row.label}\n ${row.value}\n `;\n }\n\n return `\n
    \n ${emoji} ${title}\n \n
    \n
    \n \n ${tableRows}\n
    \n
    \n
    \n \n \n
    `;\n }\n\n /**\n * Show the analysis popup, attach close + PDF / GIS export handlers.\n *\n * @param {string} emoji\n * @param {string} title - e.g. \"Area Analysis\" / \"Circle Analysis\"\n * @param {Array} dataRows - summary rows (PDF uses these)\n * @param {number[]} coordinate - popup anchor\n * @param {Object} [exportContext] - features + clip geometry for GIS export.\n * When omitted, the Export GIS button is\n * disabled (no features to export).\n * { kind: 'area' | 'circle',\n * clipGeometry,\n * parcelFeatures, zoneFeatures, otherByLayer }\n */\n _showAnalysisPopup(emoji, title, dataRows, coordinate, exportContext = null) {\n this.infoPopupElement.innerHTML = this._buildAnalysisPopupHtml(emoji, title, dataRows);\n this.infoPopup.setPosition(coordinate);\n\n this.infoPopupElement.querySelector('#info-popup-close').addEventListener('click', () => {\n this.hideInfoPopup();\n });\n\n // Summary rows, cleaned for the PDF report (HTML stripped, spacers removed).\n // These travel with the export event so the export modal can offer PDF\n // alongside the GIS formats — one \"Export\" entry point instead of two.\n const pdfRows = dataRows\n .filter((r) => !r.empty)\n .map((r) => ({ label: r.label, value: String(r.value).replace(/<[^>]*>/g, '') }));\n\n // Analyse — hand the intersecting features to the Analysis panel so the\n // user can run overlays / zonal statistics on just what the circle or\n // area caught. main.js owns the panel import.\n const analyseBtn = this.infoPopupElement.querySelector('#info-popup-analyse');\n if (analyseBtn) {\n const totalFeatures = exportContext\n ? exportContext.parcelFeatures.length\n + exportContext.zoneFeatures.length\n + Object.values(exportContext.otherByLayer).reduce((s, arr) => s + arr.length, 0)\n : 0;\n if (!exportContext || totalFeatures === 0) {\n analyseBtn.disabled = true;\n analyseBtn.style.opacity = '0.5';\n analyseBtn.style.cursor = 'not-allowed';\n analyseBtn.title = 'No intersecting features to analyse';\n } else {\n analyseBtn.addEventListener('click', () => {\n window.dispatchEvent(new CustomEvent('lupmis:analyse-features', {\n detail: { title, ...exportContext },\n }));\n });\n }\n }\n\n // GIS export — dispatches a window-level CustomEvent so main.js can open\n // the format/field-rename modal without MapView pulling in the writers.\n const gisBtn = this.infoPopupElement.querySelector('#info-popup-export-gis');\n if (gisBtn) {\n const total = exportContext\n ? exportContext.parcelFeatures.length\n + exportContext.zoneFeatures.length\n + Object.values(exportContext.otherByLayer).reduce((s, arr) => s + arr.length, 0)\n : 0;\n if (!exportContext || total === 0) {\n gisBtn.disabled = true;\n gisBtn.style.opacity = '0.5';\n gisBtn.style.cursor = 'not-allowed';\n gisBtn.title = 'No intersecting features to export';\n } else {\n gisBtn.addEventListener('click', () => {\n window.dispatchEvent(new CustomEvent('lupmis:export-gis', {\n detail: { title, pdfRows, ...exportContext },\n }));\n });\n }\n }\n }\n\n showCircleIntersectionPopup(circleFeature, coordinate) {\n const circleGeom = circleFeature.getGeometry();\n if (!circleGeom || typeof circleGeom.getCenter !== 'function') return;\n\n // Convert the OL Circle to a polygon (64 sides) for intersection testing\n const circlePoly = fromCircle(circleGeom, 64);\n const circleExtent = circlePoly.getExtent();\n\n // Read the radius from the geometry, not from the `_radius` property that\n // was written once at draw time. The intersection test below uses the live\n // geometry, so taking the radius from anywhere else risks reporting one\n // figure while analysing a different area — and that figure ends up in the\n // PDF export.\n const radius = circleGeom.getRadius();\n\n // Collect intersecting features grouped by layer type\n const parcelFeatures = [];\n const zoneFeatures = [];\n const otherByLayer = {};\n\n const intersectsCircle = (feature) => {\n const geom = feature.getGeometry();\n if (!geom) return false;\n const fExtent = geom.getExtent();\n if (\n fExtent[2] < circleExtent[0] ||\n fExtent[0] > circleExtent[2] ||\n fExtent[3] < circleExtent[1] ||\n fExtent[1] > circleExtent[3]\n ) {\n return false;\n }\n return circlePoly.intersectsExtent(fExtent) && this._geometriesIntersect(circlePoly, geom);\n };\n\n const scanGroup = (group, groupTitle) => {\n group.getLayers().forEach((layer) => {\n if (layer instanceof LayerGroup) {\n scanGroup(layer, layer.get('title') || groupTitle);\n } else if (layer instanceof VectorLayer && layer.getVisible()) {\n const layerTitle = layer.get('title') || groupTitle || 'Unknown';\n const source = layer.getSource();\n if (!source) return;\n\n const candidates = source.getFeaturesInExtent(circleExtent);\n for (const f of candidates) {\n const fType = f.get('_layerType');\n if (fType === 'measure_circle' || fType === 'measure_circle_radius') continue;\n\n if (!intersectsCircle(f)) continue;\n\n if (fType === 'parcel') {\n parcelFeatures.push(f);\n } else if (fType === 'collector_zone') {\n zoneFeatures.push(f);\n } else {\n if (!otherByLayer[layerTitle]) otherByLayer[layerTitle] = [];\n otherByLayer[layerTitle].push(f);\n }\n }\n }\n });\n };\n\n scanGroup(this.overlayGroup, 'Overlays');\n\n // Build structured data rows\n const radiusFormatted = formatLength(radius);\n const areaSqm = Math.PI * radius * radius;\n const areaFormatted = formatArea(areaSqm);\n\n const dataRows = [\n { label: 'Radius', value: radiusFormatted, _first: true },\n { label: 'Area', value: areaFormatted },\n ...this._collectIntersectionRows(parcelFeatures, zoneFeatures, otherByLayer),\n ];\n\n this._showAnalysisPopup('⭕', 'Circle Analysis', dataRows, coordinate, {\n kind: 'circle',\n clipGeometry: circlePoly,\n parcelFeatures,\n zoneFeatures,\n otherByLayer,\n });\n }\n\n /**\n * Show an intersection-analysis popup for a measured area polygon.\n * Same logic as showCircleIntersectionPopup but works with an\n * arbitrary Polygon geometry instead of a circle.\n *\n * @param {Feature} polygonFeature - The measure_area feature\n * @param {number[]} coordinate - Map coordinate for the popup anchor\n */\n showAreaIntersectionPopup(polygonFeature, coordinate) {\n const polyGeom = polygonFeature.getGeometry();\n if (!polyGeom) return;\n\n const polyExtent = polyGeom.getExtent();\n\n // Compute area via ol/sphere for geodesic accuracy\n const areaSqm = getArea(polyGeom, { projection: 'EPSG:3857' });\n const areaFormatted = formatArea(areaSqm);\n\n // Compute perimeter\n const perimeterM = getLength(polyGeom, { projection: 'EPSG:3857' });\n const perimeterFormatted = formatLength(perimeterM);\n\n // Collect intersecting features grouped by layer type\n const parcelFeatures = [];\n const zoneFeatures = [];\n const otherByLayer = {};\n\n const intersectsPoly = (feature) => {\n const geom = feature.getGeometry();\n if (!geom) return false;\n const fExtent = geom.getExtent();\n if (\n fExtent[2] < polyExtent[0] ||\n fExtent[0] > polyExtent[2] ||\n fExtent[3] < polyExtent[1] ||\n fExtent[1] > polyExtent[3]\n ) {\n return false;\n }\n return polyGeom.intersectsExtent(fExtent) && this._geometriesIntersect(polyGeom, geom);\n };\n\n const scanGroup = (group, groupTitle) => {\n group.getLayers().forEach((layer) => {\n if (layer instanceof LayerGroup) {\n scanGroup(layer, layer.get('title') || groupTitle);\n } else if (layer instanceof VectorLayer && layer.getVisible()) {\n const layerTitle = layer.get('title') || groupTitle || 'Unknown';\n const source = layer.getSource();\n if (!source) return;\n\n const candidates = source.getFeaturesInExtent(polyExtent);\n for (const f of candidates) {\n const fType = f.get('_layerType');\n if (fType === 'measure_area' || fType === 'measure_circle' || fType === 'measure_circle_radius') continue;\n\n if (!intersectsPoly(f)) continue;\n\n if (fType === 'parcel') {\n parcelFeatures.push(f);\n } else if (fType === 'collector_zone') {\n zoneFeatures.push(f);\n } else {\n if (!otherByLayer[layerTitle]) otherByLayer[layerTitle] = [];\n otherByLayer[layerTitle].push(f);\n }\n }\n }\n });\n };\n\n scanGroup(this.overlayGroup, 'Overlays');\n\n // Build structured data rows\n const dataRows = [\n { label: 'Area', value: areaFormatted, _first: true },\n { label: 'Perimeter', value: perimeterFormatted },\n ...this._collectIntersectionRows(parcelFeatures, zoneFeatures, otherByLayer),\n ];\n\n this._showAnalysisPopup('📐', 'Area Analysis', dataRows, coordinate, {\n kind: 'area',\n clipGeometry: polyGeom,\n parcelFeatures,\n zoneFeatures,\n otherByLayer,\n });\n }\n\n /**\n * Test whether two geometries truly intersect (beyond just extent overlap).\n * Works for Polygon/MultiPolygon against any geometry type.\n *\n * @param {Geometry} geomA - First geometry (usually the circle polygon)\n * @param {Geometry} geomB - Second geometry\n * @returns {boolean}\n * @private\n */\n _geometriesIntersect(geomA, geomB) {\n const typeB = geomB.getType();\n\n // For polygons / multi-polygons: check if any coordinate of B is inside A,\n // or if any coordinate of A is inside B (covers overlap & containment).\n if (typeB === 'Polygon' || typeB === 'MultiPolygon') {\n // Check if any vertex of B lies inside A (use flatCoordinates for efficiency)\n const flatB = geomB.getFlatCoordinates();\n const stride = geomB.getStride();\n for (let i = 0; i < flatB.length; i += stride) {\n if (geomA.intersectsCoordinate([flatB[i], flatB[i + 1]])) return true;\n }\n // Check if any vertex of A lies inside B\n const flatA = geomA.getFlatCoordinates();\n const strideA = geomA.getStride();\n for (let i = 0; i < flatA.length; i += strideA) {\n if (geomB.intersectsCoordinate([flatA[i], flatA[i + 1]])) return true;\n }\n return false;\n }\n\n if (typeB === 'Point') {\n return geomA.intersectsCoordinate(geomB.getCoordinates());\n }\n\n if (typeB === 'LineString' || typeB === 'MultiLineString') {\n const flatB = geomB.getFlatCoordinates();\n const stride = geomB.getStride();\n for (let i = 0; i < flatB.length; i += stride) {\n if (geomA.intersectsCoordinate([flatB[i], flatB[i + 1]])) return true;\n }\n return false;\n }\n\n // Fallback: extent overlap is good enough\n return true;\n }\n\n // ============================================================================\n // Parcel Edit Popup (single-click editable form)\n // ============================================================================\n\n /**\n * Create the parcel edit popup overlay with a dynamic form.\n */\n createParcelEditPopup() {\n this.parcelEditElement = document.createElement('div');\n this.parcelEditElement.className = 'map-parcel-edit-popup';\n this.parcelEditElement.style.cssText = `\n position: absolute;\n background: var(--card, #fff);\n color: var(--card-foreground, #1e1a4b);\n border-radius: 10px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.3);\n font-family: var(--font-body, 'Exo', sans-serif);\n font-size: 13px;\n min-width: 280px;\n max-width: 360px;\n max-height: 420px;\n z-index: 1002;\n border: 2px solid var(--primary, #005eb8);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n `;\n\n this.parcelEditPopup = new Overlay({\n element: this.parcelEditElement,\n positioning: 'bottom-center',\n offset: [0, -10],\n stopEvent: true,\n autoPan: true,\n autoPanAnimation: { duration: 250 },\n });\n\n this.map.addOverlay(this.parcelEditPopup);\n\n // Callbacks for save events\n this._parcelEditCallbacks = [];\n // Track the current feature being edited\n this._parcelEditFeature = null;\n }\n\n /**\n * Show the parcel edit popup with an editable form for all feature attributes.\n * Internal keys (_layerType, geometry) are excluded from the form.\n *\n * @param {Feature} feature - The OL feature to edit\n * @param {Array} coordinate - Map coordinate [x, y]\n */\n showParcelEditPopup(feature, coordinate) {\n this._parcelEditFeature = feature;\n const properties = feature.getProperties();\n\n // Keys to skip in the form\n const skipKeys = ['geometry', '_layerType'];\n\n // Build form fields from feature properties\n let fieldsHtml = '';\n for (const [key, value] of Object.entries(properties)) {\n if (skipKeys.includes(key)) continue;\n const displayVal = (value === null || value === undefined) ? '' : String(value);\n const escapedKey = this.escapeHtml(key);\n const escapedVal = this.escapeHtml(displayVal);\n fieldsHtml += `\n
    \n \n \n
    \n `;\n }\n\n const html = `\n
    \n ✏️ Edit Parcel\n \n
    \n
    \n ${fieldsHtml}\n
    \n \n \n
    \n
    \n `;\n\n this.parcelEditElement.innerHTML = html;\n this.parcelEditPopup.setPosition(coordinate);\n\n // Close / Cancel handlers\n this.parcelEditElement.querySelector('.parcel-edit-close').addEventListener('click', () => {\n this.hideParcelEditPopup();\n });\n this.parcelEditElement.querySelector('.parcel-edit-cancel').addEventListener('click', () => {\n this.hideParcelEditPopup();\n });\n\n // Form submit handler\n const form = this.parcelEditElement.querySelector('.parcel-edit-form');\n form.addEventListener('submit', (e) => {\n e.preventDefault();\n\n // Collect all edited values\n const formData = new FormData(form);\n const updatedProps = {};\n for (const [key, value] of formData.entries()) {\n updatedProps[key] = value;\n }\n\n // Restore internal properties that were excluded from the form\n updatedProps._layerType = 'parcel';\n\n // Update the feature's properties in-place\n for (const [key, value] of Object.entries(updatedProps)) {\n this._parcelEditFeature.set(key, value);\n }\n\n // Notify external listeners\n for (const cb of this._parcelEditCallbacks) {\n cb(this._parcelEditFeature, updatedProps);\n }\n\n this.hideParcelEditPopup();\n });\n }\n\n /**\n * Hide the parcel edit popup.\n */\n hideParcelEditPopup() {\n this.parcelEditPopup.setPosition(undefined);\n this._parcelEditFeature = null;\n }\n\n /**\n * Register a callback for when a parcel edit is saved.\n * Callback receives (feature, updatedProperties).\n *\n * @param {Function} callback\n */\n onParcelEdit(callback) {\n this._parcelEditCallbacks.push(callback);\n }\n\n // ============================================================================\n // Merge Identifier (UPN) Chooser Popup\n // ============================================================================\n\n /**\n * Create the merge identifier popup overlay.\n * Shown after two parcels are merged so the user can choose which UPN to keep.\n */\n createMergePopup() {\n this.mergePopupElement = document.createElement('div');\n this.mergePopupElement.className = 'map-merge-popup';\n this.mergePopupElement.style.cssText = `\n position: absolute;\n background: var(--card, #fff);\n color: var(--card-foreground, #1e1a4b);\n border-radius: 10px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.3);\n font-family: var(--font-body, 'Exo', sans-serif);\n font-size: 13px;\n min-width: 280px;\n max-width: 360px;\n z-index: 1002;\n border: 2px solid #10b981;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n `;\n\n this.mergePopup = new Overlay({\n element: this.mergePopupElement,\n positioning: 'bottom-center',\n offset: [0, -10],\n stopEvent: true,\n autoPan: true,\n autoPanAnimation: { duration: 250 },\n });\n\n this.map.addOverlay(this.mergePopup);\n }\n\n /**\n * Show the merge identifier popup so the user can pick which parcel's\n * attributes (including UPN) the merged polygon should inherit.\n *\n * @param {Feature} mergedFeature The newly created merged feature\n * @param {Object} propsA Properties from original parcel A\n * @param {Object} propsB Properties from original parcel B\n * @param {Array} coordinate Map coordinate [x, y] for popup placement\n */\n showMergeIdentifierPopup(mergedFeature, propsA, propsB, coordinate) {\n // Extract identifiers — try common parcel ID field names\n const idFields = ['UPN', 'upn', 'id', 'parcelid', 'parcel_id', 'PARCELID', 'PARCEL_ID', 'ID'];\n const getLabel = (props) => {\n for (const field of idFields) {\n if (props[field] !== undefined && props[field] !== null && String(props[field]).trim()) {\n return { field, value: String(props[field]) };\n }\n }\n return { field: 'id', value: 'Unknown' };\n };\n\n const labelA = getLabel(propsA);\n const labelB = getLabel(propsB);\n\n const html = `\n
    \n 🔗 Merged Parcel — Choose Identifier\n \n
    \n
    \n

    \n Select which parcel's attributes the merged polygon should keep:\n

    \n \n \n
    \n \n \n
    \n
    \n `;\n\n this.mergePopupElement.innerHTML = html;\n this.mergePopup.setPosition(coordinate);\n\n // Close / Cancel — keep parcel A properties (the default from clone)\n const close = () => {\n this.mergePopup.setPosition(undefined);\n };\n this.mergePopupElement.querySelector('.merge-popup-close').addEventListener('click', close);\n this.mergePopupElement.querySelector('.merge-popup-cancel').addEventListener('click', close);\n\n // Confirm — apply chosen parcel's properties\n this.mergePopupElement.querySelector('.merge-popup-confirm').addEventListener('click', () => {\n const choice = this.mergePopupElement.querySelector('input[name=\"merge-choice\"]:checked').value;\n const chosenProps = choice === 'A' ? propsA : propsB;\n\n // Copy all properties (except geometry) onto the merged feature\n const skipKeys = ['geometry'];\n for (const [key, value] of Object.entries(chosenProps)) {\n if (skipKeys.includes(key)) continue;\n mergedFeature.set(key, value);\n }\n // Ensure _layerType is preserved\n mergedFeature.set('_layerType', 'parcel');\n\n // Notify parcel edit callbacks\n for (const cb of this._parcelEditCallbacks) {\n cb(mergedFeature, chosenProps);\n }\n\n close();\n });\n\n // Highlight radio labels on selection\n const labels = this.mergePopupElement.querySelectorAll('label');\n const radios = this.mergePopupElement.querySelectorAll('input[name=\"merge-choice\"]');\n const updateHighlight = () => {\n labels.forEach((lbl) => {\n const radio = lbl.querySelector('input');\n lbl.style.borderColor = radio.checked ? (radio.value === 'A' ? '#0ea5e9' : '#f59e0b') : 'var(--border, #1e1a4b1f)';\n });\n };\n radios.forEach((r) => r.addEventListener('change', updateHighlight));\n updateHighlight();\n }\n\n // ============================================================================\n // Divide Polygon Popup (number input)\n // ============================================================================\n\n /**\n * Create the divide polygon popup overlay.\n * Shown after the user selects a polygon with the Divide tool, so they\n * can enter the number of equal pieces.\n */\n createDividePopup() {\n this.dividePopupElement = document.createElement('div');\n this.dividePopupElement.className = 'map-divide-popup';\n this.dividePopupElement.style.cssText = `\n position: absolute;\n background: var(--card, #fff);\n color: var(--card-foreground, #1e1a4b);\n border-radius: 10px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.3);\n font-family: var(--font-body, 'Exo', sans-serif);\n font-size: 13px;\n min-width: 260px;\n max-width: 320px;\n z-index: 1002;\n border: 2px solid #8b5cf6;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n `;\n\n this.dividePopup = new Overlay({\n element: this.dividePopupElement,\n positioning: 'bottom-center',\n offset: [0, -10],\n stopEvent: true,\n autoPan: true,\n autoPanAnimation: { duration: 250 },\n });\n\n this.map.addOverlay(this.dividePopup);\n }\n\n /**\n * Show the divide popup so the user can enter the number of divisions.\n *\n * @param {Feature} feature The selected polygon feature\n * @param {VectorSource} source The source containing the feature\n * @param {Array} coordinate Map coordinate [x, y] for popup placement\n */\n showDividePopup(feature, source, coordinate) {\n const html = `\n
    \n Divide Polygon\n \n
    \n
    \n

    \n Enter the number of equal pieces:\n

    \n \n
    \n \n \n
    \n
    \n `;\n\n this.dividePopupElement.innerHTML = html;\n this.dividePopup.setPosition(coordinate);\n\n const input = this.dividePopupElement.querySelector('.divide-input');\n input.focus();\n input.select();\n\n // Close / Cancel\n const cancel = () => {\n this.hideDividePopup();\n this._polygonDivideInteraction.cancelDivide();\n };\n this.dividePopupElement.querySelector('.divide-popup-close').addEventListener('click', cancel);\n this.dividePopupElement.querySelector('.divide-popup-cancel').addEventListener('click', cancel);\n\n // Confirm\n this.dividePopupElement.querySelector('.divide-popup-confirm').addEventListener('click', () => {\n const n = parseInt(input.value, 10);\n if (!n || n < 2) {\n input.style.borderColor = '#ef4444';\n return;\n }\n this.hideDividePopup();\n this._polygonDivideInteraction.performDivide(n);\n });\n\n // Allow Enter key to confirm\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n this.dividePopupElement.querySelector('.divide-popup-confirm').click();\n }\n });\n }\n\n /**\n * Hide the divide popup.\n */\n hideDividePopup() {\n this.dividePopup.setPosition(undefined);\n }\n\n // ============================================================================\n // Drawn Polygon Attribute Popup\n // ============================================================================\n\n /**\n * Create the drawn polygon attribute popup overlay.\n * Shown after the area measurement polygon is completed so the user can\n * attach parcel-like attributes to the drawn polygon.\n */\n createDrawnPolygonPopup() {\n this.drawnPolygonElement = document.createElement('div');\n this.drawnPolygonElement.className = 'map-drawn-polygon-popup';\n this.drawnPolygonElement.style.cssText = `\n position: absolute;\n background: var(--card, #fff);\n border-radius: var(--radius-xl, 0.75rem);\n box-shadow: 0 4px 20px rgba(0,0,0,0.2);\n font-family: var(--font-body, 'Exo', sans-serif);\n font-size: 13px;\n min-width: 280px;\n max-width: 360px;\n max-height: 420px;\n z-index: 1002;\n border: 2px solid var(--success, #006b3f);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n `;\n\n this.drawnPolygonPopup = new Overlay({\n element: this.drawnPolygonElement,\n positioning: 'bottom-center',\n offset: [0, -10],\n stopEvent: true,\n autoPan: true,\n autoPanAnimation: { duration: 250 },\n });\n\n this.map.addOverlay(this.drawnPolygonPopup);\n this._drawnPolygonCallbacks = [];\n this._drawnPolygonFeature = null;\n }\n\n /**\n * Get attribute keys from existing parcel features on the map.\n * Scans the overlay group for the first feature with _layerType='parcel'\n * and returns its property key names (excluding internal keys).\n *\n * @returns {string[]} Array of attribute key names\n */\n getParcelAttributeKeys() {\n const skipKeys = ['geometry', '_layerType'];\n const keys = [];\n\n const scanGroup = (group) => {\n if (keys.length > 0) return;\n group.getLayers().forEach((layer) => {\n if (keys.length > 0) return;\n if (layer instanceof LayerGroup) {\n scanGroup(layer);\n } else if (layer instanceof VectorLayer) {\n const source = layer.getSource();\n if (!source) return;\n for (const f of source.getFeatures()) {\n if (f.get('_layerType') !== 'parcel') continue;\n const props = f.getProperties();\n for (const key of Object.keys(props)) {\n if (!skipKeys.includes(key)) keys.push(key);\n }\n return; // one parcel is enough for the schema\n }\n }\n });\n };\n\n scanGroup(this.overlayGroup);\n return keys;\n }\n\n /**\n * Show the drawn polygon attribute popup.\n * Discovers attribute keys from existing parcel features and creates\n * a blank form with those fields.\n *\n * @param {Feature} feature - The drawn polygon feature\n * @param {Array} coordinate - Map coordinate [x, y] for popup placement\n */\n showDrawnPolygonPopup(feature, coordinate) {\n this._drawnPolygonFeature = feature;\n\n // Discover attribute keys from existing parcels\n const attributeKeys = this.getParcelAttributeKeys();\n\n if (attributeKeys.length === 0) {\n console.warn('[MapView] No parcel attributes found — cannot build form');\n return;\n }\n\n // Build form fields (all blank)\n let fieldsHtml = '';\n for (const key of attributeKeys) {\n const escapedKey = this.escapeHtml(key);\n fieldsHtml += `\n
    \n \n \n
    \n `;\n }\n\n // Area display\n const geom = feature.getGeometry();\n const areaSqm = getArea(geom, { projection: 'EPSG:3857' });\n const areaFormatted = formatArea(areaSqm);\n\n const html = `\n
    \n 📐 Polygon Attributes\n \n
    \n
    \n Area: ${areaFormatted}\n
    \n
    \n ${fieldsHtml}\n
    \n \n \n
    \n
    \n `;\n\n this.drawnPolygonElement.innerHTML = html;\n this.drawnPolygonPopup.setPosition(coordinate);\n\n // Close / Cancel handlers\n this.drawnPolygonElement.querySelector('.drawn-polygon-close').addEventListener('click', () => {\n this.hideDrawnPolygonPopup();\n });\n this.drawnPolygonElement.querySelector('.drawn-polygon-cancel').addEventListener('click', () => {\n this.hideDrawnPolygonPopup();\n });\n\n // Form submit handler\n const form = this.drawnPolygonElement.querySelector('.drawn-polygon-form');\n form.addEventListener('submit', (e) => {\n e.preventDefault();\n\n const formData = new FormData(form);\n const props = {};\n for (const [key, value] of formData.entries()) {\n props[key] = value;\n }\n\n // Set properties on the feature\n for (const [key, value] of Object.entries(props)) {\n this._drawnPolygonFeature.set(key, value);\n }\n\n // Tag as parcel so it integrates with existing parcel tools\n this._drawnPolygonFeature.set('_layerType', 'parcel');\n\n // Notify listeners\n for (const cb of this._drawnPolygonCallbacks) {\n cb(this._drawnPolygonFeature, props);\n }\n\n this.hideDrawnPolygonPopup();\n });\n }\n\n /**\n * Hide the drawn polygon attribute popup.\n */\n hideDrawnPolygonPopup() {\n this.drawnPolygonPopup.setPosition(undefined);\n this._drawnPolygonFeature = null;\n }\n\n /**\n * Register a callback for when drawn polygon attributes are saved.\n * Callback receives (feature, properties).\n *\n * @param {Function} callback\n */\n onDrawnPolygonSave(callback) {\n this._drawnPolygonCallbacks.push(callback);\n }\n\n /**\n * Register a callback fired after the user finishes modifying a feature\n * with the EditBar Modify interaction. Callback receives the OL feature\n * whose geometry just changed. Consumers (e.g. the import-staging code in\n * main.js) inspect the feature's tags (_externalImportId / _clientUuid)\n * to decide whether to react. Multiple callbacks are supported.\n *\n * @param {Function} callback - (feature) => void | Promise\n */\n onFeatureModified(callback) {\n if (!this._featureModifiedCallbacks) this._featureModifiedCallbacks = [];\n this._featureModifiedCallbacks.push(callback);\n }\n\n /**\n * Register a callback fired when the officer places a point with the\n * Draw ▸ Point tool. Callback receives the map coordinate; the sketch itself\n * has already been discarded, so the callback owns what happens next — in\n * this application, opening the Add Location form.\n *\n * @param {Function} callback - (coordinate) => void\n */\n onDrawnPoint(callback) {\n if (!this._drawnPointCallbacks) this._drawnPointCallbacks = [];\n this._drawnPointCallbacks.push(callback);\n }\n\n /**\n * Register a double-click callback.\n * Callback receives (lon, lat, feature, event).\n * Feature is the first feature found at the click pixel across all overlay layers,\n * or null if no feature was hit.\n * When a feature is hit, the default double-click-zoom is suppressed.\n */\n onDblClick(callback) {\n this.dblClickCallbacks.push(callback);\n\n // Set up the listener once\n if (this.dblClickCallbacks.length === 1) {\n this.map.on('dblclick', (evt) => {\n const [lon, lat] = toLonLat(evt.coordinate);\n\n // Find any feature at the clicked pixel (overlay layers, not just markers)\n let clickedFeature = null;\n this.map.forEachFeatureAtPixel(evt.pixel, (feature) => {\n clickedFeature = feature;\n return true; // stop at first hit\n });\n\n // If a feature was hit, prevent the default double-click zoom\n if (clickedFeature) {\n evt.preventDefault();\n evt.stopPropagation();\n }\n\n // Call all registered callbacks\n for (const cb of this.dblClickCallbacks) {\n cb(lon, lat, clickedFeature, evt);\n }\n\n // Return false to suppress DoubleClickZoom interaction when on a feature\n if (clickedFeature) return false;\n });\n }\n\n return () => {\n const idx = this.dblClickCallbacks.indexOf(callback);\n if (idx > -1) this.dblClickCallbacks.splice(idx, 1);\n };\n }\n\n /**\n * Escape HTML to prevent XSS\n */\n escapeHtml(text) {\n if (!text) return '';\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /**\n * Create the Add Location popup form overlay\n */\n createAddLocationPopup() {\n // Create popup container element\n this.addLocationPopupElement = document.createElement('div');\n this.addLocationPopupElement.className = 'map-add-location-popup';\n this.addLocationPopupElement.innerHTML = `\n
    \n ➕ Add Location\n \n
    \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n
    \n 📍 \n
    \n \n
    \n `;\n\n // Create the overlay\n this.addLocationPopup = new Overlay({\n element: this.addLocationPopupElement,\n positioning: 'bottom-center',\n offset: [0, -10],\n stopEvent: true, // Prevent click from propagating\n autoPan: true,\n autoPanAnimation: {\n duration: 250,\n },\n });\n\n this.map.addOverlay(this.addLocationPopup);\n\n // Store clicked coordinates\n this.addLocationCoords = null;\n\n // Set up close button handler\n const closeBtn = this.addLocationPopupElement.querySelector('.add-location-popup-close');\n closeBtn.addEventListener('click', () => {\n this.hideAddLocationPopup();\n });\n\n // Store form submit callbacks\n this.addLocationCallbacks = [];\n }\n\n /**\n * Show the Add Location popup at the specified coordinate\n */\n showAddLocationPopup(coordinate) {\n const [lon, lat] = toLonLat(coordinate);\n this.addLocationCoords = { lon, lat };\n\n // Update coordinates display\n const coordsEl = this.addLocationPopupElement.querySelector('#map-location-coords');\n coordsEl.textContent = `${lon.toFixed(6)}, ${lat.toFixed(6)}`;\n\n // Reset form\n const form = this.addLocationPopupElement.querySelector('#map-add-location-form');\n form.reset();\n\n // Position and show popup\n this.addLocationPopup.setPosition(coordinate);\n }\n\n /**\n * Hide the Add Location popup\n */\n hideAddLocationPopup() {\n this.addLocationPopup.setPosition(undefined);\n this.addLocationCoords = null;\n }\n\n /**\n * Register a callback for when a location is submitted via the map popup\n * Callback receives: { name, category, description, lon, lat }\n */\n onAddLocation(callback) {\n this.addLocationCallbacks.push(callback);\n\n // Set up form submit handler (only once)\n if (this.addLocationCallbacks.length === 1) {\n const form = this.addLocationPopupElement.querySelector('#map-add-location-form');\n form.addEventListener('submit', (e) => {\n e.preventDefault();\n\n if (!this.addLocationCoords) return;\n\n const formData = new FormData(form);\n const data = {\n name: formData.get('name'),\n category: formData.get('category'),\n description: formData.get('description'),\n lon: this.addLocationCoords.lon,\n lat: this.addLocationCoords.lat,\n };\n\n // Call all registered callbacks\n this.addLocationCallbacks.forEach(cb => cb(data));\n\n // Hide popup after submission\n this.hideAddLocationPopup();\n });\n }\n }\n\n /**\n * Create base layers group for LayerSwitcher\n */\n createBaseLayers(defaultBasemap) {\n\n\n const topoLayer = new TileLayer({\n title: 'Topographic',\n type: 'base',\n zIndex: -100,\n visible: defaultBasemap === 'topo',\n source: new XYZ({\n url: 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png',\n attributions: 'Map data: © OpenTopoMap',\n maxZoom: 17,\n crossOrigin: 'anonymous',\n }),\n });\n topoLayer.set('basemapKey', 'topo');\n\n const cartoLightLayer = new TileLayer({\n title: 'Carto Light',\n type: 'base',\n zIndex: -100,\n visible: defaultBasemap === 'carto-light',\n source: new XYZ({\n url: 'https://{a-c}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',\n attributions: '© CARTO',\n maxZoom: 19,\n crossOrigin: 'anonymous',\n }),\n });\n cartoLightLayer.set('basemapKey', 'carto-light');\n\n const cartoDarkLayer = new TileLayer({\n title: 'Carto Dark',\n type: 'base',\n zIndex: -100,\n visible: defaultBasemap === 'carto-dark',\n source: new XYZ({\n url: 'https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',\n attributions: '© CARTO',\n maxZoom: 19,\n crossOrigin: 'anonymous',\n }),\n });\n cartoDarkLayer.set('basemapKey', 'carto-dark');\n\n const osmCycleLayer = new TileLayer({\n title: 'OSM Cycle map',\n type: 'base',\n zIndex: -100,\n visible: false, //defaultBasemap === 'osm',\n source: new OSM({\n \t\t\t\t\t\"url\" : \"https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=ae1339c46dd3446b9c491e7336d38760\"\n\t\t\t\t\t\t}),\n });\n\n osmCycleLayer.set('basemapKey', 'cycle');\n\n const satelliteLayer = new TileLayer({\n title: 'Satellite',\n type: 'base',\n zIndex: -100,\n visible: defaultBasemap === 'satellite',\n source: new XYZ({\n url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',\n attributions: 'Tiles © Esri',\n maxZoom: 19,\n crossOrigin: 'anonymous',\n }),\n });\n satelliteLayer.set('basemapKey', 'satellite');\n const googleLayer = new TileLayer({\n title: 'Google Sat',\n type: 'base',\n zIndex: -100,\n visible: defaultBasemap === 'googlesat',\n source: new XYZ({\n// url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',\n url: 'http://mt0.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}&s=Ga',\n attributions: 'Tiles © Google',\n maxZoom: 19,\n crossOrigin: 'anonymous',\n }),\n });\n googleLayer.set('basemapKey', 'googlesat');\n\n const osmLayer = new TileLayer({\n title: 'OpenStreetMap',\n type: 'base',\n zIndex: -100,\n visible: defaultBasemap === 'osm',\n source: new OSM(),\n });\n osmLayer.set('basemapKey', 'osm');\n\n // Remember the base-map layers so setBaseMap() can toggle visibility later\n this._baseMapLayers = [\n cartoLightLayer, cartoDarkLayer, osmCycleLayer,\n satelliteLayer, googleLayer, osmLayer, topoLayer,\n ];\n\n\n // Return LayerGroup. Hidden from the main LayerSwitcher — base maps are\n // managed by the dedicated base-map picker (see _createBaseMapPicker)\n // accessed via the layers-stack icon above the My Location button.\n const baseGroup = new LayerGroup({\n title: 'Base Maps',\n layers: [\n cartoLightLayer,\n cartoDarkLayer,\n satelliteLayer,\n osmCycleLayer,\n googleLayer,\n osmLayer,\n topoLayer,\n ],\n });\n baseGroup.set('displayInLayerSwitcher', false);\n return baseGroup;\n }\n\n /**\n * Switch the active base map by key.\n * Sets exactly one base layer visible; hides all others.\n *\n * @param {string} key Basemap key: 'none' | 'topo' | 'osm' | 'satellite' | 'googlesat' | 'carto-light' | 'carto-dark' | 'cycle'\n * @returns {boolean} true if the key matched a known base layer (or 'none')\n */\n setBaseMap(key) {\n if (!this._baseMapLayers) return false;\n // 'none' switches the base map off entirely — hide every base layer so the\n // map renders on a blank background (useful over imagery overlays / when a\n // full-coverage overlay should stand alone).\n if (key === 'none') {\n for (const layer of this._baseMapLayers) layer.setVisible(false);\n console.log('[MapView] Base map switched off (none)');\n this.map.dispatchEvent({ type: 'basemapchange', key: 'none' });\n return true;\n }\n let matched = false;\n for (const layer of this._baseMapLayers) {\n const on = layer.get('basemapKey') === key;\n layer.setVisible(on);\n if (on) matched = true;\n }\n if (matched) {\n console.log('[MapView] Base map switched to:', key);\n // Notify external UIs (Settings dropdown, base-map picker, …) so they\n // can keep their visible state in sync.\n this.map.dispatchEvent({ type: 'basemapchange', key });\n }\n return matched;\n }\n\n /**\n * Build the floating \"Base Map\" picker — a small icon button stacked\n * directly above the My Location control, plus a slide-out card with\n * thumbnail chips for every selectable base map.\n *\n * Hidden in tandem with the main LayerSwitcher: clicking outside the\n * picker (or making a selection) closes it.\n *\n * Two-way sync with the existing Settings dropdown is via the\n * `basemapchange` event fired from setBaseMap().\n */\n _createBaseMapPicker() {\n // Configuration — must match the basemapKey set in createBaseLayers.\n // The colour gradients hint at each base map's character so the chip is\n // recognisable without rendering an actual tile preview.\n const OPTIONS = [\n { key: 'topo', label: 'Topographic', grad: 'linear-gradient(135deg,#e8d5b7,#a67c52)' },\n { key: 'osm', label: 'OpenStreetMap',grad: 'linear-gradient(135deg,#d4e6f1,#85c1e9)' },\n { key: 'satellite', label: 'Satellite', grad: 'linear-gradient(135deg,#1b4332,#40916c)' },\n { key: 'googlesat', label: 'Google Sat', grad: 'linear-gradient(135deg,#2a5d3d,#4a8c5a)' },\n { key: 'carto-light', label: 'Carto Light', grad: 'linear-gradient(135deg,#f5f5f5,#d4d4d4)' },\n { key: 'carto-dark', label: 'Carto Dark', grad: 'linear-gradient(135deg,#1a1a2e,#0f3460)' },\n // \"None\" turns the base map off — checkerboard hints at a blank/transparent background.\n { key: 'none', label: 'None', grad: 'repeating-conic-gradient(#e5e7eb 0 25%, #fff 0 50%) 50% / 12px 12px' },\n ];\n\n const target = this.map.getTargetElement();\n if (!target) return;\n\n // ---------- Toggle button ----------\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'ls-basemap-toggle';\n btn.title = 'Switch base map';\n btn.setAttribute('aria-label', 'Switch base map');\n btn.innerHTML =\n '' +\n '' +\n '' +\n '' +\n '';\n target.appendChild(btn);\n\n // ---------- Picker panel ----------\n const panel = document.createElement('div');\n panel.className = 'ls-basemap-panel';\n panel.innerHTML =\n '
    Base Map
    ' +\n '
    ' +\n OPTIONS.map((opt) => `\n \n `).join('') +\n '
    ';\n target.appendChild(panel);\n\n this._basemapPanel = panel;\n this._basemapToggle = btn;\n\n /** Mark the radio matching the currently-visible base layer. */\n const syncSelection = (key) => {\n const k = key || this._baseMapLayers?.find((l) => l.getVisible())?.get('basemapKey');\n panel.querySelectorAll('input[name=\"lupmis-basemap\"]').forEach((r) => {\n r.checked = (r.value === k);\n });\n };\n syncSelection();\n\n // ---------- Events ----------\n\n // Toggle button → open / close the panel\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n const open = !panel.classList.contains('open');\n panel.classList.toggle('open', open);\n btn.classList.toggle('active', open);\n if (open) syncSelection();\n });\n\n // Click outside → close\n document.addEventListener('click', (e) => {\n if (!panel.classList.contains('open')) return;\n if (panel.contains(e.target) || btn.contains(e.target)) return;\n panel.classList.remove('open');\n btn.classList.remove('active');\n });\n\n // Selection → apply, persist, close\n panel.addEventListener('change', (e) => {\n const radio = e.target.closest('input[type=radio][name=\"lupmis-basemap\"]');\n if (!radio) return;\n const key = radio.value;\n this.setBaseMap(key);\n try { localStorage.setItem('default-basemap', key); } catch {}\n panel.classList.remove('open');\n btn.classList.remove('active');\n });\n\n // Keep the radio state synced when other UIs (Settings dropdown) change it\n this.map.on('basemapchange', (evt) => syncSelection(evt.key));\n }\n\n // ============================================================================\n // GPS: current-position + trail rendering, and the expandable Location control\n //\n // NOTE: MapView deliberately knows nothing about the GeoTracker engine,\n // SQLocal, or sync. It only (a) renders what it's told and (b) emits UI\n // intents via callbacks. main.js wires those intents to the GeoTracker so the\n // map stays reusable/decoupled.\n // ============================================================================\n\n /** Create the vector layers used to draw the live position and the trail. */\n _initGpsRendering() {\n this._gpsPositionSource = new VectorSource();\n this._gpsTrailSource = new VectorSource();\n this._gpsTrailCoords = []; // [ [x,y], ... ] in map projection\n\n // Trail line (drawn under the position marker)\n this._gpsTrailLayer = new VectorLayer({\n source: this._gpsTrailSource,\n zIndex: 940,\n style: new Style({\n stroke: new Stroke({ color: '#ff6d00', width: 4, lineCap: 'round', lineJoin: 'round' }),\n }),\n properties: { title: 'GPS Trail', displayInLayerSwitcher: false },\n });\n\n // Current position: accuracy halo + solid dot\n this._gpsPositionLayer = new VectorLayer({\n source: this._gpsPositionSource,\n zIndex: 950,\n style: (feature) => {\n if (feature.get('_kind') === 'accuracy') {\n return new Style({\n fill: new Fill({ color: 'rgba(0,94,184,0.12)' }),\n stroke: new Stroke({ color: 'rgba(0,94,184,0.35)', width: 1 }),\n });\n }\n return new Style({\n image: new Circle({\n radius: 7,\n fill: new Fill({ color: '#005eb8' }),\n stroke: new Stroke({ color: '#ffffff', width: 2.5 }),\n }),\n });\n },\n properties: { title: 'GPS Position', displayInLayerSwitcher: false },\n });\n\n this.map.addLayer(this._gpsTrailLayer);\n this.map.addLayer(this._gpsPositionLayer);\n\n this._gpsCallbacks = { locate: [], record: [] };\n this._gpsRecording = false;\n }\n\n /** Register a callback fired when the user taps \"Locate Me\". */\n onLocateMe(cb) { this._gpsCallbacks.locate.push(cb); }\n /** Register a callback fired when the user toggles trail recording. Receives the desired state (true=start). */\n onToggleRecording(cb) { this._gpsCallbacks.record.push(cb); }\n\n /**\n * Draw / move the current-position marker and accuracy halo.\n * @param {number} lon\n * @param {number} lat\n * @param {number|null} [accuracy] horizontal accuracy in metres\n */\n showCurrentPosition(lon, lat, accuracy = null) {\n if (lon == null || lat == null) return;\n const center = fromLonLat([lon, lat]);\n this._gpsPositionSource.clear();\n\n if (accuracy && accuracy > 0) {\n // Approximate the accuracy circle in projected units. Good enough for a\n // visual halo at typical zoom levels.\n const resAtLat = accuracy / Math.cos((lat * Math.PI) / 180);\n const halo = new Feature({ geometry: new PolygonGeom([this._circleRing(center, resAtLat)]) });\n halo.set('_kind', 'accuracy');\n this._gpsPositionSource.addFeature(halo);\n }\n const dot = new Feature({ geometry: new Point(center) });\n dot.set('_kind', 'dot');\n this._gpsPositionSource.addFeature(dot);\n }\n\n /** @private build a ring of coordinates approximating a circle (metres → projected). */\n _circleRing(center, radiusMeters, segments = 48) {\n // Convert metres to projected units (Web Mercator) roughly via the\n // resolution at the centre latitude.\n const ring = [];\n const metersPerUnit = 1; // EPSG:3857 units are metres (approx near equator/locally)\n const r = radiusMeters / metersPerUnit;\n for (let i = 0; i <= segments; i++) {\n const a = (i / segments) * 2 * Math.PI;\n ring.push([center[0] + r * Math.cos(a), center[1] + r * Math.sin(a)]);\n }\n return ring;\n }\n\n /** Smoothly center the view on a coordinate. */\n centerOn(lon, lat, zoom = 16) {\n const view = this.map.getView();\n view.animate({ center: fromLonLat([lon, lat]), zoom, duration: 500 });\n }\n\n /** Reset the trail line (call when a new recording starts). */\n startTrailRender() {\n this._gpsTrailCoords = [];\n this._gpsTrailSource.clear();\n }\n\n /** Append a coordinate to the growing trail line. */\n appendTrailPoint(lon, lat) {\n if (lon == null || lat == null) return;\n this._gpsTrailCoords.push(fromLonLat([lon, lat]));\n this._gpsTrailSource.clear();\n if (this._gpsTrailCoords.length >= 2) {\n this._gpsTrailSource.addFeature(new Feature({ geometry: new LineString(this._gpsTrailCoords) }));\n }\n }\n\n /** Remove the rendered trail (does not affect stored data). */\n clearTrailRender() {\n this._gpsTrailCoords = [];\n this._gpsTrailSource.clear();\n }\n\n /** Reflect recording state on the control button. */\n setRecordingState(active) {\n this._gpsRecording = !!active;\n if (this._recordBtn) {\n this._recordBtn.classList.toggle('recording', this._gpsRecording);\n this._recordBtn.title = this._gpsRecording ? 'Stop trail recording' : 'Record GPS trail';\n this._recordBtn.innerHTML = this._gpsRecording\n ? ''\n : '';\n }\n if (this._locateToggle) this._locateToggle.classList.toggle('recording', this._gpsRecording);\n }\n\n /**\n * Build the expandable \"My Location\" control: a main button that reveals two\n * sub-buttons (Locate Me, Record Trail). Anchored at the same spot the old\n * ol-ext GeolocationButton occupied (bottom-right), so the base-map picker\n * still lines up above it.\n */\n _createLocationControl() {\n const target = this.map.getTargetElement();\n if (!target) return;\n\n // Main toggle\n const toggle = document.createElement('button');\n toggle.type = 'button';\n toggle.className = 'ls-locate-toggle';\n toggle.title = 'My Location';\n toggle.setAttribute('aria-label', 'My Location');\n toggle.innerHTML = '';\n target.appendChild(toggle);\n\n // Sub-button cluster (hidden until the main button is tapped)\n const actions = document.createElement('div');\n actions.className = 'ls-locate-actions';\n actions.innerHTML =\n '' +\n '';\n target.appendChild(actions);\n\n this._locateToggle = toggle;\n this._locateActions = actions;\n this._locateMeBtn = actions.querySelector('.ls-locate-me');\n this._recordBtn = actions.querySelector('.ls-locate-record');\n\n const close = () => { actions.classList.remove('open'); toggle.classList.remove('active'); };\n const open = () => { actions.classList.add('open'); toggle.classList.add('active'); };\n\n toggle.addEventListener('click', (e) => {\n e.stopPropagation();\n actions.classList.contains('open') ? close() : open();\n });\n\n // Tap outside closes the cluster (but never while recording, so the stop\n // button stays reachable).\n document.addEventListener('click', (e) => {\n if (!actions.classList.contains('open')) return;\n if (actions.contains(e.target) || toggle.contains(e.target)) return;\n if (this._gpsRecording) return;\n close();\n });\n\n this._locateMeBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n for (const cb of this._gpsCallbacks.locate) { try { cb(); } catch (err) { console.error(err); } }\n if (!this._gpsRecording) close();\n });\n\n this._recordBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n const next = !this._gpsRecording;\n for (const cb of this._gpsCallbacks.record) { try { cb(next); } catch (err) { console.error(err); } }\n });\n }\n\n /**\n * Get style for a feature (handles selection state)\n */\n getFeatureStyle(feature) {\n const category = feature.get('category') || 'default';\n const emoji = this.getEmoji(category);\n\n if (feature === this.selectedFeature) {\n // Return selected style with the correct emoji and highlight\n return [\n // Background highlight circle\n new Style({\n image: new Circle({\n radius: 22,\n fill: new Fill({ color: 'rgba(220, 38, 38, 0.25)' }),\n stroke: new Stroke({ color: '#dc2626', width: 3 }),\n }),\n }),\n // Emoji on top, larger\n new Style({\n text: new Text({\n text: emoji,\n font: '40px sans-serif',\n textBaseline: 'bottom',\n textAlign: 'center',\n offsetY: -5,\n }),\n }),\n ];\n }\n\n // Check for custom style\n const customStyle = feature.get('style');\n if (customStyle) {\n return customStyle;\n }\n\n // Return category-based emoji style\n if (this.categoryStyles[category]) {\n return this.categoryStyles[category];\n }\n\n return this.defaultStyle;\n }\n\n /**\n * Set category-based styles with emojis\n * @param {Object} styles - Map of category to config { emoji, label, fontSize }\n */\n setCategoryStyles(styles) {\n for (const [category, config] of Object.entries(styles)) {\n // Update category mapping if provided\n if (config.emoji) {\n if (!this.categoryEmojis[category]) {\n this.categoryEmojis[category] = { emoji: config.emoji, label: config.label || category };\n } else {\n this.categoryEmojis[category].emoji = config.emoji;\n if (config.label) {\n this.categoryEmojis[category].label = config.label;\n }\n }\n }\n\n // Create/update style\n const emoji = this.getEmoji(category);\n const fontSize = config.fontSize || 28;\n\n this.categoryStyles[category] = this.createEmojiStyle(emoji, fontSize);\n }\n\n // Refresh markers\n this.markerSource.changed();\n }\n\n /**\n * Add a single marker\n */\n addMarker(lon, lat, properties = {}) {\n console.log('[MapView] Adding marker at', lon, lat, 'with properties:', properties);\n\n const feature = new Feature({\n geometry: new Point(fromLonLat([lon, lat])),\n ...properties,\n });\n\n // Store original coordinates for easy access\n feature.set('lon', lon);\n feature.set('lat', lat);\n\n this.markerSource.addFeature(feature);\n console.log('[MapView] Marker added, total features:', this.markerSource.getFeatures().length);\n return feature;\n }\n\n /**\n * Add multiple markers from an array of location objects\n */\n addMarkers(locations) {\n console.log('[MapView] Adding', locations.length, 'markers');\n\n const features = locations.map((loc) => {\n const feature = new Feature({\n geometry: new Point(fromLonLat([loc.longitude, loc.latitude])),\n id: loc.id,\n name: loc.name,\n description: loc.description,\n category: loc.category,\n lon: loc.longitude,\n lat: loc.latitude,\n });\n return feature;\n });\n\n this.markerSource.addFeatures(features);\n console.log('[MapView] Markers added, total features:', this.markerSource.getFeatures().length);\n return features;\n }\n\n /**\n * Clear all markers\n */\n clearMarkers() {\n this.markerSource.clear();\n this.selectedFeature = null;\n }\n\n /**\n * Remove a specific marker by feature or ID\n */\n removeMarker(featureOrId) {\n if (typeof featureOrId === 'object') {\n this.markerSource.removeFeature(featureOrId);\n } else {\n const feature = this.markerSource.getFeatures().find(\n f => f.get('id') === featureOrId\n );\n if (feature) {\n this.markerSource.removeFeature(feature);\n }\n }\n }\n\n /**\n * Get all markers\n */\n getMarkers() {\n return this.markerSource.getFeatures();\n }\n\n /**\n * Find marker by ID\n */\n findMarker(id) {\n return this.markerSource.getFeatures().find(f => f.get('id') === id);\n }\n\n /**\n * Select a marker (highlights it)\n */\n /**\n * Make the Markers layer visible.\n *\n * The layer starts hidden so a fresh map is uncluttered, and the LayerSwitcher\n * is the only thing that ever turned it on. That is fine until the officer\n * creates or focuses a location, at which point an invisible layer means the\n * point they just placed is simply not there — the record is written, the map\n * pans to it, and nothing is drawn. Callers that act on a specific location\n * call this so the result of the action can actually be seen.\n */\n showMarkers() {\n if (this.markersLayer && !this.markersLayer.getVisible()) {\n this.markersLayer.setVisible(true);\n console.log('[MapView] Markers layer switched on to show a location');\n }\n }\n\n selectMarker(featureOrId) {\n if (typeof featureOrId === 'object') {\n this.selectedFeature = featureOrId;\n } else {\n this.selectedFeature = this.findMarker(featureOrId);\n }\n this.markerSource.changed();\n return this.selectedFeature;\n }\n\n /**\n * Clear selection\n */\n clearSelection() {\n this.selectedFeature = null;\n this.markerSource.changed();\n }\n\n /**\n * Zoom to a specific location\n */\n zoomTo(lon, lat, zoom = 15) {\n this.map.getView().animate({\n center: fromLonLat([lon, lat]),\n zoom: zoom,\n duration: 500,\n });\n }\n\n /**\n * Fit view to show all markers\n */\n fitToMarkers(padding = 50) {\n const extent = this.markerSource.getExtent();\n if (extent && extent[0] !== Infinity) {\n this.map.getView().fit(extent, {\n padding: [padding, padding, padding, padding],\n duration: 500,\n maxZoom: 16,\n });\n }\n }\n\n /**\n * Get current map center in lon/lat\n */\n getCenter() {\n const center = this.map.getView().getCenter();\n return toLonLat(center);\n }\n\n /**\n * Get current zoom level\n */\n getZoom() {\n return this.map.getView().getZoom();\n }\n\n /**\n * Set map center\n */\n setCenter(lon, lat) {\n this.map.getView().setCenter(fromLonLat([lon, lat]));\n }\n\n /**\n * Set zoom level\n */\n setZoom(zoom) {\n this.map.getView().setZoom(zoom);\n }\n\n /**\n * Register click callback\n * Callback receives (lon, lat, feature, event)\n *\n * Single-click is delayed by 300 ms so that a double-click can cancel it.\n * If the click lands on an overlay feature (e.g. district boundary) the\n * single-click is suppressed entirely — only double-click will fire.\n */\n onClick(callback) {\n this.clickCallbacks.push(callback);\n\n // Set up click handler if this is the first callback\n if (this.clickCallbacks.length === 1) {\n this._clickTimer = null;\n\n // Double-click cancels any pending single-click\n this.map.on('dblclick', () => {\n if (this._clickTimer) {\n clearTimeout(this._clickTimer);\n this._clickTimer = null;\n }\n });\n\n this.map.on('click', (evt) => {\n // Cancel any previous pending click\n if (this._clickTimer) {\n clearTimeout(this._clickTimer);\n this._clickTimer = null;\n }\n\n // When NOT in edit / draw mode, immediately clear any feature\n // the Select interaction may have grabbed on this click so the\n // user never sees a selection flash.\n if (!this._editBarActive && this._selectInteraction) {\n this._selectInteraction.getFeatures().clear();\n }\n\n // Check what features sit under the click pixel\n let hasOverlayFeature = false;\n let hasParcelFeature = false;\n let markerFeature = null;\n this.map.forEachFeatureAtPixel(evt.pixel, (feature) => {\n if (feature.get('_layerType') === 'parcel') {\n hasParcelFeature = true;\n }\n if (feature.get('name')) {\n markerFeature = feature;\n }\n hasOverlayFeature = true;\n });\n\n // If an overlay feature was hit, suppress single-click\n // UNLESS it's a parcel or a location marker\n if (hasOverlayFeature && !hasParcelFeature && !markerFeature) {\n return;\n }\n\n // Delay the single-click to allow double-click to cancel it\n const [lon, lat] = toLonLat(evt.coordinate);\n this._clickTimer = setTimeout(() => {\n this._clickTimer = null;\n\n // Find location marker at pixel\n let clickedFeature = null;\n this.map.forEachFeatureAtPixel(evt.pixel, (feature) => {\n if (feature.get('name')) {\n clickedFeature = feature;\n return true;\n }\n });\n\n for (const cb of this.clickCallbacks) {\n cb(lon, lat, clickedFeature, evt);\n }\n }, 300);\n });\n }\n\n // Return unsubscribe function\n return () => {\n const index = this.clickCallbacks.indexOf(callback);\n if (index > -1) {\n this.clickCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Register pointer move callback (for hover effects)\n */\n onPointerMove(callback) {\n this.map.on('pointermove', (evt) => {\n if (evt.dragging) return;\n\n const [lon, lat] = toLonLat(evt.coordinate);\n\n // Only find location markers (features with 'name' property)\n let hoveredFeature = null;\n this.map.forEachFeatureAtPixel(evt.pixel, (feature) => {\n if (feature.get('name')) {\n hoveredFeature = feature;\n return true;\n }\n });\n\n // Change cursor\n this.map.getTargetElement().style.cursor = hoveredFeature ? 'pointer' : '';\n\n callback(lon, lat, hoveredFeature, evt);\n });\n }\n\n /**\n * Enable cursor change on marker hover\n * Note: This is now handled automatically by the popup system\n */\n enableHoverCursor() {\n // Cursor changes are now handled by setupHoverPopup()\n // This method is kept for backwards compatibility\n }\n\n /**\n * Add a GeoJSON layer (visible in LayerSwitcher).\n * By default the layer is added to the root overlay group.\n * Pass a targetGroup (LayerGroup) to nest it inside a specific group.\n *\n * @param {Object} geojson - GeoJSON FeatureCollection or Feature\n * @param {string} title - Layer title for the LayerSwitcher\n * @param {Object} [styleOptions] - Optional style configuration\n * @param {string} [styleOptions.strokeColor='#3b82f6'] - Stroke color\n * @param {number} [styleOptions.strokeWidth=2] - Stroke width\n * @param {string} [styleOptions.fillColor='rgba(59,130,246,0.1)'] - Fill color\n * @param {number[]} [styleOptions.strokeDash] - Dash pattern for the stroke\n * (passed straight to ol/style/Stroke#lineDash, e.g. [4,4]). Useful for\n * contextual overlays (grids, draft outlines) so they read differently\n * from solid property boundaries.\n * @param {LayerGroup} [targetGroup] - Optional group to add the layer to\n * @returns {VectorLayer} The created layer\n */\n addGeoJSONLayer(geojson, title, styleOptions = {}, targetGroup = null) {\n const {\n strokeColor = '#3b82f6',\n strokeWidth = 2,\n strokeDash = null,\n fillColor = 'rgba(59,130,246,0.1)',\n // Optional line \"casing\": a thicker darker stroke drawn UNDERNEATH the\n // main stroke. Used for road-like layers to make light-colored lines\n // visible on any base map. Set lineCasingColor to enable; the casing\n // width defaults to strokeWidth + 2.\n lineCasingColor = null,\n lineCasingWidth = null,\n pointRadius = 5,\n pointFillColor = null, // defaults to strokeColor\n pointStrokeColor = '#ffffff',\n pointStrokeWidth = 1.5,\n } = styleOptions;\n\n const source = new VectorSource({\n features: new GeoJSON().readFeatures(geojson, {\n featureProjection: 'EPSG:3857',\n }),\n });\n\n // Build per-geometry styles. OpenLayers picks `image` for Point /\n // MultiPoint, `stroke`+`fill` for Polygon / MultiPolygon, and `stroke`\n // alone for LineString / MultiLineString. Putting all three on a single\n // Style is enough — but a Style with only stroke+fill leaves Points\n // invisible, which is what was happening on shapefile import.\n const fillStyle = new Fill({ color: fillColor });\n const pointStyle = new Circle({\n radius: pointRadius,\n fill: new Fill({ color: pointFillColor || strokeColor }),\n stroke: new Stroke({ color: pointStrokeColor, width: pointStrokeWidth }),\n });\n\n // If a line casing is requested, return an array of two Styles per\n // feature: the casing renders first (underneath), then the inner stroke.\n // For polygons the casing also outlines them; for points the casing has\n // no effect (Point geometries only render `image`).\n const mainStroke = new Stroke({\n color: strokeColor,\n width: strokeWidth,\n ...(strokeDash ? { lineDash: strokeDash } : {}),\n });\n\n let layerStyle;\n if (lineCasingColor) {\n const casingW = lineCasingWidth != null ? lineCasingWidth : strokeWidth + 2;\n layerStyle = [\n new Style({\n stroke: new Stroke({ color: lineCasingColor, width: casingW }),\n }),\n new Style({\n stroke: mainStroke,\n fill: fillStyle,\n image: pointStyle,\n }),\n ];\n } else {\n layerStyle = new Style({\n stroke: mainStroke,\n fill: fillStyle,\n image: pointStyle,\n });\n }\n\n const layer = new VectorLayer({\n title: title,\n source: source,\n style: layerStyle,\n // Decluttering can only be set at construction time (there is no\n // setDeclutter), so it has to come through the style options. Layers with\n // dense labels — contours especially — need it to avoid a pile-up of\n // overlapping text.\n ...(styleOptions.declutter ? { declutter: styleOptions.declutter } : {}),\n });\n layer.set('typeTag', styleOptions.typeTag || 'VEC');\n\n // Derive a friendly \"Vector / Polygon\" / \"Vector / Line\" / \"Vector / Point\"\n // subtitle from the first feature's geometry type, unless the caller\n // already supplied one in styleOptions.\n //\n // Layers created EMPTY (parcels, OSM_roads, …, populated later from the\n // API) leave the subtitle absent until the first feature arrives —\n // see the `addfeature` listener below.\n const describeFromGeom = (geomType) => {\n if (!geomType) return null;\n if (geomType.includes('Polygon')) return 'Vector / Polygon';\n if (geomType.includes('LineString')) return 'Vector / Line';\n if (geomType.includes('Point')) return 'Vector / Point';\n return 'Vector';\n };\n\n if (styleOptions.typeDescription) {\n layer.set('typeDescription', styleOptions.typeDescription);\n } else {\n const feats = source.getFeatures();\n const initial = describeFromGeom(feats[0]?.getGeometry?.()?.getType?.());\n if (initial) {\n layer.set('typeDescription', initial);\n } else {\n // Source is empty — wait for the first feature and set then.\n const once = (ev) => {\n const desc = describeFromGeom(ev.feature.getGeometry?.()?.getType?.());\n if (desc) layer.set('typeDescription', desc);\n source.un('addfeature', once);\n };\n source.on('addfeature', once);\n }\n }\n\n const group = targetGroup || this.overlayGroup;\n group.getLayers().push(layer);\n\n console.log('[MapView] GeoJSON layer added:', title, '→', source.getFeatures().length, 'features',\n targetGroup ? `(in group \"${targetGroup.get('title')}\")` : '');\n return layer;\n }\n\n /**\n * Add a LayerGroup to the overlay group.\n * Used to create layer categories from the remote catalogue;\n * individual vector layers will be added into these groups later.\n *\n * @param {number|string} id - Unique layer group id (from the API)\n * @param {string} title - Group title for the LayerSwitcher\n * @param {string} [description=''] - Group description (stored as property)\n * @returns {LayerGroup} The created (empty) layer group\n */\n addLayerGroup(id, title, description = '') {\n const group = new LayerGroup({\n title: title.trim(),\n });\n\n // Store metadata for later use\n group.set('layerId', id);\n group.set('description', description);\n\n this.overlayGroup.getLayers().push(group);\n\n console.log('[MapView] Layer group added:', title.trim(), '(id:', id + ')');\n return group;\n }\n\n /**\n * Add a WMS layer to a layer group.\n *\n * @param {string} groupTitle Title of the target LayerGroup (e.g. 'Biophysical Environment')\n * @param {string} title Display title for the layer\n * @param {string} url WMS server URL\n * @param {string} layers WMS LAYERS parameter\n * @param {Object} [options] Extra options\n * @param {string} [options.serverType='geoserver'] Server type hint ('geoserver'|'mapserver'|'qgis'|null)\n * @param {string} [options.style] WMS STYLES parameter (e.g. 'colours' for DEAfrica DEM)\n * @param {boolean} [options.visible=true] Initial visibility\n * @param {string} [options.attributions] Attribution HTML\n * @param {number} [options.opacity=1] Layer opacity (0–1). Use ~0.5 for background-style layers.\n * @param {number} [options.zIndex] Render z-index. Use negative values (e.g. -10) to force the\n * layer behind all default-z-index layers regardless of group order.\n * @param {string} [options.legendUrl] URL of a legend image to display while the layer is visible.\n * @param {boolean} [options.onlineOnly=false] If true, show a toast when the user toggles the layer on\n * while offline, explaining that the layer requires connectivity.\n * @returns {TileLayer|null} The created layer, or null if group not found\n */\n addWMSLayer(groupTitle, title, url, layers, options = {}) {\n const group = this.getLayerGroupByTitle(groupTitle);\n if (!group) {\n console.warn(`[MapView] Layer group \"${groupTitle}\" not found — cannot add WMS layer \"${title}\"`);\n return null;\n }\n\n const params = { LAYERS: layers, TILED: true, WIDTH: 256, HEIGHT: 256 };\n if (options.style !== undefined) params.STYLES = options.style;\n\n const wmsSource = new TileWMS({\n url,\n params,\n serverType: options.serverType !== undefined ? options.serverType : 'geoserver',\n crossOrigin: 'anonymous',\n hidpi: false,\n attributions: options.attributions,\n });\n\n const wmsLayer = new TileLayer({\n title,\n visible: options.visible !== undefined ? options.visible : true,\n source: wmsSource,\n opacity: options.opacity !== undefined ? options.opacity : 1,\n zIndex: options.zIndex,\n });\n wmsLayer.set('typeTag', 'WMS');\n wmsLayer.set('typeDescription', 'WMS / Raster');\n\n // Show toast on tile load errors (e.g. server rejects request)\n wmsSource.on('tileloaderror', () => {\n showToast(`WMS layer \"${title}\" — tile load error. Check the URL and layer name.`, 'warning', 5000);\n });\n\n group.getLayers().push(wmsLayer);\n\n // Register legend AFTER push so that a failure here doesn't block the LayerSwitcher\n if (options.legendUrl) {\n try {\n this._registerLegend(wmsLayer, title, options.legendUrl);\n } catch (err) {\n console.warn(`[MapView] Could not register legend for \"${title}\":`, err);\n }\n }\n\n // Online-only warning: when the user toggles the layer on while offline,\n // surface a toast explaining why nothing will render.\n if (options.onlineOnly) {\n this._attachOnlineOnlyHandler(wmsLayer, title);\n }\n\n console.log(`[MapView] WMS layer added: \"${title}\" → group \"${groupTitle}\"`);\n return wmsLayer;\n }\n\n /**\n * Add an XYZ tile layer to a layer group.\n *\n * @param {string} groupTitle Title of the target LayerGroup\n * @param {string} title Display title for the layer\n * @param {string} url XYZ tile URL template (with {z}/{x}/{y} placeholders)\n * @param {Object} [options] Extra options\n * @param {boolean} [options.visible=true] Initial visibility\n * @param {string} [options.attributions] Attribution HTML\n * @param {number} [options.maxZoom=19] Maximum zoom level\n * @param {number} [options.opacity=1] Layer opacity (0–1). Use ~0.5 for background-style layers.\n * @param {number} [options.zIndex] Render z-index. Use negative values to force behind other layers.\n * @param {string} [options.legendUrl] URL of a legend image to display while the layer is visible.\n * @param {boolean} [options.onlineOnly=false] If true, show a toast when the user toggles the layer on\n * while offline, explaining that the layer requires connectivity.\n * @returns {TileLayer|null} The created layer, or null if group not found\n */\n addXYZLayer(groupTitle, title, url, options = {}) {\n const group = this.getLayerGroupByTitle(groupTitle);\n if (!group) {\n console.warn(`[MapView] Layer group \"${groupTitle}\" not found — cannot add XYZ layer \"${title}\"`);\n return null;\n }\n\n const xyzSource = new XYZ({\n url,\n crossOrigin: 'anonymous',\n maxZoom: options.maxZoom !== undefined ? options.maxZoom : 19,\n attributions: options.attributions,\n });\n\n const xyzLayer = new TileLayer({\n title,\n visible: options.visible !== undefined ? options.visible : true,\n source: xyzSource,\n opacity: options.opacity !== undefined ? options.opacity : 1,\n zIndex: options.zIndex,\n });\n xyzLayer.set('typeTag', 'XYZ');\n xyzLayer.set('typeDescription', 'XYZ / Tile');\n\n // Show toast on tile load errors\n xyzSource.on('tileloaderror', () => {\n showToast(`XYZ layer \"${title}\" — tile load error. Check the URL.`, 'warning', 5000);\n });\n\n group.getLayers().push(xyzLayer);\n\n // Register legend AFTER push so that a failure here doesn't block the LayerSwitcher\n if (options.legendUrl) {\n try {\n this._registerLegend(xyzLayer, title, options.legendUrl);\n } catch (err) {\n console.warn(`[MapView] Could not register legend for \"${title}\":`, err);\n }\n }\n\n // Online-only warning: when the user toggles the layer on while offline,\n // surface a toast explaining why nothing will render.\n if (options.onlineOnly) {\n this._attachOnlineOnlyHandler(xyzLayer, title);\n }\n\n console.log(`[MapView] XYZ layer added: \"${title}\" → group \"${groupTitle}\"`);\n return xyzLayer;\n }\n\n /**\n * Add a Cloud-Optimized GeoTIFF (COG) raster layer.\n *\n * COGs are streamed by range-request and rendered on the GPU via\n * WebGLTile, so a large DEM or satellite scene can be displayed without\n * downloading the whole file. This is the display half of Stage 1's raster\n * support (analysis on rasters runs server-side — see the concept note).\n *\n * @param {string} groupTitle Title of the target LayerGroup\n * @param {string} title Display title for the layer\n * @param {string} url URL of the .tif (must be a valid COG, CORS-enabled)\n * @param {Object} [options]\n * @param {boolean} [options.visible=true]\n * @param {number} [options.opacity=1]\n * @param {number} [options.zIndex]\n * @param {boolean} [options.normalize=true] Auto-stretch values to 0–1 for display\n * @param {number} [options.min] Explicit min for contrast stretch\n * @param {number} [options.max] Explicit max for contrast stretch\n * @param {Object} [options.style] OpenLayers WebGLTile style (e.g. a colour ramp)\n * @param {string} [options.attributions]\n * @returns {Promise} the created layer (async: the COG libraries\n * are code-split and fetched on first use)\n */\n async addCOGLayer(groupTitle, title, url, options = {}) {\n const group = this.getLayerGroupByTitle(groupTitle);\n if (!group) {\n console.warn(`[MapView] Layer group \"${groupTitle}\" not found — cannot add COG layer \"${title}\"`);\n return null;\n }\n\n // Pre-flight the URL before handing it to geotiff.js. Without this, a bad\n // URL (a web page, a 404, a CORS block) surfaces only as an unhandled\n // AggregateError from the library's internal range-request machinery,\n // which tells the user nothing. A one-byte range request is cheap and\n // distinguishes the common failures precisely.\n const problem = await this._probeCOGUrl(url);\n if (problem) {\n console.warn(`[MapView] COG pre-flight failed for \"${title}\":`, problem);\n showToast(problem, 'error', 8000);\n return null;\n }\n\n // Lazy-load the GeoTIFF stack only when a raster is actually opened.\n let WebGLTileLayer, GeoTIFFSource;\n try {\n [{ default: WebGLTileLayer }, { default: GeoTIFFSource }] = await Promise.all([\n import('ol/layer/WebGLTile'),\n import('ol/source/GeoTIFF'),\n ]);\n } catch (err) {\n console.error('[MapView] Could not load COG support:', err);\n showToast('Raster (COG) support could not be loaded — check your connection.', 'error', 6000);\n return null;\n }\n\n let cogLayer = null;\n try {\n const sourceOpts = {\n sources: [{\n url,\n ...(options.min !== undefined ? { min: options.min } : {}),\n ...(options.max !== undefined ? { max: options.max } : {}),\n }],\n // normalize=true stretches band values into 0–1 so single-band rasters\n // (DEMs, indices) render without an explicit style.\n normalize: options.normalize !== undefined ? options.normalize : true,\n attributions: options.attributions,\n };\n const source = new GeoTIFFSource(sourceOpts);\n\n cogLayer = new WebGLTileLayer({\n title,\n source,\n visible: options.visible !== undefined ? options.visible : true,\n opacity: options.opacity !== undefined ? options.opacity : 1,\n zIndex: options.zIndex,\n ...(options.style ? { style: options.style } : {}),\n });\n cogLayer.set('typeTag', 'COG');\n cogLayer.set('typeDescription', 'Cloud-Optimized GeoTIFF');\n\n // Surface load failures — a non-COG or a CORS-blocked file is the usual cause.\n source.on('error', (evt) => {\n console.warn(`[MapView] COG \"${title}\" source error:`, evt);\n showToast(`Raster \"${title}\" could not be loaded. Check that the file is a valid COG and is CORS-enabled.`,\n 'warning', 6000);\n });\n\n group.getLayers().push(cogLayer);\n } catch (err) {\n console.error(`[MapView] Failed to add COG layer \"${title}\":`, err);\n showToast(`Could not add raster \"${title}\": ${err.message}`, 'error', 6000);\n return null;\n }\n\n if (options.onlineOnly) this._attachOnlineOnlyHandler(cogLayer, title);\n\n console.log(`[MapView] COG layer added: \"${title}\" → group \"${groupTitle}\"`);\n return cogLayer;\n }\n\n /**\n * Check that a URL can plausibly serve a Cloud-Optimized GeoTIFF.\n *\n * Issues a 1-byte range request and inspects the response. Returns a\n * human-readable problem description, or null when the URL looks usable.\n * This exists because geotiff.js reports these failures as an opaque\n * \"AggregateError: Request failed\" from deep inside its block reader.\n *\n * @param {string} url\n * @returns {Promise}\n * @private\n */\n async _probeCOGUrl(url) {\n let res;\n try {\n res = await fetch(url, { headers: { Range: 'bytes=0-0' } });\n } catch (err) {\n // Thrown for CORS rejections and network failures alike — the browser\n // deliberately doesn't tell us which, so say so rather than guess.\n return 'Could not reach that file. It may be offline, or the server may not ' +\n 'allow cross-origin (CORS) requests from this app.';\n }\n\n if (res.status === 404) return 'No file at that URL (404). Check the address.';\n if (!res.ok && res.status !== 206) {\n return `The server refused the request (HTTP ${res.status}).`;\n }\n\n const type = (res.headers.get('content-type') || '').toLowerCase();\n if (type.includes('text/html')) {\n return 'That URL is a web page, not a GeoTIFF. Paste the direct link to the ' +\n '.tif file itself — a documentation or viewer page will not work.';\n }\n if (type.startsWith('text/') || type.includes('json')) {\n return `That URL returns ${type.split(';')[0]}rather than image data. ` +\n 'Paste the direct link to the .tif file.';\n }\n // 200 instead of 206 means range requests are ignored: the file still\n // loads, but the whole thing is downloaded. Worth warning about, not fatal.\n if (res.status === 200) {\n showToast('This server does not support byte-range requests, so the whole raster ' +\n 'will be downloaded. It may be slow for a large file.', 'warning', 7000);\n }\n return null;\n }\n\n /**\n * The features currently selected with the Select tool (shift-click or\n * Ctrl/Cmd box-select). Used by the Analysis panel to restrict an analysis\n * to a hand-picked subset.\n * @returns {Array}\n */\n getSelectedFeatures() {\n if (!this._selectInteraction) return [];\n return this._selectInteraction.getFeatures().getArray().slice();\n }\n\n /**\n * List the vector layers a user can pick as analysis inputs.\n *\n * Walks the layer tree (recursing into groups) and returns the real,\n * feature-bearing layers — excluding helper overlays (vertex handles, GPS\n * trail/position, snap guides) which are never meaningful analysis inputs.\n *\n * @param {Object} [opts]\n * @param {boolean} [opts.visibleOnly=false] only include currently-visible layers\n * @param {boolean} [opts.nonEmptyOnly=true] skip layers with no features loaded\n * @returns {Array<{layer: VectorLayer, title: string, count: number}>}\n */\n listVectorLayers(opts = {}) {\n const { visibleOnly = false, nonEmptyOnly = true } = opts;\n const out = [];\n\n const walk = (layer) => {\n if (!layer) return;\n if (typeof layer.getLayers === 'function') {\n layer.getLayers().forEach(walk);\n return;\n }\n if (!(layer instanceof VectorLayer)) return;\n if (layer === this._vertexOverlayLayer ||\n layer === this._gpsTrailLayer ||\n layer === this._gpsPositionLayer ||\n layer.get('displayInLayerSwitcher') === false) return;\n if (visibleOnly && layer.getVisible && !layer.getVisible()) return;\n\n const source = layer.getSource && layer.getSource();\n if (!source || typeof source.getFeatures !== 'function') return;\n const count = source.getFeatures().length;\n if (nonEmptyOnly && count === 0) return;\n\n out.push({ layer, title: layer.get('title') || '(untitled layer)', count });\n };\n\n this.map.getLayers().forEach(walk);\n return out;\n }\n\n // ============================================================================\n // Add External Layer Dialog\n // ============================================================================\n\n /**\n * Create the add-layer dialog overlay (hidden by default).\n * Appended to the map target element so it stays within the map viewport.\n */\n _createAddLayerDialog() {\n this._addLayerDialog = document.createElement('div');\n this._addLayerDialog.className = 'map-add-layer-dialog';\n this._addLayerDialog.style.cssText = `\n display:none;position:absolute;top:0;left:0;right:0;bottom:0;\n z-index:1100;background:rgba(0,0,0,0.4);\n align-items:center;justify-content:center;\n `;\n\n const card = document.createElement('div');\n card.style.cssText = `\n background:var(--card, #fff);color:var(--card-foreground, #1e1a4b);\n border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,0.35);\n font-family:var(--font-body, 'Exo', sans-serif);font-size:13px;\n width:340px;max-width:90vw;border:2px solid #10b981;overflow:hidden;\n `;\n\n card.innerHTML = `\n
    \n Add External Layer\n \n
    \n
    \n
    \n \n
    \n \n \n \n \n
    \n
    \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n WMS LAYERS parameter (e.g. workspace:layer)\n
    \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n
    \n `;\n\n this._addLayerDialog.appendChild(card);\n this.map.getTargetElement().appendChild(this._addLayerDialog);\n\n // Type radio change — toggle layer name row visibility\n const nameRow = card.querySelector('.add-layer-name-row');\n const nameHint = card.querySelector('.add-layer-name-hint');\n const urlInput = card.querySelector('.add-layer-url');\n const typeHint = card.querySelector('.add-layer-type-hint');\n card.querySelectorAll('input[name=\"add-layer-type\"]').forEach((radio) => {\n radio.addEventListener('change', () => {\n const type = radio.value;\n typeHint.textContent = type === 'cog'\n ? 'Cloud-Optimized GeoTIFF — the server must allow CORS and byte-range requests.'\n : '';\n if (type === 'cog') {\n // A COG is a single file, so there is no layer/typename to pick.\n nameRow.style.display = 'none';\n urlInput.placeholder = 'https://example.com/data/elevation_cog.tif';\n } else if (type === 'xyz') {\n nameRow.style.display = 'none';\n urlInput.placeholder = 'https://example.com/tiles/{z}/{x}/{y}.png';\n } else {\n nameRow.style.display = '';\n urlInput.placeholder = type === 'wms'\n ? 'https://example.com/wms'\n : 'https://example.com/wfs';\n nameHint.textContent = type === 'wms'\n ? 'WMS LAYERS parameter (e.g. workspace:layer)'\n : 'WFS typename (e.g. workspace:layer)';\n }\n });\n });\n\n // Close / Cancel\n const close = () => this._hideAddLayerDialog();\n card.querySelector('.add-layer-close').addEventListener('click', close);\n card.querySelector('.add-layer-cancel').addEventListener('click', close);\n this._addLayerDialog.addEventListener('click', (e) => {\n if (e.target === this._addLayerDialog) close();\n });\n\n // Confirm\n card.querySelector('.add-layer-confirm').addEventListener('click', () => {\n const type = card.querySelector('input[name=\"add-layer-type\"]:checked').value;\n const url = card.querySelector('.add-layer-url').value.trim();\n const layerName = card.querySelector('.add-layer-name').value.trim();\n const title = card.querySelector('.add-layer-title').value.trim();\n\n if (!url) {\n card.querySelector('.add-layer-url').style.borderColor = '#ef4444';\n return;\n }\n if ((type === 'wms' || type === 'wfs') && !layerName) {\n card.querySelector('.add-layer-name').style.borderColor = '#ef4444';\n return;\n }\n if (!title) {\n card.querySelector('.add-layer-title').style.borderColor = '#ef4444';\n return;\n }\n\n this._addExternalLayer(type, url, layerName, title);\n this._hideAddLayerDialog();\n });\n\n // Enter key to confirm\n card.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n card.querySelector('.add-layer-confirm').click();\n }\n if (e.key === 'Escape') {\n e.preventDefault();\n close();\n }\n });\n }\n\n /**\n * Show the add-layer dialog.\n */\n showAddLayerDialog() {\n const dlg = this._addLayerDialog;\n // Reset form\n dlg.querySelector('.add-layer-url').value = '';\n dlg.querySelector('.add-layer-name').value = '';\n dlg.querySelector('.add-layer-title').value = '';\n dlg.querySelectorAll('input[name=\"add-layer-type\"]')[0].checked = true;\n dlg.querySelector('.add-layer-name-row').style.display = '';\n dlg.querySelector('.add-layer-url').placeholder = 'https://example.com/wms';\n dlg.querySelector('.add-layer-name-hint').textContent = 'WMS LAYERS parameter (e.g. workspace:layer)';\n const typeHintEl = dlg.querySelector('.add-layer-type-hint');\n if (typeHintEl) typeHintEl.textContent = '';\n\n // Reset border colours\n dlg.querySelectorAll('input[type=\"text\"]').forEach((inp) => {\n inp.style.borderColor = 'var(--border, #1e1a4b1f)';\n });\n\n dlg.style.display = 'flex';\n dlg.querySelector('.add-layer-url').focus();\n }\n\n /**\n * Hide the add-layer dialog.\n */\n _hideAddLayerDialog() {\n this._addLayerDialog.style.display = 'none';\n }\n\n /**\n * Add an external layer to the \"External Source\" group.\n *\n * @param {string} type 'wms' | 'wfs' | 'xyz'\n * @param {string} url Server URL\n * @param {string} layerName WMS LAYERS / WFS typename (ignored for XYZ)\n * @param {string} title Display title in layer switcher\n */\n _addExternalLayer(type, url, layerName, title) {\n const group = this._externalSourceGroup;\n if (!group) {\n showToast('Layer group \"External Source\" not found.', 'error', 4000);\n return;\n }\n\n let layer;\n\n switch (type) {\n case 'wms': {\n const wmsSrc = new TileWMS({\n url,\n params: { LAYERS: layerName, TILED: true, WIDTH: 256, HEIGHT: 256 },\n serverType: 'geoserver',\n crossOrigin: 'anonymous',\n hidpi: false,\n });\n layer = new TileLayer({\n title,\n visible: true,\n source: wmsSrc,\n });\n wmsSrc.on('tileloaderror', () => {\n showToast(`WMS \"${title}\" — tile load error. Check URL and layer name.`, 'warning', 5000);\n });\n break;\n }\n\n case 'wfs': {\n const wfsUrl = `${url}${url.includes('?') ? '&' : '?'}` +\n `service=WFS&version=1.1.0&request=GetFeature` +\n `&typename=${encodeURIComponent(layerName)}` +\n `&outputFormat=application/json&srsname=EPSG:3857`;\n\n const wfsSource = new VectorSource({\n url: wfsUrl,\n format: new GeoJSON(),\n });\n wfsSource.on('featuresloaderror', () => {\n showToast(`WFS \"${title}\" — load error. Check URL and layer name.`, 'warning', 5000);\n });\n\n layer = new VectorLayer({\n title,\n visible: true,\n source: wfsSource,\n style: new Style({\n stroke: new Stroke({ color: '#e11d48', width: 2 }),\n fill: new Fill({ color: 'rgba(225,29,72,0.15)' }),\n }),\n });\n break;\n }\n\n case 'xyz':\n layer = new TileLayer({\n title,\n visible: true,\n source: new XYZ({\n url,\n crossOrigin: 'anonymous',\n }),\n });\n layer.getSource().on('tileloaderror', () => {\n showToast(`XYZ \"${title}\" — tile load error. Check the URL template.`, 'warning', 5000);\n });\n break;\n\n case 'cog': {\n // A Cloud-Optimized GeoTIFF is a single file streamed by byte-range\n // request. addCOGLayer() lazy-loads the GeoTIFF stack, pushes the layer\n // into the group itself and reports its own failures, so we return\n // early rather than falling through to the shared push below.\n const groupTitle = group.get('title') || 'External Source';\n this.addCOGLayer(groupTitle, title, url).then((cogLayer) => {\n if (!cogLayer) return;\n cogLayer.set('removable', true);\n showToast(`Layer \"${title}\" added to External Source.`, 'success', 3000);\n console.log(`[MapView] External COG layer added: \"${title}\"`);\n });\n return;\n }\n\n default:\n showToast(`Unknown layer type: ${type}`, 'error', 4000);\n return;\n }\n\n // Tag for the LayerSwitcher chip\n layer.set('typeTag', type.toUpperCase()); // 'WMS' | 'WFS' | 'XYZ'\n layer.set('typeDescription', {\n wms: 'WMS / Raster',\n wfs: 'WFS / Vector',\n xyz: 'XYZ / Tile',\n }[type] || type.toUpperCase());\n\n // User-added external layers ARE removable — they're not part of the\n // app's built-in data model.\n layer.set('removable', true);\n\n group.getLayers().push(layer);\n showToast(`Layer \"${title}\" added to External Source.`, 'success', 3000);\n console.log(`[MapView] External ${type.toUpperCase()} layer added: \"${title}\"`);\n }\n\n // ============================================================================\n // LayerSwitcher decoration (Option A — visual refresh)\n // ============================================================================\n\n /**\n * Decorate a layer's
  • after ol-ext renders it:\n * • inject a type-tag chip next to the layer label\n * • inject the green \"+\" button on the External Source group header\n *\n * Idempotent — safe to call repeatedly; each injected element checks\n * whether it already exists in the row.\n */\n _decorateLayerListItem(layer, li) {\n // 1. Type-tag chip (e.g. WMS / XYZ / VEC) next to the layer name.\n // Inserted INSIDE the label's so it doesn't collide with the\n // label's left padding (where ol-ext draws the checkbox via ::before).\n const tag = layer.get('typeTag'); // 'WMS' | 'WFS' | 'XYZ' | 'VEC' | 'GEO' | 'BASE'\n if (tag) {\n const labelSpan = li.querySelector(':scope > .li-content > label > span');\n if (labelSpan && !labelSpan.querySelector(':scope > .ls-type-tag')) {\n const chip = document.createElement('span');\n chip.className = `ls-type-tag ls-type-tag-${String(tag).toLowerCase()}`;\n chip.textContent = String(tag);\n chip.title = `${tag} layer`;\n labelSpan.appendChild(chip);\n }\n }\n\n // 2. Replace ol-ext's bar-drawn +/- chevron with the GeoView chevron SVG.\n // The SVG uses stroke=\"currentColor\", so CSS `color` (set in\n // layerswitcher.css) tints it. Rotation handles the open/closed state.\n const btnBar = li.querySelector(':scope > .ol-layerswitcher-buttons');\n if (btnBar) {\n const chevronEl = btnBar.querySelector(':scope > .expend-layers, :scope > .collapse-layers');\n if (chevronEl && !chevronEl.querySelector(':scope > svg.ls-chevron-svg')) {\n chevronEl.innerHTML =\n '' +\n '' +\n '';\n }\n }\n\n // 3. Layer-type subtitle (\"Vector / Polygon\", \"WMS / Raster\", …) below\n // the layer name. Only rendered when layer.get('typeDescription') is\n // set. For layers that start empty and gain features later (the API\n // loaders), we listen for `change:typeDescription` and update or\n // insert the subtitle then.\n const content = li.querySelector(':scope > .li-content');\n const ensureSubtitle = () => {\n if (!content) return;\n const text = layer.get('typeDescription');\n let sub = content.querySelector(':scope > .ls-layer-subtitle');\n if (!text) {\n if (sub) sub.remove();\n return;\n }\n if (!sub) {\n sub = document.createElement('div');\n sub.className = 'ls-layer-subtitle';\n const label = content.querySelector(':scope > label');\n if (label && label.nextSibling) {\n content.insertBefore(sub, label.nextSibling);\n } else {\n content.appendChild(sub);\n }\n }\n sub.textContent = text;\n };\n ensureSubtitle();\n if (!layer._lsSubtitleHooked) {\n layer._lsSubtitleHooked = true;\n layer.on('change:typeDescription', () => {\n // The
  • may not exist any more (panel re-rendered between events)\n // — guard with a fresh lookup via the LayerSwitcher next time it draws.\n // For now we just call ensureSubtitle bound to the original li.\n ensureSubtitle();\n });\n }\n\n // 4. Per-layer Remove button — only for layers explicitly marked\n // `removable: true` (external sources, imported files, …). Built-in\n // layers (Parcels, OSM_roads, district boundary, …) are NOT removable\n // so the user can't accidentally delete them.\n if (layer.get('removable') === true && btnBar && !btnBar.querySelector(':scope > .ls-remove-btn')) {\n const removeBtn = document.createElement('button');\n removeBtn.type = 'button';\n removeBtn.className = 'ls-remove-btn';\n removeBtn.title = 'Remove this layer';\n removeBtn.setAttribute('aria-label', 'Remove layer');\n removeBtn.innerHTML =\n '' +\n '' +\n '';\n removeBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this._removeLayer(layer);\n });\n btnBar.appendChild(removeBtn);\n }\n\n // 5b. Import-state chip on layers staged via the external-dataset import\n // flow (LUPMIS2_Import_Upload_Design.docx §3.2). The chip's text and\n // behaviour depend on `_externalImportStatus`:\n // 'mapped' → \"Upload N\" (click → upload)\n // 'uploading' → spinner (disabled)\n // 'submitted' → \"✓ submitted\" (in upload_tmp, awaiting review)\n // 'migrated' → \"✓ live\" (supervisor promoted to lu_parcels)\n // 'failed' → \"N errors — fix?\"\n // 'other'/null→ no chip\n // Clicks dispatch a window-level CustomEvent so main.js can react\n // without MapView knowing anything about staging.\n const importId = layer.get('_externalImportId');\n if (importId != null) {\n const labelSpan = li.querySelector(':scope > .li-content > label > span');\n let chip = labelSpan ? labelSpan.querySelector(':scope > .ls-import-chip') : null;\n const status = layer.get('_externalImportStatus') || 'mapped';\n const featureCount = layer.getSource()?.getFeatures().length ?? 0;\n const errorCount = layer.get('_externalImportErrorCount') ?? 0;\n\n // Decide chip appearance.\n const chipSpec = (() => {\n switch (status) {\n case 'mapped':\n return { text: `Upload ${featureCount}`, cls: 'ls-import-chip-mapped',\n title: 'Upload this dataset to the database', clickable: true };\n case 'uploading':\n return { text: '…', cls: 'ls-import-chip-uploading',\n title: 'Uploading…', clickable: false };\n case 'submitted':\n return { text: '✓ submitted', cls: 'ls-import-chip-submitted',\n title: 'Uploaded — awaiting supervisor review', clickable: false };\n case 'migrated':\n return { text: '✓ live', cls: 'ls-import-chip-migrated',\n title: 'Approved by supervisor and live on the server', clickable: false };\n case 'failed':\n return { text: `${errorCount} errors — fix?`, cls: 'ls-import-chip-failed',\n title: 'Some rows failed; click to review', clickable: true };\n case 'other':\n case null:\n case undefined:\n default:\n return null;\n }\n })();\n\n if (!chipSpec) {\n if (chip) chip.remove();\n } else if (labelSpan) {\n if (!chip) {\n chip = document.createElement('span');\n chip.className = 'ls-import-chip';\n labelSpan.appendChild(chip);\n }\n chip.className = `ls-import-chip ${chipSpec.cls}`;\n chip.textContent = chipSpec.text;\n chip.title = chipSpec.title;\n chip.style.cursor = chipSpec.clickable ? 'pointer' : 'default';\n chip.style.opacity = chipSpec.clickable ? '1' : '0.85';\n\n // Replace any prior listener by cloning the node.\n const fresh = chip.cloneNode(true);\n chip.replaceWith(fresh);\n chip = fresh;\n\n if (chipSpec.clickable) {\n chip.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n window.dispatchEvent(new CustomEvent('lupmis:import-chip-click', {\n detail: { importId, status, layer },\n }));\n });\n }\n }\n }\n\n // 5. \"+\" button on the External Source group\n const groupTitle = (layer.get('title') || '').toLowerCase();\n if (groupTitle.includes('external')) {\n this._externalSourceGroup = layer;\n // btnBar already resolved above (same .ol-layerswitcher-buttons element)\n if (btnBar && !btnBar.querySelector('.ol-add-layer')) {\n const addBtn = document.createElement('span');\n addBtn.className = 'ol-add-layer';\n addBtn.title = 'Add external layer';\n addBtn.textContent = '+';\n addBtn.style.cssText = `\n display:inline-flex !important;align-items:center;justify-content:center;\n width:22px !important;height:22px !important;border-radius:50%;\n background:#41b6a6 !important;color:#fff !important;\n font-size:15px !important;font-weight:700;\n cursor:pointer;line-height:1 !important;\n margin:0 4px 0 0;vertical-align:middle;\n transition:background 0.2s;box-sizing:border-box;border:none;\n `;\n addBtn.addEventListener('mouseenter', () => { addBtn.style.background = '#329686'; });\n addBtn.addEventListener('mouseleave', () => { addBtn.style.background = '#41b6a6'; });\n addBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.showAddLayerDialog();\n });\n btnBar.prepend(addBtn);\n }\n }\n }\n\n /**\n * Remove a layer from its parent group, after confirmation. Only called\n * from the per-layer × button injected by `_decorateLayerListItem` — and\n * that button is only injected for layers marked `removable: true`, so\n * built-in layers (Parcels, OSM_roads, …) can never reach this path.\n *\n * @param {Layer} layer\n */\n _removeLayer(layer) {\n const title = layer.get('title') || 'this layer';\n if (!confirm(`Remove \"${title}\" from the map?\\n\\nThis only affects the current session — built-in layers cannot be removed.`)) {\n return;\n }\n\n // Find the parent group that owns this layer and call .remove() on its\n // collection. Walk recursively from the overlay group.\n const visit = (group) => {\n const layers = group.getLayers();\n if (layers.getArray().includes(layer)) {\n layers.remove(layer);\n return true;\n }\n let removed = false;\n layers.forEach((child) => {\n if (!removed && child.getLayers) {\n removed = visit(child);\n }\n });\n return removed;\n };\n\n const ok = visit(this.overlayGroup);\n if (ok) {\n console.log(`[MapView] Removed layer \"${title}\"`);\n showToast(`Removed \"${title}\" from the map.`, 'info', 3000);\n } else {\n console.warn(`[MapView] Could not find layer \"${title}\" in any group`);\n }\n }\n\n /**\n * Inject (or refresh) the panel chrome — an \"active count\" badge at the top\n * and a footer row with \"Reset overlays\" button at the bottom.\n *\n * The chrome lives in `.panel-container` (the wrapping
    ), not inside\n * the `
      ` — that way the badge and footer are siblings of\n * the layer list rather than malformed children of a `
        `.\n *\n * Called from drawlist via queueMicrotask, so it runs once per redraw cycle\n * regardless of how many layers are in the panel.\n */\n _refreshLayerSwitcherChrome(layerSwitcher) {\n const panelContainer = layerSwitcher.element?.querySelector('.panel-container');\n const ul = layerSwitcher.element?.querySelector('ul.panel');\n if (!panelContainer || !ul) return;\n\n // --- Active-count badge (top of panel-container, before the
          ) ---\n let badge = panelContainer.querySelector(':scope > .ls-active-badge');\n if (!badge) {\n badge = document.createElement('div');\n badge.className = 'ls-active-badge';\n badge.innerHTML = `\n Layers\n 0 active\n `;\n panelContainer.insertBefore(badge, ul);\n }\n\n // --- Footer row (bottom of panel-container, after the
            ) ---\n let footer = panelContainer.querySelector(':scope > .ls-footer-row');\n if (!footer) {\n footer = document.createElement('div');\n footer.className = 'ls-footer-row';\n footer.innerHTML = `\n — layers total\n \n `;\n panelContainer.appendChild(footer);\n\n footer.querySelector('.ls-footer-btn').addEventListener('click', (e) => {\n e.stopPropagation();\n this._resetAllOverlays();\n });\n }\n\n // --- Update counters ---\n const counts = this._countLayers();\n badge.querySelector('.ls-active-badge-count').textContent =\n `${counts.activeOverlays} active`;\n footer.querySelector('.ls-footer-note').textContent =\n `${counts.totalOverlays} overlay${counts.totalOverlays === 1 ? '' : 's'}`;\n }\n\n /**\n * Walk the overlay group recursively, returning the number of *leaf* layers\n * (i.e. not groups) and how many of them are currently visible.\n * Excludes base maps and internal layers (Markers, Drawings, vertex overlay).\n */\n _countLayers() {\n let totalOverlays = 0;\n let activeOverlays = 0;\n\n const HIDDEN_INTERNAL = new Set(['__vertex_highlight__']);\n\n const visit = (group) => {\n group.getLayers().forEach((layer) => {\n if (layer.get('displayInLayerSwitcher') === false) return;\n if (HIDDEN_INTERNAL.has(layer.get('title'))) return;\n\n if (layer.getLayers) {\n visit(layer);\n } else {\n totalOverlays++;\n if (layer.getVisible()) activeOverlays++;\n }\n });\n };\n if (this.overlayGroup) visit(this.overlayGroup);\n return { totalOverlays, activeOverlays };\n }\n\n /**\n * Hide every overlay layer (base maps stay on). Wired to the footer\n * \"Reset overlays\" button.\n */\n _resetAllOverlays() {\n const HIDDEN_INTERNAL = new Set(['__vertex_highlight__']);\n const visit = (group) => {\n group.getLayers().forEach((layer) => {\n if (layer.get('displayInLayerSwitcher') === false) return;\n if (HIDDEN_INTERNAL.has(layer.get('title'))) return;\n\n if (layer.getLayers) {\n visit(layer);\n } else {\n layer.setVisible(false);\n }\n });\n };\n if (this.overlayGroup) visit(this.overlayGroup);\n console.log('[MapView] Reset overlays — all hidden');\n }\n\n /**\n * Hook `change:visible` on every overlay leaf so the active-count badge\n * stays in sync even when ol-ext doesn't re-fire drawlist (e.g. ticking\n * a checkbox just toggles visibility without rebuilding the list).\n * Also re-hooks when a new layer is added.\n */\n _wireLayerSwitcherVisibilityHooks(layerSwitcher) {\n const refresh = () => this._refreshLayerSwitcherChrome(layerSwitcher);\n\n const hookLayer = (layer) => {\n if (layer._lsVisHooked) return;\n layer._lsVisHooked = true;\n layer.on('change:visible', refresh);\n };\n\n const visit = (group) => {\n group.getLayers().forEach((layer) => {\n if (layer.getLayers) {\n visit(layer);\n // Listen for layers added later to this group\n if (!group._lsAddHooked) {\n group._lsAddHooked = true;\n group.getLayers().on('add', (ev) => {\n const added = ev.element;\n if (added.getLayers) visit(added); else hookLayer(added);\n refresh();\n });\n }\n } else {\n hookLayer(layer);\n }\n });\n };\n\n if (this.overlayGroup) visit(this.overlayGroup);\n }\n\n // ============================================================================\n // Online-Only Layer Helper\n // ============================================================================\n\n /**\n * Attach a `change:visible` listener that shows an info toast when the user\n * toggles a layer ON while the device is offline. Used for layers that fetch\n * tiles or features from a remote service and therefore have no useful\n * cached state.\n *\n * The check uses navigator.onLine, which is the same signal as the rest of\n * the app's online detection.\n *\n * @param {Layer} layer\n * @param {string} title Display title used in the toast message\n */\n _attachOnlineOnlyHandler(layer, title) {\n layer.set('onlineOnly', true);\n layer.on('change:visible', () => {\n if (layer.getVisible() && !navigator.onLine) {\n showToast(\n `\"${title}\" requires an internet connection. Connect to view this layer.`,\n 'info',\n 5000,\n );\n }\n });\n }\n\n // ============================================================================\n // Legend Panel — shows legend images for visible layers that have one\n // ============================================================================\n\n /**\n * Create the legend panel, positioned bottom-right inside the map target.\n * Hidden when no visible layers have a registered legend.\n */\n _createLegendPanel() {\n this._legendPanel = document.createElement('div');\n this._legendPanel.className = 'map-legend-panel';\n this._legendPanel.style.cssText = `\n position:absolute;right:10px;bottom:40px;z-index:900;\n display:none;flex-direction:column;gap:6px;\n background:var(--card, #fff);color:var(--card-foreground, #1e1a4b);\n border:1px solid var(--border, #1e1a4b1f);border-radius:8px;\n box-shadow:0 4px 12px rgba(0,0,0,0.15);\n font-family:var(--font-body, 'Exo', sans-serif);font-size:11px;\n max-width:220px;max-height:60%;overflow-y:auto;\n padding:8px 10px;\n `;\n this.map.getTargetElement().appendChild(this._legendPanel);\n\n // Map of layer → { wrapper, title, imgUrl }\n this._legendEntries = new Map();\n }\n\n /**\n * Register a layer's legend image and wire up visibility tracking.\n * Called from addWMSLayer / addXYZLayer when a legendUrl is supplied.\n *\n * @param {Layer} layer The OpenLayers layer\n * @param {string} title Display title for the legend header\n * @param {string} legendUrl URL of the legend image\n */\n _registerLegend(layer, title, legendUrl) {\n if (!this._legendPanel) return;\n\n // Build the legend entry — a div with header + image\n const wrapper = document.createElement('div');\n wrapper.className = 'map-legend-entry';\n wrapper.style.cssText = 'border-bottom:1px solid var(--border, #1e1a4b1f);padding-bottom:6px;';\n wrapper.innerHTML = `\n
            \n ${this._escapeHtml(title)}\n
            \n \"${this._escapeHtml(title)}\n `;\n\n this._legendEntries.set(layer, wrapper);\n\n // Listen for visibility changes. Wrap in try/catch so a DOM error here\n // cannot break the LayerSwitcher's click handler (which fires change:visible\n // synchronously and relies on a subsequent setTimeout to update the checkbox).\n const update = () => {\n try { this._updateLegendPanel(); }\n catch (err) { console.warn('[MapView] legend panel update failed:', err); }\n };\n layer.on('change:visible', update);\n\n // Trigger initial state\n update();\n }\n\n /**\n * Refresh the legend panel contents: include entries for each visible\n * registered layer, and show/hide the panel based on whether any are visible.\n */\n _updateLegendPanel() {\n if (!this._legendPanel) return;\n\n // Rebuild children from scratch in a stable order (Map iteration order = insertion order)\n const children = [];\n for (const [layer, wrapper] of this._legendEntries) {\n if (layer.getVisible()) children.push(wrapper);\n }\n\n // Remove trailing bottom-border on the last entry for a clean look\n this._legendEntries.forEach((w) => {\n w.style.borderBottom = '1px solid var(--border, #1e1a4b1f)';\n w.style.paddingBottom = '6px';\n });\n if (children.length > 0) {\n children[children.length - 1].style.borderBottom = 'none';\n children[children.length - 1].style.paddingBottom = '0';\n }\n\n // Swap the DOM children\n this._legendPanel.replaceChildren(...children);\n this._legendPanel.style.display = children.length > 0 ? 'flex' : 'none';\n }\n\n /**\n * Escape HTML special characters for safe text insertion.\n */\n _escapeHtml(str) {\n return String(str)\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n\n /**\n * Find a LayerGroup inside the overlay group by its layerId.\n *\n * @param {number|string} id - The layerId to search for\n * @returns {LayerGroup|null} The matching group, or null\n */\n getLayerGroup(id) {\n let found = null;\n this.overlayGroup.getLayers().forEach((layer) => {\n if (layer.get('layerId') === id) {\n found = layer;\n }\n });\n return found;\n }\n\n /**\n * Find a LayerGroup inside the overlay group by its title.\n *\n * @param {string} title - The group title to search for\n * @returns {LayerGroup|null} The matching group, or null\n */\n getLayerGroupByTitle(title) {\n let found = null;\n this.overlayGroup.getLayers().forEach((layer) => {\n if (layer.get('title') === title) {\n found = layer;\n }\n });\n return found;\n }\n\n /**\n * Get the overlay LayerGroup for advanced usage\n */\n getOverlayGroup() {\n return this.overlayGroup;\n }\n\n /**\n * Get the OpenLayers map instance for advanced usage\n */\n getMap() {\n return this.map;\n }\n\n // ============================================================================\n // Extent Helpers (used by offline-tile downloader)\n // ============================================================================\n\n /**\n * Get the current map view's visible extent in EPSG:3857 (Web Mercator).\n * @returns {Array} [minX, minY, maxX, maxY]\n */\n getCurrentViewExtent() {\n const view = this.map.getView();\n const size = this.map.getSize();\n if (!size) return null;\n return view.calculateExtent(size);\n }\n\n /**\n * Get the bounding extent of the District Boundary layer (if present).\n * Searches the overlay group for a vector layer titled \"District Boundary\"\n * and returns the extent of its source.\n *\n * @returns {{ extent: Array, title: string } | null}\n */\n getDistrictBoundaryExtent() {\n let found = null;\n const visit = (group) => {\n group.getLayers().forEach((layer) => {\n if (layer.getLayers) {\n visit(layer); // sub-group\n } else if (layer.get('title') === 'District Boundary') {\n const src = layer.getSource && layer.getSource();\n if (src && typeof src.getExtent === 'function') {\n const ex = src.getExtent();\n if (ex && Number.isFinite(ex[0])) {\n found = { extent: ex, title: layer.get('title') };\n }\n }\n }\n });\n };\n visit(this.overlayGroup);\n return found;\n }\n\n /**\n * Get the marker source for advanced usage\n */\n getMarkerSource() {\n return this.markerSource;\n }\n\n /**\n * Get the markers layer for advanced usage\n */\n getMarkersLayer() {\n return this.markersLayer;\n }\n\n /**\n * Update map size (call after container resize)\n */\n updateSize() {\n this.map.updateSize();\n }\n\n /**\n * Register a callback for when a search result is selected\n * Callback receives: { coordinate, lonLat: [lon, lat], name, searchResult }\n * Navigation to the location happens automatically\n */\n onSearchSelect(callback) {\n this.searchSelectCallbacks.push(callback);\n }\n\n /**\n * Navigate/fly to a specific location\n * @param {number} lon - Longitude\n * @param {number} lat - Latitude\n * @param {number} zoom - Zoom level (default: 14)\n * @param {number} duration - Animation duration in ms (default: 500)\n */\n navigateTo(lon, lat, zoom = 14, duration = 500) {\n const coordinate = fromLonLat([lon, lat]);\n this.map.getView().animate({\n center: coordinate,\n zoom: zoom,\n duration: duration,\n });\n }\n}\n\n// Export OpenLayers utilities for convenience\nexport { fromLonLat, toLonLat };\n\nexport default MapView;\n","/**\n * MapTools - Drawing and Measurement Tools\n * \n * Provides:\n * - Circle measurement tool with radius/area tooltip\n * - Control bar with drawing tools\n * - Line and polygon measurement\n * \n * Refactored from olmapstuffgis.js for LUPMIS PWA\n */\n\nimport { Draw } from 'ol/interaction';\nimport { Circle as CircleStyle, Fill, Stroke, Style } from 'ol/style';\nimport { Vector as VectorLayer } from 'ol/layer';\nimport { Vector as VectorSource } from 'ol/source';\nimport Overlay from 'ol/Overlay';\nimport { LineString, Circle, Polygon } from 'ol/geom';\nimport { getLength, getArea } from 'ol/sphere';\nimport Feature from 'ol/Feature';\nimport { unByKey } from 'ol/Observable';\nimport { formatLength, formatArea, formatCircleExtent } from '../units.js';\n\n// ol-ext imports\nimport EditBar from 'ol-ext/control/EditBar';\nimport Toggle from 'ol-ext/control/Toggle';\nimport Button from 'ol-ext/control/Button';\n\nexport class MapTools {\n constructor(map, options = {}) {\n this.map = map;\n this.options = options;\n \n // Create measurement layer\n this.measureSource = new VectorSource();\n this.measureLayer = new VectorLayer({\n source: this.measureSource,\n style: this.getMeasureStyle(),\n title: 'Measurements',\n zIndex: 100,\n // Measurements are read-only. Without this the Digitise tool treats a\n // measured circle like any other geometry: it shows vertex handles, and\n // dragging one silently reshapes the measurement — the intersection test\n // follows the new shape while the reported radius and area do not.\n selectable: false,\n });\n\n // Create drawing layer (utility layer for temporary draw interactions;\n // hidden from LayerSwitcher since the EditBar has its own \"Drawings\" group)\n this.drawSource = new VectorSource();\n this.drawLayer = new VectorLayer({\n source: this.drawSource,\n style: this.getDrawStyle(),\n title: 'Draw sketches',\n displayInLayerSwitcher: false,\n zIndex: 99,\n });\n\n // Insert both layers just before the Overlays group so the LayerSwitcher\n // order becomes: Overlays > Measurements > Markers > Base Maps. Locate the\n // Overlays group by title rather than assuming it is the last layer —\n // other layers (e.g. the GPS trail/position layers) may sit on top of it.\n const layers = this.map.getLayers();\n let overlayIdx = layers.getArray().findIndex((l) => l.get('title') === 'Overlays');\n if (overlayIdx < 0) overlayIdx = layers.getLength();\n layers.insertAt(overlayIdx, this.drawLayer);\n layers.insertAt(overlayIdx, this.measureLayer);\n \n // Active interaction\n this.activeInteraction = null;\n this.measureTooltip = null;\n this.measureTooltipElement = null;\n \n // Callbacks\n this.onMeasureCompleteCallbacks = [];\n this.onDrawCompleteCallbacks = [];\n }\n \n /**\n * Get style for measurement features\n */\n getMeasureStyle() {\n return new Style({\n fill: new Fill({\n color: 'rgba(255, 233, 106, 0.2)'\n }),\n stroke: new Stroke({\n color: '#8B008B',\n lineDash: [10, 10],\n width: 2\n }),\n image: new CircleStyle({\n radius: 5,\n stroke: new Stroke({\n color: '#8B008B'\n }),\n fill: new Fill({\n color: 'rgba(255, 233, 106, 0.5)'\n })\n })\n });\n }\n \n /**\n * Get style for drawing features\n */\n getDrawStyle() {\n return new Style({\n fill: new Fill({\n color: 'rgba(255, 233, 106, 0.3)'\n }),\n stroke: new Stroke({\n color: '#8B008B',\n width: 2\n }),\n image: new CircleStyle({\n radius: 6,\n stroke: new Stroke({\n color: '#8B008B',\n width: 2\n }),\n fill: new Fill({\n color: '#FFE96A'\n })\n })\n });\n }\n \n /**\n * Create measurement tooltip overlay\n */\n createMeasureTooltip() {\n if (this.measureTooltipElement) {\n this.measureTooltipElement.parentNode.removeChild(this.measureTooltipElement);\n }\n this.measureTooltipElement = document.createElement('div');\n this.measureTooltipElement.className = 'measure-tooltip';\n this.measureTooltip = new Overlay({\n element: this.measureTooltipElement,\n offset: [15, 0],\n positioning: 'center-left',\n stopEvent: false,\n });\n this.map.addOverlay(this.measureTooltip);\n }\n \n /**\n * Remove any active interaction\n */\n deactivate() {\n if (this.activeInteraction) {\n this.map.removeInteraction(this.activeInteraction);\n this.activeInteraction = null;\n }\n if (this.measureTooltip) {\n this.map.removeOverlay(this.measureTooltip);\n this.measureTooltip = null;\n }\n if (this.measureTooltipElement && this.measureTooltipElement.parentNode) {\n this.measureTooltipElement.parentNode.removeChild(this.measureTooltipElement);\n this.measureTooltipElement = null;\n }\n }\n \n /**\n * Start circle measurement tool\n * Draws a circle and shows radius + area in tooltip\n */\n startCircleMeasure() {\n this.deactivate();\n this.createMeasureTooltip();\n \n const drawCircle = new Draw({\n source: this.measureSource,\n type: 'Circle',\n style: new Style({\n fill: new Fill({\n color: 'rgba(255, 233, 106, 0.2)'\n }),\n stroke: new Stroke({\n color: 'rgba(139, 0, 139, 0.7)',\n lineDash: [10, 10],\n width: 2\n }),\n image: new CircleStyle({\n radius: 5,\n stroke: new Stroke({\n color: 'rgba(139, 0, 139, 0.7)'\n }),\n fill: new Fill({\n color: 'rgba(255, 233, 106, 0.5)'\n })\n })\n })\n });\n \n this.activeInteraction = drawCircle;\n this.map.addInteraction(drawCircle);\n \n let listener;\n \n drawCircle.on('drawstart', (evt) => {\n const sketch = evt.feature;\n \n listener = sketch.getGeometry().on('change', (e) => {\n const geom = e.target;\n \n if (geom instanceof Circle) {\n const radius = geom.getRadius();\n const area = formatCircleExtent(radius);\n const radiusFormatted = formatLength(radius);\n \n const output = `${radiusFormatted}
            ${area}`;\n \n this.measureTooltipElement.innerHTML = output;\n this.measureTooltip.setPosition(geom.getLastCoordinate());\n }\n });\n });\n \n drawCircle.on('drawend', (evt) => {\n const feature = evt.feature;\n const geom = feature.getGeometry();\n const center = geom.getCenter();\n const radius = geom.getRadius();\n\n // Tag the circle feature so the dblclick handler can identify it\n feature.set('_layerType', 'measure_circle');\n // Snapshot only — NOT the source of truth. Anything reporting the radius\n // must read it from the geometry (getRadius()), otherwise the figure can\n // drift from the shape actually being analysed.\n feature.set('_radius', radius);\n feature.set('_center', center);\n\n // Create radius line for visualization\n const radiusLine = new Feature({\n geometry: new LineString([\n center,\n [center[0] + radius, center[1]]\n ])\n });\n radiusLine.set('_layerType', 'measure_circle_radius');\n this.measureSource.addFeature(radiusLine);\n \n // Make tooltip static\n this.measureTooltipElement.className = 'measure-tooltip measure-tooltip-static';\n this.measureTooltip.setOffset([0, -7]);\n \n // Create new tooltip for next measurement\n this.measureTooltipElement = null;\n this.createMeasureTooltip();\n \n unByKey(listener);\n \n // Trigger callbacks\n const result = {\n type: 'circle',\n center: center,\n radius: radius,\n area: Math.PI * radius * radius,\n feature: feature,\n };\n this.onMeasureCompleteCallbacks.forEach(cb => cb(result));\n });\n \n return drawCircle;\n }\n \n /**\n * Start line measurement tool\n */\n startLineMeasure() {\n this.deactivate();\n this.createMeasureTooltip();\n \n const drawLine = new Draw({\n source: this.measureSource,\n type: 'LineString',\n style: this.getMeasureStyle(),\n });\n \n this.activeInteraction = drawLine;\n this.map.addInteraction(drawLine);\n \n let listener;\n \n drawLine.on('drawstart', (evt) => {\n const sketch = evt.feature;\n \n listener = sketch.getGeometry().on('change', (e) => {\n const geom = e.target;\n const length = getLength(geom);\n const output = formatLength(length);\n \n this.measureTooltipElement.innerHTML = output;\n this.measureTooltip.setPosition(geom.getLastCoordinate());\n });\n });\n \n drawLine.on('drawend', (evt) => {\n const feature = evt.feature;\n const geom = feature.getGeometry();\n const length = getLength(geom);\n \n this.measureTooltipElement.className = 'measure-tooltip measure-tooltip-static';\n this.measureTooltipElement = null;\n this.createMeasureTooltip();\n \n unByKey(listener);\n \n const result = {\n type: 'line',\n length: length,\n feature: feature,\n };\n this.onMeasureCompleteCallbacks.forEach(cb => cb(result));\n });\n \n return drawLine;\n }\n \n /**\n * Start polygon/area measurement tool\n */\n startAreaMeasure() {\n this.deactivate();\n this.createMeasureTooltip();\n \n const drawPolygon = new Draw({\n source: this.measureSource,\n type: 'Polygon',\n style: this.getMeasureStyle(),\n });\n \n this.activeInteraction = drawPolygon;\n this.map.addInteraction(drawPolygon);\n \n let listener;\n \n drawPolygon.on('drawstart', (evt) => {\n const sketch = evt.feature;\n \n listener = sketch.getGeometry().on('change', (e) => {\n const geom = e.target;\n const area = getArea(geom);\n const output = formatArea(area);\n \n this.measureTooltipElement.innerHTML = output;\n this.measureTooltip.setPosition(geom.getInteriorPoint().getCoordinates());\n });\n });\n \n drawPolygon.on('drawend', (evt) => {\n const feature = evt.feature;\n const geom = feature.getGeometry();\n const area = getArea(geom);\n\n // Tag so the double-click handler can identify it\n feature.set('_layerType', 'measure_area');\n feature.set('_area', area);\n\n this.measureTooltipElement.className = 'measure-tooltip measure-tooltip-static';\n this.measureTooltipElement = null;\n this.createMeasureTooltip();\n\n unByKey(listener);\n\n const result = {\n type: 'polygon',\n area: area,\n feature: feature,\n coordinate: geom.getInteriorPoint().getCoordinates(),\n };\n this.onMeasureCompleteCallbacks.forEach(cb => cb(result));\n });\n \n return drawPolygon;\n }\n \n /**\n * Start point drawing tool\n */\n startDrawPoint() {\n this.deactivate();\n \n const drawPoint = new Draw({\n source: this.drawSource,\n type: 'Point',\n style: this.getDrawStyle(),\n });\n \n this.activeInteraction = drawPoint;\n this.map.addInteraction(drawPoint);\n \n drawPoint.on('drawend', (evt) => {\n const result = {\n type: 'point',\n feature: evt.feature,\n };\n this.onDrawCompleteCallbacks.forEach(cb => cb(result));\n });\n \n return drawPoint;\n }\n \n /**\n * Start line drawing tool\n */\n startDrawLine() {\n this.deactivate();\n \n const drawLine = new Draw({\n source: this.drawSource,\n type: 'LineString',\n style: this.getDrawStyle(),\n });\n \n this.activeInteraction = drawLine;\n this.map.addInteraction(drawLine);\n \n drawLine.on('drawend', (evt) => {\n const result = {\n type: 'line',\n feature: evt.feature,\n };\n this.onDrawCompleteCallbacks.forEach(cb => cb(result));\n });\n \n return drawLine;\n }\n \n /**\n * Start polygon drawing tool\n */\n startDrawPolygon() {\n this.deactivate();\n \n const drawPolygon = new Draw({\n source: this.drawSource,\n type: 'Polygon',\n style: this.getDrawStyle(),\n });\n \n this.activeInteraction = drawPolygon;\n this.map.addInteraction(drawPolygon);\n \n drawPolygon.on('drawend', (evt) => {\n const result = {\n type: 'polygon',\n feature: evt.feature,\n };\n this.onDrawCompleteCallbacks.forEach(cb => cb(result));\n });\n \n return drawPolygon;\n }\n \n /**\n * Clear all measurements\n */\n clearMeasurements() {\n this.measureSource.clear();\n // Remove static tooltips\n const tooltips = document.querySelectorAll('.measure-tooltip-static');\n tooltips.forEach(el => el.parentNode.removeChild(el));\n }\n \n /**\n * Clear all drawings\n */\n clearDrawings() {\n this.drawSource.clear();\n }\n \n /**\n * Clear all (measurements + drawings)\n */\n clearAll() {\n this.clearMeasurements();\n this.clearDrawings();\n }\n \n /**\n * Register callback for measurement completion\n */\n onMeasureComplete(callback) {\n this.onMeasureCompleteCallbacks.push(callback);\n }\n \n /**\n * Register callback for drawing completion\n */\n onDrawComplete(callback) {\n this.onDrawCompleteCallbacks.push(callback);\n }\n \n /**\n * Create a control bar with measurement and drawing tools\n * Returns the ol-ext Bar control\n */\n createControlBar(options = {}) {\n const position = options.position || 'top-left';\n \n // Main control bar\n const mainBar = new EditBar({\n group: true,\n className: 'map-tools-bar',\n });\n \n // Measurement toggle group\n const measureBar = new EditBar({\n toggleOne: true,\n group: true,\n });\n \n // Circle measure button\n const circleBtn = new Toggle({\n html: '',\n title: 'Measure Circle (radius & area)',\n className: 'measure-circle-btn',\n onToggle: (active) => {\n if (active) {\n this.startCircleMeasure();\n } else {\n this.deactivate();\n }\n }\n });\n measureBar.addControl(circleBtn);\n \n // Line measure button\n const lineBtn = new Toggle({\n html: '📏',\n title: 'Measure Distance',\n className: 'measure-line-btn',\n onToggle: (active) => {\n if (active) {\n this.startLineMeasure();\n } else {\n this.deactivate();\n }\n }\n });\n measureBar.addControl(lineBtn);\n \n // Area measure button\n const areaBtn = new Toggle({\n html: '',\n title: 'Measure Area',\n className: 'measure-area-btn',\n onToggle: (active) => {\n if (active) {\n this.startAreaMeasure();\n } else {\n this.deactivate();\n }\n }\n });\n measureBar.addControl(areaBtn);\n \n // Clear measurements button\n const clearBtn = new Button({\n html: '🗑️',\n title: 'Clear Measurements',\n className: 'clear-measure-btn',\n handleClick: () => {\n this.clearMeasurements();\n // Deactivate any active toggle\n circleBtn.setActive(false);\n lineBtn.setActive(false);\n areaBtn.setActive(false);\n }\n });\n measureBar.addControl(clearBtn);\n \n mainBar.addControl(measureBar);\n \n return mainBar;\n }\n \n /**\n * Get the measure layer\n */\n getMeasureLayer() {\n return this.measureLayer;\n }\n \n /**\n * Get the draw layer\n */\n getDrawLayer() {\n return this.drawLayer;\n }\n \n /**\n * Get the measure source\n */\n getMeasureSource() {\n return this.measureSource;\n }\n \n /**\n * Get the draw source\n */\n getDrawSource() {\n return this.drawSource;\n }\n \n /**\n * Check if any tool is currently active\n */\n isActive() {\n return this.activeInteraction !== null;\n }\n}\n\nexport default MapTools;\n","/**\n * PWA Module\n * \n * Handles Progressive Web App functionality:\n * - Service Worker registration\n * - Install prompt handling\n * - Offline detection\n * - Update notifications\n * \n * Note: The Service Worker (sw.js) handles caching.\n * The SharedWorker (shared-db-worker.js) handles database.\n * They are separate workers with different purposes.\n */\n\n// ============================================================================\n// Service Worker Registration\n// ============================================================================\n\nlet swRegistration = null;\n\nexport async function registerServiceWorker() {\n if (!('serviceWorker' in navigator)) {\n console.warn('[PWA] Service Workers not supported');\n return null;\n }\n \n try {\n swRegistration = await navigator.serviceWorker.register('/sw.js', {\n scope: '/'\n });\n \n console.log('[PWA] Service Worker registered:', swRegistration.scope);\n \n // Handle updates\n swRegistration.addEventListener('updatefound', () => {\n const newWorker = swRegistration.installing;\n \n newWorker.addEventListener('statechange', () => {\n if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {\n // New version available\n console.log('[PWA] New version available');\n showUpdateNotification();\n }\n });\n });\n \n return swRegistration;\n \n } catch (error) {\n console.error('[PWA] Service Worker registration failed:', error);\n return null;\n }\n}\n\n// ============================================================================\n// Install Prompt\n// ============================================================================\n\nlet deferredPrompt = null;\nlet installButton = null;\n\n/**\n * Initialize install prompt handling\n * @param {string|HTMLElement} buttonSelector - Button element or selector\n */\nexport function initInstallPrompt(buttonSelector = '#install-btn') {\n installButton = typeof buttonSelector === 'string' \n ? document.querySelector(buttonSelector)\n : buttonSelector;\n \n if (!installButton) {\n console.warn('[PWA] Install button not found:', buttonSelector);\n return;\n }\n \n // Initially hide the button\n installButton.style.display = 'none';\n \n // Listen for the beforeinstallprompt event\n window.addEventListener('beforeinstallprompt', (e) => {\n e.preventDefault();\n deferredPrompt = e;\n \n // Show the install button\n installButton.style.display = 'block';\n console.log('[PWA] Install prompt ready');\n });\n \n // Handle install button click\n installButton.addEventListener('click', async () => {\n if (!deferredPrompt) {\n // Show manual instructions for Safari\n showManualInstallInstructions();\n return;\n }\n \n deferredPrompt.prompt();\n const { outcome } = await deferredPrompt.userChoice;\n \n console.log('[PWA] Install prompt outcome:', outcome);\n \n deferredPrompt = null;\n installButton.style.display = 'none';\n });\n \n // Hide button if app is already installed\n window.addEventListener('appinstalled', () => {\n console.log('[PWA] App installed');\n deferredPrompt = null;\n installButton.style.display = 'none';\n });\n \n // Check if running as installed PWA\n if (window.matchMedia('(display-mode: standalone)').matches) {\n installButton.style.display = 'none';\n }\n}\n\nfunction showManualInstallInstructions() {\n const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);\n const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n \n let message = 'To install this app:\\n\\n';\n \n if (isIOS) {\n message += '1. Tap the Share button (square with arrow)\\n';\n message += '2. Scroll down and tap \"Add to Home Screen\"';\n } else if (isSafari) {\n message += '1. Click File menu\\n';\n message += '2. Click \"Add to Dock\"';\n } else {\n message += '1. Click the menu button (three dots)\\n';\n message += '2. Click \"Install\" or \"Add to Home Screen\"';\n }\n \n alert(message);\n}\n\n// ============================================================================\n// Offline Detection\n// ============================================================================\n\nlet offlineIndicator = null;\nconst offlineListeners = new Set();\n\n/**\n * Initialize offline detection\n * @param {string|HTMLElement} indicatorSelector - Element to show when offline\n */\nexport function initOfflineDetection(indicatorSelector = '#offline-indicator') {\n offlineIndicator = typeof indicatorSelector === 'string'\n ? document.querySelector(indicatorSelector)\n : indicatorSelector;\n \n // Set initial state\n updateOfflineUI(!navigator.onLine);\n \n // Listen for online/offline events\n window.addEventListener('online', () => {\n console.log('[PWA] Back online');\n updateOfflineUI(false);\n notifyOfflineListeners(false);\n });\n \n window.addEventListener('offline', () => {\n console.log('[PWA] Gone offline');\n updateOfflineUI(true);\n notifyOfflineListeners(true);\n });\n}\n\nfunction updateOfflineUI(isOffline) {\n if (offlineIndicator) {\n offlineIndicator.style.display = isOffline ? 'block' : 'none';\n }\n \n // Also toggle a class on body for CSS styling\n document.body.classList.toggle('is-offline', isOffline);\n}\n\n/**\n * Subscribe to offline state changes\n * @param {Function} listener - Callback(isOffline: boolean)\n * @returns {Function} Unsubscribe function\n */\nexport function onOfflineChange(listener) {\n offlineListeners.add(listener);\n // Immediately call with current state\n listener(!navigator.onLine);\n return () => offlineListeners.delete(listener);\n}\n\nfunction notifyOfflineListeners(isOffline) {\n for (const listener of offlineListeners) {\n try {\n listener(isOffline);\n } catch (e) {\n console.error('[PWA] Offline listener error:', e);\n }\n }\n}\n\n/**\n * Check if currently online\n */\nexport function isOnline() {\n return navigator.onLine;\n}\n\n// ============================================================================\n// Update Handling\n// ============================================================================\n\nlet updateCallback = null;\n\n/**\n * Set callback for when updates are available\n * @param {Function} callback - Called when new version is ready\n */\nexport function onUpdateAvailable(callback) {\n updateCallback = callback;\n}\n\nfunction showUpdateNotification() {\n if (updateCallback) {\n updateCallback();\n return;\n }\n \n // Default behavior\n if (confirm('A new version is available. Reload now?')) {\n applyUpdate();\n }\n}\n\n/**\n * Apply pending update (reload with new version)\n */\nexport function applyUpdate() {\n if (swRegistration?.waiting) {\n swRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });\n }\n window.location.reload();\n}\n\n// ============================================================================\n// Communication with Service Worker\n// ============================================================================\n\n/**\n * Send a message to the service worker\n * @param {Object} message - Message to send\n */\nexport function postToServiceWorker(message) {\n navigator.serviceWorker.controller?.postMessage(message);\n}\n\n/**\n * Request the service worker to cache specific modules\n * @param {string[]} moduleNames - Array of module names to cache\n */\nexport function cacheModules(moduleNames) {\n postToServiceWorker({\n type: 'CACHE_MODULES',\n payload: { modules: moduleNames }\n });\n}\n\n/**\n * Request the service worker to clear user-specific caches\n * (Call this on logout)\n */\nexport function clearUserCaches() {\n postToServiceWorker({\n type: 'CLEAR_USER_CACHE'\n });\n}\n\n/**\n * Get the Service Worker we can postMessage to. Resolves with:\n * • `navigator.serviceWorker.controller` if it's already in control of the\n * page (fastest path), or\n * • `registration.active` once `navigator.serviceWorker.ready` resolves\n * (covers the first-load case before the SW has claimed the page).\n *\n * Rejects after `timeoutMs` if no SW becomes available — which would only\n * happen in a private/incognito context, an unsupported browser, or when\n * registration genuinely failed.\n *\n * @param {{ timeoutMs?: number }} [opts]\n * @returns {Promise}\n */\nexport async function getActiveServiceWorker({ timeoutMs = 10000 } = {}) {\n if (!('serviceWorker' in navigator)) {\n throw new Error('Service Workers not supported in this browser');\n }\n\n // Fastest path — page is already SW-controlled\n if (navigator.serviceWorker.controller) {\n return navigator.serviceWorker.controller;\n }\n\n // Otherwise wait for the registration to become ready (active SW exists\n // for this scope, even if it hasn't claimed THIS page yet)\n const ready = navigator.serviceWorker.ready;\n const timeout = new Promise((_, reject) =>\n setTimeout(() => reject(new Error('Service-worker readiness timeout')), timeoutMs)\n );\n\n const registration = await Promise.race([ready, timeout]);\n\n // The controller may have appeared while we were waiting; otherwise use\n // the registration's active worker (we can still postMessage to it — caches\n // are shared across the origin)\n const sw = navigator.serviceWorker.controller || registration.active;\n if (!sw) {\n throw new Error('No active service worker available');\n }\n return sw;\n}\n\n/**\n * Subscribe to controller-change events. The callback fires whenever a new\n * Service Worker takes control of the page (e.g. after an SW update or first\n * activation on initial load). Useful for re-querying SW-backed state once\n * the SW has actually taken over.\n *\n * @param {() => void} callback\n * @returns {() => void} unsubscribe function\n */\nexport function onServiceWorkerControllerChange(callback) {\n if (!('serviceWorker' in navigator)) return () => {};\n const handler = () => {\n try { callback(); } catch (e) { console.error('[PWA] controllerchange handler error:', e); }\n };\n navigator.serviceWorker.addEventListener('controllerchange', handler);\n return () => navigator.serviceWorker.removeEventListener('controllerchange', handler);\n}\n\n/**\n * Send a message to the service worker and wait for a single reply with the\n * given response type. Waits for the SW to become available if it isn't yet.\n * Resolves with the reply payload, or rejects after a timeout.\n *\n * @template T\n * @param {string} requestType - Message type to send (e.g. 'GET_TILE_STATS')\n * @param {string} responseType - Message type expected back (e.g. 'TILE_STATS')\n * @param {Object} [extra={}] - Extra fields merged into the outgoing message\n * @param {number} [timeoutMs=5000] Reply timeout (after the SW is available)\n * @param {number} [readyTimeoutMs=10000] Timeout for the SW to be available\n * @returns {Promise}\n */\nasync function requestFromServiceWorker(requestType, responseType, extra = {}, timeoutMs = 5000, readyTimeoutMs = 10000) {\n const sw = await getActiveServiceWorker({ timeoutMs: readyTimeoutMs });\n\n return new Promise((resolve, reject) => {\n const channel = new MessageChannel();\n const timer = setTimeout(() => {\n channel.port1.close();\n reject(new Error(`Service-worker reply \"${responseType}\" timed out`));\n }, timeoutMs);\n\n channel.port1.onmessage = (event) => {\n if (event.data?.type === responseType) {\n clearTimeout(timer);\n channel.port1.close();\n const { type, ...rest } = event.data;\n resolve(rest);\n }\n };\n\n sw.postMessage({ type: requestType, ...extra }, [channel.port2]);\n });\n}\n\n/**\n * Get statistics about tiles cached locally on this device, broken down by\n * provider. Waits up to `readyTimeoutMs` for the service worker to become\n * available. Returns null only if the SW genuinely cannot be reached\n * (private mode, registration failure, or timeout).\n *\n * @returns {Promise<{\n * totals: { count: number, estBytes: number },\n * byProvider: Array<{ key: string, label: string, count: number, limit: number, estBytes: number }>\n * } | null>}\n */\nexport async function getTileCacheStats() {\n try {\n const reply = await requestFromServiceWorker('GET_TILE_STATS', 'TILE_STATS');\n return reply.stats;\n } catch (err) {\n console.warn('[PWA] getTileCacheStats failed:', err);\n return null;\n }\n}\n\n/**\n * Delete every cached tile from this device. Doesn't touch the app shell,\n * modules, or API caches — only the per-provider tile buckets.\n * Waits for the SW to be available before sending the request.\n *\n * @returns {Promise} true if the request was acknowledged\n */\nexport async function clearTileCaches() {\n try {\n await requestFromServiceWorker('CLEAR_TILE_CACHES', 'TILE_CACHES_CLEARED');\n return true;\n } catch (err) {\n console.warn('[PWA] clearTileCaches failed:', err);\n return false;\n }\n}\n\n/**\n * Delete cached tiles for a single provider. `cacheName` must match one of\n * the per-provider caches reported by `getTileCacheStats()` (e.g.\n * `tiles-osm-v4`, `tiles-topo-v4`). Unknown names are rejected by the SW.\n *\n * @param {string} cacheName\n * @returns {Promise} true if the cache was actually deleted\n */\nexport async function clearTileCacheForProvider(cacheName) {\n if (!cacheName) return false;\n try {\n const reply = await requestFromServiceWorker(\n 'CLEAR_TILE_CACHE', 'TILE_CACHE_CLEARED',\n { cacheName },\n );\n return !!reply.deleted;\n } catch (err) {\n console.warn(`[PWA] clearTileCacheForProvider(${cacheName}) failed:`, err);\n return false;\n }\n}\n\n/**\n * Get total disk used by this origin (Cache API + IndexedDB + OPFS).\n * Returns null if the Storage API is not available.\n *\n * @returns {Promise<{ usage: number, quota: number } | null>}\n */\nexport async function getStorageEstimate() {\n if (!navigator.storage?.estimate) return null;\n try {\n const { usage, quota } = await navigator.storage.estimate();\n return { usage: usage || 0, quota: quota || 0 };\n } catch (err) {\n console.warn('[PWA] getStorageEstimate failed:', err);\n return null;\n }\n}\n\n// ============================================================================\n// Auto-initialization\n// ============================================================================\n\n/**\n * Initialize all PWA features\n * @param {Object} options\n */\nexport async function initPWA(options = {}) {\n const {\n installButton = '#install-btn',\n offlineIndicator = '#offline-indicator',\n autoRegisterSW = true\n } = options;\n \n if (autoRegisterSW) {\n await registerServiceWorker();\n }\n \n initInstallPrompt(installButton);\n initOfflineDetection(offlineIndicator);\n \n console.log('[PWA] Initialized');\n}\n\n// Export for direct use\nexport default {\n registerServiceWorker,\n initInstallPrompt,\n initOfflineDetection,\n initPWA,\n isOnline,\n onOfflineChange,\n onUpdateAvailable,\n applyUpdate,\n postToServiceWorker,\n cacheModules,\n clearUserCaches,\n getTileCacheStats,\n clearTileCaches,\n clearTileCacheForProvider,\n getStorageEstimate,\n getActiveServiceWorker,\n onServiceWorkerControllerChange,\n};\n","/**\n * Offline Tile Downloader\n *\n * Pre-fetches map tiles for a given extent and zoom range so they are stored\n * in the Service Worker's per-host tile cache for offline use.\n *\n * The downloader simply issues `fetch()` calls; the existing SW intercepts\n * them and routes to the right cache bucket. No direct Cache API access is\n * needed here — the SW is the single source of truth for storage.\n *\n * Throttling defaults are conservative to respect tile-server usage policies:\n * • 2 concurrent requests\n * • 50 ms inter-batch delay\n * • Standard browser User-Agent / Referer headers\n *\n * Usage:\n * const downloader = new OfflineTileDownloader({\n * baseMap: 'topo',\n * extent3857: [minX, minY, maxX, maxY], // EPSG:3857\n * minZoom: 10,\n * maxZoom: 15,\n * onProgress: (s) => console.log(s),\n * });\n * await downloader.start();\n * downloader.cancel(); // any time\n */\n\n// ============================================================================\n// Base-map URL templates\n// ============================================================================\n\n/**\n * Tile URL templates for base maps that may be downloaded for offline use.\n *\n * The SW recognises these hosts in `getTileCacheName()` and routes them to\n * the matching `tiles-*-vN` cache. If you add a new entry here, also add\n * the host to the SW's classifier or the tiles will not be cached.\n */\nexport const BASEMAP_TEMPLATES = {\n topo: {\n url: 'https://a.tile.opentopomap.org/{z}/{x}/{y}.png',\n label: 'Topographic',\n maxZoom: 17,\n cacheKey: 'tiles-topo',\n },\n osm: {\n url: 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',\n label: 'OpenStreetMap',\n maxZoom: 19,\n cacheKey: 'tiles-osm',\n },\n};\n\n// Approximate bytes per raster tile — used for storage estimates.\nexport const AVG_TILE_BYTES = 30 * 1024;\n\n// ============================================================================\n// Tile coordinate math (Web Mercator XYZ scheme)\n// ============================================================================\n\nconst ORIGIN_SHIFT = 2 * Math.PI * 6378137 / 2; // 20037508.342789244\n\n/** Convert Web Mercator metres → (lon, lat) in degrees. */\nfunction metersToLonLat(x, y) {\n const lon = (x / ORIGIN_SHIFT) * 180;\n let lat = (y / ORIGIN_SHIFT) * 180;\n lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2);\n return [lon, lat];\n}\n\n/** Tile (x, y) in XYZ scheme for a given lon/lat at zoom z. */\nfunction lonLatToTile(lon, lat, z) {\n const n = Math.pow(2, z);\n const x = Math.floor((lon + 180) / 360 * n);\n const latRad = lat * Math.PI / 180;\n const y = Math.floor(\n (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n\n );\n return { x, y };\n}\n\n/** Tile range covering an EPSG:3857 extent at a given zoom level. */\nexport function tileRangeForExtent(extent3857, z) {\n const [minX, minY, maxX, maxY] = extent3857;\n const [minLon, minLat] = metersToLonLat(minX, minY);\n const [maxLon, maxLat] = metersToLonLat(maxX, maxY);\n\n const tl = lonLatToTile(minLon, maxLat, z); // top-left in XYZ (NW)\n const br = lonLatToTile(maxLon, minLat, z); // bottom-right (SE)\n\n const n = Math.pow(2, z);\n const minTileX = Math.max(0, Math.min(tl.x, br.x));\n const maxTileX = Math.min(n - 1, Math.max(tl.x, br.x));\n const minTileY = Math.max(0, Math.min(tl.y, br.y));\n const maxTileY = Math.min(n - 1, Math.max(tl.y, br.y));\n\n return {\n z,\n minX: minTileX, maxX: maxTileX,\n minY: minTileY, maxY: maxTileY,\n count: (maxTileX - minTileX + 1) * (maxTileY - minTileY + 1),\n };\n}\n\n/** Total tile count for an extent across a zoom range (inclusive). */\nexport function countTiles(extent3857, minZ, maxZ) {\n let total = 0;\n for (let z = minZ; z <= maxZ; z++) {\n total += tileRangeForExtent(extent3857, z).count;\n }\n return total;\n}\n\n/**\n * Enumerate every tile in an extent across a zoom range.\n * Returns an array of { z, x, y } objects. For very large ranges this can be\n * large — the caller is expected to validate the count first.\n */\nexport function enumerateTiles(extent3857, minZ, maxZ) {\n const out = [];\n for (let z = minZ; z <= maxZ; z++) {\n const r = tileRangeForExtent(extent3857, z);\n for (let x = r.minX; x <= r.maxX; x++) {\n for (let y = r.minY; y <= r.maxY; y++) {\n out.push({ z, x, y });\n }\n }\n }\n return out;\n}\n\n/**\n * Format a tile URL for a given coordinate using a {z}/{x}/{y} template.\n */\nexport function formatTileUrl(template, { z, x, y }) {\n return template\n .replace('{z}', z)\n .replace('{x}', x)\n .replace('{y}', y);\n}\n\n// ============================================================================\n// OfflineTileDownloader\n// ============================================================================\n\n/**\n * Concurrent, throttled tile downloader. Issues `fetch()` per tile; the\n * service worker handles caching transparently.\n *\n * Events via `onProgress` callback:\n * { phase: 'running' | 'done' | 'cancelled' | 'error',\n * done, total, ok, failed, cached,\n * elapsedMs, etaMs }\n */\nexport class OfflineTileDownloader {\n constructor({\n baseMap, // 'topo' | 'osm'\n extent3857, // [minX, minY, maxX, maxY]\n minZoom,\n maxZoom,\n concurrency = 2, // OSM ToS-friendly default\n interBatchDelayMs = 50,\n onProgress = () => {},\n }) {\n const tpl = BASEMAP_TEMPLATES[baseMap];\n if (!tpl) throw new Error(`Unknown base map: ${baseMap}`);\n if (maxZoom > tpl.maxZoom) {\n console.warn(`[OfflineTiles] ${baseMap}: maxZoom ${maxZoom} > supported ${tpl.maxZoom}; clamping`);\n maxZoom = tpl.maxZoom;\n }\n\n this.baseMap = baseMap;\n this.template = tpl.url;\n this.extent = extent3857;\n this.minZoom = minZoom;\n this.maxZoom = maxZoom;\n this.concurrency = Math.max(1, Math.min(concurrency, 6));\n this.interBatchDelayMs = interBatchDelayMs;\n this.onProgress = onProgress;\n\n this._abortCtrl = null;\n this._cancelled = false;\n }\n\n /**\n * Begin downloading. Returns a Promise that resolves with the final stats\n * when complete, or when cancelled.\n */\n async start() {\n if (this._abortCtrl) throw new Error('Downloader already started');\n this._abortCtrl = new AbortController();\n this._cancelled = false;\n\n const tiles = enumerateTiles(this.extent, this.minZoom, this.maxZoom);\n const total = tiles.length;\n const startedAt = Date.now();\n\n let done = 0, ok = 0, failed = 0, cached = 0;\n\n const emit = (phase) => {\n const elapsedMs = Date.now() - startedAt;\n const etaMs = done > 0 ? Math.round((elapsedMs / done) * (total - done)) : null;\n this.onProgress({ phase, done, total, ok, failed, cached, elapsedMs, etaMs });\n };\n\n emit('running');\n\n // Process in chunks of `concurrency`\n for (let i = 0; i < tiles.length; i += this.concurrency) {\n if (this._cancelled) break;\n\n const batch = tiles.slice(i, i + this.concurrency);\n await Promise.all(batch.map(async (t) => {\n if (this._cancelled) return;\n const url = formatTileUrl(this.template, t);\n\n try {\n const res = await fetch(url, {\n signal: this._abortCtrl.signal,\n // Hint the SW that this is a passive prefetch\n cache: 'default',\n });\n\n if (res.ok) {\n ok++;\n // Detect \"served from SW cache\" via headers — not reliable across\n // implementations, so we just count all 200s as ok. Reading the body\n // (or cancelling it) lets the browser GC the response promptly.\n if (res.body) res.body.cancel().catch(() => {});\n } else if (res.status === 408) {\n // Our SW returns 408 when offline AND nothing cached. Treat as failed.\n failed++;\n } else {\n failed++;\n }\n } catch (err) {\n if (err.name === 'AbortError') {\n // Cancellation — don't count\n } else {\n failed++;\n }\n }\n done++;\n }));\n\n emit('running');\n\n if (this.interBatchDelayMs > 0 && i + this.concurrency < tiles.length) {\n await new Promise((r) => setTimeout(r, this.interBatchDelayMs));\n }\n }\n\n emit(this._cancelled ? 'cancelled' : 'done');\n\n return {\n phase: this._cancelled ? 'cancelled' : 'done',\n done, total, ok, failed, cached,\n elapsedMs: Date.now() - startedAt,\n };\n }\n\n /**\n * Cancel an in-flight download. Resolves on the next batch boundary.\n */\n cancel() {\n this._cancelled = true;\n if (this._abortCtrl) this._abortCtrl.abort();\n }\n}\n\n// ============================================================================\n// Predefined extents\n// ============================================================================\n\n/**\n * Whole-of-Ghana bounding box in EPSG:3857.\n * Approximate: -3.3°W → 1.2°E, 4.5°N → 11.2°N.\n */\nexport const GHANA_EXTENT_3857 = (() => {\n const lonLatToMeters = (lon, lat) => {\n const x = lon * ORIGIN_SHIFT / 180;\n const y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);\n return [x, y * ORIGIN_SHIFT / 180];\n };\n const sw = lonLatToMeters(-3.3, 4.5);\n const ne = lonLatToMeters(1.2, 11.2);\n return [sw[0], sw[1], ne[0], ne[1]];\n})();\n\n// Useful for size estimates\nexport function estimatedSizeBytes(tileCount) {\n return tileCount * AVG_TILE_BYTES;\n}\n","/**\n * Remote Database Module\n *\n * Handles all API communication with the PostgreSQL backend server.\n * Provides GET and POST methods for fetching and pushing data.\n *\n * Usage:\n * import { remoteGet, remotePost, getDistrictBoundary } from './remotedb.js';\n *\n * const boundary = await getDistrictBoundary();\n */\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n// Most endpoints live under the /spatial_planning module, but not all — the\n// contours endpoint sits in its own module. Endpoint names containing a slash\n// are resolved against API_ROOT instead (see resolveEndpoint below).\nconst API_ROOT = 'https://api.lupmis4luspa.org/api';\nconst API_BASE = `${API_ROOT}/spatial_planning`;\n\n/**\n * Build the full URL for an endpoint.\n * A bare filename (\"get_layers.php\") resolves inside /spatial_planning;\n * a path (\"contours/get_by_district.php\") resolves against the API root.\n * @param {string} endpoint\n * @returns {string}\n */\nfunction resolveEndpoint(endpoint) {\n return endpoint.includes('/') ? `${API_ROOT}/${endpoint}` : `${API_BASE}/${endpoint}`;\n}\n\n/**\n * Per-request credentials sent with every API call.\n *\n * `district_id` is resolved dynamically — when the PWA is loaded via the PHP\n * entry point (public/index.php), the SSO session is injected into the page\n * as `window.LUPMIS_SESSION` and we read the authenticated user's district\n * from there. In local development (Vite serves index.html directly without\n * PHP), the global is undefined and we fall back to the hard-coded test\n * district below.\n *\n * `api_token` is currently a single global app token — not per-user.\n */\nconst FALLBACK_DISTRICT_ID = '1';\nconst API_TOKEN = '1c46538c712e9b5b';\n\n/**\n * Returns the authenticated user's district_id.\n *\n * - No SSO session at all (window.LUPMIS_SESSION undefined): we're in local\n * development → fall back to the hard-coded test district.\n * - Session present but no district_id: the user is authenticated but not\n * assigned to any district → return null. The bootstrap in main.js detects\n * this case BEFORE any API call and shows a blocking message; this null\n * is defence-in-depth so we never silently send district_id=1 for an\n * authenticated user.\n *\n * The getter runs on each spread of API_CREDENTIALS, so a session change at\n * runtime takes effect immediately.\n */\nfunction resolveDistrictId() {\n try {\n if (typeof window === 'undefined') return FALLBACK_DISTRICT_ID;\n const session = window.LUPMIS_SESSION;\n if (!session || typeof session !== 'object') return FALLBACK_DISTRICT_ID;\n const id = session.district_id;\n if (id === null || id === undefined || String(id).length === 0) return null;\n return String(id);\n } catch { /* no-op */ }\n return FALLBACK_DISTRICT_ID;\n}\n\nconst API_CREDENTIALS = {\n get district_id() { return resolveDistrictId(); },\n api_token: API_TOKEN,\n};\n\n/**\n * Get the full session payload (or null if not authenticated).\n * Exposed for UI code that wants to display the user's name, email, etc.\n *\n * Dev-mode helper: setting `localStorage['dev-session']` to a JSON object\n * (e.g. via `lupmisDevSession({...})` in the console) overrides the real\n * session — useful when running against `localhost:5173` to test the\n * authenticated UI without standing up a PHP server.\n */\nexport function getSession() {\n // 1. Real session injected by index.php (production)\n if (typeof window !== 'undefined' && window.LUPMIS_SESSION && window.LUPMIS_SESSION.user_id) {\n return window.LUPMIS_SESSION;\n }\n // 2. Dev-mode override (developer's own localStorage tweak)\n try {\n const raw = localStorage.getItem('dev-session');\n if (raw) {\n const parsed = JSON.parse(raw);\n if (parsed && parsed.user_id) return parsed;\n }\n } catch { /* ignore */ }\n return null;\n}\n\n// Console helper — set a fake session for dev work. Reload to apply.\n// lupmisDevSession({ user_id: 42, district_id: '1', full_name: 'Test User', ... })\n// Clear it via lupmisDevSession(null) or localStorage.removeItem('dev-session').\nif (typeof window !== 'undefined') {\n window.lupmisDevSession = (payload) => {\n if (payload == null) {\n localStorage.removeItem('dev-session');\n console.log('[Dev] Session override cleared. Reload to apply.');\n } else {\n localStorage.setItem('dev-session', JSON.stringify(payload));\n console.log('[Dev] Session override saved. Reload to apply:', payload);\n }\n };\n}\n\n// ============================================================================\n// Server Reachability\n// ============================================================================\n\n/** Default timeout for API requests (ms) */\nconst REQUEST_TIMEOUT = 30_000;\n\n/** Timeout for the fast reachability probe (ms) */\nconst PING_TIMEOUT = 5_000;\n\n/** Cached result of the last reachability check */\nlet _serverReachable = null;\n\n/**\n * Quick probe to determine if the API server is responding.\n * Sends a small POST to a lightweight endpoint with a short timeout.\n * The result is cached so subsequent calls within the same page load\n * return immediately.\n *\n * @param {boolean} [force=false] - Re-check even if a cached result exists\n * @returns {Promise} true if the server responded in time\n */\nexport async function checkServerReachable(force = false) {\n if (_serverReachable !== null && !force) return _serverReachable;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), PING_TIMEOUT);\n\n try {\n const response = await fetch(`${API_BASE}/get_layers.php`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },\n body: JSON.stringify(API_CREDENTIALS),\n signal: controller.signal,\n });\n _serverReachable = response.ok;\n } catch {\n _serverReachable = false;\n } finally {\n clearTimeout(timer);\n }\n\n console.log('[RemoteDB] Server reachable:', _serverReachable);\n return _serverReachable;\n}\n\n/**\n * Returns the cached server-reachability flag (synchronous).\n * Returns null if checkServerReachable() has not been called yet.\n * @returns {boolean|null}\n */\nexport function isServerReachable() {\n return _serverReachable;\n}\n\n// ============================================================================\n// Core Request Helpers\n// ============================================================================\n\n/**\n * Create an AbortController that auto-aborts after `ms` milliseconds.\n * If the caller already supplied a signal in `options`, it is combined\n * so that either the caller's abort or the timeout will cancel the request.\n */\nfunction withTimeout(options, ms = REQUEST_TIMEOUT) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), ms);\n\n // If the caller provided their own signal, chain it\n if (options.signal) {\n options.signal.addEventListener('abort', () => controller.abort());\n }\n\n return {\n signal: controller.signal,\n clear: () => clearTimeout(timer),\n };\n}\n\n/**\n * Perform a GET request to the remote API.\n * Credentials are sent as URL query parameters.\n * Automatically times out after REQUEST_TIMEOUT ms.\n *\n * @param {string} endpoint - API endpoint filename (e.g. 'get_district_boundary.php')\n * @param {Object} [params={}] - Additional query parameters\n * @param {Object} [options={}] - Extra fetch options\n * @returns {Promise} Parsed JSON response\n */\nexport async function remoteGet(endpoint, params = {}, options = {}) {\n const url = new URL(resolveEndpoint(endpoint));\n\n // Attach credentials and any extra params as query string\n const allParams = { ...API_CREDENTIALS, ...params };\n for (const [key, value] of Object.entries(allParams)) {\n url.searchParams.set(key, value);\n }\n\n console.log('[RemoteDB] GET', url.toString());\n\n const timeout = withTimeout(options);\n try {\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers: {\n 'Accept': 'application/json'\n },\n ...options,\n signal: timeout.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = await response.json();\n console.log('[RemoteDB] GET response:', endpoint, '→', typeof data === 'object' ? `${Array.isArray(data) ? data.length + ' items' : 'object'}` : data);\n return data;\n\n } catch (error) {\n if (error.name === 'AbortError') {\n console.error('[RemoteDB] GET timed out:', endpoint);\n throw new Error(`Request timed out: ${endpoint}`);\n }\n console.error('[RemoteDB] GET failed:', endpoint, error);\n throw error;\n } finally {\n timeout.clear();\n }\n}\n\n/**\n * Perform a POST request to the remote API.\n * Credentials are included in the JSON body.\n * Automatically times out after REQUEST_TIMEOUT ms.\n *\n * @param {string} endpoint - API endpoint filename (e.g. 'some_endpoint.php')\n * @param {Object} [body={}] - Request payload (credentials are merged in)\n * @param {Object} [options={}] - Extra fetch options\n * @returns {Promise} Parsed JSON response\n */\nexport async function remotePost(endpoint, body = {}, options = {}) {\n const url = resolveEndpoint(endpoint);\n\n const payload = { ...API_CREDENTIALS, ...body };\n\n console.log('[RemoteDB] POST', url);\n\n const timeout = withTimeout(options);\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json'\n },\n body: JSON.stringify(payload),\n ...options,\n signal: timeout.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = await response.json();\n console.log('[RemoteDB] POST response:', endpoint, '→', typeof data === 'object' ? `${Array.isArray(data) ? data.length + ' items' : 'object'}` : data);\n return data;\n\n } catch (error) {\n if (error.name === 'AbortError') {\n console.error('[RemoteDB] POST timed out:', endpoint);\n throw new Error(`Request timed out: ${endpoint}`);\n }\n console.error('[RemoteDB] POST failed:', endpoint, error);\n throw error;\n } finally {\n timeout.clear();\n }\n}\n\n// ============================================================================\n// Spatial Planning Endpoints\n// ============================================================================\n\n/**\n * Fetch district boundary geometry from the server.\n *\n * @returns {Promise} District boundary GeoJSON or API response\n */\nexport async function getDistrictBoundary() {\n return remotePost('get_district_boundary.php');\n}\n\n/**\n * Fetch the list of available map layer categories from the server.\n *\n * Response format:\n * { success: true, data: [{ id, name, description, createdt, editdt }, ...] }\n *\n * @returns {Promise} Layer categories list\n */\nexport async function getLayers() {\n return remotePost('get_layers.php');\n}\n\n/**\n * Fetch all collector zones for the current district.\n *\n * Expected response:\n * { success: true, data: [{ id, zone_name, boundary: \"MULTIPOLYGON(...)\", ... }, ...] }\n *\n * @returns {Promise} Collector zones list\n */\nexport async function getCollectorZones() {\n return remotePost('get_all_collector_zone_per_district.php');\n}\n\n/**\n * Fetch all parcels for the current district.\n *\n * Expected response:\n * { success: true, data: [{ id, ..., polygon: \"POLYGON(...)\" | \"MULTIPOLYGON(...)\", ... }, ...] }\n *\n * @returns {Promise} Parcels list\n */\nexport async function getDistrictParcels() {\n return remotePost('get_parcels_per_district.php');\n}\n\n/**\n * Fetch all building footprints for the current district.\n *\n * Expected response:\n * { success: true, data: [{ id, ..., polygon: \"POLYGON(...)\" | \"MULTIPOLYGON(...)\", ... }, ...] }\n *\n * @returns {Promise} Building footprints list\n */\nexport async function getBuildingFootprints() {\n return remotePost('get_all_footprint_per_district.php');\n}\n\n/**\n * Fetch the Contours hillshade elevation layer from the server.\n *\n * Endpoint: POST /api/contours/get_by_district.php\n * Source: table `spatial.be_contours_hillshade` — elevation contours produced\n * with gdal_contour from OpenTopography's SRTM 30 m / Copernicus 30 m\n * DEM, scoped per district.\n *\n * Response rows carry the elevation in `elev` (metres) and the geometry as WKT\n * in `geom`:\n * { fid, id, elev: 180, districtid, geom: \"LINESTRING(lon lat, …)\" }\n *\n * @returns {Promise} Contours hillshade list\n */\nexport async function getContoursHillshade() {\n // This endpoint lives in its own /contours module and names the district\n // parameter `districtid` (no underscore, numeric), unlike the\n // /spatial_planning endpoints which use `district_id`. remotePost still\n // merges in the standard credentials, so api_token is sent as usual.\n const districtId = API_CREDENTIALS.district_id;\n return remotePost('contours/get_by_district.php', {\n districtid: districtId == null ? null : Number(districtId),\n });\n}\n\n/**\n * Fetch the OSM roads layer from the server.\n *\n * Source: table `pi_osm_roads` in the local PostgreSQL `public` schema\n * (imported from OpenStreetMap road network for the district).\n *\n * Expected response:\n * { success: true, data: [{ id, ..., geom: \"LINESTRING(...)\" | \"MULTILINESTRING(...)\", ... }, ...] }\n *\n * @returns {Promise} OSM roads list\n */\nexport async function getOSMRoads() {\n return remotePost('get_osm_roads.php');\n}\n\n/**\n * Fetch the UPN-grid (district sub-division) for the current district.\n *\n * The grid is static per district, so callers cache it locally and only\n * re-fetch when the user becomes associated with a different district\n * (see saveUpnGrid / getLocalUpnGrid in database.js and loadUpnGrid in\n * main.js).\n *\n * Source: spatial.upn_grid; endpoint runs ST_AsText(geom_4326) AS polygon.\n *\n * Expected response:\n * { success: true,\n * data: [{ polygon: \"POLYGON((...))\" | \"MULTIPOLYGON(((...)))\",\n * districtid: 122,\n * upn_prefix: \"AB\" }, ...] }\n *\n * @returns {Promise}\n */\nexport async function getUpnGrid() {\n return remotePost('get_upn_grid_per_district.php');\n}\n\n/**\n * Push a recorded GPS trail (with all its points) to the server.\n *\n * Implements the GeoTracker \"sync adapter\" contract: store-and-forward — the\n * whole trail is uploaded once recording stops (and retried when back online).\n * `district_id` and `api_token` are attached automatically by remotePost().\n *\n * ── SERVER SIDE (NOT YET CREATED) ───────────────────────────────────────\n * Proposed endpoint: `save_gps_trail.php`\n * Proposed PostgreSQL/PostGIS tables (SRID 4326):\n *\n * CREATE TABLE be_gps_trail (\n * id SERIAL PRIMARY KEY,\n * client_uuid TEXT UNIQUE, -- de-dupe re-syncs\n * district_id INTEGER,\n * name TEXT,\n * started_at TIMESTAMPTZ,\n * ended_at TIMESTAMPTZ,\n * point_count INTEGER,\n * distance_m DOUBLE PRECISION,\n * track GEOMETRY(LineStringZ, 4326), -- optional aggregate line\n * createdt TIMESTAMPTZ DEFAULT now()\n * );\n * CREATE TABLE be_gps_trail_point (\n * id SERIAL PRIMARY KEY,\n * trail_id INTEGER REFERENCES be_gps_trail(id) ON DELETE CASCADE,\n * seq INTEGER,\n * geom GEOMETRY(PointZ, 4326),\n * accuracy DOUBLE PRECISION,\n * altitude DOUBLE PRECISION,\n * heading DOUBLE PRECISION,\n * speed DOUBLE PRECISION,\n * satellites INTEGER, -- nullable (web has no sat count)\n * recorded_at TIMESTAMPTZ\n * );\n *\n * Request body (JSON, plus injected credentials):\n * { client_uuid, name, started_at, ended_at, point_count, distance_m,\n * points: [ { seq, longitude, latitude, altitude, accuracy,\n * altitude_accuracy, heading, speed, satellites, recorded_at } ] }\n *\n * Expected response: { success: true, id: }\n * Should be idempotent on client_uuid (INSERT ... ON CONFLICT DO UPDATE).\n * ─────────────────────────────────────────────────────────────────────────\n *\n * @param {Object} trail local trail row (client_uuid, name, started_at, …)\n * @param {Array} points local point rows (seq, longitude, latitude, …)\n * @returns {Promise<{ remoteId: (string|number|null) }>}\n */\nexport async function pushGpsTrail(trail, points) {\n const payload = {\n client_uuid: trail.client_uuid,\n name: trail.name ?? null,\n started_at: trail.started_at,\n ended_at: trail.ended_at,\n point_count: trail.point_count ?? points.length,\n distance_m: trail.distance_m ?? 0,\n points: (points || []).map((p) => ({\n seq: p.seq,\n longitude: p.longitude,\n latitude: p.latitude,\n altitude: p.altitude ?? null,\n accuracy: p.accuracy ?? null,\n altitude_accuracy: p.altitude_accuracy ?? null,\n heading: p.heading ?? null,\n speed: p.speed ?? null,\n satellites: p.satellites ?? null,\n recorded_at: p.recorded_at,\n })),\n };\n const res = await remotePost('save_gps_trail.php', payload);\n return { remoteId: res?.id ?? res?.remote_id ?? null };\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport default {\n getSession,\n checkServerReachable,\n isServerReachable,\n remoteGet,\n remotePost,\n getDistrictBoundary,\n getLayers,\n getDistrictParcels,\n getCollectorZones,\n getBuildingFootprints,\n getContoursHillshade,\n getOSMRoads,\n getUpnGrid,\n pushGpsTrail,\n};\n","/**\n * geo-utils.js — pure, dependency-free geospatial helpers for the GeoTracker\n * module. No browser APIs, no framework imports — safe to reuse anywhere\n * (including Node, web workers, or other projects).\n *\n * @module geotracker/geo-utils\n */\n\nconst EARTH_RADIUS_M = 6371008.8; // mean Earth radius (metres)\nconst DEG2RAD = Math.PI / 180;\n\n/**\n * Great-circle distance between two lon/lat points using the haversine\n * formula.\n *\n * @param {number} lon1\n * @param {number} lat1\n * @param {number} lon2\n * @param {number} lat2\n * @returns {number} distance in metres\n */\nexport function haversineMeters(lon1, lat1, lon2, lat2) {\n const dLat = (lat2 - lat1) * DEG2RAD;\n const dLon = (lon2 - lon1) * DEG2RAD;\n const a =\n Math.sin(dLat / 2) ** 2 +\n Math.cos(lat1 * DEG2RAD) * Math.cos(lat2 * DEG2RAD) * Math.sin(dLon / 2) ** 2;\n return 2 * EARTH_RADIUS_M * Math.asin(Math.min(1, Math.sqrt(a)));\n}\n\n/**\n * Total length of a polyline expressed as an array of points.\n * @param {Array<{lon:number, lat:number}>} points\n * @returns {number} metres\n */\nexport function pathLengthMeters(points) {\n let total = 0;\n for (let i = 1; i < points.length; i++) {\n total += haversineMeters(points[i - 1].lon, points[i - 1].lat, points[i].lon, points[i].lat);\n }\n return total;\n}\n\n/**\n * Format a latitude or longitude for compact display.\n * @param {number} value\n * @param {number} [decimals=5] ~1.1 m precision at 5 dp\n * @returns {string}\n */\nexport function formatCoord(value, decimals = 5) {\n if (value == null || Number.isNaN(value)) return '—';\n return value.toFixed(decimals);\n}\n\n// ---------------------------------------------------------------------------\n// UTM (Universal Transverse Mercator) — WGS84 forward projection\n//\n// Self-contained implementation of the standard USGS series-expansion forward\n// formulas (Snyder, \"Map Projections — A Working Manual\"). Accurate to a few\n// millimetres within a zone, which is far beyond what a consumer GPS fix\n// needs. Ghana spans UTM zones 30N and 31N, but this handles the whole globe.\n// No proj4 / OpenLayers dependency so it stays usable from the pure GeoTracker\n// layer and from plain display code.\n// ---------------------------------------------------------------------------\n\nconst UTM_A = 6378137.0; // WGS84 semi-major axis (metres)\nconst UTM_F = 1 / 298.257223563; // WGS84 flattening\nconst UTM_K0 = 0.9996; // UTM scale factor on the central meridian\nconst UTM_E2 = UTM_F * (2 - UTM_F); // first eccentricity squared\nconst UTM_EP2 = UTM_E2 / (1 - UTM_E2); // second eccentricity squared (e'^2)\n\n/**\n * Convert WGS84 latitude/longitude to UTM.\n *\n * @param {number} lat latitude in decimal degrees (−80 … 84 is the UTM band)\n * @param {number} lon longitude in decimal degrees (−180 … 180)\n * @returns {{zone:number, hemisphere:'N'|'S', easting:number, northing:number}|null}\n * null when the input is missing / not finite.\n */\nexport function latLonToUTM(lat, lon) {\n if (lat == null || lon == null || Number.isNaN(lat) || Number.isNaN(lon)) return null;\n\n // Normalise longitude to [−180, 180) so the zone maths is well-defined.\n let lonNorm = ((lon + 180) % 360 + 360) % 360 - 180;\n\n const zone = Math.floor((lonNorm + 180) / 6) + 1;\n const lon0 = (zone - 1) * 6 - 180 + 3; // central meridian of the zone (deg)\n\n const phi = lat * DEG2RAD;\n const lambda = lonNorm * DEG2RAD;\n const lambda0 = lon0 * DEG2RAD;\n\n const sinPhi = Math.sin(phi);\n const cosPhi = Math.cos(phi);\n const tanPhi = Math.tan(phi);\n\n const N = UTM_A / Math.sqrt(1 - UTM_E2 * sinPhi * sinPhi);\n const T = tanPhi * tanPhi;\n const C = UTM_EP2 * cosPhi * cosPhi;\n const A = cosPhi * (lambda - lambda0);\n\n // Meridional arc length from the equator to the given latitude.\n const M = UTM_A * (\n (1 - UTM_E2 / 4 - 3 * UTM_E2 ** 2 / 64 - 5 * UTM_E2 ** 3 / 256) * phi\n - (3 * UTM_E2 / 8 + 3 * UTM_E2 ** 2 / 32 + 45 * UTM_E2 ** 3 / 1024) * Math.sin(2 * phi)\n + (15 * UTM_E2 ** 2 / 256 + 45 * UTM_E2 ** 3 / 1024) * Math.sin(4 * phi)\n - (35 * UTM_E2 ** 3 / 3072) * Math.sin(6 * phi)\n );\n\n let easting = UTM_K0 * N * (\n A\n + (1 - T + C) * A ** 3 / 6\n + (5 - 18 * T + T * T + 72 * C - 58 * UTM_EP2) * A ** 5 / 120\n ) + 500000; // false easting\n\n let northing = UTM_K0 * (\n M + N * tanPhi * (\n A * A / 2\n + (5 - T + 9 * C + 4 * C * C) * A ** 4 / 24\n + (61 - 58 * T + T * T + 600 * C - 330 * UTM_EP2) * A ** 6 / 720\n )\n );\n\n const hemisphere = lat >= 0 ? 'N' : 'S';\n if (hemisphere === 'S') northing += 10000000; // false northing (southern hemi)\n\n return {\n zone,\n hemisphere,\n easting: Math.round(easting),\n northing: Math.round(northing),\n };\n}\n\n/**\n * Format a latitude/longitude as a compact UTM string for display, e.g.\n * `30N 706123E 619876N`. Returns the em-dash placeholder when the position\n * is unavailable.\n *\n * @param {number} lat\n * @param {number} lon\n * @returns {string}\n */\nexport function formatUTM(lat, lon) {\n const u = latLonToUTM(lat, lon);\n if (!u) return '—';\n return `${u.zone}${u.hemisphere} ${u.easting}E ${u.northing}N`;\n}\n\n/**\n * Format a distance in metres into a friendly string (m below 1 km, km above).\n * @param {number} meters\n * @returns {string}\n */\nexport function formatDistance(meters) {\n if (meters == null || Number.isNaN(meters)) return '—';\n if (meters < 1000) return `${Math.round(meters)} m`;\n return `${(meters / 1000).toFixed(2)} km`;\n}\n\n/**\n * Format a horizontal accuracy (metres) into a friendly ± string.\n * @param {number|null} meters\n * @returns {string}\n */\nexport function formatAccuracy(meters) {\n if (meters == null || Number.isNaN(meters)) return '—';\n return `±${Math.round(meters)} m`;\n}\n\n/**\n * Classify a horizontal accuracy into a qualitative fix-quality bucket. Useful\n * for colour-coding the UI without needing satellite count.\n * @param {number|null} meters\n * @returns {'good'|'fair'|'poor'|'none'}\n */\nexport function accuracyQuality(meters) {\n if (meters == null || Number.isNaN(meters)) return 'none';\n if (meters <= 10) return 'good';\n if (meters <= 30) return 'fair';\n return 'poor';\n}\n","/**\n * GeoTracker.js — a framework-agnostic GPS live-position + trail-recording\n * engine. It has **no** dependency on OpenLayers, Bootstrap, SQLocal, or any\n * LUPMIS code, so it can be dropped into any web project. Persistence and\n * server sync are provided by the host through small adapter objects.\n *\n * ─────────────────────────────────────────────────────────────────────────\n * STORAGE ADAPTER (required for recording) — all methods may be async:\n * createTrail(meta) -> trailId // meta: {uuid,name,startedAt,...}\n * addPoint(trailId, point) -> void // point: normalized fix (see below)\n * finishTrail(trailId, summary)-> void // summary: {endedAt,pointCount,distanceM}\n * getUnsyncedTrails() -> Array // trails with synced=0 and completed\n * getTrailPoints(trailId) -> Array\n * markTrailSynced(trailId, remoteId) -> void\n *\n * SYNC ADAPTER (optional) — store-and-forward:\n * pushTrail(trail, points) -> { remoteId } | throws\n * isOnline?() -> boolean // optional connectivity probe\n *\n * NORMALIZED FIX shape emitted on 'position' and stored via addPoint:\n * { lon, lat, accuracy, altitude, altitudeAccuracy, heading, speed,\n * satellites:null, timestamp }\n * (satellites is always null on the web Geolocation API — kept for parity\n * with native builds that can populate it.)\n * ─────────────────────────────────────────────────────────────────────────\n *\n * @module geotracker/GeoTracker\n */\n\nimport { haversineMeters } from './geo-utils.js';\n\n/** @typedef {'idle'|'watching'|'recording'} GeoTrackerState */\n\nconst DEFAULTS = {\n /** Minimum metres between two recorded trail points. */\n minDistanceM: 5,\n /** Ignore fixes arriving faster than this (throttle, ms). */\n minIntervalMs: 1000,\n /** Record a point at least this often even when stationary (heartbeat, ms). */\n heartbeatMs: 20000,\n /** Drop fixes worse than this horizontal accuracy (metres). 0 = accept all. */\n maxAccuracyM: 50,\n /** navigator.geolocation options. */\n enableHighAccuracy: true,\n timeoutMs: 15000,\n maximumAgeMs: 0,\n};\n\nexport class GeoTracker {\n /**\n * @param {object} [options]\n * @param {object} [options.storage] storage adapter (see module docs)\n * @param {object} [options.sync] sync adapter (see module docs)\n * @param {Geolocation} [options.geolocation] inject navigator.geolocation (for tests)\n * @param {number} [options.minDistanceM]\n * @param {number} [options.minIntervalMs]\n * @param {number} [options.heartbeatMs]\n * @param {number} [options.maxAccuracyM]\n * @param {boolean} [options.enableHighAccuracy]\n */\n constructor(options = {}) {\n this.opts = { ...DEFAULTS, ...options };\n this.storage = options.storage || null;\n this.sync = options.sync || null;\n this._geo = options.geolocation ||\n (typeof navigator !== 'undefined' ? navigator.geolocation : null);\n\n /** @type {GeoTrackerState} */\n this._state = 'idle';\n this._watchId = null;\n this._live = false; // live readout requested\n this._recording = false; // recording in progress\n\n this._activeTrailId = null;\n this._activeTrailUuid = null;\n this._lastRecorded = null; // last point actually written {lon,lat,timestamp}\n this._lastRecordedAt = 0;\n this._distanceM = 0;\n this._pointCount = 0;\n this._lastFix = null; // most recent normalized fix (any quality)\n\n /** @type {Record>} */\n this._listeners = Object.create(null);\n }\n\n // ── Events ────────────────────────────────────────────────────────────\n\n /**\n * Subscribe to an event. Returns an unsubscribe function.\n * Events: 'position' | 'point' | 'statechange' | 'trailstart' |\n * 'trailstop' | 'error' | 'syncstatus'\n * @param {string} event\n * @param {Function} cb\n * @returns {() => void}\n */\n on(event, cb) {\n (this._listeners[event] || (this._listeners[event] = new Set())).add(cb);\n return () => this._listeners[event]?.delete(cb);\n }\n\n _emit(event, payload) {\n const set = this._listeners[event];\n if (!set) return;\n for (const cb of set) {\n try { cb(payload); } catch (err) { console.error(`[GeoTracker] listener for \"${event}\" threw`, err); }\n }\n }\n\n // ── Public state ──────────────────────────────────────────────────────\n\n /** @returns {GeoTrackerState} */\n get state() { return this._state; }\n get isRecording() { return this._recording; }\n get lastFix() { return this._lastFix; }\n get isSupported() { return !!this._geo; }\n\n _setState(s) {\n if (this._state === s) return;\n this._state = s;\n this._emit('statechange', s);\n }\n\n // ── Live readout (watch without recording) ──────────────────────────────\n\n /**\n * Begin a position watch purely for the live readout (no trail is recorded).\n * Safe to call repeatedly.\n */\n startLive() {\n if (!this._geo) { this._emit('error', new Error('Geolocation not supported')); return; }\n this._live = true;\n this._ensureWatch();\n }\n\n /** Stop the live readout. Has no effect while a recording is in progress. */\n stopLive() {\n this._live = false;\n if (!this._recording) this._teardownWatch();\n }\n\n /**\n * One-shot position request (e.g. for a \"Locate me\" button). Resolves with a\n * normalized fix. Does not start/stop the watch.\n * @returns {Promise}\n */\n getCurrentPosition() {\n return new Promise((resolve, reject) => {\n if (!this._geo) { reject(new Error('Geolocation not supported')); return; }\n this._geo.getCurrentPosition(\n (pos) => {\n const fix = GeoTracker.normalize(pos);\n this._lastFix = fix;\n this._emit('position', fix);\n resolve(fix);\n },\n (err) => { this._emit('error', err); reject(err); },\n {\n enableHighAccuracy: this.opts.enableHighAccuracy,\n timeout: this.opts.timeoutMs,\n maximumAge: this.opts.maximumAgeMs,\n }\n );\n });\n }\n\n // ── Recording ───────────────────────────────────────────────────────────\n\n /**\n * Start recording a new trail. Creates the trail in storage, then records\n * filtered points as the device moves.\n * @param {object} [meta] e.g. { name, districtId }\n * @returns {Promise<{trailId:*, uuid:string}>}\n */\n async startRecording(meta = {}) {\n if (!this._geo) throw new Error('Geolocation not supported');\n if (!this.storage) throw new Error('GeoTracker: no storage adapter configured');\n if (this._recording) return { trailId: this._activeTrailId, uuid: this._activeTrailUuid };\n\n const uuid = GeoTracker.uuid();\n const startedAt = new Date().toISOString();\n const trailMeta = { uuid, name: meta.name || null, startedAt, ...meta };\n const trailId = await this.storage.createTrail(trailMeta);\n\n this._activeTrailId = trailId;\n this._activeTrailUuid = uuid;\n this._lastRecorded = null;\n this._lastRecordedAt = 0;\n this._distanceM = 0;\n this._pointCount = 0;\n this._recording = true;\n\n this._ensureWatch();\n this._setState('recording');\n this._emit('trailstart', { trailId, uuid, startedAt });\n return { trailId, uuid };\n }\n\n /**\n * Stop the active recording, finalise the trail summary, and (if a sync\n * adapter is present) attempt to push it immediately.\n * @returns {Promise<{trailId:*, pointCount:number, distanceM:number, synced:boolean}>}\n */\n async stopRecording() {\n if (!this._recording) return null;\n const trailId = this._activeTrailId;\n const endedAt = new Date().toISOString();\n const summary = { endedAt, pointCount: this._pointCount, distanceM: this._distanceM };\n\n this._recording = false;\n if (!this._live) this._teardownWatch();\n this._setState(this._live ? 'watching' : 'idle');\n\n try {\n await this.storage.finishTrail(trailId, summary);\n } catch (err) {\n this._emit('error', err);\n }\n this._emit('trailstop', { trailId, ...summary });\n\n let synced = false;\n if (this.sync) {\n try { synced = await this._syncTrail(trailId); }\n catch (err) { this._emit('error', err); }\n }\n\n this._activeTrailId = null;\n this._activeTrailUuid = null;\n return { trailId, pointCount: summary.pointCount, distanceM: summary.distanceM, synced };\n }\n\n // ── Sync (store-and-forward) ────────────────────────────────────────────\n\n /**\n * Push all completed-but-unsynced trails to the server via the sync adapter.\n * Call on app start and whenever connectivity returns.\n * @returns {Promise<{pushed:number, failed:number}>}\n */\n async syncPending() {\n if (!this.sync || !this.storage) return { pushed: 0, failed: 0 };\n if (this.sync.isOnline && !this.sync.isOnline()) return { pushed: 0, failed: 0 };\n\n let pushed = 0, failed = 0;\n const trails = await this.storage.getUnsyncedTrails();\n for (const trail of trails) {\n try {\n const ok = await this._syncTrail(trail.id ?? trail.trailId, trail);\n ok ? pushed++ : failed++;\n } catch (err) {\n failed++;\n this._emit('error', err);\n }\n }\n this._emit('syncstatus', { pushed, failed });\n return { pushed, failed };\n }\n\n /** @private push a single trail by id. */\n async _syncTrail(trailId, trailRow) {\n const points = await this.storage.getTrailPoints(trailId);\n const trail = trailRow || { id: trailId };\n const result = await this.sync.pushTrail(trail, points);\n const remoteId = result && (result.remoteId ?? result.id ?? null);\n await this.storage.markTrailSynced(trailId, remoteId);\n return true;\n }\n\n // ── Internal watch handling ──────────────────────────────────────────────\n\n /** @private start the geolocation watch if not already running. */\n _ensureWatch() {\n if (this._watchId != null || !this._geo) {\n if (this._state === 'idle' && this._live) this._setState('watching');\n return;\n }\n this._watchId = this._geo.watchPosition(\n (pos) => this._onFix(pos),\n (err) => this._emit('error', err),\n {\n enableHighAccuracy: this.opts.enableHighAccuracy,\n timeout: this.opts.timeoutMs,\n maximumAge: this.opts.maximumAgeMs,\n }\n );\n if (!this._recording) this._setState('watching');\n }\n\n /** @private stop the geolocation watch. */\n _teardownWatch() {\n if (this._watchId != null && this._geo) {\n this._geo.clearWatch(this._watchId);\n }\n this._watchId = null;\n }\n\n /** @private handle a raw Geolocation fix. */\n async _onFix(pos) {\n const fix = GeoTracker.normalize(pos);\n this._lastFix = fix;\n this._emit('position', fix); // always emit for the live readout\n\n if (!this._recording) return;\n\n const { minIntervalMs, minDistanceM, heartbeatMs, maxAccuracyM } = this.opts;\n const now = fix.timestamp;\n\n // Throttle very frequent fixes.\n if (this._lastRecordedAt && (now - this._lastRecordedAt) < minIntervalMs) return;\n // Drop low-quality fixes (unless this is the very first point).\n if (maxAccuracyM > 0 && fix.accuracy != null && fix.accuracy > maxAccuracyM && this._lastRecorded) return;\n\n let keep = false;\n let stepM = 0;\n if (!this._lastRecorded) {\n keep = true; // always record the first point\n } else {\n stepM = haversineMeters(this._lastRecorded.lon, this._lastRecorded.lat, fix.lon, fix.lat);\n const elapsed = now - this._lastRecordedAt;\n if (stepM >= minDistanceM || elapsed >= heartbeatMs) keep = true;\n }\n if (!keep) return;\n\n if (this._lastRecorded) this._distanceM += stepM;\n this._pointCount += 1;\n this._lastRecorded = { lon: fix.lon, lat: fix.lat, timestamp: now };\n this._lastRecordedAt = now;\n\n try {\n await this.storage.addPoint(this._activeTrailId, { ...fix, seq: this._pointCount });\n this._emit('point', {\n trailId: this._activeTrailId,\n seq: this._pointCount,\n point: fix,\n distanceM: this._distanceM,\n pointCount: this._pointCount,\n });\n } catch (err) {\n this._emit('error', err);\n }\n }\n\n // ── Static helpers ────────────────────────────────────────────────────────\n\n /** Normalize a browser GeolocationPosition into the module's fix shape. */\n static normalize(pos) {\n const c = pos.coords || {};\n const num = (v) => (v != null && !Number.isNaN(v) ? v : null);\n return {\n lon: c.longitude,\n lat: c.latitude,\n accuracy: num(c.accuracy),\n altitude: num(c.altitude),\n altitudeAccuracy: num(c.altitudeAccuracy),\n heading: num(c.heading),\n speed: num(c.speed),\n satellites: null, // not exposed by the web Geolocation API\n timestamp: pos.timestamp || Date.now(),\n };\n }\n\n /** RFC4122-ish UUID, using crypto when available. */\n static uuid() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) return crypto.randomUUID();\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (ch) => {\n const r = (Math.random() * 16) | 0;\n const v = ch === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n}\n\nexport default GeoTracker;\n","/**\n * geotracker-lupmis.js — LUPMIS2 integration layer for the reusable GeoTracker.\n *\n * This is the ONLY place that couples the generic src/geotracker/ engine to\n * LUPMIS specifics (SQLocal storage, the PHP sync endpoint, district id,\n * online checks). To reuse GeoTracker in another app, copy src/geotracker/ and\n * write a file like this one with that app's storage + sync adapters.\n *\n * @module geotracker-lupmis\n */\n\nimport { GeoTracker } from './geotracker/GeoTracker.js';\nimport {\n createGpsTrail,\n addGpsTrailPoint,\n finishGpsTrail,\n getUnsyncedGpsTrails,\n getGpsTrailPoints,\n markGpsTrailSynced,\n} from './database.js';\nimport { pushGpsTrail } from './remotedb.js';\nimport { isOnline } from './pwa.js';\nimport { getSession } from './remotedb.js';\n\n/**\n * Storage adapter — maps the GeoTracker contract onto the SQLocal helpers in\n * database.js. The district id is stamped onto each trail at creation time.\n */\nconst sqlocalStorage = {\n async createTrail(meta) {\n const districtId = meta.districtId\n ?? getSession()?.district_id\n ?? null;\n return createGpsTrail({ ...meta, districtId: districtId != null ? String(districtId) : null });\n },\n addPoint: (trailId, point) => addGpsTrailPoint(trailId, point),\n finishTrail: (trailId, summary) => finishGpsTrail(trailId, summary),\n getUnsyncedTrails: () => getUnsyncedGpsTrails(),\n getTrailPoints: (trailId) => getGpsTrailPoints(trailId),\n markTrailSynced: (trailId, remote) => markGpsTrailSynced(trailId, remote),\n};\n\n/**\n * Sync adapter — store-and-forward upload via the PHP endpoint. `isOnline()`\n * lets the tracker skip pushes while offline (it retries later).\n */\nconst remoteSync = {\n pushTrail: (trail, points) => pushGpsTrail(trail, points),\n isOnline: () => isOnline(),\n};\n\n/**\n * The configured, app-wide tracker instance. Tunables chosen for field\n * walking/driving: a point every ~5 m, throttled to ≥1 s, with a 20 s\n * heartbeat so stationary pauses still leave a breadcrumb, dropping fixes\n * worse than 50 m accuracy.\n */\nexport const geoTracker = new GeoTracker({\n storage: sqlocalStorage,\n sync: remoteSync,\n minDistanceM: 5,\n minIntervalMs: 1000,\n heartbeatMs: 20000,\n maxAccuracyM: 50,\n enableHighAccuracy: true,\n});\n\nexport default geoTracker;\n","/**\n * LUPMIS2 — iframe embed bridge\n *\n * Implements the postMessage contract defined in\n * LUPMIS2_Permit_Map_Integration.docx §2\n * which itself follows the iframe channel from\n * LUPMIS2_Reusable_Mapping_Concept.docx §3.2 / §4.\n *\n * Outbound (embed → host):\n * { type: 'ready' }\n * { type: 'parcel:select', upn, parcel_id, lon, lat,\n * zone_code, zone_name, landuse,\n * min_height, max_height }\n * { type: 'parcel:cleared' }\n * { type: 'error', code, message }\n *\n * Inbound (host → embed):\n * { type: 'set:view', lon, lat, zoom }\n * { type: 'set:selected', upn }\n * { type: 'clear:selected' }\n * { type: 'set:basemap', key }\n *\n * The bridge is framework-agnostic apart from the OpenLayers imports — it\n * lives one level above the proposed `map-core` library so once that library\n * is extracted (concept §3.1) this file can be lifted into it unchanged.\n *\n * Note on security: outbound messages are sent with target origin `*` because\n * the embed cannot know its parent's origin in advance and the payload is\n * non-sensitive (selection metadata only). The HOST is expected to verify\n * `event.origin === ''` before trusting any message, as\n * documented in §2.2 of the integration doc. Inbound commands are\n * type-checked against a strict whitelist before being acted on.\n */\n\nimport { fromLonLat, toLonLat } from 'ol/proj.js';\nimport { getCenter } from 'ol/extent.js';\nimport VectorLayer from 'ol/layer/Vector.js';\nimport VectorSource from 'ol/source/Vector.js';\nimport { Style, Stroke, Fill } from 'ol/style.js';\n\nconst KNOWN_COMMANDS = new Set([\n 'set:view',\n 'set:selected',\n 'clear:selected',\n 'set:basemap',\n]);\n\n/**\n * Create and install the embed bridge.\n *\n * @param {Object} opts\n * @param {Object} opts.mapView — the MapView instance (uses onClick,\n * getMap, setBaseMap).\n * @param {Object} opts.embedConfig — window.LUPMIS_EMBED contents.\n * @returns {{ attachParcelsLayer: (layer) => void, emitError: (code, msg) => void }}\n */\nexport function createEmbedBridge({ mapView, embedConfig }) {\n const map = mapView.getMap();\n const parent = (window.parent && window.parent !== window) ? window.parent : null;\n\n // -------------------------------------------------------------------------\n // Highlight layer — visual cue for the currently-selected parcel.\n // -------------------------------------------------------------------------\n const highlightSource = new VectorSource();\n const highlightLayer = new VectorLayer({\n source: highlightSource,\n zIndex: 9999,\n style: new Style({\n stroke: new Stroke({ color: '#f97316', width: 3 }),\n fill: new Fill({ color: 'rgba(249,115,22,0.18)' }),\n }),\n properties: {\n title: 'Permit selection',\n displayInLayerSwitcher: false,\n },\n });\n map.addLayer(highlightLayer);\n\n let parcelsLayer = null;\n let pendingSelectUpn = embedConfig?.upn ? String(embedConfig.upn) : null;\n let readyEmitted = false;\n\n // -------------------------------------------------------------------------\n // Outbound\n // -------------------------------------------------------------------------\n function send(message) {\n if (!parent) {\n console.warn('[embed-bridge] No parent window — would have sent:', message);\n return;\n }\n try {\n parent.postMessage(message, '*');\n } catch (e) {\n console.warn('[embed-bridge] postMessage failed:', e);\n }\n }\n\n function emitError(code, message) {\n send({ type: 'error', code, message });\n }\n\n function emitReady() {\n if (readyEmitted) return;\n readyEmitted = true;\n send({ type: 'ready' });\n }\n\n /** Build a `parcel:select` payload from a feature and (optionally) the click point. */\n function parcelPayload(feature, lon, lat) {\n const p = feature.getProperties();\n let outLon = lon, outLat = lat;\n if (outLon == null || outLat == null) {\n const ext = feature.getGeometry()?.getExtent();\n if (ext) {\n const [cx, cy] = toLonLat(getCenter(ext));\n outLon = cx; outLat = cy;\n }\n }\n return {\n type: 'parcel:select',\n upn: p.upn ?? null,\n parcel_id: p.id ?? null,\n lon: outLon ?? null,\n lat: outLat ?? null,\n zone_code: p.zone_code ?? null,\n zone_name: p.zone_name ?? null,\n landuse: p.landuse ?? null,\n min_height: p.min_height ?? null,\n max_height: p.max_height ?? null,\n };\n }\n\n function highlightFeature(feature) {\n highlightSource.clear();\n if (feature) {\n const clone = feature.clone();\n highlightSource.addFeature(clone);\n }\n }\n\n // -------------------------------------------------------------------------\n // Click → parcel:select / parcel:cleared\n // -------------------------------------------------------------------------\n mapView.onClick((lon, lat, _markerFeature, evt) => {\n let parcelFeature = null;\n map.forEachFeatureAtPixel(evt.pixel, (f) => {\n if (f.get('_layerType') === 'parcel') {\n parcelFeature = f;\n return true;\n }\n });\n if (parcelFeature) {\n highlightFeature(parcelFeature);\n send(parcelPayload(parcelFeature, lon, lat));\n } else {\n highlightFeature(null);\n send({ type: 'parcel:cleared' });\n }\n });\n\n // -------------------------------------------------------------------------\n // Inbound commands\n // -------------------------------------------------------------------------\n window.addEventListener('message', (event) => {\n const msg = event.data;\n if (!msg || typeof msg !== 'object' || !KNOWN_COMMANDS.has(msg.type)) return;\n try {\n switch (msg.type) {\n case 'set:view': {\n if (typeof msg.lon === 'number' && typeof msg.lat === 'number') {\n const view = map.getView();\n view.setCenter(fromLonLat([msg.lon, msg.lat]));\n if (typeof msg.zoom === 'number') view.setZoom(msg.zoom);\n }\n break;\n }\n case 'set:selected':\n if (msg.upn) selectByUpn(String(msg.upn));\n break;\n case 'clear:selected':\n highlightFeature(null);\n pendingSelectUpn = null;\n break;\n case 'set:basemap':\n if (msg.key && typeof mapView.setBaseMap === 'function') {\n mapView.setBaseMap(msg.key);\n }\n break;\n }\n } catch (e) {\n emitError('COMMAND_FAILED', `Failed to handle ${msg.type}: ${e.message}`);\n }\n });\n\n /**\n * Find the parcel with the given UPN, highlight it, fit the view to it,\n * and emit a synthesized parcel:select so the host receives the metadata.\n * If the parcels haven't finished loading yet, the UPN is queued and the\n * lookup is retried as features stream in.\n */\n function selectByUpn(upn) {\n if (!parcelsLayer) { pendingSelectUpn = upn; return; }\n const features = parcelsLayer.getSource().getFeatures();\n const feature = features.find((f) => String(f.get('upn') ?? '') === upn);\n if (!feature) { pendingSelectUpn = upn; return; }\n\n pendingSelectUpn = null;\n highlightFeature(feature);\n const ext = feature.getGeometry()?.getExtent();\n if (ext) {\n map.getView().fit(ext, { padding: [50, 50, 50, 50], duration: 400, maxZoom: 17 });\n }\n send(parcelPayload(feature, null, null));\n }\n\n // -------------------------------------------------------------------------\n // Parcels attached → emit `ready`, drain any pending set:selected\n // -------------------------------------------------------------------------\n function attachParcelsLayer(layer) {\n parcelsLayer = layer;\n const source = layer.getSource();\n\n const drain = () => {\n // Microtask hop so a batch addFeatures() finishes before we react.\n queueMicrotask(() => {\n if (pendingSelectUpn) selectByUpn(pendingSelectUpn);\n emitReady();\n });\n };\n\n if (source.getFeatures().length > 0) {\n drain();\n } else {\n // Stay subscribed: parcels may arrive in waves (cache then API refresh),\n // and a pending UPN may only resolve after the second wave.\n let scheduled = false;\n source.on('addfeature', () => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n if (pendingSelectUpn) selectByUpn(pendingSelectUpn);\n emitReady();\n });\n });\n }\n }\n\n // -------------------------------------------------------------------------\n // Apply initial config: basemap + view (UPN is handled after parcels load)\n // -------------------------------------------------------------------------\n if (embedConfig?.basemap && typeof mapView.setBaseMap === 'function') {\n mapView.setBaseMap(embedConfig.basemap);\n }\n if (typeof embedConfig?.lon === 'number' && typeof embedConfig?.lat === 'number') {\n const view = map.getView();\n view.setCenter(fromLonLat([embedConfig.lon, embedConfig.lat]));\n view.setZoom(typeof embedConfig?.zoom === 'number' ? embedConfig.zoom : 15);\n }\n\n return { attachParcelsLayer, emitError };\n}\n","/**\n * Target-type auto-detect + field auto-mapping for imported FeatureCollections.\n *\n * Pure helpers — no DOM, no map, no DB. The mapping modal calls these once\n * after the file has been parsed to seed its initial state, and re-uses them\n * whenever the user changes target type so the field map updates accordingly.\n *\n * Contract per LUPMIS2_Import_Upload_Design.docx §3.\n *\n * detectTargetType(fc) → 'parcels'|'collector_zones'|'osm_roads'\n * |'building_footprints'|'other'\n * autoMapFields(fc, targetType) → { [lupmisColumn]: sourceField | null }\n * TARGET_TYPES → metadata used by the modal dropdown\n * TARGET_FIELDS[targetType] → ordered list of LUPMIS2 columns the\n * upload payload must populate\n */\n\n// ---------------------------------------------------------------------------\n// Target-type registry\n// ---------------------------------------------------------------------------\n\nexport const TARGET_TYPES = [\n { key: 'parcels', label: 'Parcels', geometryFamily: 'polygon' },\n { key: 'collector_zones', label: 'Collector Zones', geometryFamily: 'polygon' },\n { key: 'building_footprints', label: 'Building Footprints', geometryFamily: 'polygon' },\n { key: 'osm_roads', label: 'OSM Roads', geometryFamily: 'line' },\n { key: 'other', label: 'Other (view only)', geometryFamily: 'any' },\n];\n\n// LUPMIS2 column lists per target type, in the order the mapping modal\n// should display them. Subset of the typed-table schemas the user actually\n// edits — system columns (id, created_at, updated_at, fetched_at, status)\n// are NOT user-mapped.\nexport const TARGET_FIELDS = {\n parcels: [\n 'upn', 'landuse', 'zone_code', 'zone_name', 'sector', 'block',\n 'parcel_no', 'prop_no', 'st_name', 'prop_add', 'fac_name',\n 'min_height', 'max_height', 'eff_date', 'lp_name', 'locality',\n 'mmda', 'last_update', 'remarks',\n ],\n collector_zones: [\n 'zone_name',\n ],\n building_footprints: [\n // The remote table is a thin schema today; we leave room for the\n // database team to add columns later (the modal will accept more).\n ],\n osm_roads: [\n 'osm_id', 'name', 'highway',\n ],\n other: [],\n};\n\n// ---------------------------------------------------------------------------\n// Type-detect signals\n// ---------------------------------------------------------------------------\n\n/**\n * Inspect every feature's geometry type. We return the dominant family\n * because shapefiles can sometimes mix (rare but possible).\n *\n * @returns {'polygon'|'line'|'point'|'mixed'|'none'}\n */\nfunction dominantGeometryFamily(fc) {\n if (!fc?.features?.length) return 'none';\n let polys = 0, lines = 0, points = 0;\n for (const f of fc.features) {\n const t = f?.geometry?.type;\n if (!t) continue;\n if (t === 'Polygon' || t === 'MultiPolygon') polys++;\n else if (t === 'LineString' || t === 'MultiLineString') lines++;\n else if (t === 'Point' || t === 'MultiPoint') points++;\n }\n const total = polys + lines + points;\n if (total === 0) return 'none';\n const max = Math.max(polys, lines, points);\n if (max < total * 0.85) return 'mixed';\n if (polys === max) return 'polygon';\n if (lines === max) return 'line';\n return 'point';\n}\n\n/**\n * Build a normalised attribute-name set across the first N features (so\n * inconsistent rows don't skew detection).\n */\nfunction attributeFingerprint(fc, sampleSize = 50) {\n const set = new Set();\n const n = Math.min(sampleSize, fc.features.length);\n for (let i = 0; i < n; i++) {\n const props = fc.features[i]?.properties;\n if (!props || typeof props !== 'object') continue;\n for (const key of Object.keys(props)) {\n set.add(String(key).toLowerCase());\n }\n }\n return set;\n}\n\n/** Does the fingerprint contain at least one of the given candidates? */\nfunction has(fp, ...candidates) {\n for (const c of candidates) if (fp.has(c)) return true;\n return false;\n}\n\n/**\n * Suggest a target type based on geometry family + attribute fingerprint.\n * Conservative: returns 'other' whenever the signal isn't strong enough,\n * leaving the user to choose explicitly.\n */\nexport function detectTargetType(fc) {\n const family = dominantGeometryFamily(fc);\n if (family === 'none' || family === 'mixed') return 'other';\n const fp = attributeFingerprint(fc);\n\n if (family === 'line') {\n if (has(fp, 'osm_id', 'highway')) return 'osm_roads';\n return 'other';\n }\n\n if (family === 'polygon') {\n // Parcels are the most attribute-rich polygon: UPN is the strongest tell.\n if (has(fp, 'upn', 'parcel_no', 'landuse', 'lu_code', 'zone_code') &&\n // a single \"zone_name\" without UPN is more likely a zone overlay\n has(fp, 'upn', 'parcel_no', 'landuse', 'lu_code')) {\n return 'parcels';\n }\n if (has(fp, 'zone_name', 'colzonename', 'colzonenr')) return 'collector_zones';\n if (has(fp, 'building', 'building:levels', 'building_levels', 'height',\n 'min_height', 'max_height') &&\n !has(fp, 'upn', 'parcel_no')) {\n return 'building_footprints';\n }\n return 'other';\n }\n\n // Points have no upload target in the current design.\n return 'other';\n}\n\n// ---------------------------------------------------------------------------\n// Field auto-mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Per-LUPMIS2-column candidate source field names. Lower-case, ordered by\n * preference. Matching is case-insensitive and ignores spaces / underscores\n * / hyphens.\n */\nconst FIELD_CANDIDATES = {\n // Parcels (lu_parcels)\n upn: ['upn', 'unique_parcel_no', 'parcel_id', 'pid'],\n landuse: ['landuse', 'land_use', 'lu', 'lu_code'],\n zone_code: ['zone_code', 'zonecode', 'zone'],\n zone_name: ['zone_name', 'zonename', 'colzonename'],\n sector: ['sector', 'sec'],\n block: ['block', 'blk'],\n parcel_no: ['parcel_no', 'parcelno', 'plot_no', 'plotno'],\n prop_no: ['prop_no', 'propertyno', 'property_no'],\n st_name: ['st_name', 'street', 'street_name', 'road'],\n prop_add: ['prop_add', 'address', 'addr'],\n fac_name: ['fac_name', 'facility', 'facilityname'],\n min_height: ['min_height', 'minheight', 'h_min'],\n max_height: ['max_height', 'maxheight', 'h_max', 'height'],\n eff_date: ['eff_date', 'effectivedate', 'effdate'],\n lp_name: ['lp_name', 'lpname', 'localplan'],\n locality: ['locality', 'town', 'settlement'],\n mmda: ['mmda', 'district', 'assembly'],\n last_update:['last_update', 'lastupdate', 'updated'],\n remarks: ['remarks', 'notes', 'comments'],\n\n // OSM roads\n osm_id: ['osm_id', 'osmid', 'id'],\n name: ['name', 'street_name', 'st_name'],\n highway: ['highway', 'road_class', 'class'],\n};\n\n/** Normalise a field name for matching (lowercase, strip _ - and spaces). */\nfunction normaliseKey(s) {\n return String(s).toLowerCase().replace(/[\\s_\\-]+/g, '');\n}\n\n/**\n * Suggest a source-field → LUPMIS2-column mapping for the given target type.\n * Unmapped columns get null so the modal can show \"(none)\" and let the user\n * pick from a dropdown of available source fields.\n *\n * @param {Object} fc — parsed FeatureCollection\n * @param {string} targetType — one of TARGET_TYPES keys\n * @returns {Object} { [lupmisColumn]: sourceField|null }\n */\nexport function autoMapFields(fc, targetType) {\n const columns = TARGET_FIELDS[targetType] || [];\n if (columns.length === 0) return {};\n\n const fp = attributeFingerprint(fc, 50);\n // Build normalised → original lookup for available source fields\n const sourceByNorm = new Map();\n for (const key of fp) {\n sourceByNorm.set(normaliseKey(key), key);\n }\n\n const mapping = {};\n for (const col of columns) {\n const candidates = FIELD_CANDIDATES[col] || [col];\n let matched = null;\n for (const cand of candidates) {\n const n = normaliseKey(cand);\n if (sourceByNorm.has(n)) {\n matched = sourceByNorm.get(n);\n break;\n }\n }\n mapping[col] = matched;\n }\n return mapping;\n}\n\n/**\n * Apply a mapping to one source-feature properties object and return the\n * LUPMIS2-shaped properties (column-name keys). Source fields not in the\n * mapping are dropped. Used during the staging-row insert.\n */\nexport function applyFieldMapping(sourceProps, mapping) {\n const out = {};\n for (const [targetCol, sourceKey] of Object.entries(mapping || {})) {\n if (sourceKey == null) continue;\n if (sourceProps && Object.prototype.hasOwnProperty.call(sourceProps, sourceKey)) {\n out[targetCol] = sourceProps[sourceKey];\n }\n }\n return out;\n}\n\n/**\n * Return the list of distinct source-property names (the union across all\n * features), preserving discovery order — used to populate the per-column\n * dropdown in the mapping modal.\n */\nexport function listSourceFields(fc) {\n const seen = new Map();\n for (const f of fc.features || []) {\n const props = f?.properties;\n if (!props || typeof props !== 'object') continue;\n for (const k of Object.keys(props)) {\n if (!seen.has(k)) seen.set(k, true);\n }\n }\n return Array.from(seen.keys());\n}\n\n/**\n * For each source-property name in the FeatureCollection, return one sample\n * value drawn from the first feature that has a non-empty value for it.\n * Used by the mapping modal to show users what each attribute actually\n * contains — e.g. dropdown options become `upn [12345-6789]` rather than\n * just `upn`. Empty / null / undefined values are skipped while scanning;\n * arrays and objects are JSON-stringified so something readable surfaces.\n *\n * @param {Object} fc — parsed FeatureCollection\n * @param {number} [maxLen] — max sample length (truncated with `…`)\n * @returns {Object} { [sourceField]: sample }\n */\nexport function sampleSourceValues(fc, maxLen = 35) {\n const samples = {};\n for (const feature of fc.features || []) {\n const props = feature?.properties;\n if (!props || typeof props !== 'object') continue;\n for (const [key, value] of Object.entries(props)) {\n if (samples[key] !== undefined) continue; // already have one\n if (value === null || value === undefined || value === '') continue;\n\n let str;\n if (typeof value === 'object') {\n try { str = JSON.stringify(value); } catch { str = String(value); }\n } else {\n str = String(value);\n }\n // Collapse whitespace so a multi-line cell value still renders inline.\n str = str.replace(/\\s+/g, ' ').trim();\n if (!str) continue;\n if (str.length > maxLen) str = str.slice(0, maxLen - 1) + '…';\n samples[key] = str;\n }\n }\n return samples;\n}\n","/**\n * Import-mapping modal controller.\n *\n * openImportMappingModal({ importId, filename, fc, onResult })\n *\n * Populates the modal from the parsed FeatureCollection, lets the user pick\n * a target type and adjust the field map, then calls onResult with one of:\n *\n * { action: 'cancel' } — keep as Other / view only\n * { action: 'save', targetType, mapping }\n * { action: 'upload', targetType, mapping }\n *\n * The caller is responsible for updating external_imports + the staged\n * features (this module knows nothing about the DB or the map).\n *\n * See LUPMIS2_Import_Upload_Design.docx §3.1.\n */\n\nimport { Modal } from 'bootstrap';\nimport {\n TARGET_TYPES,\n TARGET_FIELDS,\n detectTargetType,\n autoMapFields,\n listSourceFields,\n sampleSourceValues,\n} from './import-detect.js';\n\nconst els = {}; // cached DOM lookups\nlet modal = null;\nlet state = null; // { importId, filename, fc, sourceFields, mapping, targetType, onResult }\n\nfunction cacheEls() {\n if (els.root) return;\n els.root = document.getElementById('importMappingModal');\n els.filename = document.getElementById('import-modal-filename');\n els.summary = document.getElementById('import-modal-summary');\n els.target = document.getElementById('import-modal-target');\n els.targetHint = document.getElementById('import-modal-target-hint');\n els.fieldsWrap = document.getElementById('import-modal-fields-wrap');\n els.tbody = document.getElementById('import-modal-fields-tbody');\n els.btnSave = document.getElementById('import-modal-save');\n els.btnSaveUpload = document.getElementById('import-modal-save-upload');\n els.btnCancel = document.getElementById('import-modal-cancel');\n\n // Populate the target-type dropdown once.\n if (els.target && !els.target.dataset.populated) {\n els.target.innerHTML = TARGET_TYPES\n .map((t) => ``)\n .join('');\n els.target.dataset.populated = '1';\n }\n\n // Event wiring (idempotent).\n if (els.target && !els.target.dataset.wired) {\n els.target.dataset.wired = '1';\n els.target.addEventListener('change', onTargetChange);\n }\n if (els.btnSave && !els.btnSave.dataset.wired) {\n els.btnSave.dataset.wired = '1';\n els.btnSave.addEventListener('click', () => finish('save'));\n }\n if (els.btnSaveUpload && !els.btnSaveUpload.dataset.wired) {\n els.btnSaveUpload.dataset.wired = '1';\n els.btnSaveUpload.addEventListener('click', () => finish('upload'));\n }\n // Cancel uses Bootstrap's data-bs-dismiss; we hook the hidden event so\n // closing via × / ESC / Cancel all behave the same: a 'cancel' result.\n if (els.root && !els.root.dataset.wired) {\n els.root.dataset.wired = '1';\n els.root.addEventListener('hidden.bs.modal', () => {\n if (state?.onResult && !state._resolved) {\n state._resolved = true;\n state.onResult({ action: 'cancel' });\n }\n state = null;\n });\n }\n}\n\n/** Render the field-mapping table for the current targetType. */\nfunction renderFieldsTable() {\n const targetType = state.targetType;\n const columns = TARGET_FIELDS[targetType] || [];\n\n // \"Other (view only)\" → no fields to map.\n if (targetType === 'other' || columns.length === 0) {\n els.fieldsWrap.style.display = 'none';\n return;\n }\n els.fieldsWrap.style.display = '';\n\n // Each dropdown option shows the source field name AND a one-feature\n // sample value so the user can recognise the attribute by its content,\n // not just its name. Sample is computed once in state.sourceSamples; see\n // sampleSourceValues() in import-detect.js.\n //\n // The field name is rendered with Unicode mathematical-bold characters\n // (U+1D400 / U+1D41A / U+1D7CE blocks) because HTML / CSS bold doesn't\n // render inside ']\n .concat(state.sourceFields.map((s) =>\n ``))\n .join('');\n\n els.tbody.innerHTML = columns.map((col) => {\n const current = state.mapping[col] || '';\n // Mark the matching option as selected. The opening `