const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/geotiff-B_Wfowsp.js","assets/openlayers-D5-VSKzq.js","assets/openlayers-BtPuoxOl.css","assets/vite-helpers-BlTxHScW.js","assets/pako-n3Pgozwg.js","assets/pdf-export-DJBZ6C0H.js","assets/jspdf-HFui8oS7.js","assets/analysis-modal-f8_Vb45H.js","assets/bootstrap-D1-uvFxm.js","assets/bootstrap-BtmJYOxZ.css","assets/turf-B-0GbNlN.js","assets/ol-ext-DV70ljNY.js","assets/ol-ext-BgKrOIxx.css"])))=>i.map(i=>d[i]); import{_ as Le}from"./vite-helpers-BlTxHScW.js";import{M as lt}from"./bootstrap-D1-uvFxm.js";import{ak as xe,h as C,F as M,j as I,k as he,m as fo,b as G,V as D,L as de,D as Ae,P as at,al as We,n as fe,am as Ce,M as hr,an as gr,ao as ee,ap as mr,S as yr,J as br,G as wr,aq as _r,ar as vr,as as Er,o as dt,O as Ee,at as nt,au as vt,A as xr,T as se,av as Me,aw as To,ax as le,ay as Lo,e as Sr,u as Nt,s as Tr,az as hn,aA as ho}from"./openlayers-D5-VSKzq.js";import{o as Lr,a as kr,b as Ir,c as Pr,d as Xt,e as ko,f as tt,g as Cr,h as Se,i as Mr,j as Io}from"./ol-ext-DV70ljNY.js";(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))r(n);new MutationObserver(n=>{for(const s of n)if(s.type==="childList")for(const a of s.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&r(a)}).observe(document,{childList:!0,subtree:!0});function t(n){const s={};return n.integrity&&(s.integrity=n.integrity),n.referrerPolicy&&(s.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?s.credentials="include":n.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(n){if(n.ep)return;n.ep=!0;const s=t(n);fetch(n.href,s)}})();const Po="function",je="64e10b34-2bf7-4616-9668-f99de5aa046e",Ar="get",Fr="has",Or="set",{isArray:yt}=Array;let{SharedArrayBuffer:Et,window:Dr}=globalThis,{notify:gn,wait:mn,waitAsync:xt}=Atomics,yn=null;xt||(xt=o=>({value:new Promise(e=>{let t=new Worker("data:application/javascript,onmessage%3D(%7Bdata%3Ab%7D)%3D%3E(Atomics.wait(b%2C0)%2CpostMessage(0))");t.onmessage=e,t.postMessage(o)})}));try{new Et(4)}catch{Et=ArrayBuffer;const e=new WeakMap;if(Dr){const t=new Map,{prototype:{postMessage:r}}=Worker,n=s=>{const a=s.data?.[je];if(!yt(a)){s.stopImmediatePropagation();const{id:i,sb:l}=a;t.get(i)(l)}};yn=function(s,...a){const i=s?.[je];if(yt(i)){const[l,c]=i;e.set(c,l),this.addEventListener("message",n)}return r.call(this,s,...a)},xt=s=>({value:new Promise(a=>{t.set(e.get(s),a)}).then(a=>{t.delete(e.get(s)),e.delete(s);for(let i=0;i({[je]:{id:r,sb:n}});gn=r=>{postMessage(t(e.get(r),r))},addEventListener("message",r=>{const n=r.data?.[je];if(yt(n)){const[s,a]=n;e.set(a,s)}})}}/*! (c) Andrea Giammarchi - ISC */const{Int32Array:Yt,Map:Co,Uint16Array:Jt}=globalThis,{BYTES_PER_ELEMENT:Mo}=Yt,{BYTES_PER_ELEMENT:Rr}=Jt,Nr=(o,e,t)=>{for(;mn(o,0,0,e)==="timed-out";)t()},Zt=new WeakSet,$t=new WeakMap,$r={value:{then:o=>o()}};let Br=0;const go=(o,{parse:e=JSON.parse,stringify:t=JSON.stringify,transform:r,interrupt:n}=JSON)=>{if(!$t.has(o)){const s=yn||o.postMessage,a=(f,...p)=>s.call(o,{[je]:p},{transfer:f}),i=typeof n===Po?n:n?.handler,l=n?.delay||42,c=new TextDecoder("utf-16"),d=(f,p)=>f?xt(p,0):(i?Nr(p,l,i):mn(p,0),$r);let u=!1;$t.set(o,new Proxy(new Co,{[Fr]:(f,p)=>typeof p=="string"&&!p.startsWith("_"),[Ar]:(f,p)=>p==="then"?null:((...h)=>{const g=Br++;let m=new Yt(new Et(Mo*2)),y=[];Zt.has(h.at(-1)||y)&&Zt.delete(y=h.pop()),a(y,g,m,p,r?h.map(r):h);const w=o!==globalThis;let _=0;return u&&w&&(_=setTimeout(console.warn,1e3,`💀🔒 - Possible deadlock if proxy.${p}(...args) is awaited`)),d(w,m).value.then(()=>{clearTimeout(_);const E=m[1];if(!E)return;const T=Rr*E;return m=new Yt(new Et(T+T%Mo)),a([],g,m),d(w,m).value.then(()=>e(c.decode(new Jt(m.buffer).slice(0,E))))})}),[Or](f,p,h){const g=typeof h;if(g!==Po)throw new Error(`Unable to assign ${p} as ${g}`);if(!f.size){const m=new Co;o.addEventListener("message",async y=>{const w=y.data?.[je];if(yt(w)){y.stopImmediatePropagation();const[_,E,...T]=w;let x;if(T.length){const[S,P]=T;if(f.has(S)){u=!0;try{const k=await f.get(S)(...P);if(k!==void 0){const j=t(r?r(k):k);m.set(_,j),E[1]=j.length}}catch(k){x=k}finally{u=!1}}else x=new Error(`Unsupported action: ${S}`);E[0]=1}else{const S=m.get(_);m.delete(_);for(let P=new Jt(E.buffer),k=0;k(Zt.add(o),o);function Ao(){let o,e;return{lock:async()=>{for(;o;)await o;o=new Promise(n=>{e=n})},unlock:async()=>{const n=e;o=void 0,e=void 0,n?.()}}}async function bn(o,e){let t;if(o instanceof Blob?t=o.stream():t=o,t instanceof ReadableStream&&e){const n=t.getReader();switch(e){case"callback":return async()=>(await n.read()).value;case"buffer":const s=[];let a=!1;for(;!a;){const d=await n.read();d.value&&s.push(d.value),a=d.done}const i=s.reduce((d,u)=>d+u.length,0),l=new Uint8Array(i);let c=0;return s.forEach(d=>{l.set(d,c),c+=d.length}),l.buffer}}else return t}class St{constructor(e){Object.defineProperty(this,"sqlite3InitModule",{enumerable:!0,configurable:!0,writable:!0,value:e}),Object.defineProperty(this,"sqlite3",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"db",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"pointers",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"writeCallbacks",{enumerable:!0,configurable:!0,writable:!0,value:new Set}),Object.defineProperty(this,"storageType",{enumerable:!0,configurable:!0,writable:!0,value:"memory"})}async init(e){const{databasePath:t}=e,r=this.getFlags(e);if(!this.sqlite3InitModule){const{default:n}=await Le(async()=>{const{default:s}=await import("./index-DTMgZTfd.js");return{default:s}},[]);this.sqlite3InitModule=n}this.sqlite3||(this.sqlite3=await this.sqlite3InitModule()),this.db&&await this.destroy(),this.db=new this.sqlite3.oo1.DB(t,r),this.config=e,this.initWriteHook()}onWrite(e){return this.writeCallbacks.add(e),()=>{this.writeCallbacks.delete(e)}}async exec(e){if(!this.db)throw new Error("Driver not initialized");return this.execOnDb(this.db,e)}async execBatch(e){if(!this.db)throw new Error("Driver not initialized");const t=[];return this.db.transaction(r=>{const n=new Map;try{for(let s of e){let a=n.get(s.sql);if(!a){const c=r.prepare(s.sql);n.set(s.sql,c),a=c}s.params?.length&&a.bind(s.params);let i=[],l=[];for(;a.step();)i=a.getColumnNames([]),l.push(a.get([]));t.push({columns:i,rows:l}),a.reset()}}finally{n.forEach(s=>{s.finalize()})}}),t}async isDatabasePersisted(){return!1}async getDatabaseSizeBytes(){const t=(await this.exec({sql:`SELECT page_count * page_size AS size FROM pragma_page_count(), pragma_page_size()`,method:"get"}))?.rows?.[0];if(typeof t!="number")throw new Error("Failed to query database size");return t}async createFunction(e){if(!this.db)throw new Error("Driver not initialized");switch(e.type){case"callback":case"scalar":this.db.createFunction({name:e.name,xFunc:(t,...r)=>e.func(...r),arity:-1});break;case"aggregate":this.db.createFunction({name:e.name,xStep:(t,...r)=>e.func.step(...r),xFinal:(t,...r)=>e.func.final(...r),arity:-1});break}}async import(e){if(!this.sqlite3||!this.db||!this.config)throw new Error("Driver not initialized");const t=await bn(e,"buffer"),r=this.sqlite3.wasm.allocFromTypedArray(t);this.pointers.push(r);const n=this.sqlite3.capi.sqlite3_deserialize(this.db,"main",r,t.byteLength,t.byteLength,this.config.readOnly?this.sqlite3.capi.SQLITE_DESERIALIZE_READONLY:this.sqlite3.capi.SQLITE_DESERIALIZE_RESIZEABLE);this.db.checkRc(n)}async export(){if(!this.sqlite3||!this.db)throw new Error("Driver not initialized");return{name:"database.sqlite3",data:this.sqlite3.capi.sqlite3_js_db_export(this.db)}}async clear(){}async destroy(){this.closeDb(),this.pointers.forEach(e=>this.sqlite3?.wasm.dealloc(e)),this.pointers=[],this.writeCallbacks.clear()}getFlags(e){const{readOnly:t,verbose:r}=e;return[t===!0?"r":"cw",r===!0?"t":""].join("")}execOnDb(e,t){const r={rows:[],columns:[]},n=e.exec({sql:t.sql,bind:t.params,returnValue:"resultRows",rowMode:"array",columnNames:r.columns});switch(t.method){case"run":break;case"get":r.rows=n[0]??[];break;case"all":default:r.rows=n;break}return r}initWriteHook(){if(!this.config?.reactive)return;if(!this.sqlite3||!this.db)throw new Error("Driver not initialized");const e={[this.sqlite3.capi.SQLITE_INSERT]:"insert",[this.sqlite3.capi.SQLITE_UPDATE]:"update",[this.sqlite3.capi.SQLITE_DELETE]:"delete"};this.sqlite3.capi.sqlite3_update_hook(this.db,(t,r,n,s,a)=>{this.writeCallbacks.forEach(i=>{i({table:s,rowid:a,operation:e[r]})})},0)}closeDb(){this.db&&(this.db.close(),this.db=void 0)}}function Gr(o,e,t){let r,n,s,a,i,l,c=0,d=!1,u=!1,f=!0;if(typeof o!="function")throw new TypeError("Expected a function");e=Number(e)||0,typeof t=="object"&&t!==null&&(d=!!t.leading,u="maxWait"in t,s=u?Math.max(Number(t.maxWait)||0,e):0,f="trailing"in t?!!t.trailing:f);function p(x){const S=r,P=n;return r=n=void 0,c=x,a=o.apply(P,S),a}function h(x){return c=x,i=setTimeout(y,e),d?p(x):a}function g(x){const S=x-(l??0),P=x-c,k=e-S;return u?Math.min(k,s-P):k}function m(x){const S=x-(l??0),P=x-c;return l===void 0||S>=e||S<0||u&&P>=s}function y(){const x=Date.now();if(m(x))return w(x);i=setTimeout(y,g(x))}function w(x){return i=void 0,f&&r?p(x):(r=n=void 0,a)}function _(){i!==void 0&&clearTimeout(i),c=0,r=l=n=i=void 0}function E(){return i===void 0?a:w(Date.now())}function T(){const x=Date.now(),S=m(x);if(r=arguments,n=this,l=x,S){if(i===void 0)return h(l);if(u)return i=setTimeout(y,e),p(l)}return i===void 0&&(i=setTimeout(y,e)),a}return T.cancel=_,T.flush=E,T}function bt(){return crypto.randomUUID()}function wn(o,e){switch(o){case"session":case":sessionStorage:":let t=sessionStorage._sqlocal_session_key;return t||(t=bt(),sessionStorage._sqlocal_session_key=t),`session:${t}`;case"local":case":localStorage:":return"local";case":memory:":return`memory:${e}`;default:return`path:${o}`}}class ut{constructor(e){Object.defineProperty(this,"driver",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:{}}),Object.defineProperty(this,"userFunctions",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"initMutex",{enumerable:!0,configurable:!0,writable:!0,value:Ao()}),Object.defineProperty(this,"transactionMutex",{enumerable:!0,configurable:!0,writable:!0,value:Ao()}),Object.defineProperty(this,"transactionKey",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"proxy",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"dirtyTables",{enumerable:!0,configurable:!0,writable:!0,value:new Set}),Object.defineProperty(this,"effectsChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"reinitChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"onmessage",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"init",{enumerable:!0,configurable:!0,writable:!0,value:async n=>{if(!(!this.config.databasePath||!this.config.clientKey)){await this.initMutex.lock();try{try{await this.driver.init(this.config)}catch{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).`),this.config.databasePath=":memory:",this.driver=new St,await this.driver.init(this.config)}const s=wn(this.config.databasePath,this.config.clientKey);this.reinitChannel=new BroadcastChannel(`_sqlocal_reinit_(${s})`),this.reinitChannel.onmessage=a=>{const i=a.data;if(this.config.clientKey!==i.clientKey)switch(i.type){case"reinit":this.init(i.reason);break;case"close":this.driver.destroy();break}},this.config.reactive&&(this.effectsChannel=new BroadcastChannel(`_sqlocal_effects_(${s})`),this.driver.onWrite(async a=>{this.dirtyTables.add(a.table),await this.transactionMutex.lock(),this.emitEffectsDebounced(),await this.transactionMutex.unlock()})),await Promise.all(Array.from(this.userFunctions.values()).map(a=>this.initUserFunction(a))),await this.execInitStatements(),this.emitMessage({type:"event",event:"connect",reason:n})}catch(s){this.emitMessage({type:"error",error:s,queryKey:null}),await this.destroy()}finally{await this.initMutex.unlock()}}}}),Object.defineProperty(this,"postMessage",{enumerable:!0,configurable:!0,writable:!0,value:async(n,s)=>{const a=n instanceof MessageEvent?n.data:n;switch(await this.initMutex.lock(),a.type){case"config":this.editConfig(a);break;case"query":case"batch":case"transaction":this.exec(a);break;case"function":this.createUserFunction(a);break;case"getinfo":this.getDatabaseInfo(a);break;case"import":this.importDb(a);break;case"export":this.exportDb(a);break;case"delete":this.deleteDb(a);break;case"destroy":this.destroy(a);break}await this.initMutex.unlock()}}),Object.defineProperty(this,"emitMessage",{enumerable:!0,configurable:!0,writable:!0,value:(n,s=[])=>{this.onmessage&&this.onmessage(n,s)}}),Object.defineProperty(this,"emitEffects",{enumerable:!0,configurable:!0,writable:!0,value:()=>{!this.effectsChannel||this.dirtyTables.size===0||(this.effectsChannel.postMessage({type:"effects",tables:[...this.dirtyTables]}),this.dirtyTables.clear())}}),Object.defineProperty(this,"emitEffectsDebounced",{enumerable:!0,configurable:!0,writable:!0,value:Gr(()=>this.emitEffects(),32,{maxWait:180})}),Object.defineProperty(this,"editConfig",{enumerable:!0,configurable:!0,writable:!0,value:n=>{this.config=n.config,this.init("initial")}}),Object.defineProperty(this,"exec",{enumerable:!0,configurable:!0,writable:!0,value:async n=>{try{const s={type:"data",queryKey:n.queryKey,data:[]};switch(n.type){case"query":const a=this.transactionKey!==null&&this.transactionKey===n.transactionKey;try{a||await this.transactionMutex.lock();const i=await this.driver.exec(n);s.data.push(i)}finally{a||await this.transactionMutex.unlock()}break;case"batch":try{await this.transactionMutex.lock();const i=await this.driver.execBatch(n.statements);s.data.push(...i)}finally{await this.transactionMutex.unlock()}break;case"transaction":if(n.action==="begin"&&(await this.transactionMutex.lock(),this.transactionKey=n.transactionKey,await this.driver.exec({sql:"BEGIN"})),(n.action==="commit"||n.action==="rollback")&&this.transactionKey!==null&&this.transactionKey===n.transactionKey){const i=n.action==="commit"?"COMMIT":"ROLLBACK";await this.driver.exec({sql:i}),this.transactionKey=null,await this.transactionMutex.unlock()}break}this.emitMessage(s)}catch(s){this.emitMessage({type:"error",error:s,queryKey:n.queryKey})}}}),Object.defineProperty(this,"execInitStatements",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{if(this.config.onInitStatements)for(let n of this.config.onInitStatements)await this.driver.exec(n)}}),Object.defineProperty(this,"getDatabaseInfo",{enumerable:!0,configurable:!0,writable:!0,value:async n=>{try{this.emitMessage({type:"info",queryKey:n.queryKey,info:{databasePath:this.config.databasePath,storageType:this.driver.storageType,databaseSizeBytes:await this.driver.getDatabaseSizeBytes(),persisted:await this.driver.isDatabasePersisted()}})}catch(s){this.emitMessage({type:"error",queryKey:n.queryKey,error:s})}}}),Object.defineProperty(this,"createUserFunction",{enumerable:!0,configurable:!0,writable:!0,value:async n=>{const{functionName:s,functionType:a,queryKey:i}=n;let l;if(this.userFunctions.has(s)){this.emitMessage({type:"error",error:new Error(`A user-defined function with the name "${s}" has already been created for this SQLocal instance.`),queryKey:i});return}switch(a){case"callback":l={type:a,name:s,func:(...c)=>{this.emitMessage({type:"callback",name:s,args:c})}};break;case"scalar":l={type:a,name:s,func:this.proxy[`_sqlocal_func_${s}`]};break;case"aggregate":l={type:a,name:s,func:{step:this.proxy[`_sqlocal_func_${s}_step`],final:this.proxy[`_sqlocal_func_${s}_final`]}};break}try{await this.initUserFunction(l),this.emitMessage({type:"success",queryKey:i})}catch(c){this.emitMessage({type:"error",error:c,queryKey:i})}}}),Object.defineProperty(this,"initUserFunction",{enumerable:!0,configurable:!0,writable:!0,value:async n=>{await this.driver.createFunction(n),this.userFunctions.set(n.name,n)}}),Object.defineProperty(this,"importDb",{enumerable:!0,configurable:!0,writable:!0,value:async n=>{const{queryKey:s,database:a}=n;let i=!1;try{await this.driver.import(a),this.driver.storageType==="memory"&&await this.execInitStatements()}catch(l){this.emitMessage({type:"error",error:l,queryKey:s}),i=!0}finally{this.driver.storageType!=="memory"&&await this.init("overwrite")}i||this.emitMessage({type:"success",queryKey:s})}}),Object.defineProperty(this,"exportDb",{enumerable:!0,configurable:!0,writable:!0,value:async n=>{const{queryKey:s}=n;try{const{name:a,data:i}=await this.driver.export();this.emitMessage({type:"buffer",queryKey:s,bufferName:a,buffer:i},[i])}catch(a){this.emitMessage({type:"error",error:a,queryKey:s})}}}),Object.defineProperty(this,"deleteDb",{enumerable:!0,configurable:!0,writable:!0,value:async n=>{const{queryKey:s}=n;let a=!1;try{await this.driver.clear()}catch(i){this.emitMessage({type:"error",error:i,queryKey:s}),a=!0}finally{await this.init("delete")}a||this.emitMessage({type:"success",queryKey:s})}}),Object.defineProperty(this,"destroy",{enumerable:!0,configurable:!0,writable:!0,value:async n=>{await this.driver.exec({sql:"PRAGMA optimize"}),await this.driver.destroy(),this.effectsChannel&&(this.emitEffectsDebounced.flush(),this.effectsChannel.close(),this.effectsChannel=void 0),this.reinitChannel&&(this.reinitChannel.close(),this.reinitChannel=void 0),n&&this.emitMessage({type:"success",queryKey:n.queryKey})}});const r=typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope?go(globalThis):globalThis;this.proxy=r,this.driver=e}}function Tt(o,...e){return{sql:o.join("?"),params:e}}function Ur(o){return!o.some(e=>!Array.isArray(e))}function Bt(o,e){let t;return Ur(o)?t=o:t=[o],t.map(r=>{const n={};return e.forEach((s,a)=>{n[s]=r[a]}),n})}function jr(o){return typeof o=="object"&&o!==null&&"getSQL"in o&&typeof o.getSQL=="function"}function zr(o){return typeof o=="object"&&o!==null&&"sql"in o&&typeof o.sql=="string"&&"params"in o}function Fo(o){if(typeof o=="function"&&(o=o(Tt)),jr(o))try{if(!("toSQL"in o&&typeof o.toSQL=="function"))throw 1;const r=o.toSQL();if(!zr(r))throw 2;const n="all"in o&&typeof o.all=="function"?o.all:void 0;return{...r,exec:n?()=>n():void 0}}catch{throw new Error("The passed statement could not be parsed.")}const e=o.sql;let t=[];return"params"in o?t=o.params:"parameters"in o&&(t=o.parameters),{sql:e,params:t}}function Oo(o,e){let t;return typeof o=="string"?t={sql:o,params:e}:t=Tt(o,...e),t}async function pt(o,e,t,r){return!e&&"locks"in navigator?navigator.locks.request(`_sqlocal_mutation_(${t.databasePath})`,{mode:o},r):r()}class Do extends St{constructor(e,t){super(t),Object.defineProperty(this,"storageType",{enumerable:!0,configurable:!0,writable:!0,value:e})}async init(e){const t=this.getFlags(e);if(e.readOnly)throw new Error(`SQLite storage type "${this.storageType}" does not support read-only mode.`);if(!this.sqlite3InitModule){const{default:r}=await Le(async()=>{const{default:n}=await import("./index-DTMgZTfd.js");return{default:n}},[]);this.sqlite3InitModule=r}this.sqlite3||(this.sqlite3=await this.sqlite3InitModule()),this.db&&await this.destroy(),this.db=new this.sqlite3.oo1.JsStorageDb({filename:this.storageType,flags:t}),this.config=e,this.initWriteHook()}async isDatabasePersisted(){return navigator.storage?.persisted()}async getDatabaseSizeBytes(){if(!this.db)throw new Error("Driver not initialized");return this.db.storageSize()}async import(e){const t=new St;await t.init({}),await t.import(e),await this.clear(),await t.exec({sql:`VACUUM INTO 'file:${this.storageType}?vfs=kvvfs'`}),await t.destroy()}async clear(){if(!this.db)throw new Error("Driver not initialized");this.db.clearStorage()}async destroy(){this.closeDb(),this.writeCallbacks.clear()}}var _n,vn;class qr{constructor(e){Object.defineProperty(this,"config",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"clientKey",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"processor",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"isDestroyed",{enumerable:!0,configurable:!0,writable:!0,value:!1}),Object.defineProperty(this,"bypassMutationLock",{enumerable:!0,configurable:!0,writable:!0,value:!1}),Object.defineProperty(this,"transactionQueryKeyQueue",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"userCallbacks",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"queriesInProgress",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"proxy",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"reinitChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"effectsChannel",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"processMessageEvent",{enumerable:!0,configurable:!0,writable:!0,value:c=>{const d=c instanceof MessageEvent?c.data:c,u=this.queriesInProgress;switch(d.type){case"success":case"data":case"buffer":case"info":case"error":if(d.queryKey&&u.has(d.queryKey)){const[p,h]=u.get(d.queryKey);d.type==="error"?h(d.error):p(d),u.delete(d.queryKey)}else if(d.type==="error")throw d.error;break;case"callback":const f=this.userCallbacks.get(d.name);f&&f(...d.args??[]);break;case"event":this.config.onConnect?.(d.reason);break}}}),Object.defineProperty(this,"createQuery",{enumerable:!0,configurable:!0,writable:!0,value:async c=>pt("shared",this.bypassMutationLock||c.type==="import"||c.type==="delete",this.config,async()=>{if(this.isDestroyed===!0)throw new Error("This SQLocal client has been destroyed. You will need to initialize a new client in order to make further queries.");const d=bt();switch(c.type){case"import":this.processor.postMessage({...c,queryKey:d},[c.database]);break;default:this.processor.postMessage({...c,queryKey:d});break}return new Promise((u,f)=>{this.queriesInProgress.set(d,[u,f])})})}),Object.defineProperty(this,"broadcast",{enumerable:!0,configurable:!0,writable:!0,value:c=>{this.reinitChannel.postMessage(c)}}),Object.defineProperty(this,"exec",{enumerable:!0,configurable:!0,writable:!0,value:async(c,d,u="all",f)=>{const p=await this.createQuery({type:"query",transactionKey:f,sql:c,params:d,method:u}),h={rows:[],columns:[]};return p.type==="data"&&(h.rows=p.data[0]?.rows??[],h.columns=p.data[0]?.columns??[]),h}}),Object.defineProperty(this,"execBatch",{enumerable:!0,configurable:!0,writable:!0,value:async c=>{const d=await this.createQuery({type:"batch",statements:c}),u=new Array(c.length).fill({rows:[],columns:[]});return d.type==="data"&&d.data.forEach((f,p)=>{u[p]=f}),u}}),Object.defineProperty(this,"sql",{enumerable:!0,configurable:!0,writable:!0,value:async(c,...d)=>{const u=Oo(c,d),{rows:f,columns:p}=await this.exec(u.sql,u.params,"all");return Bt(f,p)}}),Object.defineProperty(this,"batch",{enumerable:!0,configurable:!0,writable:!0,value:async c=>{const d=c(Tt);return(await this.execBatch(d)).map(({rows:f,columns:p})=>Bt(f,p))}}),Object.defineProperty(this,"beginTransaction",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{const c=bt();await this.createQuery({type:"transaction",transactionKey:c,action:"begin"});const d=async h=>{const g=Fo(h);if(g.exec)return this.transactionQueryKeyQueue.push(c),g.exec();const{rows:m,columns:y}=await this.exec(g.sql,g.params,"all",c);return Bt(m,y)};return{query:d,sql:async(h,...g)=>{const m=Oo(h,g);return await d(m)},commit:async()=>{await this.createQuery({type:"transaction",transactionKey:c,action:"commit"})},rollback:async()=>{await this.createQuery({type:"transaction",transactionKey:c,action:"rollback"})}}}}),Object.defineProperty(this,"transaction",{enumerable:!0,configurable:!0,writable:!0,value:async c=>pt("exclusive",!1,this.config,async()=>{let d;this.bypassMutationLock=!0;try{d=await this.beginTransaction();const u=await c({sql:d.sql,query:d.query});return await d.commit(),u}catch(u){throw await d?.rollback(),u}finally{this.bypassMutationLock=!1}})}),Object.defineProperty(this,"reactiveQuery",{enumerable:!0,configurable:!0,writable:!0,value:c=>{let d=[],u=!1,f=!1,p=0;const h=Fo(c),g=new Set,m=new Set,y=new Set,w=async()=>{try{const E=++p;if(g.size===0){const x=await this.sql("SELECT name, wr FROM tables_used(?) WHERE type = 'table'",h.sql),S=new Set,P=new Set;if(x.forEach(k=>{typeof k.name=="string"&&(k.wr?P.add(k.name):S.add(k.name))}),S.size===0)throw new Error("The passed SQL does not read any tables.");if(Array.from(P).some(k=>S.has(k)))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.");S.forEach(k=>g.add(k))}const T=h.exec?await h.exec():await this.sql(h.sql,...h.params);E===p&&(d=T,u=!0,m.forEach(x=>x(d)))}catch(E){y.forEach(T=>{T(E instanceof Error?E:new Error(String(E)))})}},_=E=>{E.data.tables.some(T=>g.has(T))&&w()};return{get value(){return d},subscribe:(E,T)=>{if(!this.effectsChannel)throw new Error('This SQLocal instance is not configured for reactive queries. Set the "reactive" option to enable them.');return T||(T=x=>{throw x}),m.add(E),y.add(T),f?u&&E(d):(this.effectsChannel.addEventListener("message",_),f=!0,w()),{unsubscribe:()=>{m.delete(E),y.delete(T),m.size===0&&(this.effectsChannel?.removeEventListener("message",_),f=!1)}}}}}}),Object.defineProperty(this,"createCallbackFunction",{enumerable:!0,configurable:!0,writable:!0,value:async(c,d)=>{await this.createQuery({type:"function",functionName:c,functionType:"callback"}),this.userCallbacks.set(c,d)}}),Object.defineProperty(this,"createScalarFunction",{enumerable:!0,configurable:!0,writable:!0,value:async(c,d)=>{const u=`_sqlocal_func_${c}`,f=()=>{this.proxy[u]=d};this.proxy===globalThis&&f(),await this.createQuery({type:"function",functionName:c,functionType:"scalar"}),this.proxy!==globalThis&&f()}}),Object.defineProperty(this,"createAggregateFunction",{enumerable:!0,configurable:!0,writable:!0,value:async(c,d)=>{const u=`_sqlocal_func_${c}`,f=()=>{this.proxy[`${u}_step`]=d.step,this.proxy[`${u}_final`]=d.final};this.proxy===globalThis&&f(),await this.createQuery({type:"function",functionName:c,functionType:"aggregate"}),this.proxy!==globalThis&&f()}}),Object.defineProperty(this,"getDatabaseInfo",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{const c=await this.createQuery({type:"getinfo"});if(c.type==="info")return c.info;throw new Error("The database failed to return valid information.")}}),Object.defineProperty(this,"getDatabaseFile",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{const c=await this.createQuery({type:"export"});if(c.type==="buffer")return new File([c.buffer],c.bufferName,{type:"application/x-sqlite3"});throw new Error("The database failed to export.")}}),Object.defineProperty(this,"overwriteDatabaseFile",{enumerable:!0,configurable:!0,writable:!0,value:async(c,d)=>{await pt("exclusive",!1,this.config,async()=>{try{this.broadcast({type:"close",clientKey:this.clientKey});const u=await bn(c,"buffer");await this.createQuery({type:"import",database:u}),typeof d=="function"&&(this.bypassMutationLock=!0,await d()),this.broadcast({type:"reinit",clientKey:this.clientKey,reason:"overwrite"})}finally{this.bypassMutationLock=!1}})}}),Object.defineProperty(this,"deleteDatabaseFile",{enumerable:!0,configurable:!0,writable:!0,value:async c=>{await pt("exclusive",!1,this.config,async()=>{try{this.broadcast({type:"close",clientKey:this.clientKey}),await this.createQuery({type:"delete"}),typeof c=="function"&&(this.bypassMutationLock=!0,await c()),this.broadcast({type:"reinit",clientKey:this.clientKey,reason:"delete"})}finally{this.bypassMutationLock=!1}})}}),Object.defineProperty(this,"destroy",{enumerable:!0,configurable:!0,writable:!0,value:async()=>{await this.createQuery({type:"destroy"}),typeof globalThis.Worker<"u"&&this.processor instanceof Worker&&(this.processor.removeEventListener("message",this.processMessageEvent),this.processor.terminate()),this.queriesInProgress.clear(),this.userCallbacks.clear(),this.reinitChannel.close(),this.effectsChannel?.close(),this.isDestroyed=!0}}),Object.defineProperty(this,_n,{enumerable:!0,configurable:!0,writable:!0,value:()=>{this.destroy()}}),Object.defineProperty(this,vn,{enumerable:!0,configurable:!0,writable:!0,value:async()=>{await this.destroy()}});const t=typeof e=="string"?{databasePath:e}:e,{onInit:r,onConnect:n,processor:s,...a}=t,{databasePath:i}=a;this.config=t,this.clientKey=bt();const l=wn(i,this.clientKey);if(this.reinitChannel=new BroadcastChannel(`_sqlocal_reinit_(${l})`),a.reactive&&(this.effectsChannel=new BroadcastChannel(`_sqlocal_effects_(${l})`)),typeof s<"u")this.processor=s;else if(i==="local"||i===":localStorage:"){const c=new Do("local");this.processor=new ut(c)}else if(i==="session"||i===":sessionStorage:"){const c=new Do("session");this.processor=new ut(c)}else if(typeof globalThis.Worker<"u"&&i!==":memory:")this.processor=new Worker(new URL("/assets/worker-CuIBOSaM.js",import.meta.url),{type:"module"});else{const c=new St;this.processor=new ut(c)}this.processor instanceof ut?(this.processor.onmessage=c=>this.processMessageEvent(c),this.proxy=globalThis):(this.processor.addEventListener("message",this.processMessageEvent),this.proxy=go(this.processor)),this.processor.postMessage({type:"config",config:{...a,clientKey:this.clientKey,onInitStatements:r?.(Tt)??[]}})}}_n=Symbol.dispose,vn=Symbol.asyncDispose;const mo="lupmis2.db",Hr="lupmis-db-sync",En=new qr(mo),{sql:b}=En;console.log("[Database] SQLocal instance created for:",mo);const xn=new BroadcastChannel(Hr);let Sn=!1,Tn,Ln;const Ro=new Promise((o,e)=>{Tn=o,Ln=e}),Lt=new Set;function Wr(o){return Lt.add(o),()=>Lt.delete(o)}xn.onmessage=o=>{const{type:e,payload:t}=o.data;if(e==="DB_CHANGE")for(const r of Lt)try{r(t)}catch(n){console.error("[Database] Change listener error:",n)}};function Y(o,e,t=null){xn.postMessage({type:"DB_CHANGE",payload:{table:o,action:e,id:t,timestamp:Date.now()}});for(const r of Lt)try{r({table:o,action:e,id:t,timestamp:Date.now(),local:!0})}catch(n){console.error("[Database] Change listener error:",n)}}async function Vr(){try{console.log("[Database] Initializing schema...");const o=await b`SELECT sqlite_version() as version`;console.log("[Database] SQLite version:",o[0]?.version),console.log("[Database] Creating locations table..."),await b` CREATE TABLE IF NOT EXISTS locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, longitude REAL NOT NULL, latitude REAL NOT NULL, description TEXT, category TEXT DEFAULT 'default', created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, synced INTEGER DEFAULT 0 ) `;const e=await b`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;console.log("[Database] Locations table exists:",e.length>0),console.log("[Database] Creating sync_log table..."),await b` CREATE TABLE IF NOT EXISTS sync_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, table_name TEXT NOT NULL, record_id INTEGER NOT NULL, action TEXT NOT NULL, timestamp TEXT DEFAULT CURRENT_TIMESTAMP, synced INTEGER DEFAULT 0 ) `,console.log("[Database] Creating remote_data table..."),await b` CREATE TABLE IF NOT EXISTS remote_data ( key TEXT PRIMARY KEY, data TEXT NOT NULL, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating collector_zones table..."),await b` CREATE TABLE IF NOT EXISTS collector_zones ( id INTEGER PRIMARY KEY, zone_name TEXT, geometry_wkt TEXT, properties TEXT, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating parcels table...");try{const r=await b`PRAGMA table_info(parcels)`;r.length>0&&!r.some(n=>n.name==="upn")&&(console.log("[Database] Migrating parcels table to lu_parcels structure (dropping old cache)..."),await b`DROP TABLE parcels`)}catch(r){console.warn("[Database] parcels migration check failed:",r)}await b` CREATE TABLE IF NOT EXISTS parcels ( id INTEGER PRIMARY KEY, upn TEXT, style INTEGER, landuse TEXT, zone_code TEXT, zone_name TEXT, sector TEXT, block TEXT, parcel_no TEXT, prop_no TEXT, st_name TEXT, prop_add TEXT, fac_name TEXT, min_height INTEGER, max_height INTEGER, eff_date TEXT, lp_name TEXT, locality TEXT, mmda TEXT, last_update TEXT, remarks TEXT, geometry_wkt TEXT, created_at TEXT, updated_at TEXT, districtid INTEGER, status TEXT DEFAULT 'verified', fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating building_footprints table..."),await b` CREATE TABLE IF NOT EXISTS building_footprints ( id INTEGER PRIMARY KEY, geometry_wkt TEXT, properties TEXT, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating osm_roads table..."),await b` CREATE TABLE IF NOT EXISTS osm_roads ( osm_id INTEGER PRIMARY KEY, geometry_wkt TEXT, properties TEXT, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating upn_grid table..."),await b` CREATE TABLE IF NOT EXISTS upn_grid ( id INTEGER PRIMARY KEY AUTOINCREMENT, districtid INTEGER, upn_prefix TEXT, geometry_wkt TEXT, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating gps_trails table..."),await b` CREATE TABLE IF NOT EXISTS gps_trails ( id INTEGER PRIMARY KEY AUTOINCREMENT, client_uuid TEXT UNIQUE, name TEXT, district_id TEXT, started_at TEXT NOT NULL, ended_at TEXT, status TEXT NOT NULL DEFAULT 'recording', point_count INTEGER NOT NULL DEFAULT 0, distance_m REAL NOT NULL DEFAULT 0, synced INTEGER NOT NULL DEFAULT 0, remote_id TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ) `,console.log("[Database] Creating gps_trail_points table..."),await b` CREATE TABLE IF NOT EXISTS gps_trail_points ( id INTEGER PRIMARY KEY AUTOINCREMENT, trail_id INTEGER NOT NULL, seq INTEGER NOT NULL, longitude REAL NOT NULL, latitude REAL NOT NULL, altitude REAL, accuracy REAL, altitude_accuracy REAL, heading REAL, speed REAL, satellites INTEGER, recorded_at TEXT NOT NULL ) `,console.log("[Database] Creating external_imports table..."),await b` CREATE TABLE IF NOT EXISTS external_imports ( id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT NOT NULL, target_type TEXT NOT NULL DEFAULT 'other', mapping_json TEXT, status TEXT NOT NULL DEFAULT 'imported', feature_count INTEGER NOT NULL DEFAULT 0, error_count INTEGER NOT NULL DEFAULT 0, client_import_id TEXT UNIQUE, created_at TEXT DEFAULT CURRENT_TIMESTAMP, last_uploaded_at TEXT ) `,console.log("[Database] Creating external_import_features table..."),await b` CREATE TABLE IF NOT EXISTS external_import_features ( id INTEGER PRIMARY KEY AUTOINCREMENT, import_id INTEGER NOT NULL REFERENCES external_imports(id) ON DELETE CASCADE, client_uuid TEXT UNIQUE, geometry_wkt TEXT NOT NULL, properties_json TEXT, upload_status TEXT NOT NULL DEFAULT 'pending', server_id INTEGER, error_message TEXT ) `,await b`CREATE INDEX IF NOT EXISTS idx_locations_category ON locations(category)`,await b`CREATE INDEX IF NOT EXISTS idx_locations_synced ON locations(synced)`,await b`CREATE INDEX IF NOT EXISTS idx_gps_trails_synced ON gps_trails(synced, status)`,await b`CREATE INDEX IF NOT EXISTS idx_gps_trail_points_trail ON gps_trail_points(trail_id, seq)`,await b`CREATE INDEX IF NOT EXISTS idx_external_imports_status ON external_imports(status)`,await b`CREATE INDEX IF NOT EXISTS idx_external_import_features_import ON external_import_features(import_id, upload_status)`;const t=await b`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;console.log("[Database] All tables:",t.map(r=>r.name)),Sn=!0,Tn(!0),console.log("[Database] ✓ Schema initialized")}catch(o){throw console.error("[Database] ✗ Schema init failed:",o),Ln(o),o}}async function Kr(o,e,t,r={}){const{description:n=null,category:s="default"}=r;console.log("[Database] Adding location:",o,e,t,s);try{const a=await b`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;if(console.log("[Database] Table check before insert:",a),a.length===0)throw console.error("[Database] ✗ locations table does not exist!"),new Error("locations table does not exist");console.log("[Database] Executing INSERT..."),await b` INSERT INTO locations (name, longitude, latitude, description, category) VALUES (${o}, ${e}, ${t}, ${n}, ${s}) `,console.log("[Database] INSERT completed");const l=(await b`SELECT last_insert_rowid() as id`)[0]?.id;console.log("[Database] New ID:",l);const c=await b`SELECT * FROM locations WHERE id = ${l}`;if(console.log("[Database] Verify insert:",c),c.length===0)throw console.error("[Database] ✗ Insert verification failed - row not found!"),new Error("Insert verification failed");return await b` INSERT INTO sync_log (table_name, record_id, action) VALUES ('locations', ${l}, 'INSERT') `,Y("locations","INSERT",l),console.log("[Database] ✓ Location added:",l),{id:l}}catch(a){throw console.error("[Database] ✗ Failed to add location:",a),a}}async function kn(o={}){const{category:e=null,limit:t=1e3}=o;try{const r=await b`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;if(console.log("[Database] getLocations - table exists:",r.length>0),r.length===0)return console.warn("[Database] locations table does not exist yet"),[];let n;return e?n=await b` SELECT * FROM locations WHERE category = ${e} ORDER BY created_at DESC LIMIT ${t} `:n=await b` SELECT * FROM locations ORDER BY created_at DESC LIMIT ${t} `,console.log("[Database] getLocations returned",n.length,"rows"),n}catch(r){return console.error("[Database] getLocations error:",r),[]}}async function Xr(){try{return(await b`SELECT COUNT(*) as count FROM locations`)[0]?.count??0}catch(o){return console.error("[Database] getLocationCount error:",o),0}}async function In(o,e){try{const t=JSON.stringify(e);await b` INSERT OR REPLACE INTO remote_data (key, data, fetched_at) VALUES (${o}, ${t}, CURRENT_TIMESTAMP) `,console.log("[Database] ✓ Remote data cached:",o)}catch(t){throw console.error("[Database] ✗ Failed to cache remote data:",o,t),t}}async function Pn(o){try{const e=await b`SELECT data, fetched_at FROM remote_data WHERE key = ${o}`;if(e.length===0)return null;const t=JSON.parse(e[0].data);return console.log("[Database] ✓ Remote data loaded from cache:",o,"(fetched",e[0].fetched_at+")"),t}catch(e){return console.error("[Database] ✗ Failed to read cached remote data:",o,e),null}}async function Yr(o){try{await b`DELETE FROM collector_zones`;for(const e of o){const t=JSON.stringify(e);await b` INSERT INTO collector_zones (id, zone_name, geometry_wkt, properties, fetched_at) VALUES (${e.colzonenr||e.id}, ${e.colzonename||e.zone_name||""}, ${e.polygon||e.boundary||""}, ${t}, CURRENT_TIMESTAMP) `}console.log("[Database] ✓ Saved",o.length,"collector zones")}catch(e){throw console.error("[Database] ✗ Failed to save collector zones:",e),e}}async function Jr(){try{const o=await b`SELECT properties FROM collector_zones ORDER BY id`;return o.length===0?null:o.map(e=>JSON.parse(e.properties))}catch(o){return console.error("[Database] ✗ Failed to read local collector zones:",o),null}}async function Zr(o,e){try{await b`BEGIN`,await b`DELETE FROM upn_grid`;let t=0;for(const r of o){const n=r.polygon||r.geometry_wkt||r.geom||"";await b` INSERT INTO upn_grid (districtid, upn_prefix, geometry_wkt, fetched_at) VALUES (${X(e)}, ${r.upn_prefix??null}, ${n}, CURRENT_TIMESTAMP) `,t++}await b`COMMIT`,console.log("[Database] ✓ Saved",t,"UPN-grid cells (district",e,")")}catch(t){try{await b`ROLLBACK`}catch{}throw console.error("[Database] ✗ Failed to save UPN grid:",t),t}}async function Qr(o){try{const e=await b` SELECT id, districtid, upn_prefix, geometry_wkt FROM upn_grid WHERE districtid = ${X(o)} ORDER BY id `;return e.length===0?null:e}catch(e){return console.error("[Database] ✗ Failed to read UPN grid:",e),null}}function Cn(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,o=>{const e=Math.random()*16|0;return(o==="x"?e:e&3|8).toString(16)})}async function es(o){const{filename:e,targetType:t="other",featureCount:r=0}=o,n=Cn();try{await b` INSERT INTO external_imports (filename, target_type, status, feature_count, client_import_id) VALUES (${e}, ${t}, ${t==="other"?"other":"imported"}, ${r}, ${n}) `;const a=(await b`SELECT last_insert_rowid() AS id`)[0]?.id;return Y("external_imports","INSERT",a),{id:a,client_import_id:n}}catch(s){throw console.error("[Database] ✗ Failed to create external import:",s),s}}async function ts(o,e){if(!Array.isArray(e)||e.length===0)return 0;try{await b`BEGIN`;let t=0;for(const r of e){const n=r.geometry_wkt||"";if(!n)continue;const s=JSON.stringify(r.properties??{}),a=r.client_uuid||Cn();await b` INSERT INTO external_import_features (import_id, client_uuid, geometry_wkt, properties_json, upload_status) VALUES (${o}, ${a}, ${n}, ${s}, 'pending') `,t++}return await b`COMMIT`,Y("external_import_features","INSERT",o),t}catch(t){try{await b`ROLLBACK`}catch{}throw console.error("[Database] ✗ Failed to add import features:",t),t}}async function kt(o,e={}){try{const t=await b`SELECT * FROM external_imports WHERE id = ${o}`;if(t.length===0)throw new Error(`Import ${o} not found`);const r=t[0],n=e.targetType??r.target_type,s=e.mapping!==void 0?e.mapping?JSON.stringify(e.mapping):null:r.mapping_json,a=e.status??r.status,i=e.errorCount??r.error_count,l=e.lastUploadedAt??r.last_uploaded_at;await b` UPDATE external_imports SET target_type = ${n}, mapping_json = ${s}, status = ${a}, error_count = ${i}, last_uploaded_at = ${l} WHERE id = ${o} `,Y("external_imports","UPDATE",o)}catch(t){throw console.error("[Database] ✗ Failed to update external import:",t),t}}async function os(o){try{const e=await b`SELECT * FROM external_imports WHERE id = ${o}`;if(e.length===0)return null;const t=e[0];return{...t,mapping:t.mapping_json?JSON.parse(t.mapping_json):null}}catch(e){return console.error("[Database] ✗ Failed to read external import:",e),null}}async function ns(o){try{return(await b` SELECT id, client_uuid, geometry_wkt, properties_json, upload_status, server_id, error_message FROM external_import_features WHERE import_id = ${o} ORDER BY id `).map(t=>({...t,properties:t.properties_json?JSON.parse(t.properties_json):{}}))}catch(e){return console.error("[Database] ✗ Failed to read import features:",e),[]}}async function rs(o,e){try{const t=await b` SELECT id, properties_json FROM external_import_features WHERE import_id = ${o} `;if(t.length===0)return 0;await b`BEGIN`;let r=0;for(const n of t){const s=n.properties_json?JSON.parse(n.properties_json):{},a=e(s)??{};await b` UPDATE external_import_features SET properties_json = ${JSON.stringify(a)} WHERE id = ${n.id} `,r++}return await b`COMMIT`,Y("external_import_features","UPDATE",o),r}catch(t){try{await b`ROLLBACK`}catch{}throw console.error("[Database] ✗ Failed to remap import features:",t),t}}async function ss(o,e,t){try{if(t!==void 0){const r=JSON.stringify(t);await b` UPDATE external_import_features SET geometry_wkt = ${e}, properties_json = ${r} WHERE client_uuid = ${o} `}else await b` UPDATE external_import_features SET geometry_wkt = ${e} WHERE client_uuid = ${o} `;Y("external_import_features","UPDATE",o)}catch(r){throw console.error("[Database] ✗ Failed to update import feature geometry:",r),r}}async function as(o){try{const e=await b` SELECT import_id FROM external_import_features WHERE client_uuid = ${o} `;if(e.length===0)return;const t=e[0].import_id;await b`BEGIN`;try{await b`DELETE FROM external_import_features WHERE client_uuid = ${o}`,await b` UPDATE external_imports SET feature_count = MAX(0, feature_count - 1) WHERE id = ${t} `,await b`COMMIT`}catch(r){try{await b`ROLLBACK`}catch{}throw r}Y("external_import_features","DELETE",o),Y("external_imports","UPDATE",t)}catch(e){throw console.error("[Database] ✗ Failed to delete import feature:",e),e}}function X(o){if(o===""||o===null||o===void 0)return null;const e=Number(o);return Number.isNaN(e)?null:e}async function is(o){try{await b`BEGIN`,await b`DELETE FROM parcels`;let e=0;for(const t of o){const r=t.id??t.parcelid??t.parcel_id??null;if(r==null)continue;const n=t.boundary||t.geometry_wkt||t.polygon||t.wkt||(typeof t.geom=="string"?t.geom:"");await b` INSERT OR REPLACE INTO parcels ( id, upn, style, landuse, zone_code, zone_name, sector, block, parcel_no, prop_no, st_name, prop_add, fac_name, min_height, max_height, eff_date, lp_name, locality, mmda, last_update, remarks, geometry_wkt, created_at, updated_at, districtid, status, fetched_at ) VALUES ( ${r}, ${t.upn??null}, ${X(t.style)}, ${t.landuse??null}, ${t.zone_code??null}, ${t.zone_name??null}, ${t.sector??null}, ${t.block??null}, ${t.parcel_no??null}, ${t.prop_no??null}, ${t.st_name??null}, ${t.prop_add??null}, ${t.fac_name??null}, ${X(t.min_height)}, ${X(t.max_height)}, ${t.eff_date??null}, ${t.lp_name??null}, ${t.locality??null}, ${t.mmda??null}, ${t.last_update??null}, ${t.remarks??null}, ${n}, ${t.created_at??null}, ${t.updated_at??null}, ${X(t.districtid)}, 'verified', CURRENT_TIMESTAMP ) `,e++}await b`COMMIT`,console.log("[Database] ✓ Saved",e,"parcels (from",o.length,"rows,",o.length-e,"skipped/replaced)")}catch(e){try{await b`ROLLBACK`}catch{}throw console.error("[Database] ✗ Failed to save parcels:",e),e}}async function ls(){try{const o=await b`SELECT * FROM parcels ORDER BY id`;return o.length===0?null:o}catch(o){return console.error("[Database] ✗ Failed to read local parcels:",o),null}}async function cs(o,e){try{await b` UPDATE parcels SET upn = ${e.upn??null}, style = ${X(e.style)}, landuse = ${e.landuse??null}, zone_code = ${e.zone_code??null}, zone_name = ${e.zone_name??null}, sector = ${e.sector??null}, block = ${e.block??null}, parcel_no = ${e.parcel_no??null}, prop_no = ${e.prop_no??null}, st_name = ${e.st_name??null}, prop_add = ${e.prop_add??null}, fac_name = ${e.fac_name??null}, min_height = ${X(e.min_height)}, max_height = ${X(e.max_height)}, eff_date = ${e.eff_date??null}, lp_name = ${e.lp_name??null}, locality = ${e.locality??null}, mmda = ${e.mmda??null}, last_update = ${e.last_update??null}, remarks = ${e.remarks??null}, districtid = ${X(e.districtid)}, updated_at = CURRENT_TIMESTAMP WHERE id = ${o} `,console.log("[Database] ✓ Parcel updated:",o),Y("parcels","UPDATE",o)}catch(t){throw console.error("[Database] ✗ Failed to update parcel:",o,t),t}}async function ds(o,e={}){try{await b` INSERT INTO parcels ( id, upn, style, landuse, zone_code, zone_name, sector, block, parcel_no, prop_no, st_name, prop_add, fac_name, min_height, max_height, eff_date, lp_name, locality, mmda, last_update, remarks, geometry_wkt, created_at, updated_at, districtid, status, fetched_at ) VALUES ( NULL, ${e.upn??null}, ${X(e.style)}, ${e.landuse??null}, ${e.zone_code??null}, ${e.zone_name??null}, ${e.sector??null}, ${e.block??null}, ${e.parcel_no??null}, ${e.prop_no??null}, ${e.st_name??null}, ${e.prop_add??null}, ${e.fac_name??null}, ${X(e.min_height)}, ${X(e.max_height)}, ${e.eff_date??null}, ${e.lp_name??null}, ${e.locality??null}, ${e.mmda??null}, ${e.last_update??null}, ${e.remarks??null}, ${o}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ${X(e.districtid)}, 'new', CURRENT_TIMESTAMP ) `;const r=(await b`SELECT last_insert_rowid() as id`)[0]?.id;return console.log("[Database] ✓ New parcel inserted:",r,"(status: new)"),Y("parcels","INSERT",r),{id:r}}catch(t){throw console.error("[Database] ✗ Failed to insert new parcel:",t),t}}async function us(o){try{if(o.length>0){const e=o[0],t={};for(const[r,n]of Object.entries(e))t[r]=n===null?"null":typeof n;console.log("[Database] First footprint field types:",t)}await b`DELETE FROM building_footprints`;for(const e of o){const t=JSON.stringify(e);let r=e.polygon||e.boundary||e.geom||e.wkt||e.footprint||"";const n=typeof r=="object"?JSON.stringify(r):String(r);let s=e.id||e.footprint_id||e.building_id||null;await b` INSERT INTO building_footprints (id, geometry_wkt, properties, fetched_at) VALUES (${s!==null&&typeof s=="object"?null:s}, ${n}, ${t}, CURRENT_TIMESTAMP) `}console.log("[Database] ✓ Saved",o.length,"building footprints")}catch(e){throw console.error("[Database] ✗ Failed to save building footprints:",e),e}}async function ps(){try{const o=await b`SELECT properties FROM building_footprints ORDER BY id`;return o.length===0?null:o.map(e=>JSON.parse(e.properties))}catch(o){return console.error("[Database] ✗ Failed to read local building footprints:",o),null}}async function fs(o){try{if(o.length>0){const e=o[0],t={};for(const[r,n]of Object.entries(e))t[r]=n===null?"null":typeof n;console.log("[Database] First road field types:",t)}await b`DELETE FROM osm_roads`;for(const e of o){const t=JSON.stringify(e);let r=e.geom||e.geometry||e.wkt||e.road||e.line||"";const n=typeof r=="object"?JSON.stringify(r):String(r);let s=e.osm_id??e.osmid??e.id??null;await b` INSERT OR REPLACE INTO osm_roads (osm_id, geometry_wkt, properties, fetched_at) VALUES (${s!==null&&typeof s=="object"?null:s}, ${n}, ${t}, CURRENT_TIMESTAMP) `}console.log("[Database] ✓ Saved",o.length,"OSM roads")}catch(e){throw console.error("[Database] ✗ Failed to save OSM roads:",e),e}}async function hs(){try{const o=await b`SELECT properties FROM osm_roads ORDER BY osm_id`;return o.length===0?null:o.map(e=>JSON.parse(e.properties))}catch(o){return console.error("[Database] ✗ Failed to read local OSM roads:",o),null}}async function gs(){return En.getDatabaseFile()}async function ms(o="lupmis-backup.sqlite3"){const e=await gs(),t=new Blob([e],{type:"application/x-sqlite3"}),r=URL.createObjectURL(t),n=document.createElement("a");n.href=r,n.download=o,n.click(),URL.revokeObjectURL(r)}async function ys(){return{type:"FeatureCollection",features:(await kn()).map(e=>({type:"Feature",properties:{id:e.id,name:e.name,category:e.category,notes:e.notes,created_at:e.created_at},geometry:{type:"Point",coordinates:[e.lon,e.lat]}}))}}async function yo(){try{const o=await b` SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name `,e=await Xr();return{ready:Sn,databasePath:mo,tables:o.map(t=>t.name),locationCount:e}}catch(o){return{ready:!1,error:o.message}}}const Mn=Object.freeze(["parcels","building_footprints","osm_roads","collector_zones","upn_grid","remote_data"]);function An(o){return Mn.includes(o)}async function Fn(o){if(!An(o))throw new Error(`Refusing to clear "${o}" — not a known cached-layer table`);const t=(await b(`SELECT COUNT(*) AS n FROM "${o}"`))[0]?.n??0;return await b(`DELETE FROM "${o}"`),console.log(`[Database] ✓ Cleared "${o}" (${t} rows)`),Y(o,"CLEAR",null),t}async function bo(){const o=await b` SELECT name FROM sqlite_master WHERE type='table' AND name IN ( 'parcels', 'building_footprints', 'osm_roads', 'collector_zones', 'upn_grid', 'remote_data' ) `,e=new Set(o.map(n=>n.name)),t=[];for(const n of Mn)if(e.has(n))try{const s=await Fn(n);t.push({table:n,count:s})}catch(s){console.error(`[Database] Failed to clear ${n}:`,s),t.push({table:n,count:0,error:s.message})}const r=t.reduce((n,s)=>n+s.count,0);return console.log(`[Database] ✓ Cleared all cached layers: ${r} rows across ${t.length} tables`),t}async function bs(){const o=await b` SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name `;if(o.length===0)return[];const e=o.map(t=>`SELECT '${t.name}' AS name, COUNT(*) AS count FROM "${t.name}"`).join(" UNION ALL ");return b(e)}async function ws(o,e=200){if((await b` SELECT name FROM sqlite_master WHERE type='table' AND name = ${o} `).length===0)throw new Error(`Table "${o}" does not exist`);const r=await b(`SELECT * FROM "${o}" LIMIT ${e}`);return{columns:r.length>0?Object.keys(r[0]):[],rows:r}}async function _s(){console.log("=== DATABASE TEST ===");try{const o=await b`SELECT sqlite_version() as v`;console.log("1. SQLite version:",o[0].v);const e=await b`SELECT name FROM sqlite_master WHERE type='table'`;console.log("2. Tables:",e.map(n=>n.name)),console.log("3. Inserting test row..."),await b`INSERT INTO locations (name, longitude, latitude, category) VALUES ('TEST', -1.0, 7.0, 'test')`;const t=await b`SELECT * FROM locations WHERE name = 'TEST'`;console.log("4. Test row:",t);const r=await b`SELECT COUNT(*) as c FROM locations`;return console.log("5. Total rows:",r[0].c),await b`DELETE FROM locations WHERE name = 'TEST'`,console.log("6. Test row deleted"),console.log("=== TEST PASSED ==="),!0}catch(o){return console.error("=== TEST FAILED ===",o),!1}}typeof window<"u"&&(window.testDatabase=_s,window.dbStatus=yo);async function vs(o){const{uuid:e,name:t=null,startedAt:r,districtId:n=null}=o;await b` INSERT INTO gps_trails (client_uuid, name, district_id, started_at, status) VALUES (${e}, ${t}, ${n}, ${r}, 'recording') `;const a=(await b`SELECT last_insert_rowid() as id`)[0]?.id;return Y("gps_trails","insert",a),a}async function Es(o,e){const{seq:t,lon:r,lat:n,altitude:s=null,accuracy:a=null,altitudeAccuracy:i=null,heading:l=null,speed:c=null,satellites:d=null,timestamp:u}=e,f=typeof u=="number"?new Date(u).toISOString():u||new Date().toISOString();await b` INSERT INTO gps_trail_points (trail_id, seq, longitude, latitude, altitude, accuracy, altitude_accuracy, heading, speed, satellites, recorded_at) VALUES (${o}, ${t}, ${r}, ${n}, ${s}, ${a}, ${i}, ${l}, ${c}, ${d}, ${f}) `}async function xs(o,e){const{endedAt:t,pointCount:r=0,distanceM:n=0}=e;await b` UPDATE gps_trails SET ended_at = ${t}, point_count = ${r}, distance_m = ${n}, status = 'completed' WHERE id = ${o} `,Y("gps_trails","update",o)}async function Ss(){return b`SELECT * FROM gps_trails WHERE synced = 0 AND status = 'completed' ORDER BY started_at ASC`}async function Ts(o){return b`SELECT * FROM gps_trail_points WHERE trail_id = ${o} ORDER BY seq ASC`}async function Ls(o,e=null){await b`UPDATE gps_trails SET synced = 1, remote_id = ${e} WHERE id = ${o}`,Y("gps_trails","update",o)}const On=3.28084,Dn=621371e-9,Rn=10.7639,Nn=247105e-9,$n=3861e-10;function Mt(){return localStorage.getItem("measurement-system")||"metric"}function rt(o){if(Mt()==="imperial"){const e=o*On;return e>=5280?Math.round(o*Dn*100)/100+" mi":Math.round(e)+" ft"}return o>1e3?Math.round(o/1e3*100)/100+" km":Math.round(o*100)/100+" m"}function ks(o){if(Mt()==="imperial"){const e=o*On,t=o*Dn;return e>=5280?`${t.toFixed(2)} mi (${e.toLocaleString("en",{maximumFractionDigits:0})} ft)`:`${e.toLocaleString("en",{maximumFractionDigits:1})} ft`}return o>=1e3?`${(o/1e3).toFixed(2)} km (${o.toLocaleString("en",{maximumFractionDigits:0})} m)`:`${o.toLocaleString("en",{maximumFractionDigits:1})} m`}function qe(o){if(Mt()==="imperial"){const e=o*Nn;return e>=640?Math.round(o*$n*100)/100+" mi²":e>=1?Math.round(e*100)/100+" acres":Math.round(o*Rn).toLocaleString("en")+" ft²"}return o>1e6?Math.round(o/1e6*100)/100+" km²":Math.round(o*100)/100+" m²"}function Is(o){if(Mt()==="imperial"){const e=o*Rn,t=o*Nn,r=o*$n;return t>=640?`${r.toFixed(2)} mi² (${t.toLocaleString("en",{maximumFractionDigits:0})} acres)`:t>=1?`${t.toLocaleString("en",{maximumFractionDigits:1})} acres (${e.toLocaleString("en",{maximumFractionDigits:0})} ft²)`:`${e.toLocaleString("en",{maximumFractionDigits:0})} ft²`}return o>1e6?`${(o/1e6).toFixed(2)} km² (${o.toLocaleString("en",{maximumFractionDigits:0})} m²)`:`${o.toLocaleString("en",{maximumFractionDigits:0})} m²`}function Ps(o){return qe(Math.PI*o*o)}function Bn(o){const e=xe(o)[1];return Math.cos(e*Math.PI/180)}function Qt(o,e){return o*Bn(e)}function Cs(o,e){const t=Bn(e);return t===0?o:o/t}function Ms(o){return o<50?1:o<200?5:o<1e3?10:o<5e3?50:100}function As(o,e,t,r,n=1e-10){const s=e[0]-o[0],a=e[1]-o[1],i=r[0]-t[0],l=r[1]-t[1],c=s*l-a*i;if(Math.abs(c)1+n||p<-n||p>1+n?null:{point:[o[0]+f*s,o[1]+f*a],t:Math.max(0,Math.min(1,f)),u:Math.max(0,Math.min(1,p))}}function Gn(o){let e=0;for(let t=0,r=o.length;to[1]!=l>o[1]&&o[0]<(i-s)*(o[1]-a)/(l-a)+s&&(t=!t)}return t}function it(o,e){return(o[0]-e[0])**2+(o[1]-e[1])**2}function Fs(o,e){const t=[];for(let n=0;nn.lineSegIdx!==s.lineSegIdx?n.lineSegIdx-s.lineSegIdx:n.lineT-s.lineT),t}function Os(o,e){const t=e.map((s,a)=>({...s,origOrder:a}));t.sort((s,a)=>s.ringSegIdx!==a.ringSegIdx?s.ringSegIdx-a.ringSegIdx:s.ringT-a.ringT);const r=o.slice(),n=new Array(t.length);for(let s=t.length-1;s>=0;s--){const a=t[s],i=a.ringSegIdx+1,l=1e-6;if(it(a.point,r[a.ringSegIdx])=i&&n[t[c].origOrder]++}return{ring:r,indices:n}}function No(o,e,t){const r=o.length-1,n=(e%r+r)%r,s=(t%r+r)%r,a=[];let i=n;for(;a.push(o[i]),i!==s;)i=(i+1)%r;return a}function $o(o,e,t){const r=[e.point],n=e.lineSegIdx,s=t.lineSegIdx;for(let a=n+1;a<=s;a++)r.push(o[a]);return it(r[r.length-1],t.point)>1e-10&&r.push(t.point),r}function Bo(o,e){const t=Gn(o);return e&&t<0||!e&&t>0?o.slice().reverse():o}function Go(o){if(o.length<2)return o;const e=o[0],t=o[o.length-1];return it(e,t)>1e-10?[...o,e.slice()]:o}function Ds(o,e){let t=1/0,r=1/0,n=-1/0,s=-1/0;for(const c of e)c[0]n&&(n=c[0]),c[1]>s&&(s=c[1]);const a=Math.sqrt((n-t)**2+(s-r)**2)||1,i=o.slice();if(eo(i[0],e)){const c=i[0],d=i[1],u=c[0]-d[0],f=c[1]-d[1],p=Math.sqrt(u*u+f*f)||1,h=a*2/p;i[0]=[c[0]+u*h,c[1]+f*h]}const l=i.length-1;if(eo(i[l],e)){const c=i[l],d=i[l-1],u=c[0]-d[0],f=c[1]-d[1],p=Math.sqrt(u*u+f*f)||1,h=a*2/p;i[l]=[c[0]+u*h,c[1]+f*h]}return i}function wt(o,e){const t=o[0],r=o.slice(1),n=Ds(e,t),s=Fs(t,n);if(s.length!==2)return console.warn(`[polygonSplit] Expected 2 intersections, got ${s.length}`),null;const[a,i]=s,{ring:l,indices:c}=Os(t,s),d=c[0],u=c[1],[f,p]=d0,T=Bo(y,E),x=Bo(_,E),S=[T],P=[x];for(const k of r){const j=Rs(k);eo(j,T)?S.push(k):P.push(k)}return[S,P]}function Rs(o){let e=0,t=0;const r=o.length-1;for(let n=0;n{s.style.opacity="1",s.style.transform="translateY(0)"});const a=()=>{s.style.opacity="0",s.style.transform="translateY(-8px)",setTimeout(()=>s.remove(),300)};s.addEventListener("click",a),setTimeout(a,t)}const ft=[{stroke:"#ef4444",fill:"rgba(239,68,68,0.25)"},{stroke:"#3b82f6",fill:"rgba(59,130,246,0.25)"}],$s=new C({stroke:new I({color:"#0ea5e9",width:3}),fill:new M({color:"rgba(14,165,233,0.15)"})}),Bs=new C({stroke:new I({color:"#f43f5e",width:2,lineDash:[8,6]}),image:new he({radius:5,fill:new M({color:"#f43f5e"}),stroke:new I({color:"#fff",width:1.5})})});class Gs extends fo{constructor(e={}){super({handleEvent:t=>this._handleEvent(t)}),this.snapDistance_=e.snapDistance||25,this._sources=e.sources?Array.isArray(e.sources)?e.sources:[e.sources]:null,this._phase="select",this._selectedFeature=null,this._selectedSource=null,this._drawInteraction=null,this._splitFeatures=null,this._overlaySource=new G({useSpatialIndex:!1}),this._overlayLayer=new D({source:this._overlaySource,displayInLayerSwitcher:!1,style:$s})}setMap(e){this.getMap()&&(this.getMap().removeLayer(this._overlayLayer),this._removeDrawInteraction()),super.setMap(e),e&&this._overlayLayer.setMap(e)}setActive(e){super.setActive(e),e||this._reset()}_getSources(){if(this._sources)return this._sources;if(!this.getMap())return[];const e=[],t=r=>{r.forEach(n=>{n.getVisible()&&(n.getSource&&n.getSource()instanceof G?e.push(n.getSource()):n.getLayers&&t(n.getLayers()))})};return t(this.getMap().getLayers()),e}_handleEvent(e){if(!this.getActive())return!0;if(this._phase==="select"){if(e.type==="pointermove")return this._onSelectMove(e);if(e.type==="singleclick")return this._onSelectClick(e)}if(this._phase==="draw"&&e.type==="keydown"&&e.originalEvent?.key==="Escape")return this._cancelDraw(),!1;if(this._phase==="pick"){if(e.type==="pointermove")return this._onPickMove(e);if(e.type==="singleclick")return this._onPickClick(e);if(e.type==="keydown"&&e.originalEvent?.key==="Escape")return this._reset(),!1}return!0}_onSelectMove(e){const t=this.getMap();if(!t)return!0;this._overlaySource.clear();const r=this._closestPolygon(e);if(r){const n=r.feature.clone();this._overlaySource.addFeature(n),t.getTargetElement().style.cursor="pointer"}else t.getTargetElement().style.cursor="";return!0}_onSelectClick(e){const t=this._closestPolygon(e);if(!t)return!0;this._selectedFeature=t.feature,this._selectedSource=t.source,this._overlaySource.clear();const r=t.feature.clone();return this._overlaySource.addFeature(r),this._startDrawPhase(),!1}_closestPolygon(e){let t=null,r=this.snapDistance_+1;for(const n of this._getSources()){const s=n.getClosestFeatureToCoordinate(e.coordinate);if(!s)continue;const a=s.getGeometry();if(!a)continue;const i=a.getType();if(i!=="Polygon"&&i!=="MultiPolygon")continue;const l=a.getClosestPoint(e.coordinate),d=new de([e.coordinate,l]).getLength()/e.frameState.viewState.resolution;d{const r=t.feature.getGeometry().getCoordinates();this._performSplit(r)}),e.addInteraction(this._drawInteraction))}_removeDrawInteraction(){this._drawInteraction&&this.getMap()&&this.getMap().removeInteraction(this._drawInteraction),this._drawInteraction=null}_cancelDraw(){this._removeDrawInteraction(),this._reset()}_performSplit(e){const t=this._selectedFeature,r=this._selectedSource,n=t.getGeometry();let s;n.getType()==="Polygon"?s=n.getCoordinates():n.getType()==="MultiPolygon"&&(s=n.getCoordinates()[0]);const a=wt(s,e);if(!a){console.warn("[PolygonSplit] Split failed — line must cross the polygon boundary at exactly 2 points."),this._removeDrawInteraction(),this._startDrawPhase();return}const[i,l]=a,c=t.clone();c.setGeometry(new at(i)),c.setStyle(new C({stroke:new I({color:ft[0].stroke,width:2.5}),fill:new M({color:ft[0].fill})}));const d=t.clone();d.setGeometry(new at(l)),d.setStyle(new C({stroke:new I({color:ft[1].stroke,width:2.5}),fill:new M({color:ft[1].fill})}));const u=[c,d];this.dispatchEvent({type:"beforesplit",original:t,features:u}),r.dispatchEvent({type:"beforesplit",original:t,features:u});const f=this.getMap();if(f?.dispatchEvent("undoblockstart"),r.removeFeature(t),r.addFeature(c),r.addFeature(d),f?.dispatchEvent("undoblockend"),this.dispatchEvent({type:"aftersplit",original:t,features:u}),r.dispatchEvent({type:"aftersplit",original:t,features:u}),this._removeDrawInteraction(),t.get("_layerType")==="parcel"){this._splitFeatures=u,this._phase="pick",this._overlaySource.clear();const h=this.getMap();h&&(h.getTargetElement().style.cursor=""),F("Click the polygon that should keep the original identifier.","info",5e3),this.dispatchEvent({type:"splitparcel",features:u,originalProps:t.getProperties(),source:r})}else this._reset()}_onPickMove(e){const t=this.getMap();if(!t)return!0;this._overlaySource.clear();const r=this._closestSplitPiece(e);if(r){const n=r.clone();this._overlaySource.addFeature(n),t.getTargetElement().style.cursor="pointer"}else t.getTargetElement().style.cursor="";return!0}_onPickClick(e){const t=this._closestSplitPiece(e);return t?(this.dispatchEvent({type:"splitpick",picked:t,features:this._splitFeatures}),this._reset(),!1):!0}_closestSplitPiece(e){if(!this._splitFeatures)return null;let t=null,r=this.snapDistance_+1;for(const n of this._splitFeatures){const s=n.getGeometry();if(!s)continue;const a=s.getClosestPoint(e.coordinate),l=new de([e.coordinate,a]).getLength()/e.frameState.viewState.resolution;lo[1]!=l>o[1]&&o[0]<(i-s)*(o[1]-a)/(l-a)+s&&(t=!t)}return t}function js(o,e){const t=ot(o);return e&&t<0||!e&&t>0?o.slice().reverse():o}function zs(o){return o.length<2?o:Ve(o[0],o[o.length-1])>1e-10?[...o,o[0].slice()]:o}function ze(o,e,t){const r=t[0]-e[0],n=t[1]-e[1],s=r*r+n*n;if(s<1e-20)return Ve(o,e);let a=((o[0]-e[0])*r+(o[1]-e[1])*n)/s;a=Math.max(0,Math.min(1,a));const i=e[0]+a*r,l=e[1]+a*n;return(o[0]-i)**2+(o[1]-l)**2}function jo(o,e){let t=0,r=1/0;const n=o.length-1;for(let s=0;s0;){const x=(w+1)%s,S=m?(E-1+a)%a:(E+1)%a;if(x===y||S===_)break;if(Ne(o[x],e[S],i)){w=x,E=S;continue}if(ze(o[x],e[E],e[S])0;){const x=(y-1+s)%s,S=m?(_+1)%a:(_-1+a)%a;if(x===w||S===E)break;if(Ne(o[x],e[S],i)){y=x,_=S;continue}if(ze(o[x],e[_],e[S])r+1)););return n}function Hs(o,e,t,r,n=5){const s=o[0],a=e[0],i=o.slice(1),l=e.slice(1),c=jo(s,t),d=jo(a,r),u=qs(s,a,c.segIdx,d.segIdx,n);if(!u)return console.warn("[polygonMerge] Could not find shared boundary between polygons — seed edges are not near the other ring"),{coords:null,error:"The selected edges are not on a shared boundary. Click edges that lie on the common border between the two polygons."};const{startA:f,endA:p,startB:h,endB:g,reversed:m}=u;s.length-1,a.length-1;const y=Gt(s,p,f);let w;m?w=Gt(a,h,g):w=Gt(a,g,h);const _=[...y,...w.slice(1)],E=n*n;_.length>2&&Ve(_[_.length-1],_[0])k*1.5)return console.warn(`[polygonMerge] Area mismatch: A=${x.toFixed(1)}, B=${S.toFixed(1)}, merged=${P.toFixed(1)}, expected≈${k.toFixed(1)}`),{coords:null,error:"Merge produced an invalid polygon (area mismatch). The polygons may not be truly adjacent — try clicking closer to the shared boundary."};const j=ot(s)>0,q=js(T,j),$=[...i,...l].filter(ce=>{const Q=ce.reduce((H,be)=>H+be[0],0)/(ce.length-1),ye=ce.reduce((H,be)=>H+be[1],0)/(ce.length-1);return Us([Q,ye],q)});return{coords:[q,...$]}}const zo=new C({stroke:new I({color:"#0ea5e9",width:3}),fill:new M({color:"rgba(14,165,233,0.15)"})}),Ws=new C({stroke:new I({color:"#f59e0b",width:3}),fill:new M({color:"rgba(245,158,11,0.15)"})}),Vs=new C({stroke:new I({color:"#0ea5e9",width:3}),fill:new M({color:"rgba(14,165,233,0.15)"}),text:new We({text:"A",font:"bold 22px Exo, sans-serif",fill:new M({color:"#0ea5e9"}),stroke:new I({color:"#fff",width:4}),overflow:!0})}),Ks=new C({stroke:new I({color:"#f59e0b",width:3}),fill:new M({color:"rgba(245,158,11,0.15)"}),text:new We({text:"B",font:"bold 22px Exo, sans-serif",fill:new M({color:"#f59e0b"}),stroke:new I({color:"#fff",width:4}),overflow:!0})}),Xs=new C({stroke:new I({color:"#ec4899",width:4,lineDash:[10,6]})}),Ys=new C({stroke:new I({color:"#10b981",width:2.5}),fill:new M({color:"rgba(16,185,129,0.3)"})});class Js extends fo{constructor(e={}){super({handleEvent:t=>this._handleEvent(t)}),this.snapDistance_=e.snapDistance||25,this.tolerance_=e.tolerance||5,this._phase="select_a",this._featureA=null,this._sourceA=null,this._featureB=null,this._sourceB=null,this._edgeClickA=null,this._edgeClickB=null,this._highlightSource=new G({useSpatialIndex:!1}),this._highlightLayer=new D({source:this._highlightSource,displayInLayerSwitcher:!1,style:t=>t.get("_highlightStyle")||zo}),this._edgeSource=new G({useSpatialIndex:!1}),this._edgeLayer=new D({source:this._edgeSource,displayInLayerSwitcher:!1,style:Xs})}setMap(e){this.getMap()&&(this.getMap().removeLayer(this._highlightLayer),this.getMap().removeLayer(this._edgeLayer)),super.setMap(e),e&&(this._highlightLayer.setMap(e),this._edgeLayer.setMap(e))}setActive(e){super.setActive(e),e||this._reset()}_getSources(){if(!this.getMap())return[];const e=[],t=r=>{r.forEach(n=>{n.getVisible()&&(n.getSource&&n.getSource()instanceof G?e.push(n.getSource()):n.getLayers&&t(n.getLayers()))})};return t(this.getMap().getLayers()),e}_handleEvent(e){if(!this.getActive())return!0;if(e.type==="keydown"&&e.originalEvent?.key==="Escape")return this._reset(),!1;switch(this._phase){case"select_a":if(e.type==="pointermove")return this._onSelectMove(e,null);if(e.type==="singleclick")return this._onSelectAClick(e);break;case"select_b":if(e.type==="pointermove")return this._onSelectMove(e,this._featureA);if(e.type==="singleclick")return this._onSelectBClick(e);break;case"click_edge_a":if(e.type==="pointermove")return this._onEdgeMove(e,this._featureA);if(e.type==="singleclick")return this._onEdgeAClick(e);break;case"click_edge_b":if(e.type==="pointermove")return this._onEdgeMove(e,this._featureB);if(e.type==="singleclick")return this._onEdgeBClick(e);break}return!0}_onSelectMove(e,t){const r=this.getMap();if(!r)return!0;this._highlightSource.clear(),this._edgeSource.clear(),this._rebuildHighlights();const n=this._closestPolygon(e,t);if(n){const s=this._phase==="select_a"?zo:Ws,a=n.feature.clone();a.set("_highlightStyle",s),this._highlightSource.addFeature(a),r.getTargetElement().style.cursor="pointer"}else r.getTargetElement().style.cursor="";return!0}_onSelectAClick(e){const t=this._closestPolygon(e,null);return t?(this._featureA=t.feature,this._sourceA=t.source,this._phase="select_b",this._rebuildHighlights(),!1):!0}_onSelectBClick(e){const t=this._closestPolygon(e,this._featureA);return t?(this._featureB=t.feature,this._sourceB=t.source,this._phase="click_edge_a",this._rebuildHighlights(),this.getMap().getTargetElement().style.cursor="crosshair",!1):!0}_closestPolygon(e,t){let r=null,n=this.snapDistance_+1;for(const s of this._getSources()){const a=s.getClosestFeatureToCoordinate(e.coordinate);if(!a||t&&a===t)continue;const i=a.getGeometry();if(!i)continue;const l=i.getType();if(l!=="Polygon"&&l!=="MultiPolygon")continue;const c=i.getClosestPoint(e.coordinate),u=new de([e.coordinate,c]).getLength()/e.frameState.viewState.resolution;u{t.get("_permanent")&&e.push(t)}),e.forEach(t=>this._highlightSource.removeFeature(t)),this._featureA){const t=this._featureA.clone();t.set("_highlightStyle",Vs),t.set("_permanent",!0),this._highlightSource.addFeature(t)}if(this._featureB){const t=this._featureB.clone();t.set("_highlightStyle",Ks),t.set("_permanent",!0),this._highlightSource.addFeature(t)}}_reset(){this._phase="select_a",this._featureA=null,this._sourceA=null,this._featureB=null,this._sourceB=null,this._edgeClickA=null,this._edgeClickB=null,this._highlightSource.clear(),this._edgeSource.clear();const e=this.getMap();e&&(e.getTargetElement().style.cursor="")}}function Zs(o,e){return(o[0]-e[0])**2+(o[1]-e[1])**2}function qo(o){let e=0;for(let t=0,r=o.length;tt&&(t=d,r=c)}const n=o[r],s=o[r+1],a=Math.sqrt(t),i=[(s[0]-n[0])/a,(s[1]-n[1])/a],l=[-i[1],i[0]];return{p0:n,p1:s,along:i,perp:l}}function Ut(o,e,t,r,n){const s=o[0]+r*e[0],a=o[1]+r*e[1];return[[s-n*t[0],a-n*t[1]],[s+n*t[0],a+n*t[1]]]}function $e(o,e,t){const r=o[0],n=r.length-1;let s=0,a=0;for(let c=0;cu&&(u=_)}const f=(u-d)*1.5,p=[];let h=o,g=e;for(let m=0;mx&&(x=Q)}let S=T,P=x,k=null,j=null,q=1/0;for(let Z=0;Z<40;Z++){const $=(S+P)/2,ce=Ut(l,a,i,$,f),Q=wt(h,ce);if(!Q){const R=(P-S)*.01,W=Ut(l,a,i,$+R,f),re=wt(h,W);if(re){const[Ie,Pe]=re,Ye=$e(Ie,l,a),Je=$e(Pe,l,a),Ze=Yethis._handleEvent(t)}),this.snapDistance_=e.snapDistance||25,this._sources=e.sources?Array.isArray(e.sources)?e.sources:[e.sources]:null,this._phase="select",this._selectedFeature=null,this._selectedSource=null,this._selectedEdge=null,this._dividedFeatures=null,this._overlaySource=new G({useSpatialIndex:!1}),this._overlayLayer=new D({source:this._overlaySource,displayInLayerSwitcher:!1,style:ta}),this._edgeSource=new G({useSpatialIndex:!1}),this._edgeLayer=new D({source:this._edgeSource,displayInLayerSwitcher:!1,style:oa})}setMap(e){this.getMap()&&(this.getMap().removeLayer(this._overlayLayer),this.getMap().removeLayer(this._edgeLayer)),super.setMap(e),e&&(this._overlayLayer.setMap(e),this._edgeLayer.setMap(e))}setActive(e){super.setActive(e),e||this._reset()}_getSources(){if(this._sources)return this._sources;if(!this.getMap())return[];const e=[],t=r=>{r.forEach(n=>{n.getVisible()&&(n.getSource&&n.getSource()instanceof G?e.push(n.getSource()):n.getLayers&&t(n.getLayers()))})};return t(this.getMap().getLayers()),e}_handleEvent(e){if(!this.getActive())return!0;if(e.type==="keydown"&&e.originalEvent?.key==="Escape")return this._phase==="form"?this.cancelDivide():this._reset(),!1;if(this._phase==="select"){if(e.type==="pointermove")return this._onSelectMove(e);if(e.type==="singleclick")return this._onSelectClick(e)}if(this._phase==="edge"){if(e.type==="pointermove")return this._onEdgeMove(e);if(e.type==="singleclick")return this._onEdgeClick(e)}if(this._phase==="pick"){if(e.type==="pointermove")return this._onPickMove(e);if(e.type==="singleclick")return this._onPickClick(e)}return!0}_onSelectMove(e){const t=this.getMap();if(!t)return!0;this._overlaySource.clear();const r=this._closestPolygon(e);if(r){const n=r.feature.clone();this._overlaySource.addFeature(n),t.getTargetElement().style.cursor="pointer"}else t.getTargetElement().style.cursor="";return!0}_onSelectClick(e){const t=this._closestPolygon(e);if(!t)return!0;this._selectedFeature=t.feature,this._selectedSource=t.source,this._overlaySource.clear();const r=t.feature.clone();return r.set("_permanent",!0),this._overlaySource.addFeature(r),this._phase="edge",F("Click the edge to divide along.","info",3e3),!1}_closestPolygon(e){let t=null,r=this.snapDistance_+1;for(const n of this._getSources()){const s=n.getClosestFeatureToCoordinate(e.coordinate);if(!s)continue;const a=s.getGeometry();if(!a)continue;const i=a.getType();if(i!=="Polygon"&&i!=="MultiPolygon")continue;const l=a.getClosestPoint(e.coordinate),d=new de([e.coordinate,l]).getLength()/e.frameState.viewState.resolution;d{const g=t.clone();return g.setGeometry(new at(p)),g.setStyle(new C({stroke:new I({color:i[h].stroke,width:2.5}),fill:new M({color:i[h].fill})})),g}),c={type:"beforedivide",original:t,features:l};this.dispatchEvent(c),r.dispatchEvent({...c});const d=this.getMap();d?.dispatchEvent("undoblockstart"),r.removeFeature(t);for(const p of l)r.addFeature(p);d?.dispatchEvent("undoblockend");const u={type:"afterdivide",original:t,features:l};this.dispatchEvent(u),r.dispatchEvent({...u}),t.get("_layerType")==="parcel"?(this._dividedFeatures=l,this._phase="pick",F("Click the polygon that should keep the original identifier.","info",5e3),this.dispatchEvent({type:"dividedparcel",features:l,originalProps:t.getProperties(),source:r})):(F(`Polygon divided into ${e} equal pieces.`,"success"),this._reset())}_onPickMove(e){const t=this.getMap();if(!t)return!0;this._overlaySource.clear();const r=this._closestDividedPiece(e);if(r){const n=r.clone();this._overlaySource.addFeature(n),t.getTargetElement().style.cursor="pointer"}else t.getTargetElement().style.cursor="";return!0}_onPickClick(e){const t=this._closestDividedPiece(e);return t?(this.dispatchEvent({type:"dividepick",picked:t,features:this._dividedFeatures}),this._reset(),!1):!0}_closestDividedPiece(e){if(!this._dividedFeatures)return null;let t=null,r=this.snapDistance_+1;for(const n of this._dividedFeatures){const s=n.getGeometry();if(!s)continue;const a=s.getClosestPoint(e.coordinate),l=new de([e.coordinate,a]).getLength()/e.frameState.viewState.resolution;l{const l=this.categoryEmojis[i];return l?l.emoji:"📍"},this.getCategoryOptionsHtml=()=>Object.entries(this.categoryEmojis).map(([i,{emoji:l,label:c}])=>``).join(` `),this.createEmojiStyle=(i,l=24)=>new C({text:new We({text:i,font:`${l}px sans-serif`,textBaseline:"bottom",textAlign:"center",offsetY:-5})}),this.defaultStyle=this.createEmojiStyle("📍",32),this.selectedStyle=this.createEmojiStyle("📍",42),this.categoryStyles={};for(const[i,{emoji:l}]of Object.entries(this.categoryEmojis))this.categoryStyles[i]=this.createEmojiStyle(l,32);const r=this.createBaseLayers(t.basemap||"topo");this.markersLayer=new D({title:"Markers",source:this.markerSource,style:i=>this.getFeatureStyle(i),visible:!1}),this.overlayGroup=new Ce({title:"Overlays"}),this.map=new hr({target:e,layers:[r,this.markersLayer,this.overlayGroup],view:new gr({center:ee(t.center||[0,0]),zoom:t.zoom||2,minZoom:t.minZoom||2,maxZoom:t.maxZoom||19})});const n=new Lr({collapsed:!0,mouseover:!0,extent:!0,trash:!1,oninfo:null});this.map.addControl(n),queueMicrotask(()=>{const i=n.element?.querySelector(":scope > button");if(i){const l="/".replace(/\/?$/,"/");i.style.backgroundImage=`url('${l}app-icons/luspa-72x72.png')`}});let s=!1;n.on("drawlist",i=>{this._decorateLayerListItem(i.layer,i.li),s||(s=!0,queueMicrotask(()=>{s=!1,this._refreshLayerSwitcherChrome(n)}))}),this.map.getLayers().on("change",()=>{this._refreshLayerSwitcherChrome(n)}),this._wireLayerSwitcherVisibilityHooks(n),this._createAddLayerDialog(),this._createLegendPanel(),this.scaleBar=new mr({bar:!0,steps:4,text:!0,minWidth:140}),this.map.addControl(this.scaleBar),this._initGpsRendering(),this._createLocationControl(),this._createBaseMapPicker();const a=new kr({placeholder:"Search location...",typing:300,minLength:3,maxItems:10,collapsed:!0});this.map.addControl(a),a.on("select",i=>{const l=i.search;if(l){const c=parseFloat(l.lon),d=parseFloat(l.lat),u=[c,d],f=ee(u);this.navigateTo(c,d,14);const p={coordinate:f,lonLat:u,name:l.display_name||l.name||"Unknown",searchResult:l};this.searchSelectCallbacks.forEach(h=>h(p))}}),this.searchNominatim=a,this.searchSelectCallbacks=[],this.selectedFeature=null,this.createPopup(),this.createInfoPopup(),this.createAddLocationPopup(),this.createParcelEditPopup(),this.createDrawnPolygonPopup(),this.createMergePopup(),this.createDividePopup(),this.dblClickCallbacks=[],this.editBar=null,this.drawingsSource=null,this.drawingsLayer=null,this.touchCursor=null,this._editBarActive=!1}initEditBar(){this.drawingsSource=new G,this.drawingsLayer=new D({title:"sketches",source:this.drawingsSource,style:new C({stroke:new I({color:"#f59e0b",width:2.5}),fill:new M({color:"rgba(245,158,11,0.15)"}),image:new he({radius:6,fill:new M({color:"#f59e0b"}),stroke:new I({color:"#fff",width:1.5})})})}),this._drawingsGroup=new Ce({title:"Drawings",layers:[this.drawingsLayer]});const e=this.map.getLayers(),t=e.getArray().indexOf(this.overlayGroup);e.insertAt(t>=0?t:e.getLength(),this._drawingsGroup),this._selectInteraction=new yr({condition:wr,toggleCondition:br,filter:(p,h)=>!!h,layers:p=>p instanceof D&&p.get("selectable")!==!1}),this._selectInteraction.setActive(!1),this.map.addInteraction(this._selectInteraction),this._dragBoxSelect=new _r({condition:vr}),this._dragBoxSelect.setActive(!1),this.map.addInteraction(this._dragBoxSelect),this._dragBoxSelect.on("boxend",()=>{const p=this._dragBoxSelect.getGeometry().getExtent(),h=this._selectInteraction.getFeatures();this.map.getLayers().forEach(g=>this._boxAddFromLayer(g,p,h)),this._refreshVertexOverlay?.()}),this._selectInteraction.on("change:active",()=>{this._dragBoxSelect.setActive(this._selectInteraction.getActive())}),this._modifyInteraction=new Ir({features:this._selectInteraction.getFeatures()}),this._modifyInteraction.setActive(!1),this._modifyInteraction.on("modifyend",p=>{if(!this._featureModifiedCallbacks?.length)return;const h=p.features?.getArray?.()||[];for(const g of h)for(const m of this._featureModifiedCallbacks)try{m(g)}catch(y){console.warn("[MapView] onFeatureModified callback failed:",y)}}),this._undoRedo=new Pr,this.map.addInteraction(this._undoRedo),this.editBar=new Xt({source:this.drawingsSource,interactions:{Select:this._selectInteraction,ModifySelect:this._modifyInteraction,DrawPoint:!0,DrawLine:!0,DrawPolygon:!0,DrawRegular:!0,DrawHole:!0,Delete:!0,Info:!0,Transform:!0,Split:!1}}),this.map.addControl(this.editBar);const r=this.editBar.getInteraction("DrawPoint");r&&r.on("drawend",p=>{const h=p.feature?.getGeometry?.()?.getCoordinates?.();if(!h)return;this._discardPendingLocationSketch(),this._pendingLocationSketch=p.feature;const g=this._undoRedo,m=g?._record;g&&(g._record=!1),setTimeout(()=>{g&&(g._record=m)},0);for(const y of this._drawnPointCallbacks||[])try{y(h)}catch(w){console.warn("[MapView] onDrawnPoint callback failed:",w)}}),this._setupVertexOverlay();const n=new ko({group:!0,className:"ol-editbar-actions",controls:[new tt({html:'',className:"ol-undo",title:"Undo",handleClick:()=>{this._undoRedo.hasUndo()&&this._undoRedo.undo()}}),new tt({html:'',className:"ol-redo",title:"Redo",handleClick:()=>{this._undoRedo.hasRedo()&&this._undoRedo.redo()}}),new tt({html:'',className:"ol-save",title:"Save drawings",handleClick:()=>{this.dispatchEditEvent("save")}})]});this.editBar.addControl(n),this._lineSplitInteraction=new Cr,this._polygonSplitInteraction=new Gs,this.map.addInteraction(this._lineSplitInteraction),this.map.addInteraction(this._polygonSplitInteraction),this._lineSplitInteraction.setActive(!1),this._polygonSplitInteraction.setActive(!1),this._polygonSplitInteraction.on("splitpick",p=>{const h=["UPN","upn","id","parcelid","parcel_id","PARCELID","PARCEL_ID","ID"];for(const g of p.features)if(g!==p.picked)for(const m of h)g.get(m)!==void 0&&g.set(m,"")}),this._polygonDivideInteraction=new ra,this.map.addInteraction(this._polygonDivideInteraction),this._polygonDivideInteraction.setActive(!1);const s=new Se({html:'',className:"ol-split-line",title:"Split Lines",name:"SplitLine",interaction:this._lineSplitInteraction,autoActivate:!0}),a=new Se({html:'',className:"ol-split-polygon",title:"Split Polygons",name:"SplitPolygon",interaction:this._polygonSplitInteraction}),i=new Se({html:'',className:"ol-split-divide",title:"Divide Polygon",name:"DividePolygon",interaction:this._polygonDivideInteraction}),l=new ko({toggleOne:!0,autoDeactivate:!0,controls:[s,a,i]}),c=new Se({className:"ol-split",title:"Split",name:"Split",bar:l,onToggle:p=>{p||(this._lineSplitInteraction.setActive(!1),this._polygonSplitInteraction.setActive(!1),this._polygonDivideInteraction.setActive(!1))}});this.editBar.addControl(c),this._polygonDivideInteraction.on("divideform",p=>{this.showDividePopup(p.feature,p.source,p.coordinate)}),this._polygonDivideInteraction.on("dividecancel",()=>{this.hideDividePopup()}),this._polygonDivideInteraction.on("dividepick",p=>{const h=["UPN","upn","id","parcelid","parcel_id","PARCELID","PARCEL_ID","ID"];for(const g of p.features)if(g!==p.picked)for(const m of h)g.get(m)!==void 0&&g.set(m,"")}),this._polygonMergeInteraction=new Js,this.map.addInteraction(this._polygonMergeInteraction),this._polygonMergeInteraction.setActive(!1);const d=new Se({html:'',className:"ol-merge",title:"Merge Polygons",name:"Merge",interaction:this._polygonMergeInteraction});this.editBar.addControl(d),this._polygonMergeInteraction.on("mergedparcel",p=>{this.showMergeIdentifierPopup(p.merged,p.propsA,p.propsB,p.coordinate)}),this._snapGuidesEnabled=localStorage.getItem("snap-guides-enabled")==="1",this._snapGuides=new Mr({pixelTolerance:10,vectorClass:Er}),this.map.addInteraction(this._snapGuides);const u=["DrawPoint","DrawLine","DrawPolygon","DrawHole","DrawRegular"];for(const p of u){const h=this.editBar.getInteraction(p);h&&h.on("change:active",()=>{h.getActive()&&this._snapGuides.setDrawInteraction(h)})}this._modifyInteraction&&this._snapGuides.setModifyInteraction(this._modifyInteraction);const f=new tt({html:'',className:"ol-snap-toggle"+(this._snapGuidesEnabled?" ol-active":""),title:"Toggle Snap Guides",handleClick:()=>{this._snapGuidesEnabled=!this._snapGuidesEnabled,localStorage.setItem("snap-guides-enabled",this._snapGuidesEnabled?"1":"0"),f.element.classList.toggle("ol-active",this._snapGuidesEnabled),this._snapGuides&&this._snapGuides.setActive(this._snapGuidesEnabled&&this._editBarActive),console.log("[MapView] Snap guides:",this._snapGuidesEnabled?"ON":"OFF")}});this._snapToggleBtn=f,n.addControl(f),this._buildToolbarRail(),this.setEditMode(!1),this._drawingsGroup.on("change:visible",()=>{const p=this._drawingsGroup.getVisible();this.setEditMode(p)}),this._isTouchOnlyDevice()?(this.touchCursor=new Io({className:"ol-editbar-cursor"}),this.map.addInteraction(this.touchCursor),this.touchCursor.setActive(!1),console.log("[MapView] Touch-only device detected — TouchCursor added")):console.log("[MapView] Fine pointer available — TouchCursor skipped"),this._installPointerCapabilityWatcher(),this.drawingsSource.on("addfeature",p=>{const h=p.feature,g=h.getGeometry();if(!g||g.getType()!=="Polygon")return;const m=g.getInteriorPoint().getCoordinates();this.showDrawnPolygonPopup(h,m)}),this._editKeyHandler=p=>{if(p.key!=="Delete"&&p.key!=="Backspace"||!this._editBarActive)return;const h=p.target,g=h&&h.tagName;h?.isContentEditable||g==="INPUT"||g==="TEXTAREA"||g==="SELECT"||this._deleteSelectedFeatures()&&p.preventDefault()},document.addEventListener("keydown",this._editKeyHandler),console.log("[MapView] EditBar initialised with Drawings group, UndoRedo and SnapGuides (default:",this._snapGuidesEnabled?"ON":"OFF",")")}_deleteSelectedFeatures(){if(!this._editBarActive)return!1;const e=this.editBar?.getInteraction("Delete"),t=this._selectInteraction?.getFeatures();return!e||!t||t.getLength()===0?!1:(e.delete(t),t.clear(),this._refreshVertexOverlay?.(),!0)}_boxAddFromLayer(e,t,r){if(!e)return;if(typeof e.getLayers=="function"){e.getLayers().forEach(s=>this._boxAddFromLayer(s,t,r));return}if(!(e instanceof D)||e.getVisible&&!e.getVisible()||e===this._vertexOverlayLayer||e===this._gpsTrailLayer||e===this._gpsPositionLayer||e.get("selectable")===!1||e.get("displayInLayerSwitcher")===!1)return;const n=e.getSource&&e.getSource();!n||typeof n.forEachFeatureIntersectingExtent!="function"||n.forEachFeatureIntersectingExtent(t,s=>{r.getArray().includes(s)||r.push(s)})}dispatchEditEvent(e){if(!this._editEventListeners)return;const t=this._editEventListeners[e];t&&t.forEach(r=>r())}onEditEvent(e,t){this._editEventListeners||(this._editEventListeners={}),this._editEventListeners[e]||(this._editEventListeners[e]=[]),this._editEventListeners[e].push(t)}setEditMode(e){const t=this._editBarActive;if(this._editBarActive=!!e,this._editBarActive&&!t&&this._undoRedo)try{this._undoRedo._watchSources(),this._undoRedo.clear()}catch(r){console.warn("[MapView] UndoRedo re-watch/clear failed:",r)}this.editBar&&(this.editBar.setVisible(this._editBarActive),this._editBarActive||this.editBar.deactivateControls()),this._selectInteraction&&(this._editBarActive||this._selectInteraction.getFeatures().clear(),this._selectInteraction.setActive(this._editBarActive)),this._modifyInteraction&&this._modifyInteraction.setActive(this._editBarActive),this._snapGuides&&this._snapGuides.setActive(this._snapGuidesEnabled&&this._editBarActive),this.touchCursor&&this.touchCursor.setActive(this._editBarActive),this._editBarActive||this._discardPendingLocationSketch(),!this._editBarActive&&this._vertexOverlaySource&&this._withoutUndoRecording(()=>this._silentClearSource(this._vertexOverlaySource)),console.log("[MapView] Edit mode:",this._editBarActive?"ON":"OFF")}isEditMode(){return this._editBarActive}_isTouchOnlyDevice(){if(!("ontouchstart"in window||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0))return!1;if(typeof window.matchMedia!="function")return!0;const t=window.matchMedia("(any-pointer: fine)").matches,r=window.matchMedia("(any-hover: hover)").matches;return!t&&!r}_refreshTouchCursor(){const e=this._isTouchOnlyDevice();e&&!this.touchCursor?(this.touchCursor=new Io({className:"ol-editbar-cursor"}),this.map.addInteraction(this.touchCursor),this.touchCursor.setActive(this._editBarActive),console.log("[MapView] Pointer change → TouchCursor added")):!e&&this.touchCursor&&(this.map.removeInteraction(this.touchCursor),this.touchCursor=null,console.log("[MapView] Pointer change → TouchCursor removed (fine pointer present)"))}_installPointerCapabilityWatcher(){if(typeof window.matchMedia!="function"||this._pointerMediaQueries)return;const e=["(any-pointer: fine)","(any-hover: hover)"].map(r=>window.matchMedia(r)),t=()=>this._refreshTouchCursor();for(const r of e)typeof r.addEventListener=="function"?r.addEventListener("change",t):typeof r.addListener=="function"&&r.addListener(t);this._pointerMediaQueries=e,this._pointerMediaListener=t}_buildToolbarRail(){const e=this.editBar?.element;if(e)try{this._applyToolbarRail(e)}catch(t){console.warn("[MapView] Could not build the toolbar rail; falling back to the default bar:",t),e.classList.remove("ol-left","ol-editbar-rail")}}_applyToolbarRail(e){e.classList.add("ol-left","ol-editbar-rail");for(const[n,s]of Object.entries(aa))for(const a of e.querySelectorAll(`.${n}`)){const i=a.matches("button")?a:a.querySelector(":scope > button");i&&(i.innerHTML=``)}const t=document.createDocumentFragment();for(const n of ia){const s=n.controls.map(i=>e.querySelector(`:scope > .${i}`)).filter(Boolean);if(!s.length)continue;const a=document.createElement("div");a.className="ol-rail-label",a.textContent=n.label,a.setAttribute("aria-hidden","true"),t.appendChild(a);for(const i of s)t.appendChild(i)}e.appendChild(t);const r=document.createElement("div");r.className="ol-rail-grip",r.title="Drag to move the toolbar — double-click to reset",r.setAttribute("role","button"),r.setAttribute("aria-label","Move the drawing toolbar"),e.insertBefore(r,e.firstChild),this._makeToolbarDraggable(e,r)}_makeToolbarDraggable(e,t){const r="editbar-position",n=window.matchMedia("(max-width: 576px), (max-height: 560px)"),s=(g,m,y)=>Math.min(Math.max(g,m),y),a=(g,m)=>{const y=e.offsetParent||this.map.getTargetElement();if(!y)return;const w=Math.max(0,y.clientWidth-e.offsetWidth-4),_=Math.max(0,y.clientHeight-e.offsetHeight-4);e.style.left=`${s(g,4,w)}px`,e.style.top=`${s(m,4,_)}px`,e.style.transform="none",e.style.bottom="auto"},i=()=>{e.style.left="",e.style.top="",e.style.transform="",e.style.bottom=""},l=()=>{if(n.matches){i();return}try{const g=JSON.parse(localStorage.getItem(r)||"null");g&&Number.isFinite(g.left)&&Number.isFinite(g.top)&&a(g.left,g.top)}catch{}};let c=0,d=0,u=0,f=0;const p=g=>{a(u+(g.clientX-c),f+(g.clientY-d))},h=g=>{t.releasePointerCapture?.(g.pointerId),window.removeEventListener("pointermove",p),window.removeEventListener("pointerup",h),e.classList.remove("is-dragging");try{localStorage.setItem(r,JSON.stringify({left:parseFloat(e.style.left)||0,top:parseFloat(e.style.top)||0}))}catch{}};t.addEventListener("pointerdown",g=>{if(n.matches||g.button!==0)return;g.preventDefault();const m=e.getBoundingClientRect(),y=(e.offsetParent||this.map.getTargetElement()).getBoundingClientRect();c=g.clientX,d=g.clientY,u=m.left-y.left,f=m.top-y.top,e.classList.add("is-dragging"),t.setPointerCapture?.(g.pointerId),window.addEventListener("pointermove",p),window.addEventListener("pointerup",h)}),t.addEventListener("dblclick",()=>{try{localStorage.removeItem(r)}catch{}i()}),typeof n.addEventListener=="function"?n.addEventListener("change",l):typeof n.addListener=="function"&&n.addListener(l),l()}_setupVertexOverlay(){this._vertexOverlaySource=new G,this._vertexOverlayLayer=new D({title:"__vertex_highlight__",source:this._vertexOverlaySource,zIndex:990,style:new C({image:new he({radius:4,fill:new M({color:"rgba(14,165,233,0.85)"}),stroke:new I({color:"#fff",width:1.2})})})}),this._vertexOverlayLayer.set("displayInLayerSwitcher",!1),this.map.addLayer(this._vertexOverlayLayer),this._onSelectedFeatureGeomChange=()=>this._refreshVertexOverlay(),this._vertexTrackedFeatures=new Set,this._selectInteraction.on("select",()=>this._refreshVertexOverlay())}_withoutUndoRecording(e){const t=this._undoRedo;if(!t)return e();const r=t._record;t._record=!1;try{return e()}finally{t._record=r}}_silentClearSource(e){if(!e)return;const t=e.getFeatures();for(let r=t.length-1;r>=0;r--)e.removeFeature(t[r])}_refreshVertexOverlay(){this._vertexOverlaySource&&this._withoutUndoRecording(()=>{if(this._silentClearSource(this._vertexOverlaySource),this._vertexTrackedFeatures){for(const t of this._vertexTrackedFeatures)t.un("change",this._onSelectedFeatureGeomChange);this._vertexTrackedFeatures.clear()}if(!this._editBarActive||!this._selectInteraction)return;const e=this._selectInteraction.getFeatures().getArray();for(const t of e){const r=t.getGeometry();if(!r)continue;const n=r.getType();if(!["Polygon","MultiPolygon","LineString","MultiLineString"].includes(n))continue;const s=this._collectAllVertices(r);for(const a of s)this._vertexOverlaySource.addFeature(new fe(new dt(a)));t.on("change",this._onSelectedFeatureGeomChange),this._vertexTrackedFeatures.add(t)}})}_collectAllVertices(e){const t=[],r=i=>Array.isArray(i)&&typeof i[0]=="number",n=(i,l)=>{const c=l&&i.length>1?i.length-1:i.length;for(let d=0;d{if(r(l))t.push(l);else if(Array.isArray(l))for(const c of l)i(c)};i(a)}return t}getDrawingsLayer(){return this.drawingsLayer}getDrawingsSource(){return this.drawingsSource}getEditBar(){return this.editBar}setScaleBarUnits(e){this.scaleBar&&this.scaleBar.setUnits(e==="imperial"?"imperial":"metric")}createPopup(){this.popupElement=document.createElement("div"),this.popupElement.className="map-popup",this.popupElement.style.cssText=` position: absolute; background: var(--card, #fff); color: var(--card-foreground, #1e1a4b); border-radius: 8px; padding: 10px 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.25); font-family: var(--font-body, 'Exo', sans-serif); font-size: 13px; min-width: 150px; max-width: 280px; pointer-events: none; z-index: 1000; border: 1px solid var(--border, #1e1a4b1f); `,this.popup=new Ee({element:this.popupElement,positioning:"bottom-center",offset:[0,-15],stopEvent:!1}),this.map.addOverlay(this.popup),this.setupHoverPopup()}setupHoverPopup(){let e=null;this.map.on("pointermove",t=>{if(t.dragging){this.hidePopup();return}const r=this.map.forEachFeatureAtPixel(t.pixel,n=>n.get("name")?n:null);r&&r!==e?(e=r,this.showPopup(r,t.coordinate)):!r&&e&&(e=null,this.hidePopup()),this.map.getTargetElement().style.cursor=r?"pointer":""}),this.map.getTargetElement().addEventListener("mouseleave",()=>{this.hidePopup(),e=null})}showPopup(e,t){const r=e.get("name")||"Unnamed",n=e.get("category")||"default",s=e.get("description"),a=e.get("lon"),i=e.get("lat");let c=`
${this.getEmoji(n)} ${this.escapeHtml(r)}
`;const u={water:"#3b82f6",school:"#f59e0b",health:"#ef4444",market:"#8b5cf6",default:"#2d5016",other:"#6b7280"}[n]||"#6b7280";c+=`
${n}
`,s&&(c+=`
${this.escapeHtml(s)}
`),a!==void 0&&i!==void 0&&(c+=`
${Number(a).toFixed(5)}, ${Number(i).toFixed(5)}
`),this.popupElement.innerHTML=c,this.popup.setPosition(t)}hidePopup(){this.popup.setPosition(void 0)}createInfoPopup(){this.infoPopupElement=document.createElement("div"),this.infoPopupElement.className="map-info-popup",this.infoPopupElement.style.cssText=` position: absolute; background: var(--card, #fff); color: var(--card-foreground, #1e1a4b); border-radius: 10px; padding: 0; box-shadow: 0 4px 16px rgba(0,0,0,0.3); font-family: var(--font-body, 'Exo', sans-serif); font-size: 13px; min-width: 220px; max-width: 320px; max-height: 70vh; display: flex; flex-direction: column; z-index: 1001; border: 1px solid var(--border, #1e1a4b1f); overflow: hidden; `,this.infoPopup=new Ee({element:this.infoPopupElement,positioning:"bottom-center",offset:gt.slice(),stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.infoPopup)}showInfoPopup(e,t,r={}){const{title:n="Feature Info",color:s="#e11d48"}=r,a=e.getProperties(),i=e.getGeometry(),l=i.getType(),c=["geometry","_layerType"];let d="";for(const[f,p]of Object.entries(a))c.includes(f)||p===void 0||p===null||(d+=` ${this.escapeHtml(f)} ${this.escapeHtml(String(p))} `);if(l==="Polygon"||l==="MultiPolygon"){const f=nt(i,{projection:"EPSG:3857"}),p=Is(f);d+=` area ${p} `}else if(l==="LineString"||l==="MultiLineString"){const f=vt(i,{projection:"EPSG:3857"}),p=ks(f);d+=` length ${p} `}else if(l==="Point"){const f=xe(i.getCoordinates()),p=f[0].toFixed(6),h=f[1].toFixed(6);d+=` longitude ${p} latitude ${h} `}const u=`
${this.escapeHtml(n)}
${d}
`;this.infoPopupElement.innerHTML=u,this.infoPopup.setPosition(t),this.infoPopupElement.querySelector("#info-popup-close").addEventListener("click",()=>{this.hideInfoPopup()})}hideInfoPopup(){this.infoPopup.setPosition(void 0)}_collectIntersectionRows(e,t,r){const n=[];if(e.length>0&&n.push({label:"Parcels",value:String(e.length),color:"#0ea5e9"}),t.length>0){const s=t.map(a=>a.get("colzonename")||a.get("zone_name")||a.get("name")||"unnamed");n.push({label:"Zones",value:String(t.length),color:"#7c3aed"}),n.push({label:"Zone Names",value:s.map(a=>this.escapeHtml(a)).join(", "),color:"#7c3aed"})}for(const[s,a]of Object.entries(r))n.push({label:this.escapeHtml(s),value:`${a.length} feature(s)`});return n.length===0&&n.push({label:"",value:"No intersecting features found",empty:!0}),n}_makePopupDraggable(){const e=this.infoPopupElement.querySelector("#info-popup-header");if(!e)return;this.infoPopup.setOffset(gt.slice());let t=0,r=0,n=gt.slice();const s=i=>{this.infoPopup.setOffset([n[0]+(i.clientX-t),n[1]+(i.clientY-r)])},a=i=>{e.releasePointerCapture?.(i.pointerId),e.removeEventListener("pointermove",s),e.removeEventListener("pointerup",a),e.removeEventListener("pointercancel",a),e.style.cursor="grab"};e.addEventListener("pointerdown",i=>{i.target.closest("#info-popup-close")||i.button!==0||(i.preventDefault(),t=i.clientX,r=i.clientY,n=(this.infoPopup.getOffset()||gt).slice(),e.style.cursor="grabbing",e.setPointerCapture?.(i.pointerId),e.addEventListener("pointermove",s),e.addEventListener("pointerup",a),e.addEventListener("pointercancel",a))})}_buildAnalysisPopupHtml(e,t,r){let n="";for(const s of r){if(s.empty){n+=` ${s.value} `;continue}const a=s.color||"var(--muted-foreground, #7a7a7a)",i=s._first?"":"border-top:1px solid var(--border, #1e1a4b1f);";n+=` ${s.label} ${s.value} `}return`
${e} ${t}
${n}
`}_showAnalysisPopup(e,t,r,n,s=null){this.infoPopupElement.innerHTML=this._buildAnalysisPopupHtml(e,t,r),this.infoPopup.setPosition(n),this.infoPopupElement.querySelector("#info-popup-close").addEventListener("click",()=>{this.hideInfoPopup()}),this._makePopupDraggable();const a=r.filter(c=>!c.empty).map(c=>({label:c.label,value:String(c.value).replace(/<[^>]*>/g,"")})),i=this.infoPopupElement.querySelector("#info-popup-analyse");if(i){const c=s?s.parcelFeatures.length+s.zoneFeatures.length+Object.values(s.otherByLayer).reduce((d,u)=>d+u.length,0):0;!s||c===0?(i.disabled=!0,i.style.opacity="0.5",i.style.cursor="not-allowed",i.title="No intersecting features to analyse"):i.addEventListener("click",()=>{window.dispatchEvent(new CustomEvent("lupmis:analyse-features",{detail:{title:t,...s}}))})}const l=this.infoPopupElement.querySelector("#info-popup-export-gis");if(l){const c=s?s.parcelFeatures.length+s.zoneFeatures.length+Object.values(s.otherByLayer).reduce((d,u)=>d+u.length,0):0;!s||c===0?(l.disabled=!0,l.style.opacity="0.5",l.style.cursor="not-allowed",l.title="No intersecting features to export"):l.addEventListener("click",()=>{window.dispatchEvent(new CustomEvent("lupmis:export-gis",{detail:{title:t,pdfRows:a,...s}}))})}}showCircleIntersectionPopup(e,t){const r=e.getGeometry();if(!r||typeof r.getCenter!="function")return;const n=xr(r,64),s=n.getExtent(),a=r.getRadius(),i=[],l=[],c={},d=y=>{const w=y.getGeometry();if(!w)return!1;const _=w.getExtent();return _[2]s[2]||_[3]s[3]?!1:n.intersectsExtent(_)&&this._geometriesIntersect(n,w)},u=(y,w)=>{y.getLayers().forEach(_=>{if(_ instanceof Ce)u(_,_.get("title")||w);else if(_ instanceof D&&_.getVisible()){const E=_.get("title")||w||"Unknown",T=_.getSource();if(!T)return;const x=T.getFeaturesInExtent(s);for(const S of x){const P=S.get("_layerType");P==="measure_circle"||P==="measure_circle_radius"||d(S)&&(P==="parcel"?i.push(S):P==="collector_zone"?l.push(S):(c[E]||(c[E]=[]),c[E].push(S)))}}})};u(this.overlayGroup,"Overlays");const f=Qt(a,r.getCenter()),p=rt(f),h=Math.PI*f*f,g=qe(h),m=[{label:"Radius",value:p,_first:!0},{label:"Area",value:g},...this._collectIntersectionRows(i,l,c)];this._showAnalysisPopup("⭕","Circle Analysis",m,t,{kind:"circle",clipGeometry:n,parcelFeatures:i,zoneFeatures:l,otherByLayer:c})}showAreaIntersectionPopup(e,t){const r=e.getGeometry();if(!r)return;const n=r.getExtent(),s=nt(r,{projection:"EPSG:3857"}),a=qe(s),i=vt(r,{projection:"EPSG:3857"}),l=rt(i),c=[],d=[],u={},f=g=>{const m=g.getGeometry();if(!m)return!1;const y=m.getExtent();return y[2]n[2]||y[3]n[3]?!1:r.intersectsExtent(y)&&this._geometriesIntersect(r,m)},p=(g,m)=>{g.getLayers().forEach(y=>{if(y instanceof Ce)p(y,y.get("title")||m);else if(y instanceof D&&y.getVisible()){const w=y.get("title")||m||"Unknown",_=y.getSource();if(!_)return;const E=_.getFeaturesInExtent(n);for(const T of E){const x=T.get("_layerType");x==="measure_area"||x==="measure_circle"||x==="measure_circle_radius"||f(T)&&(x==="parcel"?c.push(T):x==="collector_zone"?d.push(T):(u[w]||(u[w]=[]),u[w].push(T)))}}})};p(this.overlayGroup,"Overlays");const h=[{label:"Area",value:a,_first:!0},{label:"Perimeter",value:l},...this._collectIntersectionRows(c,d,u)];this._showAnalysisPopup("📐","Area Analysis",h,t,{kind:"area",clipGeometry:r,parcelFeatures:c,zoneFeatures:d,otherByLayer:u})}_geometriesIntersect(e,t){const r=t.getType();if(r==="Polygon"||r==="MultiPolygon"){const n=t.getFlatCoordinates(),s=t.getStride();for(let l=0;l `}const a=`
✏️ Edit Parcel
${s}
`;this.parcelEditElement.innerHTML=a,this.parcelEditPopup.setPosition(t),this.parcelEditElement.querySelector(".parcel-edit-close").addEventListener("click",()=>{this.hideParcelEditPopup()}),this.parcelEditElement.querySelector(".parcel-edit-cancel").addEventListener("click",()=>{this.hideParcelEditPopup()});const i=this.parcelEditElement.querySelector(".parcel-edit-form");i.addEventListener("submit",l=>{l.preventDefault();const c=new FormData(i),d={};for(const[u,f]of c.entries())d[u]=f;d._layerType="parcel";for(const[u,f]of Object.entries(d))this._parcelEditFeature.set(u,f);for(const u of this._parcelEditCallbacks)u(this._parcelEditFeature,d);this.hideParcelEditPopup()})}hideParcelEditPopup(){this.parcelEditPopup.setPosition(void 0),this._parcelEditFeature=null}onParcelEdit(e){this._parcelEditCallbacks.push(e)}createMergePopup(){this.mergePopupElement=document.createElement("div"),this.mergePopupElement.className="map-merge-popup",this.mergePopupElement.style.cssText=` position: absolute; background: var(--card, #fff); color: var(--card-foreground, #1e1a4b); border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); font-family: var(--font-body, 'Exo', sans-serif); font-size: 13px; min-width: 280px; max-width: 360px; z-index: 1002; border: 2px solid #10b981; overflow: hidden; display: flex; flex-direction: column; `,this.mergePopup=new Ee({element:this.mergePopupElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.mergePopup)}showMergeIdentifierPopup(e,t,r,n){const s=["UPN","upn","id","parcelid","parcel_id","PARCELID","PARCEL_ID","ID"],a=h=>{for(const g of s)if(h[g]!==void 0&&h[g]!==null&&String(h[g]).trim())return{field:g,value:String(h[g])};return{field:"id",value:"Unknown"}},i=a(t),l=a(r),c=`
🔗 Merged Parcel — Choose Identifier

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

`;this.mergePopupElement.innerHTML=c,this.mergePopup.setPosition(n);const d=()=>{this.mergePopup.setPosition(void 0)};this.mergePopupElement.querySelector(".merge-popup-close").addEventListener("click",d),this.mergePopupElement.querySelector(".merge-popup-cancel").addEventListener("click",d),this.mergePopupElement.querySelector(".merge-popup-confirm").addEventListener("click",()=>{const g=this.mergePopupElement.querySelector('input[name="merge-choice"]:checked').value==="A"?t:r,m=["geometry"];for(const[y,w]of Object.entries(g))m.includes(y)||e.set(y,w);e.set("_layerType","parcel");for(const y of this._parcelEditCallbacks)y(e,g);d()});const u=this.mergePopupElement.querySelectorAll("label"),f=this.mergePopupElement.querySelectorAll('input[name="merge-choice"]'),p=()=>{u.forEach(h=>{const g=h.querySelector("input");h.style.borderColor=g.checked?g.value==="A"?"#0ea5e9":"#f59e0b":"var(--border, #1e1a4b1f)"})};f.forEach(h=>h.addEventListener("change",p)),p()}createDividePopup(){this.dividePopupElement=document.createElement("div"),this.dividePopupElement.className="map-divide-popup",this.dividePopupElement.style.cssText=` position: absolute; background: var(--card, #fff); color: var(--card-foreground, #1e1a4b); border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); font-family: var(--font-body, 'Exo', sans-serif); font-size: 13px; min-width: 260px; max-width: 320px; z-index: 1002; border: 2px solid #8b5cf6; overflow: hidden; display: flex; flex-direction: column; `,this.dividePopup=new Ee({element:this.dividePopupElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.dividePopup)}showDividePopup(e,t,r){const n=`
Divide Polygon

Enter the number of equal pieces:

`;this.dividePopupElement.innerHTML=n,this.dividePopup.setPosition(r);const s=this.dividePopupElement.querySelector(".divide-input");s.focus(),s.select();const a=()=>{this.hideDividePopup(),this._polygonDivideInteraction.cancelDivide()};this.dividePopupElement.querySelector(".divide-popup-close").addEventListener("click",a),this.dividePopupElement.querySelector(".divide-popup-cancel").addEventListener("click",a),this.dividePopupElement.querySelector(".divide-popup-confirm").addEventListener("click",()=>{const i=parseInt(s.value,10);if(!i||i<2){s.style.borderColor="#ef4444";return}this.hideDividePopup(),this._polygonDivideInteraction.performDivide(i)}),s.addEventListener("keydown",i=>{i.key==="Enter"&&(i.preventDefault(),this.dividePopupElement.querySelector(".divide-popup-confirm").click())})}hideDividePopup(){this.dividePopup.setPosition(void 0)}createDrawnPolygonPopup(){this.drawnPolygonElement=document.createElement("div"),this.drawnPolygonElement.className="map-drawn-polygon-popup",this.drawnPolygonElement.style.cssText=` position: absolute; background: var(--card, #fff); border-radius: var(--radius-xl, 0.75rem); box-shadow: 0 4px 20px rgba(0,0,0,0.2); font-family: var(--font-body, 'Exo', sans-serif); font-size: 13px; min-width: 280px; max-width: 360px; max-height: 420px; z-index: 1002; border: 2px solid var(--success, #006b3f); overflow: hidden; display: flex; flex-direction: column; `,this.drawnPolygonPopup=new Ee({element:this.drawnPolygonElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.drawnPolygonPopup),this._drawnPolygonCallbacks=[],this._drawnPolygonFeature=null}getParcelAttributeKeys(){const e=["geometry","_layerType"],t=[],r=n=>{t.length>0||n.getLayers().forEach(s=>{if(!(t.length>0)){if(s instanceof Ce)r(s);else if(s instanceof D){const a=s.getSource();if(!a)return;for(const i of a.getFeatures()){if(i.get("_layerType")!=="parcel")continue;const l=i.getProperties();for(const c of Object.keys(l))e.includes(c)||t.push(c);return}}}})};return r(this.overlayGroup),t}showDrawnPolygonPopup(e,t){this._drawnPolygonFeature=e;const r=this.getParcelAttributeKeys();if(r.length===0){console.warn("[MapView] No parcel attributes found — cannot build form");return}let n="";for(const d of r){const u=this.escapeHtml(d);n+=`
`}const s=e.getGeometry(),a=nt(s,{projection:"EPSG:3857"}),l=`
📐 Polygon Attributes
Area: ${qe(a)}
${n}
`;this.drawnPolygonElement.innerHTML=l,this.drawnPolygonPopup.setPosition(t),this.drawnPolygonElement.querySelector(".drawn-polygon-close").addEventListener("click",()=>{this.hideDrawnPolygonPopup()}),this.drawnPolygonElement.querySelector(".drawn-polygon-cancel").addEventListener("click",()=>{this.hideDrawnPolygonPopup()});const c=this.drawnPolygonElement.querySelector(".drawn-polygon-form");c.addEventListener("submit",d=>{d.preventDefault();const u=new FormData(c),f={};for(const[p,h]of u.entries())f[p]=h;for(const[p,h]of Object.entries(f))this._drawnPolygonFeature.set(p,h);this._drawnPolygonFeature.set("_layerType","parcel");for(const p of this._drawnPolygonCallbacks)p(this._drawnPolygonFeature,f);this.hideDrawnPolygonPopup()})}hideDrawnPolygonPopup(){this.drawnPolygonPopup.setPosition(void 0),this._drawnPolygonFeature=null}onDrawnPolygonSave(e){this._drawnPolygonCallbacks.push(e)}onFeatureModified(e){this._featureModifiedCallbacks||(this._featureModifiedCallbacks=[]),this._featureModifiedCallbacks.push(e)}onDrawnPoint(e){this._drawnPointCallbacks||(this._drawnPointCallbacks=[]),this._drawnPointCallbacks.push(e)}onDblClick(e){return this.dblClickCallbacks.push(e),this.dblClickCallbacks.length===1&&this.map.on("dblclick",t=>{const[r,n]=xe(t.coordinate);let s=null;this.map.forEachFeatureAtPixel(t.pixel,a=>(s=a,!0)),s&&(t.preventDefault(),t.stopPropagation());for(const a of this.dblClickCallbacks)a(r,n,s,t);if(s)return!1}),()=>{const t=this.dblClickCallbacks.indexOf(e);t>-1&&this.dblClickCallbacks.splice(t,1)}}escapeHtml(e){if(!e)return"";const t=document.createElement("div");return t.textContent=e,t.innerHTML}createAddLocationPopup(){this.addLocationPopupElement=document.createElement("div"),this.addLocationPopupElement.className="map-add-location-popup",this.addLocationPopupElement.innerHTML=`
➕ Add Location
📍
`,this.addLocationPopup=new Ee({element:this.addLocationPopupElement,positioning:"bottom-center",offset:[0,-10],stopEvent:!0,autoPan:!0,autoPanAnimation:{duration:250}}),this.map.addOverlay(this.addLocationPopup),this.addLocationCoords=null,this.addLocationPopupElement.querySelector(".add-location-popup-close").addEventListener("click",()=>{this.hideAddLocationPopup()}),this.addLocationCallbacks=[]}showAddLocationPopup(e){const[t,r]=xe(e);this.addLocationCoords={lon:t,lat:r};const n=this.addLocationPopupElement.querySelector("#map-location-coords");n.textContent=`${t.toFixed(6)}, ${r.toFixed(6)}`,this.addLocationPopupElement.querySelector("#map-add-location-form").reset(),this.addLocationPopup.setPosition(e)}hideAddLocationPopup(){this.addLocationPopup.setPosition(void 0),this.addLocationCoords=null,this._discardPendingLocationSketch()}_discardPendingLocationSketch(){const e=this._pendingLocationSketch;e&&(this._pendingLocationSketch=null,this._withoutUndoRecording(()=>{try{this.drawingsSource.removeFeature(e)}catch{}}))}onAddLocation(e){if(this.addLocationCallbacks.push(e),this.addLocationCallbacks.length===1){const t=this.addLocationPopupElement.querySelector("#map-add-location-form");t.addEventListener("submit",r=>{if(r.preventDefault(),!this.addLocationCoords)return;const n=new FormData(t),s={name:n.get("name"),category:n.get("category"),description:n.get("description"),lon:this.addLocationCoords.lon,lat:this.addLocationCoords.lat};this.addLocationCallbacks.forEach(a=>a(s)),this.hideAddLocationPopup()})}}createBaseLayers(e){const t=new se({title:"Topographic",type:"base",zIndex:-100,visible:e==="topo",source:new Me({url:"https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png",attributions:"Map data: © OpenTopoMap",maxZoom:17,crossOrigin:"anonymous"})});t.set("basemapKey","topo");const r=new se({title:"Carto Light",type:"base",zIndex:-100,visible:e==="carto-light",source:new Me({url:Ho("light_all"),attributions:"© CARTO",maxZoom:19,crossOrigin:"anonymous"})});r.set("basemapKey","carto-light");const n=new se({title:"Carto Dark",type:"base",zIndex:-100,visible:e==="carto-dark",source:new Me({url:Ho("dark_all"),attributions:"© CARTO",maxZoom:19,crossOrigin:"anonymous"})});n.set("basemapKey","carto-dark");const s=new se({title:"OSM Cycle map",type:"base",zIndex:-100,visible:!1,source:new To({url:"https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=ae1339c46dd3446b9c491e7336d38760"})});s.set("basemapKey","cycle");const a=new se({title:"Satellite",type:"base",zIndex:-100,visible:e==="satellite",source:new Me({url:"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",attributions:"Tiles © Esri",maxZoom:19,crossOrigin:"anonymous"})});a.set("basemapKey","satellite");const i=new se({title:"Google Sat",type:"base",zIndex:-100,visible:e==="googlesat",source:new Me({url:"http://mt0.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}&s=Ga",attributions:"Tiles © Google",maxZoom:19,crossOrigin:"anonymous"})});i.set("basemapKey","googlesat");const l=new se({title:"OpenStreetMap",type:"base",zIndex:-100,visible:e==="osm",source:new To});l.set("basemapKey","osm"),this._baseMapLayers=[r,n,s,a,i,l,t];const c=new Ce({title:"Base Maps",layers:[r,n,a,s,i,l,t]});return c.set("displayInLayerSwitcher",!1),c}setBaseMap(e){if(!this._baseMapLayers)return!1;if(e==="none"){for(const r of this._baseMapLayers)r.setVisible(!1);return console.log("[MapView] Base map switched off (none)"),this.map.dispatchEvent({type:"basemapchange",key:"none"}),!0}let t=!1;for(const r of this._baseMapLayers){const n=r.get("basemapKey")===e;r.setVisible(n),n&&(t=!0)}return t&&(console.log("[MapView] Base map switched to:",e),this.map.dispatchEvent({type:"basemapchange",key:e})),t}_createBaseMapPicker(){const e=[{key:"topo",label:"Topographic",grad:"linear-gradient(135deg,#e8d5b7,#a67c52)"},{key:"osm",label:"OpenStreetMap",grad:"linear-gradient(135deg,#d4e6f1,#85c1e9)"},{key:"satellite",label:"Satellite",grad:"linear-gradient(135deg,#1b4332,#40916c)"},{key:"googlesat",label:"Google Sat",grad:"linear-gradient(135deg,#2a5d3d,#4a8c5a)"},{key:"carto-light",label:"Carto Light",grad:"linear-gradient(135deg,#f5f5f5,#d4d4d4)"},{key:"carto-dark",label:"Carto Dark",grad:"linear-gradient(135deg,#1a1a2e,#0f3460)"},{key:"none",label:"None",grad:"repeating-conic-gradient(#e5e7eb 0 25%, #fff 0 50%) 50% / 12px 12px"}],t=this.map.getTargetElement();if(!t)return;const r=document.createElement("button");r.type="button",r.className="ls-basemap-toggle",r.title="Switch base map",r.setAttribute("aria-label","Switch base map"),r.innerHTML='',t.appendChild(r);const n=document.createElement("div");n.className="ls-basemap-panel",n.innerHTML='
Base Map
'+e.map(a=>` `).join("")+"
",t.appendChild(n),this._basemapPanel=n,this._basemapToggle=r;const s=a=>{const i=a||this._baseMapLayers?.find(l=>l.getVisible())?.get("basemapKey");n.querySelectorAll('input[name="lupmis-basemap"]').forEach(l=>{l.checked=l.value===i})};s(),r.addEventListener("click",a=>{a.stopPropagation();const i=!n.classList.contains("open");n.classList.toggle("open",i),r.classList.toggle("active",i),i&&s()}),document.addEventListener("click",a=>{n.classList.contains("open")&&(n.contains(a.target)||r.contains(a.target)||(n.classList.remove("open"),r.classList.remove("active")))}),n.addEventListener("change",a=>{const i=a.target.closest('input[type=radio][name="lupmis-basemap"]');if(!i)return;const l=i.value;this.setBaseMap(l);try{localStorage.setItem("default-basemap",l)}catch{}n.classList.remove("open"),r.classList.remove("active")}),this.map.on("basemapchange",a=>s(a.key))}_initGpsRendering(){this._gpsPositionSource=new G,this._gpsTrailSource=new G,this._gpsTrailCoords=[],this._gpsTrailLayer=new D({source:this._gpsTrailSource,zIndex:940,style:new C({stroke:new I({color:"#ff6d00",width:4,lineCap:"round",lineJoin:"round"})}),properties:{title:"GPS Trail",displayInLayerSwitcher:!1}}),this._gpsPositionLayer=new D({source:this._gpsPositionSource,zIndex:950,style:e=>e.get("_kind")==="accuracy"?new C({fill:new M({color:"rgba(0,94,184,0.12)"}),stroke:new I({color:"rgba(0,94,184,0.35)",width:1})}):new C({image:new he({radius:7,fill:new M({color:"#005eb8"}),stroke:new I({color:"#ffffff",width:2.5})})}),properties:{title:"GPS Position",displayInLayerSwitcher:!1}}),this.map.addLayer(this._gpsTrailLayer),this.map.addLayer(this._gpsPositionLayer),this._gpsCallbacks={locate:[],record:[]},this._gpsRecording=!1}onLocateMe(e){this._gpsCallbacks.locate.push(e)}onToggleRecording(e){this._gpsCallbacks.record.push(e)}showCurrentPosition(e,t,r=null){if(e==null||t==null)return;const n=ee([e,t]);if(this._gpsPositionSource.clear(),r&&r>0){const a=r/Math.cos(t*Math.PI/180),i=new fe({geometry:new at([this._circleRing(n,a)])});i.set("_kind","accuracy"),this._gpsPositionSource.addFeature(i)}const s=new fe({geometry:new dt(n)});s.set("_kind","dot"),this._gpsPositionSource.addFeature(s)}_circleRing(e,t,r=48){const n=[],a=t/1;for(let i=0;i<=r;i++){const l=i/r*2*Math.PI;n.push([e[0]+a*Math.cos(l),e[1]+a*Math.sin(l)])}return n}centerOn(e,t,r=16){this.map.getView().animate({center:ee([e,t]),zoom:r,duration:500})}startTrailRender(){this._gpsTrailCoords=[],this._gpsTrailSource.clear()}appendTrailPoint(e,t){e==null||t==null||(this._gpsTrailCoords.push(ee([e,t])),this._gpsTrailSource.clear(),this._gpsTrailCoords.length>=2&&this._gpsTrailSource.addFeature(new fe({geometry:new de(this._gpsTrailCoords)})))}clearTrailRender(){this._gpsTrailCoords=[],this._gpsTrailSource.clear()}setRecordingState(e){this._gpsRecording=!!e,this._recordBtn&&(this._recordBtn.classList.toggle("recording",this._gpsRecording),this._recordBtn.title=this._gpsRecording?"Stop trail recording":"Record GPS trail",this._recordBtn.innerHTML=this._gpsRecording?'':''),this._locateToggle&&this._locateToggle.classList.toggle("recording",this._gpsRecording)}_createLocationControl(){const e=this.map.getTargetElement();if(!e)return;const t=document.createElement("button");t.type="button",t.className="ls-locate-toggle",t.title="My Location",t.setAttribute("aria-label","My Location"),t.innerHTML='',e.appendChild(t);const r=document.createElement("div");r.className="ls-locate-actions",r.innerHTML='',e.appendChild(r),this._locateToggle=t,this._locateActions=r,this._locateMeBtn=r.querySelector(".ls-locate-me"),this._recordBtn=r.querySelector(".ls-locate-record");const n=()=>{r.classList.remove("open"),t.classList.remove("active")},s=()=>{r.classList.add("open"),t.classList.add("active")};t.addEventListener("click",a=>{a.stopPropagation(),r.classList.contains("open")?n():s()}),document.addEventListener("click",a=>{r.classList.contains("open")&&(r.contains(a.target)||t.contains(a.target)||this._gpsRecording||n())}),this._locateMeBtn.addEventListener("click",a=>{a.stopPropagation();for(const i of this._gpsCallbacks.locate)try{i()}catch(l){console.error(l)}this._gpsRecording||n()}),this._recordBtn.addEventListener("click",a=>{a.stopPropagation();const i=!this._gpsRecording;for(const l of this._gpsCallbacks.record)try{l(i)}catch(c){console.error(c)}})}getFeatureStyle(e){const t=e.get("category")||"default",r=this.getEmoji(t);if(e===this.selectedFeature)return[new C({image:new he({radius:22,fill:new M({color:"rgba(220, 38, 38, 0.25)"}),stroke:new I({color:"#dc2626",width:3})})}),new C({text:new We({text:r,font:"40px sans-serif",textBaseline:"bottom",textAlign:"center",offsetY:-5})})];const n=e.get("style");return n||(this.categoryStyles[t]?this.categoryStyles[t]:this.defaultStyle)}setCategoryStyles(e){for(const[t,r]of Object.entries(e)){r.emoji&&(this.categoryEmojis[t]?(this.categoryEmojis[t].emoji=r.emoji,r.label&&(this.categoryEmojis[t].label=r.label)):this.categoryEmojis[t]={emoji:r.emoji,label:r.label||t});const n=this.getEmoji(t),s=r.fontSize||28;this.categoryStyles[t]=this.createEmojiStyle(n,s)}this.markerSource.changed()}addMarker(e,t,r={}){console.log("[MapView] Adding marker at",e,t,"with properties:",r);const n=new fe({geometry:new dt(ee([e,t])),...r});return n.set("lon",e),n.set("lat",t),this.markerSource.addFeature(n),console.log("[MapView] Marker added, total features:",this.markerSource.getFeatures().length),n}addMarkers(e){console.log("[MapView] Adding",e.length,"markers");const t=e.map(r=>new fe({geometry:new dt(ee([r.longitude,r.latitude])),id:r.id,name:r.name,description:r.description,category:r.category,lon:r.longitude,lat:r.latitude}));return this._withoutUndoRecording(()=>this.markerSource.addFeatures(t)),console.log("[MapView] Markers added, total features:",this.markerSource.getFeatures().length),t}clearMarkers(){this._withoutUndoRecording(()=>this._silentClearSource(this.markerSource)),this.selectedFeature=null}removeMarker(e){if(typeof e=="object")this.markerSource.removeFeature(e);else{const t=this.markerSource.getFeatures().find(r=>r.get("id")===e);t&&this.markerSource.removeFeature(t)}}getMarkers(){return this.markerSource.getFeatures()}findMarker(e){return this.markerSource.getFeatures().find(t=>t.get("id")===e)}showMarkers(){this.markersLayer&&!this.markersLayer.getVisible()&&(this.markersLayer.setVisible(!0),console.log("[MapView] Markers layer switched on to show a location"))}selectMarker(e){return typeof e=="object"?this.selectedFeature=e:this.selectedFeature=this.findMarker(e),this.markerSource.changed(),this.selectedFeature}clearSelection(){this.selectedFeature=null,this.markerSource.changed()}zoomTo(e,t,r=15){this.map.getView().animate({center:ee([e,t]),zoom:r,duration:500})}fitToMarkers(e=50){const t=this.markerSource.getExtent();t&&t[0]!==1/0&&this.map.getView().fit(t,{padding:[e,e,e,e],duration:500,maxZoom:16})}getCenter(){const e=this.map.getView().getCenter();return xe(e)}getZoom(){return this.map.getView().getZoom()}setCenter(e,t){this.map.getView().setCenter(ee([e,t]))}setZoom(e){this.map.getView().setZoom(e)}onClick(e){return this.clickCallbacks.push(e),this.clickCallbacks.length===1&&(this._clickTimer=null,this.map.on("dblclick",()=>{this._clickTimer&&(clearTimeout(this._clickTimer),this._clickTimer=null)}),this.map.on("click",t=>{this._clickTimer&&(clearTimeout(this._clickTimer),this._clickTimer=null),!this._editBarActive&&this._selectInteraction&&this._selectInteraction.getFeatures().clear();let r=!1,n=!1,s=null;if(this.map.forEachFeatureAtPixel(t.pixel,l=>{l.get("_layerType")==="parcel"&&(n=!0),l.get("name")&&(s=l),r=!0}),r&&!n&&!s)return;const[a,i]=xe(t.coordinate);this._clickTimer=setTimeout(()=>{this._clickTimer=null;let l=null;this.map.forEachFeatureAtPixel(t.pixel,c=>{if(c.get("name"))return l=c,!0});for(const c of this.clickCallbacks)c(a,i,l,t)},300)})),()=>{const t=this.clickCallbacks.indexOf(e);t>-1&&this.clickCallbacks.splice(t,1)}}onPointerMove(e){this.map.on("pointermove",t=>{if(t.dragging)return;const[r,n]=xe(t.coordinate);let s=null;this.map.forEachFeatureAtPixel(t.pixel,a=>{if(a.get("name"))return s=a,!0}),this.map.getTargetElement().style.cursor=s?"pointer":"",e(r,n,s,t)})}enableHoverCursor(){}addGeoJSONLayer(e,t,r={},n=null){const{strokeColor:s="#3b82f6",strokeWidth:a=2,strokeDash:i=null,fillColor:l="rgba(59,130,246,0.1)",lineCasingColor:c=null,lineCasingWidth:d=null,pointRadius:u=5,pointFillColor:f=null,pointStrokeColor:p="#ffffff",pointStrokeWidth:h=1.5}=r,g=new G({features:new le().readFeatures(e,{featureProjection:"EPSG:3857"})}),m=new M({color:l}),y=new he({radius:u,fill:new M({color:f||s}),stroke:new I({color:p,width:h})}),w=new I({color:s,width:a,...i?{lineDash:i}:{}});let _;if(c){const S=d??a+2;_=[new C({stroke:new I({color:c,width:S})}),new C({stroke:w,fill:m,image:y})]}else _=new C({stroke:w,fill:m,image:y});const E=new D({title:t,source:g,style:_,...r.declutter?{declutter:r.declutter}:{}});E.set("typeTag",r.typeTag||"VEC");const T=S=>S?S.includes("Polygon")?"Vector / Polygon":S.includes("LineString")?"Vector / Line":S.includes("Point")?"Vector / Point":"Vector":null;if(r.typeDescription)E.set("typeDescription",r.typeDescription);else{const S=g.getFeatures(),P=T(S[0]?.getGeometry?.()?.getType?.());if(P)E.set("typeDescription",P);else{const k=j=>{const q=T(j.feature.getGeometry?.()?.getType?.());q&&E.set("typeDescription",q),g.un("addfeature",k)};g.on("addfeature",k)}}return(n||this.overlayGroup).getLayers().push(E),console.log("[MapView] GeoJSON layer added:",t,"→",g.getFeatures().length,"features",n?`(in group "${n.get("title")}")`:""),E}addLayerGroup(e,t,r=""){const n=new Ce({title:t.trim()});return n.set("layerId",e),n.set("description",r),this.overlayGroup.getLayers().push(n),console.log("[MapView] Layer group added:",t.trim(),"(id:",e+")"),n}addWMSLayer(e,t,r,n,s={}){const a=this.getLayerGroupByTitle(e);if(!a)return console.warn(`[MapView] Layer group "${e}" not found — cannot add WMS layer "${t}"`),null;const i={LAYERS:n,TILED:!0,WIDTH:256,HEIGHT:256};s.style!==void 0&&(i.STYLES=s.style);const l=new Lo({url:r,params:i,serverType:s.serverType!==void 0?s.serverType:"geoserver",crossOrigin:"anonymous",hidpi:!1,attributions:s.attributions}),c=new se({title:t,visible:s.visible!==void 0?s.visible:!0,source:l,opacity:s.opacity!==void 0?s.opacity:1,zIndex:s.zIndex});if(c.set("typeTag","WMS"),c.set("typeDescription","WMS / Raster"),l.on("tileloaderror",()=>{F(`WMS layer "${t}" — tile load error. Check the URL and layer name.`,"warning",5e3)}),a.getLayers().push(c),s.legendUrl)try{this._registerLegend(c,t,s.legendUrl)}catch(d){console.warn(`[MapView] Could not register legend for "${t}":`,d)}return s.onlineOnly&&this._attachOnlineOnlyHandler(c,t),console.log(`[MapView] WMS layer added: "${t}" → group "${e}"`),c}addXYZLayer(e,t,r,n={}){const s=this.getLayerGroupByTitle(e);if(!s)return console.warn(`[MapView] Layer group "${e}" not found — cannot add XYZ layer "${t}"`),null;const a=new Me({url:r,crossOrigin:"anonymous",maxZoom:n.maxZoom!==void 0?n.maxZoom:19,attributions:n.attributions}),i=new se({title:t,visible:n.visible!==void 0?n.visible:!0,source:a,opacity:n.opacity!==void 0?n.opacity:1,zIndex:n.zIndex});if(i.set("typeTag","XYZ"),i.set("typeDescription","XYZ / Tile"),a.on("tileloaderror",()=>{F(`XYZ layer "${t}" — tile load error. Check the URL.`,"warning",5e3)}),s.getLayers().push(i),n.legendUrl)try{this._registerLegend(i,t,n.legendUrl)}catch(l){console.warn(`[MapView] Could not register legend for "${t}":`,l)}return n.onlineOnly&&this._attachOnlineOnlyHandler(i,t),console.log(`[MapView] XYZ layer added: "${t}" → group "${e}"`),i}async addCOGLayer(e,t,r,n={}){const s=this.getLayerGroupByTitle(e);if(!s)return console.warn(`[MapView] Layer group "${e}" not found — cannot add COG layer "${t}"`),null;const a=await this._probeCOGUrl(r);if(a)return console.warn(`[MapView] COG pre-flight failed for "${t}":`,a),F(a,"error",8e3),null;let i,l;try{[{default:i},{default:l}]=await Promise.all([Le(()=>import("./geotiff-B_Wfowsp.js").then(d=>d.W),__vite__mapDeps([0,1,2,3,4])),Le(()=>import("./geotiff-B_Wfowsp.js").then(d=>d.G),__vite__mapDeps([0,1,2,3,4]))])}catch(d){return console.error("[MapView] Could not load COG support:",d),F("Raster (COG) support could not be loaded — check your connection.","error",6e3),null}let c=null;try{const d={sources:[{url:r,...n.min!==void 0?{min:n.min}:{},...n.max!==void 0?{max:n.max}:{}}],normalize:n.normalize!==void 0?n.normalize:!0,attributions:n.attributions},u=new l(d);c=new i({title:t,source:u,visible:n.visible!==void 0?n.visible:!0,opacity:n.opacity!==void 0?n.opacity:1,zIndex:n.zIndex,...n.style?{style:n.style}:{}}),c.set("typeTag","COG"),c.set("typeDescription","Cloud-Optimized GeoTIFF"),u.on("error",f=>{console.warn(`[MapView] COG "${t}" source error:`,f),F(`Raster "${t}" could not be loaded. Check that the file is a valid COG and is CORS-enabled.`,"warning",6e3)}),s.getLayers().push(c)}catch(d){return console.error(`[MapView] Failed to add COG layer "${t}":`,d),F(`Could not add raster "${t}": ${d.message}`,"error",6e3),null}return n.onlineOnly&&this._attachOnlineOnlyHandler(c,t),console.log(`[MapView] COG layer added: "${t}" → group "${e}"`),c}async _probeCOGUrl(e){let t;try{t=await fetch(e,{headers:{Range:"bytes=0-0"}})}catch{return"Could not reach that file. It may be offline, or the server may not allow cross-origin (CORS) requests from this app."}if(t.status===404)return"No file at that URL (404). Check the address.";if(!t.ok&&t.status!==206)return`The server refused the request (HTTP ${t.status}).`;const r=(t.headers.get("content-type")||"").toLowerCase();return r.includes("text/html")?"That URL is a web page, not a GeoTIFF. Paste the direct link to the .tif file itself — a documentation or viewer page will not work.":r.startsWith("text/")||r.includes("json")?`That URL returns ${r.split(";")[0]}rather than image data. Paste the direct link to the .tif file.`:(t.status===200&&F("This server does not support byte-range requests, so the whole raster will be downloaded. It may be slow for a large file.","warning",7e3),null)}getSelectedFeatures(){return this._selectInteraction?this._selectInteraction.getFeatures().getArray().slice():[]}listVectorLayers(e={}){const{visibleOnly:t=!1,nonEmptyOnly:r=!0}=e,n=[],s=a=>{if(!a)return;if(typeof a.getLayers=="function"){a.getLayers().forEach(s);return}if(!(a instanceof D)||a===this._vertexOverlayLayer||a===this._gpsTrailLayer||a===this._gpsPositionLayer||a.get("displayInLayerSwitcher")===!1||t&&a.getVisible&&!a.getVisible())return;const i=a.getSource&&a.getSource();if(!i||typeof i.getFeatures!="function")return;const l=i.getFeatures().length;r&&l===0||n.push({layer:a,title:a.get("title")||"(untitled layer)",count:l})};return this.map.getLayers().forEach(s),n}_createAddLayerDialog(){this._addLayerDialog=document.createElement("div"),this._addLayerDialog.className="map-add-layer-dialog",this._addLayerDialog.style.cssText=` display:none;position:absolute;top:0;left:0;right:0;bottom:0; z-index:1100;background:rgba(0,0,0,0.4); align-items:center;justify-content:center; `;const e=document.createElement("div");e.style.cssText=` background:var(--card, #fff);color:var(--card-foreground, #1e1a4b); border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,0.35); font-family:var(--font-body, 'Exo', sans-serif);font-size:13px; width:340px;max-width:90vw;border:2px solid #10b981;overflow:hidden; `,e.innerHTML=`
Add External Layer
WMS LAYERS parameter (e.g. workspace:layer)
`,this._addLayerDialog.appendChild(e),this.map.getTargetElement().appendChild(this._addLayerDialog);const t=e.querySelector(".add-layer-name-row"),r=e.querySelector(".add-layer-name-hint"),n=e.querySelector(".add-layer-url"),s=e.querySelector(".add-layer-type-hint");e.querySelectorAll('input[name="add-layer-type"]').forEach(i=>{i.addEventListener("change",()=>{const l=i.value;s.textContent=l==="cog"?"Cloud-Optimized GeoTIFF — the server must allow CORS and byte-range requests.":"",l==="cog"?(t.style.display="none",n.placeholder="https://example.com/data/elevation_cog.tif"):l==="xyz"?(t.style.display="none",n.placeholder="https://example.com/tiles/{z}/{x}/{y}.png"):(t.style.display="",n.placeholder=l==="wms"?"https://example.com/wms":"https://example.com/wfs",r.textContent=l==="wms"?"WMS LAYERS parameter (e.g. workspace:layer)":"WFS typename (e.g. workspace:layer)")})});const a=()=>this._hideAddLayerDialog();e.querySelector(".add-layer-close").addEventListener("click",a),e.querySelector(".add-layer-cancel").addEventListener("click",a),this._addLayerDialog.addEventListener("click",i=>{i.target===this._addLayerDialog&&a()}),e.querySelector(".add-layer-confirm").addEventListener("click",()=>{const i=e.querySelector('input[name="add-layer-type"]:checked').value,l=e.querySelector(".add-layer-url").value.trim(),c=e.querySelector(".add-layer-name").value.trim(),d=e.querySelector(".add-layer-title").value.trim();if(!l){e.querySelector(".add-layer-url").style.borderColor="#ef4444";return}if((i==="wms"||i==="wfs")&&!c){e.querySelector(".add-layer-name").style.borderColor="#ef4444";return}if(!d){e.querySelector(".add-layer-title").style.borderColor="#ef4444";return}this._addExternalLayer(i,l,c,d),this._hideAddLayerDialog()}),e.addEventListener("keydown",i=>{i.key==="Enter"&&(i.preventDefault(),e.querySelector(".add-layer-confirm").click()),i.key==="Escape"&&(i.preventDefault(),a())})}showAddLayerDialog(){const e=this._addLayerDialog;e.querySelector(".add-layer-url").value="",e.querySelector(".add-layer-name").value="",e.querySelector(".add-layer-title").value="",e.querySelectorAll('input[name="add-layer-type"]')[0].checked=!0,e.querySelector(".add-layer-name-row").style.display="",e.querySelector(".add-layer-url").placeholder="https://example.com/wms",e.querySelector(".add-layer-name-hint").textContent="WMS LAYERS parameter (e.g. workspace:layer)";const t=e.querySelector(".add-layer-type-hint");t&&(t.textContent=""),e.querySelectorAll('input[type="text"]').forEach(r=>{r.style.borderColor="var(--border, #1e1a4b1f)"}),e.style.display="flex",e.querySelector(".add-layer-url").focus()}_hideAddLayerDialog(){this._addLayerDialog.style.display="none"}_addExternalLayer(e,t,r,n){const s=this._externalSourceGroup;if(!s){F('Layer group "External Source" not found.',"error",4e3);return}let a;switch(e){case"wms":{const i=new Lo({url:t,params:{LAYERS:r,TILED:!0,WIDTH:256,HEIGHT:256},serverType:"geoserver",crossOrigin:"anonymous",hidpi:!1});a=new se({title:n,visible:!0,source:i}),i.on("tileloaderror",()=>{F(`WMS "${n}" — tile load error. Check URL and layer name.`,"warning",5e3)});break}case"wfs":{const i=`${t}${t.includes("?")?"&":"?"}service=WFS&version=1.1.0&request=GetFeature&typename=${encodeURIComponent(r)}&outputFormat=application/json&srsname=EPSG:3857`,l=new G({url:i,format:new le});l.on("featuresloaderror",()=>{F(`WFS "${n}" — load error. Check URL and layer name.`,"warning",5e3)}),a=new D({title:n,visible:!0,source:l,style:new C({stroke:new I({color:"#e11d48",width:2}),fill:new M({color:"rgba(225,29,72,0.15)"})})});break}case"xyz":a=new se({title:n,visible:!0,source:new Me({url:t,crossOrigin:"anonymous"})}),a.getSource().on("tileloaderror",()=>{F(`XYZ "${n}" — tile load error. Check the URL template.`,"warning",5e3)});break;case"cog":{const i=s.get("title")||"External Source";this.addCOGLayer(i,n,t).then(l=>{l&&(l.set("removable",!0),F(`Layer "${n}" added to External Source.`,"success",3e3),console.log(`[MapView] External COG layer added: "${n}"`))});return}default:F(`Unknown layer type: ${e}`,"error",4e3);return}a.set("typeTag",e.toUpperCase()),a.set("typeDescription",{wms:"WMS / Raster",wfs:"WFS / Vector",xyz:"XYZ / Tile"}[e]||e.toUpperCase()),a.set("removable",!0),s.getLayers().push(a),F(`Layer "${n}" added to External Source.`,"success",3e3),console.log(`[MapView] External ${e.toUpperCase()} layer added: "${n}"`)}_decorateLayerListItem(e,t){const r=e.get("typeTag");if(r){const c=t.querySelector(":scope > .li-content > label > span");if(c&&!c.querySelector(":scope > .ls-type-tag")){const d=document.createElement("span");d.className=`ls-type-tag ls-type-tag-${String(r).toLowerCase()}`,d.textContent=String(r),d.title=`${r} layer`,c.appendChild(d)}}const n=t.querySelector(":scope > .ol-layerswitcher-buttons");if(n){const c=n.querySelector(":scope > .expend-layers, :scope > .collapse-layers");c&&!c.querySelector(":scope > svg.ls-chevron-svg")&&(c.innerHTML='')}const s=t.querySelector(":scope > .li-content"),a=()=>{if(!s)return;const c=e.get("typeDescription");let d=s.querySelector(":scope > .ls-layer-subtitle");if(!c){d&&d.remove();return}if(!d){d=document.createElement("div"),d.className="ls-layer-subtitle";const u=s.querySelector(":scope > label");u&&u.nextSibling?s.insertBefore(d,u.nextSibling):s.appendChild(d)}d.textContent=c};if(a(),e._lsSubtitleHooked||(e._lsSubtitleHooked=!0,e.on("change:typeDescription",()=>{a()})),e.get("removable")===!0&&n&&!n.querySelector(":scope > .ls-remove-btn")){const c=document.createElement("button");c.type="button",c.className="ls-remove-btn",c.title="Remove this layer",c.setAttribute("aria-label","Remove layer"),c.innerHTML='',c.addEventListener("click",d=>{d.stopPropagation(),this._removeLayer(e)}),n.appendChild(c)}const i=e.get("_externalImportId");if(i!=null){const c=t.querySelector(":scope > .li-content > label > span");let d=c?c.querySelector(":scope > .ls-import-chip"):null;const u=e.get("_externalImportStatus")||"mapped",f=e.getSource()?.getFeatures().length??0,p=e.get("_externalImportErrorCount")??0,h=(()=>{switch(u){case"mapped":return{text:`Upload ${f}`,cls:"ls-import-chip-mapped",title:"Upload this dataset to the database",clickable:!0};case"uploading":return{text:"…",cls:"ls-import-chip-uploading",title:"Uploading…",clickable:!1};case"submitted":return{text:"✓ submitted",cls:"ls-import-chip-submitted",title:"Uploaded — awaiting supervisor review",clickable:!1};case"migrated":return{text:"✓ live",cls:"ls-import-chip-migrated",title:"Approved by supervisor and live on the server",clickable:!1};case"failed":return{text:`${p} errors — fix?`,cls:"ls-import-chip-failed",title:"Some rows failed; click to review",clickable:!0};case"other":case null:case void 0:default:return null}})();if(!h)d&&d.remove();else if(c){d||(d=document.createElement("span"),d.className="ls-import-chip",c.appendChild(d)),d.className=`ls-import-chip ${h.cls}`,d.textContent=h.text,d.title=h.title,d.style.cursor=h.clickable?"pointer":"default",d.style.opacity=h.clickable?"1":"0.85";const g=d.cloneNode(!0);d.replaceWith(g),d=g,h.clickable&&d.addEventListener("click",m=>{m.preventDefault(),m.stopPropagation(),window.dispatchEvent(new CustomEvent("lupmis:import-chip-click",{detail:{importId:i,status:u,layer:e}}))})}}if((e.get("title")||"").toLowerCase().includes("external")&&(this._externalSourceGroup=e,n&&!n.querySelector(".ol-add-layer"))){const c=document.createElement("span");c.className="ol-add-layer",c.title="Add external layer",c.textContent="+",c.style.cssText=` display:inline-flex !important;align-items:center;justify-content:center; width:22px !important;height:22px !important;border-radius:50%; background:#41b6a6 !important;color:#fff !important; font-size:15px !important;font-weight:700; cursor:pointer;line-height:1 !important; margin:0 4px 0 0;vertical-align:middle; transition:background 0.2s;box-sizing:border-box;border:none; `,c.addEventListener("mouseenter",()=>{c.style.background="#329686"}),c.addEventListener("mouseleave",()=>{c.style.background="#41b6a6"}),c.addEventListener("click",d=>{d.stopPropagation(),this.showAddLayerDialog()}),n.prepend(c)}}_removeLayer(e){const t=e.get("title")||"this layer";if(!confirm(`Remove "${t}" from the map? This only affects the current session — built-in layers cannot be removed.`))return;const r=s=>{const a=s.getLayers();if(a.getArray().includes(e))return a.remove(e),!0;let i=!1;return a.forEach(l=>{!i&&l.getLayers&&(i=r(l))}),i};r(this.overlayGroup)?(console.log(`[MapView] Removed layer "${t}"`),F(`Removed "${t}" from the map.`,"info",3e3)):console.warn(`[MapView] Could not find layer "${t}" in any group`)}_refreshLayerSwitcherChrome(e){const t=e.element?.querySelector(".panel-container"),r=e.element?.querySelector("ul.panel");if(!t||!r)return;let n=t.querySelector(":scope > .ls-active-badge");n||(n=document.createElement("div"),n.className="ls-active-badge",n.innerHTML=` Layers 0 active `,t.insertBefore(n,r));let s=t.querySelector(":scope > .ls-footer-row");s||(s=document.createElement("div"),s.className="ls-footer-row",s.innerHTML=` — layers total `,t.appendChild(s),s.querySelector(".ls-footer-btn").addEventListener("click",i=>{i.stopPropagation(),this._resetAllOverlays()}));const a=this._countLayers();n.querySelector(".ls-active-badge-count").textContent=`${a.activeOverlays} active`,s.querySelector(".ls-footer-note").textContent=`${a.totalOverlays} overlay${a.totalOverlays===1?"":"s"}`}_countLayers(){let e=0,t=0;const r=new Set(["__vertex_highlight__"]),n=s=>{s.getLayers().forEach(a=>{a.get("displayInLayerSwitcher")!==!1&&(r.has(a.get("title"))||(a.getLayers?n(a):(e++,a.getVisible()&&t++)))})};return this.overlayGroup&&n(this.overlayGroup),{totalOverlays:e,activeOverlays:t}}_resetAllOverlays(){const e=new Set(["__vertex_highlight__"]),t=r=>{r.getLayers().forEach(n=>{n.get("displayInLayerSwitcher")!==!1&&(e.has(n.get("title"))||(n.getLayers?t(n):n.setVisible(!1)))})};this.overlayGroup&&t(this.overlayGroup),console.log("[MapView] Reset overlays — all hidden")}_wireLayerSwitcherVisibilityHooks(e){const t=()=>this._refreshLayerSwitcherChrome(e),r=s=>{s._lsVisHooked||(s._lsVisHooked=!0,s.on("change:visible",t))},n=s=>{s.getLayers().forEach(a=>{a.getLayers?(n(a),s._lsAddHooked||(s._lsAddHooked=!0,s.getLayers().on("add",i=>{const l=i.element;l.getLayers?n(l):r(l),t()}))):r(a)})};this.overlayGroup&&n(this.overlayGroup)}_attachOnlineOnlyHandler(e,t){e.set("onlineOnly",!0),e.on("change:visible",()=>{e.getVisible()&&!navigator.onLine&&F(`"${t}" requires an internet connection. Connect to view this layer.`,"info",5e3)})}_createLegendPanel(){this._legendPanel=document.createElement("div"),this._legendPanel.className="map-legend-panel",this._legendPanel.style.cssText=` position:absolute;right:10px;bottom:40px;z-index:900; display:none;flex-direction:column;gap:6px; background:var(--card, #fff);color:var(--card-foreground, #1e1a4b); border:1px solid var(--border, #1e1a4b1f);border-radius:8px; box-shadow:0 4px 12px rgba(0,0,0,0.15); font-family:var(--font-body, 'Exo', sans-serif);font-size:11px; max-width:220px;max-height:60%;overflow-y:auto; padding:8px 10px; `,this.map.getTargetElement().appendChild(this._legendPanel),this._legendEntries=new Map}_registerLegend(e,t,r){if(!this._legendPanel)return;const n=document.createElement("div");n.className="map-legend-entry",n.style.cssText="border-bottom:1px solid var(--border, #1e1a4b1f);padding-bottom:6px;";const s=document.createElement("div");s.style.cssText="font-weight:600;font-size:11px;margin-bottom:4px;line-height:1.3;",s.textContent=t,n.appendChild(s);const a=document.createElement("a");a.href=r,a.target="_blank",a.rel="noopener noreferrer",a.title=`Open the full ${t} legend`,a.style.cssText="display:block;position:relative;max-height:200px;overflow:hidden;border-radius:3px;text-decoration:none;color:inherit;";const i=document.createElement("img");i.alt=`${t} legend`,i.crossOrigin="anonymous",i.style.cssText="display:block;max-width:100%;height:auto;",i.addEventListener("error",()=>{if(i.dataset.viaProxy==="1"){n.style.display="none";return}i.dataset.viaProxy="1",i.removeAttribute("crossorigin"),i.src=`/legend.php?url=${encodeURIComponent(r)}`}),i.addEventListener("load",()=>{if(n.style.display="",a.scrollHeight<=a.clientHeight+1)return;const c=document.createElement("div");c.style.cssText="position:absolute;left:0;right:0;bottom:0;height:34px;pointer-events:none;background:linear-gradient(to bottom, transparent, var(--card, #fff));",a.appendChild(c);const d=document.createElement("div");d.style.cssText="font-size:10px;margin-top:3px;color:var(--muted-foreground, #7a7a7a);",d.textContent="Open the full legend ↗",n.appendChild(d)}),i.src=r,a.appendChild(i),n.appendChild(a),this._legendEntries.set(e,n);const l=()=>{try{this._updateLegendPanel()}catch(c){console.warn("[MapView] legend panel update failed:",c)}};e.on("change:visible",l),l()}_updateLegendPanel(){if(!this._legendPanel)return;const e=[];for(const[t,r]of this._legendEntries)t.getVisible()&&e.push(r);this._legendEntries.forEach(t=>{t.style.borderBottom="1px solid var(--border, #1e1a4b1f)",t.style.paddingBottom="6px"}),e.length>0&&(e[e.length-1].style.borderBottom="none",e[e.length-1].style.paddingBottom="0"),this._legendPanel.replaceChildren(...e),this._legendPanel.style.display=e.length>0?"flex":"none"}_escapeHtml(e){return String(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}getLayerGroup(e){let t=null;return this.overlayGroup.getLayers().forEach(r=>{r.get("layerId")===e&&(t=r)}),t}getLayerGroupByTitle(e){let t=null;return this.overlayGroup.getLayers().forEach(r=>{r.get("title")===e&&(t=r)}),t}getOverlayGroup(){return this.overlayGroup}getMap(){return this.map}getCurrentViewExtent(){const e=this.map.getView(),t=this.map.getSize();return t?e.calculateExtent(t):null}getDistrictBoundaryExtent(){let e=null;const t=r=>{r.getLayers().forEach(n=>{if(n.getLayers)t(n);else if(n.get("title")==="District Boundary"){const s=n.getSource&&n.getSource();if(s&&typeof s.getExtent=="function"){const a=s.getExtent();a&&Number.isFinite(a[0])&&(e={extent:a,title:n.get("title")})}}})};return t(this.overlayGroup),e}getMarkerSource(){return this.markerSource}getMarkersLayer(){return this.markersLayer}updateSize(){this.map.updateSize()}onSearchSelect(e){this.searchSelectCallbacks.push(e)}navigateTo(e,t,r=14,n=500){const s=ee([e,t]);this.map.getView().animate({center:s,zoom:r,duration:n})}}class ca{constructor(e,t={}){this.map=e,this.options=t,this.measureSource=new G,this.measureLayer=new D({source:this.measureSource,style:this.getMeasureStyle(),title:"Measurements",zIndex:100,selectable:!1}),this.drawSource=new G,this.drawLayer=new D({source:this.drawSource,style:this.getDrawStyle(),title:"Draw sketches",displayInLayerSwitcher:!1,zIndex:99});const r=this.map.getLayers();let n=r.getArray().findIndex(s=>s.get("title")==="Overlays");n<0&&(n=r.getLength()),r.insertAt(n,this.drawLayer),r.insertAt(n,this.measureLayer),this.activeInteraction=null,this.measureTooltip=null,this.measureTooltipElement=null,this.onMeasureCompleteCallbacks=[],this.onDrawCompleteCallbacks=[]}getMeasureStyle(){return new C({fill:new M({color:"rgba(255, 233, 106, 0.2)"}),stroke:new I({color:"#8B008B",lineDash:[10,10],width:2}),image:new he({radius:5,stroke:new I({color:"#8B008B"}),fill:new M({color:"rgba(255, 233, 106, 0.5)"})})})}getDrawStyle(){return new C({fill:new M({color:"rgba(255, 233, 106, 0.3)"}),stroke:new I({color:"#8B008B",width:2}),image:new he({radius:6,stroke:new I({color:"#8B008B",width:2}),fill:new M({color:"#FFE96A"})})})}createMeasureTooltip(){this.measureTooltipElement&&this.measureTooltipElement.parentNode.removeChild(this.measureTooltipElement),this.measureTooltipElement=document.createElement("div"),this.measureTooltipElement.className="measure-tooltip",this.measureTooltip=new Ee({element:this.measureTooltipElement,offset:[15,0],positioning:"center-left",stopEvent:!1}),this.map.addOverlay(this.measureTooltip)}setCircleRadius(e,t){const r=e.getGeometry();if(!r||typeof r.setRadius!="function")return;const n=r.getCenter(),s=Math.max(1,Number(t)||0),a=Cs(s,n);r.setRadius(a),e.set("_radius",a);const i=e.get("_radiusLine");i&&i.setGeometry(new de([n,[n[0]+a,n[1]]])),this._refreshRadiusAdjuster(e)}getCircleGroundRadius(e){const t=e.getGeometry();return Math.round(Qt(t.getRadius(),t.getCenter())*1e6)/1e6}_attachRadiusAdjuster(e,t,r){if(r){t.setPosition(t.getPosition()),r.classList.add("measure-tooltip-adjust"),r.innerHTML=`
`;for(const n of["pointerdown","pointerup","click","dblclick","touchstart"])r.addEventListener(n,s=>s.stopPropagation());r.querySelectorAll(".mt-step").forEach(n=>{n.addEventListener("click",()=>{const s=this.getCircleGroundRadius(e),a=Ms(s),i=Number(n.dataset.dir),l=Math.round(s/a)*a,c=Math.abs(l-s)>.01&&Math.sign(l-s)===i?l:l+i*a;this.setCircleRadius(e,c)})}),r.querySelector(".mt-value").addEventListener("click",()=>{this._promptRadius(e,r)}),e.set("_adjustEl",r),this._refreshRadiusAdjuster(e)}}_promptRadius(e,t){const r=t.querySelector(".mt-value");if(!r||t.querySelector(".mt-input"))return;const n=this.getCircleGroundRadius(e),s=document.createElement("input");s.type="number",s.className="mt-input",s.inputMode="decimal",s.min="1",s.step="any",s.value=String(Math.round(n*100)/100),s.setAttribute("aria-label","Radius in metres"),r.replaceWith(s),s.focus(),s.select();let a=!1;const i=l=>{a||!s.isConnected||(a=!0,l&&this.setCircleRadius(e,parseFloat(s.value)),s.replaceWith(r),this._refreshRadiusAdjuster(e))};s.addEventListener("keydown",l=>{l.key==="Enter"&&i(!0),l.key==="Escape"&&i(!1)}),s.addEventListener("blur",()=>i(!0))}_refreshRadiusAdjuster(e){const t=e.get("_adjustEl");if(!t)return;const r=this.getCircleGroundRadius(e),n=t.querySelector(".mt-value");n&&(n.textContent=rt(r));const s=t.querySelector(".mt-area");s&&(s.textContent=qe(Math.PI*r*r))}deactivate(){this.activeInteraction&&(this.map.removeInteraction(this.activeInteraction),this.activeInteraction=null),this.measureTooltip&&(this.map.removeOverlay(this.measureTooltip),this.measureTooltip=null),this.measureTooltipElement&&this.measureTooltipElement.parentNode&&(this.measureTooltipElement.parentNode.removeChild(this.measureTooltipElement),this.measureTooltipElement=null)}startCircleMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new Ae({source:this.measureSource,type:"Circle",style:new C({fill:new M({color:"rgba(255, 233, 106, 0.2)"}),stroke:new I({color:"rgba(139, 0, 139, 0.7)",lineDash:[10,10],width:2}),image:new he({radius:5,stroke:new I({color:"rgba(139, 0, 139, 0.7)"}),fill:new M({color:"rgba(255, 233, 106, 0.5)"})})})});this.activeInteraction=e,this.map.addInteraction(e);let t;return e.on("drawstart",r=>{t=r.feature.getGeometry().on("change",s=>{const a=s.target;if(a instanceof Sr){const i=Qt(a.getRadius(),a.getCenter()),l=Ps(i),d=`${rt(i)}
${l}`;this.measureTooltipElement.innerHTML=d,this.measureTooltip.setPosition(a.getLastCoordinate())}})}),e.on("drawend",r=>{const n=r.feature,s=n.getGeometry(),a=s.getCenter(),i=s.getRadius();n.set("_layerType","measure_circle"),n.set("_radius",i),n.set("_center",a);const l=new fe({geometry:new de([a,[a[0]+i,a[1]]])});l.set("_layerType","measure_circle_radius"),this.measureSource.addFeature(l),n.set("_radiusLine",l),this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltip.setOffset([0,-7]),this._attachRadiusAdjuster(n,this.measureTooltip,this.measureTooltipElement),this.measureTooltipElement=null,this.createMeasureTooltip(),Nt(t);const c={type:"circle",center:a,radius:i,area:Math.PI*i*i,feature:n};this.onMeasureCompleteCallbacks.forEach(d=>d(c))}),e}startLineMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new Ae({source:this.measureSource,type:"LineString",style:this.getMeasureStyle()});this.activeInteraction=e,this.map.addInteraction(e);let t;return e.on("drawstart",r=>{t=r.feature.getGeometry().on("change",s=>{const a=s.target,i=vt(a),l=rt(i);this.measureTooltipElement.innerHTML=l,this.measureTooltip.setPosition(a.getLastCoordinate())})}),e.on("drawend",r=>{const n=r.feature,s=n.getGeometry(),a=vt(s);this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltipElement=null,this.createMeasureTooltip(),Nt(t);const i={type:"line",length:a,feature:n};this.onMeasureCompleteCallbacks.forEach(l=>l(i))}),e}startAreaMeasure(){this.deactivate(),this.createMeasureTooltip();const e=new Ae({source:this.measureSource,type:"Polygon",style:this.getMeasureStyle()});this.activeInteraction=e,this.map.addInteraction(e);let t;return e.on("drawstart",r=>{t=r.feature.getGeometry().on("change",s=>{const a=s.target,i=nt(a),l=qe(i);this.measureTooltipElement.innerHTML=l,this.measureTooltip.setPosition(a.getInteriorPoint().getCoordinates())})}),e.on("drawend",r=>{const n=r.feature,s=n.getGeometry(),a=nt(s);n.set("_layerType","measure_area"),n.set("_area",a),this.measureTooltipElement.className="measure-tooltip measure-tooltip-static",this.measureTooltipElement=null,this.createMeasureTooltip(),Nt(t);const i={type:"polygon",area:a,feature:n,coordinate:s.getInteriorPoint().getCoordinates()};this.onMeasureCompleteCallbacks.forEach(l=>l(i))}),e}startDrawPoint(){this.deactivate();const e=new Ae({source:this.drawSource,type:"Point",style:this.getDrawStyle()});return this.activeInteraction=e,this.map.addInteraction(e),e.on("drawend",t=>{const r={type:"point",feature:t.feature};this.onDrawCompleteCallbacks.forEach(n=>n(r))}),e}startDrawLine(){this.deactivate();const e=new Ae({source:this.drawSource,type:"LineString",style:this.getDrawStyle()});return this.activeInteraction=e,this.map.addInteraction(e),e.on("drawend",t=>{const r={type:"line",feature:t.feature};this.onDrawCompleteCallbacks.forEach(n=>n(r))}),e}startDrawPolygon(){this.deactivate();const e=new Ae({source:this.drawSource,type:"Polygon",style:this.getDrawStyle()});return this.activeInteraction=e,this.map.addInteraction(e),e.on("drawend",t=>{const r={type:"polygon",feature:t.feature};this.onDrawCompleteCallbacks.forEach(n=>n(r))}),e}clearMeasurements(){this.measureSource.clear(),document.querySelectorAll(".measure-tooltip-static").forEach(t=>t.parentNode.removeChild(t))}clearDrawings(){this.drawSource.clear()}clearAll(){this.clearMeasurements(),this.clearDrawings()}onMeasureComplete(e){this.onMeasureCompleteCallbacks.push(e)}onDrawComplete(e){this.onDrawCompleteCallbacks.push(e)}createControlBar(e={}){e.position;const t=new Xt({group:!0,className:"map-tools-bar"}),r=new Xt({toggleOne:!0,group:!0}),n=new Se({html:'⭕',title:"Measure Circle (radius & area)",className:"measure-circle-btn",onToggle:l=>{l?this.startCircleMeasure():this.deactivate()}});r.addControl(n);const s=new Se({html:'📏',title:"Measure Distance",className:"measure-line-btn",onToggle:l=>{l?this.startLineMeasure():this.deactivate()}});r.addControl(s);const a=new Se({html:'⬛',title:"Measure Area",className:"measure-area-btn",onToggle:l=>{l?this.startAreaMeasure():this.deactivate()}});r.addControl(a);const i=new tt({html:'🗑️',title:"Clear Measurements",className:"clear-measure-btn",handleClick:()=>{this.clearMeasurements(),n.setActive(!1),s.setActive(!1),a.setActive(!1)}});return r.addControl(i),t.addControl(r),t}getMeasureLayer(){return this.measureLayer}getDrawLayer(){return this.drawLayer}getMeasureSource(){return this.measureSource}getDrawSource(){return this.drawSource}isActive(){return this.activeInteraction!==null}}let Oe=null;async function da(){if(!("serviceWorker"in navigator))return console.warn("[PWA] Service Workers not supported"),null;try{return Oe=await navigator.serviceWorker.register("/sw.js",{scope:"/"}),console.log("[PWA] Service Worker registered:",Oe.scope),Oe.addEventListener("updatefound",()=>{const o=Oe.installing;o.addEventListener("statechange",()=>{o.state==="installed"&&navigator.serviceWorker.controller&&(console.log("[PWA] New version available"),ga())})}),Oe}catch(o){return console.error("[PWA] Service Worker registration failed:",o),null}}async function ua(){if(!("serviceWorker"in navigator))return!1;let o=!1;try{for(const e of await navigator.serviceWorker.getRegistrations())await e.unregister()&&(o=!0);if("caches"in window){const e=await caches.keys();await Promise.all(e.filter(t=>!t.startsWith("tiles-")).map(t=>caches.delete(t)))}o&&console.log("[PWA] Service worker unregistered (development build) — reload once more to drop it completely")}catch(e){console.warn("[PWA] Could not unregister the service worker:",e)}return o}let Be=null,_e=null;function pa(o="#install-btn"){if(_e=typeof o=="string"?document.querySelector(o):o,!_e){console.warn("[PWA] Install button not found:",o);return}_e.style.display="none",window.addEventListener("beforeinstallprompt",e=>{e.preventDefault(),Be=e,_e.style.display="block",console.log("[PWA] Install prompt ready")}),_e.addEventListener("click",async()=>{if(!Be){fa();return}Be.prompt();const{outcome:e}=await Be.userChoice;console.log("[PWA] Install prompt outcome:",e),Be=null,_e.style.display="none"}),window.addEventListener("appinstalled",()=>{console.log("[PWA] App installed"),Be=null,_e.style.display="none"}),window.matchMedia("(display-mode: standalone)").matches&&(_e.style.display="none")}function fa(){const o=/iPad|iPhone|iPod/.test(navigator.userAgent),e=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);let t=`To install this app: `;o?(t+=`1. Tap the Share button (square with arrow) `,t+='2. Scroll down and tap "Add to Home Screen"'):e?(t+=`1. Click File menu `,t+='2. Click "Add to Dock"'):(t+=`1. Click the menu button (three dots) `,t+='2. Click "Install" or "Add to Home Screen"'),alert(t)}let to=null;const oo=new Set;function ha(o="#offline-indicator"){to=typeof o=="string"?document.querySelector(o):o,jt(!navigator.onLine),window.addEventListener("online",()=>{console.log("[PWA] Back online"),jt(!1),Wo(!1)}),window.addEventListener("offline",()=>{console.log("[PWA] Gone offline"),jt(!0),Wo(!0)})}function jt(o){to&&(to.style.display=o?"block":"none"),document.body.classList.toggle("is-offline",o)}function Un(o){return oo.add(o),o(!navigator.onLine),()=>oo.delete(o)}function Wo(o){for(const e of oo)try{e(o)}catch(t){console.error("[PWA] Offline listener error:",t)}}function K(){return navigator.onLine}function ga(){confirm("A new version is available. Reload now?")&&ma()}function ma(){Oe?.waiting&&Oe.waiting.postMessage({type:"SKIP_WAITING"}),window.location.reload()}async function ya({timeoutMs:o=1e4}={}){if(!("serviceWorker"in navigator))throw new Error("Service Workers not supported in this browser");if(navigator.serviceWorker.controller)return navigator.serviceWorker.controller;const e=navigator.serviceWorker.ready,t=new Promise((s,a)=>setTimeout(()=>a(new Error("Service-worker readiness timeout")),o)),r=await Promise.race([e,t]),n=navigator.serviceWorker.controller||r.active;if(!n)throw new Error("No active service worker available");return n}function ba(o){if(!("serviceWorker"in navigator))return()=>{};const e=()=>{try{o()}catch(t){console.error("[PWA] controllerchange handler error:",t)}};return navigator.serviceWorker.addEventListener("controllerchange",e),()=>navigator.serviceWorker.removeEventListener("controllerchange",e)}async function wo(o,e,t={},r=5e3,n=1e4){const s=await ya({timeoutMs:n});return new Promise((a,i)=>{const l=new MessageChannel,c=setTimeout(()=>{l.port1.close(),i(new Error(`Service-worker reply "${e}" timed out`))},r);l.port1.onmessage=d=>{if(d.data?.type===e){clearTimeout(c),l.port1.close();const{type:u,...f}=d.data;a(f)}},s.postMessage({type:o,...t},[l.port2])})}async function wa(){try{return(await wo("GET_TILE_STATS","TILE_STATS")).stats}catch(o){return console.warn("[PWA] getTileCacheStats failed:",o),null}}async function _a(){try{return await wo("CLEAR_TILE_CACHES","TILE_CACHES_CLEARED"),!0}catch(o){return console.warn("[PWA] clearTileCaches failed:",o),!1}}async function va(o){if(!o)return!1;try{return!!(await wo("CLEAR_TILE_CACHE","TILE_CACHE_CLEARED",{cacheName:o})).deleted}catch(e){return console.warn(`[PWA] clearTileCacheForProvider(${o}) failed:`,e),!1}}async function Ea(){if(!navigator.storage?.estimate)return null;try{const{usage:o,quota:e}=await navigator.storage.estimate();return{usage:o||0,quota:e||0}}catch(o){return console.warn("[PWA] getStorageEstimate failed:",o),null}}async function xa(o={}){const{installButton:e="#install-btn",offlineIndicator:t="#offline-indicator",autoRegisterSW:r=!0}=o;r?await da():await ua(),pa(e),ha(t),console.log("[PWA] Initialized")}const jn={topo:{url:"https://a.tile.opentopomap.org/{z}/{x}/{y}.png",label:"Topographic",maxZoom:17,cacheKey:"tiles-topo"},osm:{url:"https://a.tile.openstreetmap.org/{z}/{x}/{y}.png",label:"OpenStreetMap",maxZoom:19,cacheKey:"tiles-osm"}},Sa=30*1024,It=2*Math.PI*6378137/2;function Vo(o,e){const t=o/It*180;let r=e/It*180;return r=180/Math.PI*(2*Math.atan(Math.exp(r*Math.PI/180))-Math.PI/2),[t,r]}function Ko(o,e,t){const r=Math.pow(2,t),n=Math.floor((o+180)/360*r),s=e*Math.PI/180,a=Math.floor((1-Math.log(Math.tan(s)+1/Math.cos(s))/Math.PI)/2*r);return{x:n,y:a}}function zn(o,e){const[t,r,n,s]=o,[a,i]=Vo(t,r),[l,c]=Vo(n,s),d=Ko(a,c,e),u=Ko(l,i,e),f=Math.pow(2,e),p=Math.max(0,Math.min(d.x,u.x)),h=Math.min(f-1,Math.max(d.x,u.x)),g=Math.max(0,Math.min(d.y,u.y)),m=Math.min(f-1,Math.max(d.y,u.y));return{z:e,minX:p,maxX:h,minY:g,maxY:m,count:(h-p+1)*(m-g+1)}}function Ta(o,e,t){let r=0;for(let n=e;n<=t;n++)r+=zn(o,n).count;return r}function La(o,e,t){const r=[];for(let n=e;n<=t;n++){const s=zn(o,n);for(let a=s.minX;a<=s.maxX;a++)for(let i=s.minY;i<=s.maxY;i++)r.push({z:n,x:a,y:i})}return r}function ka(o,{z:e,x:t,y:r}){return o.replace("{z}",e).replace("{x}",t).replace("{y}",r)}class Ia{constructor({baseMap:e,extent3857:t,minZoom:r,maxZoom:n,concurrency:s=2,interBatchDelayMs:a=50,onProgress:i=()=>{}}){const l=jn[e];if(!l)throw new Error(`Unknown base map: ${e}`);n>l.maxZoom&&(console.warn(`[OfflineTiles] ${e}: maxZoom ${n} > supported ${l.maxZoom}; clamping`),n=l.maxZoom),this.baseMap=e,this.template=l.url,this.extent=t,this.minZoom=r,this.maxZoom=n,this.concurrency=Math.max(1,Math.min(s,6)),this.interBatchDelayMs=a,this.onProgress=i,this._abortCtrl=null,this._cancelled=!1}async start(){if(this._abortCtrl)throw new Error("Downloader already started");this._abortCtrl=new AbortController,this._cancelled=!1;const e=La(this.extent,this.minZoom,this.maxZoom),t=e.length,r=Date.now();let n=0,s=0,a=0,i=0;const l=c=>{const d=Date.now()-r,u=n>0?Math.round(d/n*(t-n)):null;this.onProgress({phase:c,done:n,total:t,ok:s,failed:a,cached:i,elapsedMs:d,etaMs:u})};l("running");for(let c=0;c{if(this._cancelled)return;const f=ka(this.template,u);try{const p=await fetch(f,{signal:this._abortCtrl.signal,cache:"default"});p.ok?(s++,p.body&&p.body.cancel().catch(()=>{})):(p.status,a++)}catch(p){p.name==="AbortError"||a++}n++})),l("running"),this.interBatchDelayMs>0&&c+this.concurrencysetTimeout(u,this.interBatchDelayMs))}return l(this._cancelled?"cancelled":"done"),{phase:this._cancelled?"cancelled":"done",done:n,total:t,ok:s,failed:a,cached:i,elapsedMs:Date.now()-r}}cancel(){this._cancelled=!0,this._abortCtrl&&this._abortCtrl.abort()}}const Pa=(()=>{const o=(r,n)=>{const s=r*It/180,a=Math.log(Math.tan((90+n)*Math.PI/360))/(Math.PI/180);return[s,a*It/180]},e=o(-3.3,4.5),t=o(1.2,11.2);return[e[0],e[1],t[0],t[1]]})();function Ca(o){return o*Sa}const qn="/api",Hn=`${qn}/spatial_planning`;function Ma(o){return o.includes("/")?`${qn}/${o}`:`${Hn}/${o}`}const zt="1",Aa="1c46538c712e9b5b";function Fa(){try{if(typeof window>"u")return zt;const o=window.LUPMIS_SESSION;if(!o||typeof o!="object")return zt;const e=o.district_id;return e==null||String(e).length===0?null:String(e)}catch{}return zt}const _o={get district_id(){return Fa()},api_token:Aa};function Ke(){if(typeof window<"u"&&window.LUPMIS_SESSION&&window.LUPMIS_SESSION.user_id)return window.LUPMIS_SESSION;try{const o=localStorage.getItem("dev-session");if(o){const e=JSON.parse(o);if(e&&e.user_id)return e}}catch{}return null}typeof window<"u"&&(window.lupmisDevSession=o=>{o==null?(localStorage.removeItem("dev-session"),console.log("[Dev] Session override cleared. Reload to apply.")):(localStorage.setItem("dev-session",JSON.stringify(o)),console.log("[Dev] Session override saved. Reload to apply:",o))});const Oa=3e4,Da=5e3;let Fe=null;async function Ra(o=!1){if(Fe!==null&&!o)return Fe;const e=new AbortController,t=setTimeout(()=>e.abort(),Da);try{Fe=(await fetch(`${Hn}/get_layers.php`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(_o),signal:e.signal})).ok}catch{Fe=!1}finally{clearTimeout(t)}return console.log("[RemoteDB] Server reachable:",Fe),Fe}function ke(){return Fe}function Na(o,e=Oa){const t=new AbortController,r=setTimeout(()=>t.abort(),e);return o.signal&&o.signal.addEventListener("abort",()=>t.abort()),{signal:t.signal,clear:()=>clearTimeout(r)}}async function me(o,e={},t={}){const r=Ma(o),n={..._o,...e};console.log("[RemoteDB] POST",r);const s=Na(t);try{const a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(n),...t,signal:s.signal});if(!a.ok)throw new Error(`HTTP ${a.status}: ${a.statusText}`);const i=await a.json();return console.log("[RemoteDB] POST response:",o,"→",typeof i=="object"?`${Array.isArray(i)?i.length+" items":"object"}`:i),i}catch(a){throw a.name==="AbortError"?(console.error("[RemoteDB] POST timed out:",o),new Error(`Request timed out: ${o}`)):(console.error("[RemoteDB] POST failed:",o,a),a)}finally{s.clear()}}async function $a(){return me("get_district_boundary.php")}async function Ba(){return me("get_layers.php")}async function Ga(){return me("get_all_collector_zone_per_district.php")}async function Ua(){return me("get_parcels_per_district.php")}async function ja(){return me("get_all_footprint_per_district.php")}async function za(){const o=_o.district_id;return me("contours/get_by_district.php",{districtid:o==null?null:Number(o)})}async function qa(){return me("get_osm_roads.php")}async function Ha(){return me("get_upn_grid_per_district.php")}async function Wa(o,e){const t={client_uuid:o.client_uuid,name:o.name??null,started_at:o.started_at,ended_at:o.ended_at,point_count:o.point_count??e.length,distance_m:o.distance_m??0,points:(e||[]).map(n=>({seq:n.seq,longitude:n.longitude,latitude:n.latitude,altitude:n.altitude??null,accuracy:n.accuracy??null,altitude_accuracy:n.altitude_accuracy??null,heading:n.heading??null,speed:n.speed??null,satellites:n.satellites??null,recorded_at:n.recorded_at}))},r=await me("save_gps_trail.php",t);return{remoteId:r?.id??r?.remote_id??null}}const Va=63710088e-1,De=Math.PI/180;function Ka(o,e,t,r){const n=(r-e)*De,s=(t-o)*De,a=Math.sin(n/2)**2+Math.cos(e*De)*Math.cos(r*De)*Math.sin(s/2)**2;return 2*Va*Math.asin(Math.min(1,Math.sqrt(a)))}function Xo(o,e=5){return o==null||Number.isNaN(o)?"—":o.toFixed(e)}const Yo=6378137,Jo=1/298.257223563,Zo=.9996,te=Jo*(2-Jo),qt=te/(1-te);function Xa(o,e){if(o==null||e==null||Number.isNaN(o)||Number.isNaN(e))return null;let t=((e+180)%360+360)%360-180;const r=Math.floor((t+180)/6)+1,n=(r-1)*6-180+3,s=o*De,a=t*De,i=n*De,l=Math.sin(s),c=Math.cos(s),d=Math.tan(s),u=Yo/Math.sqrt(1-te*l*l),f=d*d,p=qt*c*c,h=c*(a-i),g=Yo*((1-te/4-3*te**2/64-5*te**3/256)*s-(3*te/8+3*te**2/32+45*te**3/1024)*Math.sin(2*s)+(15*te**2/256+45*te**3/1024)*Math.sin(4*s)-35*te**3/3072*Math.sin(6*s));let m=Zo*u*(h+(1-f+p)*h**3/6+(5-18*f+f*f+72*p-58*qt)*h**5/120)+5e5,y=Zo*(g+u*d*(h*h/2+(5-f+9*p+4*p*p)*h**4/24+(61-58*f+f*f+600*p-330*qt)*h**6/720));const w=o>=0?"N":"S";return w==="S"&&(y+=1e7),{zone:r,hemisphere:w,easting:Math.round(m),northing:Math.round(y)}}function Ya(o,e){const t=Xa(o,e);return t?`${t.zone}${t.hemisphere} ${t.easting}E ${t.northing}N`:"—"}function Ja(o){return o==null||Number.isNaN(o)?"—":o<1e3?`${Math.round(o)} m`:`${(o/1e3).toFixed(2)} km`}function Za(o){return o==null||Number.isNaN(o)?"—":`±${Math.round(o)} m`}function Qa(o){return o==null||Number.isNaN(o)?"none":o<=10?"good":o<=30?"fair":"poor"}const ei={minDistanceM:5,minIntervalMs:1e3,heartbeatMs:2e4,maxAccuracyM:50,enableHighAccuracy:!0,timeoutMs:15e3,maximumAgeMs:0};class st{constructor(e={}){this.opts={...ei,...e},this.storage=e.storage||null,this.sync=e.sync||null,this._geo=e.geolocation||(typeof navigator<"u"?navigator.geolocation:null),this._state="idle",this._watchId=null,this._live=!1,this._recording=!1,this._activeTrailId=null,this._activeTrailUuid=null,this._lastRecorded=null,this._lastRecordedAt=0,this._distanceM=0,this._pointCount=0,this._lastFix=null,this._listeners=Object.create(null)}on(e,t){return(this._listeners[e]||(this._listeners[e]=new Set)).add(t),()=>this._listeners[e]?.delete(t)}_emit(e,t){const r=this._listeners[e];if(r)for(const n of r)try{n(t)}catch(s){console.error(`[GeoTracker] listener for "${e}" threw`,s)}}get state(){return this._state}get isRecording(){return this._recording}get lastFix(){return this._lastFix}get isSupported(){return!!this._geo}_setState(e){this._state!==e&&(this._state=e,this._emit("statechange",e))}startLive(){if(!this._geo){this._emit("error",new Error("Geolocation not supported"));return}this._live=!0,this._ensureWatch()}stopLive(){this._live=!1,this._recording||this._teardownWatch()}getCurrentPosition(){return new Promise((e,t)=>{if(!this._geo){t(new Error("Geolocation not supported"));return}this._geo.getCurrentPosition(r=>{const n=st.normalize(r);this._lastFix=n,this._emit("position",n),e(n)},r=>{this._emit("error",r),t(r)},{enableHighAccuracy:this.opts.enableHighAccuracy,timeout:this.opts.timeoutMs,maximumAge:this.opts.maximumAgeMs})})}async startRecording(e={}){if(!this._geo)throw new Error("Geolocation not supported");if(!this.storage)throw new Error("GeoTracker: no storage adapter configured");if(this._recording)return{trailId:this._activeTrailId,uuid:this._activeTrailUuid};const t=st.uuid(),r=new Date().toISOString(),n={uuid:t,name:e.name||null,startedAt:r,...e},s=await this.storage.createTrail(n);return this._activeTrailId=s,this._activeTrailUuid=t,this._lastRecorded=null,this._lastRecordedAt=0,this._distanceM=0,this._pointCount=0,this._recording=!0,this._ensureWatch(),this._setState("recording"),this._emit("trailstart",{trailId:s,uuid:t,startedAt:r}),{trailId:s,uuid:t}}async stopRecording(){if(!this._recording)return null;const e=this._activeTrailId,r={endedAt:new Date().toISOString(),pointCount:this._pointCount,distanceM:this._distanceM};this._recording=!1,this._live||this._teardownWatch(),this._setState(this._live?"watching":"idle");try{await this.storage.finishTrail(e,r)}catch(s){this._emit("error",s)}this._emit("trailstop",{trailId:e,...r});let n=!1;if(this.sync)try{n=await this._syncTrail(e)}catch(s){this._emit("error",s)}return this._activeTrailId=null,this._activeTrailUuid=null,{trailId:e,pointCount:r.pointCount,distanceM:r.distanceM,synced:n}}async syncPending(){if(!this.sync||!this.storage)return{pushed:0,failed:0};if(this.sync.isOnline&&!this.sync.isOnline())return{pushed:0,failed:0};let e=0,t=0;const r=await this.storage.getUnsyncedTrails();for(const n of r)try{await this._syncTrail(n.id??n.trailId,n)?e++:t++}catch(s){t++,this._emit("error",s)}return this._emit("syncstatus",{pushed:e,failed:t}),{pushed:e,failed:t}}async _syncTrail(e,t){const r=await this.storage.getTrailPoints(e),n=t||{id:e},s=await this.sync.pushTrail(n,r),a=s&&(s.remoteId??s.id??null);return await this.storage.markTrailSynced(e,a),!0}_ensureWatch(){if(this._watchId!=null||!this._geo){this._state==="idle"&&this._live&&this._setState("watching");return}this._watchId=this._geo.watchPosition(e=>this._onFix(e),e=>this._emit("error",e),{enableHighAccuracy:this.opts.enableHighAccuracy,timeout:this.opts.timeoutMs,maximumAge:this.opts.maximumAgeMs}),this._recording||this._setState("watching")}_teardownWatch(){this._watchId!=null&&this._geo&&this._geo.clearWatch(this._watchId),this._watchId=null}async _onFix(e){const t=st.normalize(e);if(this._lastFix=t,this._emit("position",t),!this._recording)return;const{minIntervalMs:r,minDistanceM:n,heartbeatMs:s,maxAccuracyM:a}=this.opts,i=t.timestamp;if(this._lastRecordedAt&&i-this._lastRecordedAt0&&t.accuracy!=null&&t.accuracy>a&&this._lastRecorded)return;let l=!1,c=0;if(!this._lastRecorded)l=!0;else{c=Ka(this._lastRecorded.lon,this._lastRecorded.lat,t.lon,t.lat);const d=i-this._lastRecordedAt;(c>=n||d>=s)&&(l=!0)}if(l){this._lastRecorded&&(this._distanceM+=c),this._pointCount+=1,this._lastRecorded={lon:t.lon,lat:t.lat,timestamp:i},this._lastRecordedAt=i;try{await this.storage.addPoint(this._activeTrailId,{...t,seq:this._pointCount}),this._emit("point",{trailId:this._activeTrailId,seq:this._pointCount,point:t,distanceM:this._distanceM,pointCount:this._pointCount})}catch(d){this._emit("error",d)}}}static normalize(e){const t=e.coords||{},r=n=>n!=null&&!Number.isNaN(n)?n:null;return{lon:t.longitude,lat:t.latitude,accuracy:r(t.accuracy),altitude:r(t.altitude),altitudeAccuracy:r(t.altitudeAccuracy),heading:r(t.heading),speed:r(t.speed),satellites:null,timestamp:e.timestamp||Date.now()}}static uuid(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}}const ti={async createTrail(o){const e=o.districtId??Ke()?.district_id??null;return vs({...o,districtId:e!=null?String(e):null})},addPoint:(o,e)=>Es(o,e),finishTrail:(o,e)=>xs(o,e),getUnsyncedTrails:()=>Ss(),getTrailPoints:o=>Ts(o),markTrailSynced:(o,e)=>Ls(o,e)},oi={pushTrail:(o,e)=>Wa(o,e),isOnline:()=>K()},ve=new st({storage:ti,sync:oi,minDistanceM:5,minIntervalMs:1e3,heartbeatMs:2e4,maxAccuracyM:50,enableHighAccuracy:!0}),ni=new Set(["set:view","set:selected","clear:selected","set:basemap"]);function ri({mapView:o,embedConfig:e}){const t=o.getMap(),r=window.parent&&window.parent!==window?window.parent:null,n=new G,s=new D({source:n,zIndex:9999,style:new C({stroke:new I({color:"#f97316",width:3}),fill:new M({color:"rgba(249,115,22,0.18)"})}),properties:{title:"Permit selection",displayInLayerSwitcher:!1}});t.addLayer(s);let a=null,i=e?.upn?String(e.upn):null,l=!1;function c(m){if(!r){console.warn("[embed-bridge] No parent window — would have sent:",m);return}try{r.postMessage(m,"*")}catch(y){console.warn("[embed-bridge] postMessage failed:",y)}}function d(m,y){c({type:"error",code:m,message:y})}function u(){l||(l=!0,c({type:"ready"}))}function f(m,y,w){const _=m.getProperties();let E=y,T=w;if(E==null||T==null){const x=m.getGeometry()?.getExtent();if(x){const[S,P]=xe(Tr(x));E=S,T=P}}return{type:"parcel:select",upn:_.upn??null,parcel_id:_.id??null,lon:E??null,lat:T??null,zone_code:_.zone_code??null,zone_name:_.zone_name??null,landuse:_.landuse??null,min_height:_.min_height??null,max_height:_.max_height??null}}function p(m){if(n.clear(),m){const y=m.clone();n.addFeature(y)}}o.onClick((m,y,w,_)=>{let E=null;t.forEachFeatureAtPixel(_.pixel,T=>{if(T.get("_layerType")==="parcel")return E=T,!0}),E?(p(E),c(f(E,m,y))):(p(null),c({type:"parcel:cleared"}))}),window.addEventListener("message",m=>{const y=m.data;if(!(!y||typeof y!="object"||!ni.has(y.type)))try{switch(y.type){case"set:view":{if(typeof y.lon=="number"&&typeof y.lat=="number"){const w=t.getView();w.setCenter(ee([y.lon,y.lat])),typeof y.zoom=="number"&&w.setZoom(y.zoom)}break}case"set:selected":y.upn&&h(String(y.upn));break;case"clear:selected":p(null),i=null;break;case"set:basemap":y.key&&typeof o.setBaseMap=="function"&&o.setBaseMap(y.key);break}}catch(w){d("COMMAND_FAILED",`Failed to handle ${y.type}: ${w.message}`)}});function h(m){if(!a){i=m;return}const w=a.getSource().getFeatures().find(E=>String(E.get("upn")??"")===m);if(!w){i=m;return}i=null,p(w);const _=w.getGeometry()?.getExtent();_&&t.getView().fit(_,{padding:[50,50,50,50],duration:400,maxZoom:17}),c(f(w,null,null))}function g(m){a=m;const y=m.getSource(),w=()=>{queueMicrotask(()=>{i&&h(i),u()})};if(y.getFeatures().length>0)w();else{let _=!1;y.on("addfeature",()=>{_||(_=!0,queueMicrotask(()=>{_=!1,i&&h(i),u()}))})}}if(e?.basemap&&typeof o.setBaseMap=="function"&&o.setBaseMap(e.basemap),typeof e?.lon=="number"&&typeof e?.lat=="number"){const m=t.getView();m.setCenter(ee([e.lon,e.lat])),m.setZoom(typeof e?.zoom=="number"?e.zoom:15)}return{attachParcelsLayer:g,emitError:d}}const si=[{key:"parcels",label:"Parcels",geometryFamily:"polygon"},{key:"collector_zones",label:"Collector Zones",geometryFamily:"polygon"},{key:"building_footprints",label:"Building Footprints",geometryFamily:"polygon"},{key:"osm_roads",label:"OSM Roads",geometryFamily:"line"},{key:"other",label:"Other (view only)",geometryFamily:"any"}],Wn={parcels:["upn","landuse","zone_code","zone_name","sector","block","parcel_no","prop_no","st_name","prop_add","fac_name","min_height","max_height","eff_date","lp_name","locality","mmda","last_update","remarks"],collector_zones:["zone_name"],building_footprints:[],osm_roads:["osm_id","name","highway"],other:[]};function ai(o){if(!o?.features?.length)return"none";let e=0,t=0,r=0;for(const a of o.features){const i=a?.geometry?.type;i&&(i==="Polygon"||i==="MultiPolygon"?e++:i==="LineString"||i==="MultiLineString"?t++:(i==="Point"||i==="MultiPoint")&&r++)}const n=e+t+r;if(n===0)return"none";const s=Math.max(e,t,r);return se&&(i=i.slice(0,e-1)+"…"),t[s]=i)}}return t}const L={};let no=null,N=null;function pi(){L.root||(L.root=document.getElementById("importMappingModal"),L.filename=document.getElementById("import-modal-filename"),L.summary=document.getElementById("import-modal-summary"),L.target=document.getElementById("import-modal-target"),L.targetHint=document.getElementById("import-modal-target-hint"),L.fieldsWrap=document.getElementById("import-modal-fields-wrap"),L.tbody=document.getElementById("import-modal-fields-tbody"),L.btnSave=document.getElementById("import-modal-save"),L.btnSaveUpload=document.getElementById("import-modal-save-upload"),L.btnCancel=document.getElementById("import-modal-cancel"),L.target&&!L.target.dataset.populated&&(L.target.innerHTML=si.map(o=>``).join(""),L.target.dataset.populated="1"),L.target&&!L.target.dataset.wired&&(L.target.dataset.wired="1",L.target.addEventListener("change",Xn)),L.btnSave&&!L.btnSave.dataset.wired&&(L.btnSave.dataset.wired="1",L.btnSave.addEventListener("click",()=>en("save"))),L.btnSaveUpload&&!L.btnSaveUpload.dataset.wired&&(L.btnSaveUpload.dataset.wired="1",L.btnSaveUpload.addEventListener("click",()=>en("upload"))),L.root&&!L.root.dataset.wired&&(L.root.dataset.wired="1",L.root.addEventListener("hidden.bs.modal",()=>{N?.onResult&&!N._resolved&&(N._resolved=!0,N.onResult({action:"cancel"})),N=null})))}function fi(){const o=N.targetType,e=Wn[o]||[];if(o==="other"||e.length===0){L.fieldsWrap.style.display="none";return}L.fieldsWrap.style.display="";const t=n=>{const s=N.sourceSamples[n];return s?` — [${_t(s)}]`:" — [(empty)]"},r=[''].concat(N.sourceFields.map(n=>``)).join("");L.tbody.innerHTML=e.map(n=>{const s=N.mapping[n]||"",a=r.replace(`