Compare commits

...

2 Commits

Author SHA1 Message Date
9b57ff9e22 Add .gitignore and untrack node_modules / IDE state
Comprehensive .gitignore covering:
  * node_modules/ (re-installable from package-lock.json) — also untracks
    the 5 679 files that were carried over from the initial commit
  * Vite dependency cache (.vite/) — pure build churn
  * IDE state: BBEdit (*.bbprojectd/), VS Code, JetBrains, Zed, Sublime,
    Vim swap files
  * OS metadata: macOS .DS_Store / ._*, Windows Thumbs.db, etc.
  * Interim Word-document backups (*-v[0-9].docx pattern), env files,
    test coverage, common cache directories

dist/ deliberately NOT ignored — the repo currently serves the built
output directly. If you switch to a CI deploy later, uncomment the
dist/ lines in .gitignore.

After this commit, `git status` will be empty until real source changes
are made (no more node_modules / .vite cache noise).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:58:25 +02:00
ef12e4477b Offline tile cache, polygon Divide, topographic layer integrations
Major feature batch covering drawing-tool improvements, layer additions,
and offline-first capabilities. Largest changes in MapView.js (+1700),
main.js (+1500), public/sw.js (+367), and new modules under src/.

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:55:30 +02:00
5770 changed files with 10912 additions and 878673 deletions

104
.gitignore vendored Normal file
View File

@ -0,0 +1,104 @@
# ============================================================================
# LUPMIS2 PWA — .gitignore
# ============================================================================
# ----- Node / npm dependencies -----
# Re-installable from package-lock.json; never tracked.
node_modules/
# Vite's dependency optimisation cache (lives inside node_modules but listed
# explicitly for clarity).
node_modules/.vite/
.vite/
# npm / yarn / pnpm logs and debug files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# ----- Build artefacts -----
# Vite produces these but we do NOT ignore `dist/` because the repo serves
# the built output directly. If you switch to a CI-based deploy pipeline
# later, uncomment the next two lines.
# dist/
# dist-ssr/
# Pre-compressed / source-map sidecar files outside dist/
*.tsbuildinfo
# ----- Local environment & secrets -----
.env
.env.local
.env.*.local
*.local
# ----- IDE / editor configuration -----
# BBEdit project / scratchpad state
*.bbprojectd/
# Visual Studio Code (keep recommended-extensions and tasks.json if added)
.vscode/*
!.vscode/extensions.json
!.vscode/tasks.json
!.vscode/launch.json
# JetBrains IDEs (WebStorm, IntelliJ, etc.)
.idea/
*.iml
# Zed
.zed/
# Sublime Text
*.sublime-workspace
*.sublime-project
# Vim / Emacs swap files
*.swp
*.swo
*~
.#*
# ----- OS metadata -----
# macOS
.DS_Store
.AppleDouble
.LSOverride
Icon
Icon?
._*
# Windows
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
Desktop.ini
$RECYCLE.BIN/
# Linux
.directory
.Trash-*
# ----- Project-specific -----
# Interim Word-document backups produced when iterating on reports
# (the *current* report file stays tracked; only versioned snapshots are
# excluded).
LUPMIS2_Development_Status_Report-v*.docx
*-v[0-9].docx
*-v[0-9][0-9].docx
# SQLite databases dropped in the project root for ad-hoc inspection
/*.sqlite3
/*.sqlite3-journal
/*.db
# Coverage / test output
coverage/
.nyc_output/
# Misc caches
.eslintcache
.parcel-cache/
.cache/

Binary file not shown.

BIN
OpenTopography_Workflow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

181
OpenTopography_Workflow.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

23
dist/assets/html2canvas.esm-B0tyYwQk.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

636
dist/assets/index-B4XzHtZX.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/index-B4XzHtZX.js.map vendored Normal file

File diff suppressed because one or more lines are too long

19
dist/assets/index.es-CRPDPo17.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/index.es-CRPDPo17.js.map vendored Normal file

File diff suppressed because one or more lines are too long

172
dist/assets/jspdf-Cu-2SCgw.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/jspdf-Cu-2SCgw.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/assets/ol-ext-CSk2UikI.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/ol-ext-CSk2UikI.js.map vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

573
dist/assets/openlayers-CUDtI0S3.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/assets/pdf-export-Vpiz8VA4.js vendored Normal file
View File

@ -0,0 +1,2 @@
import{E as f,a as b}from"./jspdf-Cu-2SCgw.js";import"./openlayers-CUDtI0S3.js";b(f);let s=null;async function x(){if(s)return s;try{const e=new Image;e.crossOrigin="anonymous",await new Promise((r,i)=>{e.onload=r,e.onerror=i,e.src="./icons/luspa-pdf.jpg"});const n=document.createElement("canvas");n.width=e.naturalWidth,n.height=e.naturalHeight;const t=n.getContext("2d");return t.fillStyle="#ffffff",t.fillRect(0,0,n.width,n.height),t.drawImage(e,0,0),s=n.toDataURL("image/jpeg",.92),s}catch(e){return console.warn("[PDF] Could not load logo:",e),null}}async function v({title:e,rows:n}){const t=new f({orientation:"portrait",unit:"mm",format:"a4"}),r=t.internal.pageSize.getWidth(),i=[30,26,75],g=await x(),c=28,a=14;let o=14;g&&t.addImage(g,"JPEG",a,o,c,c);const m=a+c+6;t.setFont("helvetica","bold"),t.setFontSize(18),t.setTextColor(...i),t.text("LUPMIS",m,o+11),t.setFont("helvetica","normal"),t.setFontSize(12),t.text(e,m,o+19);const d=new Date,h=d.toLocaleDateString(void 0,{year:"numeric",month:"long",day:"numeric"}),y=d.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});t.setFontSize(9),t.setTextColor(120,120,120),t.text(`${h} ${y}`,r-a,o+11,{align:"right"}),o+=c+6,t.setDrawColor(...i),t.setLineWidth(.5),t.line(a,o,r-a,o),o+=6;const S=n.map(l=>[l.label,l.value]);t.autoTable({startY:o,head:[["Property","Value"]],body:S,margin:{left:a,right:a},styles:{font:"helvetica",fontSize:10,cellPadding:4},headStyles:{fillColor:i,textColor:[255,255,255],fontStyle:"bold"},alternateRowStyles:{fillColor:[245,245,250]},columnStyles:{0:{fontStyle:"bold",cellWidth:50}}});const p=t.lastAutoTable.finalY+10;t.setFontSize(8),t.setTextColor(160,160,160),t.text("Generated by LUPMIS2 Land Use Planning & Management Information System",a,p);const w=t.output("blob"),u=URL.createObjectURL(w);if(!window.open(u,"_blank")){const l=document.createElement("a");l.href=u,l.download=`${e.replace(/\s+/g,"_")}_${d.toISOString().slice(0,10)}.pdf`,document.body.appendChild(l),l.click(),document.body.removeChild(l)}}export{v as exportAnalysisPDF};
//# sourceMappingURL=pdf-export-Vpiz8VA4.js.map

File diff suppressed because one or more lines are too long

3
dist/assets/purify.es-BgtpMKW3.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/purify.es-BgtpMKW3.js.map vendored Normal file

File diff suppressed because one or more lines are too long

5
dist/assets/shpjs-CNrRgkgn.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/shpjs-CNrRgkgn.js.map vendored Normal file

File diff suppressed because one or more lines are too long

BIN
dist/fonts/bebas-neue-latin-ext.woff2 vendored Normal file

Binary file not shown.

BIN
dist/fonts/bebas-neue-latin.woff2 vendored Normal file

Binary file not shown.

BIN
dist/fonts/exo-latin-ext.woff2 vendored Normal file

Binary file not shown.

BIN
dist/fonts/exo-latin.woff2 vendored Normal file

Binary file not shown.

BIN
dist/fonts/exo-vietnamese.woff2 vendored Normal file

Binary file not shown.

View File

@ -1 +0,0 @@
Place PWA icons here (icon-72.png, icon-96.png, icon-128.png, icon-144.png, icon-152.png, icon-192.png, icon-384.png, icon-512.png)

BIN
dist/icons/luspa-128x128.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
dist/icons/luspa-144x144.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
dist/icons/luspa-152x152.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
dist/icons/luspa-384x384.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

BIN
dist/icons/luspa-72x72.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
dist/icons/luspa-96x96.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
dist/icons/luspa-pdf.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

745
dist/index.html vendored
View File

@ -2,19 +2,64 @@
<html lang="en" data-bs-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#005eb8">
<meta name="description" content="LUPMIS2 Drawing Tools">
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json">
<link rel="apple-touch-icon" href="/icons/luspa.icon">
<link rel="icon" href="/icons/luspa.icon">
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" sizes="192x192" href="icons/luspa-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/luspa-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/luspa-16x16.png">
<!-- LUSPA Design System Fonts: Bebas Neue (display) + Exo (body) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Exo:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- LUSPA Design System Fonts: Bebas Neue (display) + Exo (body) — self-hosted -->
<style>
/* Bebas Neue 400 — latin-ext */
@font-face {
font-family: 'Bebas Neue';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/bebas-neue-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Bebas Neue 400 — latin */
@font-face {
font-family: 'Bebas Neue';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/bebas-neue-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Exo 300-800 — vietnamese */
@font-face {
font-family: 'Exo';
font-style: normal;
font-weight: 300 800;
font-display: swap;
src: url('/fonts/exo-vietnamese.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* Exo 300-800 — latin-ext */
@font-face {
font-family: 'Exo';
font-style: normal;
font-weight: 300 800;
font-display: swap;
src: url('/fonts/exo-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Exo 300-800 — latin */
@font-face {
font-family: 'Exo';
font-style: normal;
font-weight: 300 800;
font-display: swap;
src: url('/fonts/exo-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
</style>
<title>LUPMIS2 Drawing Tools</title>
@ -106,6 +151,295 @@
--radius-2xl: 1rem;
}
/* ─── Fieldwork Mode ─── high-contrast + larger touch targets ─── */
.fieldwork-mode {
--foreground: #000;
--background: #fff;
--card: #fff;
--card-foreground: #000;
--primary: #0044aa;
--primary-foreground: #fff;
--primary-hover: #003080;
--muted: #e0e0e0;
--muted-foreground: #333;
--accent: #cce0ff;
--accent-foreground: #000;
--border: rgba(0,0,0,0.25);
--success: #005a00;
--success-foreground: #fff;
--warning: #b36b00;
--warning-foreground: #000;
--destructive: #b80000;
--destructive-foreground: #fff;
--ring: #0044aa;
--bs-body-color: #000;
}
/* Fieldwork: larger dock buttons */
.fieldwork-mode .dock-btn {
min-width: 72px;
min-height: 58px;
font-size: 1.6rem;
border-width: 2px;
}
.fieldwork-mode .dock-btn-label {
font-size: 0.75rem;
font-weight: 600;
}
/* Fieldwork: bolder navbar */
.fieldwork-mode .navbar {
border-bottom-width: 4px;
}
.fieldwork-mode .navbar .navbar-brand {
font-size: 1.6rem;
}
/* Fieldwork: larger offcanvas toggle buttons */
.fieldwork-mode .offcanvas-toggle {
width: 44px;
height: 44px;
font-size: 1.2rem;
}
/* Fieldwork: thicker bottom dock border */
.fieldwork-mode .bottom-dock {
border-top-width: 4px;
}
/* Fieldwork: larger text in cards / lists */
.fieldwork-mode .card-header h6 {
font-size: 1rem;
}
.fieldwork-mode .list-group-item {
font-size: 0.95rem;
padding: 0.65rem 1rem;
}
/* Fieldwork: larger buttons globally */
.fieldwork-mode .btn {
font-size: 0.95rem;
padding: 0.5rem 1rem;
font-weight: 600;
}
.fieldwork-mode .btn-sm {
font-size: 0.85rem;
padding: 0.4rem 0.75rem;
}
/* Fieldwork: stronger borders on inputs / form controls */
.fieldwork-mode .form-control,
.fieldwork-mode .form-select {
border-width: 2px;
border-color: #555;
font-size: 1rem;
}
/* Fieldwork: bolder map controls (ol-ext) */
.fieldwork-mode .ol-control button {
font-size: 1.3rem;
width: 2.2em;
height: 2.2em;
}
/* Fieldwork: scale bar text legibility */
.fieldwork-mode .ol-scale-bar .ol-scale-step-text,
.fieldwork-mode .ol-scale-bar .ol-scale-text {
font-size: 12px;
font-weight: 700;
text-shadow: 0 0 4px #fff, 0 0 8px #fff;
}
/* ─── Dark Mode ─── reversed colour scheme ─── */
.dark-mode {
--foreground: #e0dff0;
--background: #131325;
--card: #1e1e38;
--card-foreground: #e0dff0;
--primary: #4d9de6;
--primary-foreground: #fff;
--primary-hover: #6fb3f0;
--muted: #272745;
--muted-foreground: #9594a8;
--accent: #1e3a5f;
--accent-foreground: #e0dff0;
--border: rgba(255,255,255,0.12);
--ring: #4d9de6;
--success: #2dd46a;
--success-foreground: #131325;
--warning: #ffb84d;
--warning-foreground: #131325;
--destructive: #f04040;
--destructive-foreground: #fff;
--bs-body-color: #e0dff0;
--bs-body-bg: #131325;
--bs-tertiary-bg: #1e1e38;
color-scheme: dark;
}
/* Dark: navbar */
.dark-mode .navbar {
background-color: #1a1a30 !important;
box-shadow: 0 1px 6px rgba(0,0,0,0.4);
}
/* Dark: bottom dock */
.dark-mode .bottom-dock {
background-color: #1a1a30;
box-shadow: 0 -2px 10px rgba(0,0,0,0.3);
}
.dark-mode .dock-btn {
border-color: var(--primary);
color: var(--foreground);
}
.dark-mode .dock-btn:hover {
background-color: var(--muted);
}
.dark-mode .dock-btn.active {
background-color: var(--primary);
color: var(--primary-foreground);
}
/* Dark: offcanvas panels */
.dark-mode .offcanvas {
background-color: var(--background) !important;
color: var(--foreground) !important;
}
.dark-mode .offcanvas-header {
border-bottom-color: var(--border) !important;
}
.dark-mode .btn-close {
filter: invert(1) grayscale(100%) brightness(200%);
}
/* Dark: cards */
.dark-mode .card {
background-color: var(--card) !important;
color: var(--card-foreground) !important;
border-color: var(--border) !important;
}
/* Dark: offcanvas toggle buttons */
.dark-mode .offcanvas-toggle {
background-color: var(--card);
color: var(--foreground);
border-color: var(--border);
}
.dark-mode .offcanvas-toggle:hover {
background-color: var(--primary);
color: var(--primary-foreground);
}
/* Dark: form controls */
.dark-mode .form-control,
.dark-mode .form-select {
background-color: var(--muted) !important;
color: var(--foreground) !important;
border-color: var(--border) !important;
}
.dark-mode .form-check-input {
background-color: var(--muted);
border-color: var(--muted-foreground);
}
.dark-mode .form-check-input:checked {
background-color: var(--primary);
border-color: var(--primary);
}
/* Dark: list groups */
.dark-mode .list-group-item {
background-color: var(--card) !important;
color: var(--card-foreground) !important;
border-color: var(--border) !important;
}
/* Dark: buttons */
.dark-mode .btn-outline-primary {
color: var(--primary);
border-color: var(--primary);
}
.dark-mode .btn-outline-danger {
color: var(--destructive);
border-color: var(--destructive);
}
/* Dark: text utilities */
.dark-mode .text-muted {
color: var(--muted-foreground) !important;
}
/* Dark: measurement tooltips */
.dark-mode .measure-tooltip {
background: rgba(30, 30, 56, 0.95);
color: var(--foreground);
border-color: var(--primary);
}
.dark-mode .measure-tooltip::before {
border-right-color: var(--primary);
}
/* Dark: OL controls */
.dark-mode .ol-control button {
background-color: var(--card) !important;
color: var(--foreground) !important;
}
.dark-mode .ol-control button:hover {
background-color: var(--primary) !important;
color: var(--primary-foreground) !important;
}
.dark-mode .ol-attribution,
.dark-mode .ol-attribution a {
color: var(--muted-foreground) !important;
}
/* Dark: scale bar */
.dark-mode .ol-scale-bar .ol-scale-step-text,
.dark-mode .ol-scale-bar .ol-scale-text {
color: #fff !important;
text-shadow: 0 0 4px #000, 0 0 8px #000 !important;
}
.dark-mode .ol-scale-bar .ol-scale-singlebar-even {
background-color: #fff !important;
}
.dark-mode .ol-scale-bar .ol-scale-singlebar-odd {
background-color: #999 !important;
}
/* Dark: map drop overlay */
.dark-mode .map-drop-overlay {
background: rgba(19, 19, 37, 0.85);
border-color: var(--primary);
color: var(--foreground);
}
/* Dark: ol-ext LayerSwitcher */
.dark-mode .ol-layerswitcher {
background-color: var(--card) !important;
}
.dark-mode .ol-layerswitcher .panel {
background-color: var(--card) !important;
color: var(--foreground) !important;
}
.dark-mode .ol-layerswitcher .panel li {
color: var(--foreground);
}
.dark-mode .ol-layerswitcher .ol-switchertopdiv,
.dark-mode .ol-layerswitcher .ol-switcherbottomdiv {
background: var(--card) !important;
}
/* Dark: alert boxes */
.dark-mode .alert-danger {
background-color: rgba(240, 64, 64, 0.15) !important;
color: var(--destructive) !important;
border-color: var(--destructive) !important;
}
.dark-mode .alert-success {
background-color: rgba(45, 212, 106, 0.15) !important;
color: var(--success) !important;
border-color: var(--success) !important;
}
/* Full height layout */
html, body {
height: 100%;
@ -214,9 +548,12 @@
font-family: var(--font-body);
}
/* Main container - full height */
/* Main container - full height.
100dvh accounts for mobile browser chrome and OS nav bars.
Falls back to 100vh for older browsers. */
.app-container {
height: 100vh;
height: 100dvh;
display: flex;
flex-direction: column;
}
@ -244,6 +581,35 @@
height: 100%;
}
/* Drag-and-drop overlay shown when files are dragged over the map */
.map-drop-overlay {
position: absolute;
inset: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 94, 184, 0.15);
border: 3px dashed var(--primary, #005eb8);
border-radius: 8px;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s ease;
}
.map-container.drag-over .map-drop-overlay {
opacity: 1;
}
.map-drop-overlay span {
font-family: var(--font-body, 'Exo', sans-serif);
font-size: 1.15rem;
font-weight: 600;
color: var(--primary, #005eb8);
background: var(--card, #fff);
padding: 0.6rem 1.4rem;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.12);
}
/* Offline indicator */
#offline-indicator {
display: none;
@ -301,9 +667,14 @@
z-index: 1050;
}
/* Fix ol-ext LayerSwitcher z-index */
.ol-layerswitcher {
z-index: 100;
/* OL controls stacking context fix — OpenLayers sets z-index:0 on
.ol-overlaycontainer-stopevent, trapping all controls below the
offcanvas-toggle buttons (z-index:500). Raising the container
to 501 lets the LayerSwitcher dropdown render above the toggles.
pointer-events:none on the container still lets clicks through
to the toggle buttons underneath. */
.ol-overlaycontainer-stopevent {
z-index: 501 !important;
}
/* Alert hint box */
@ -419,7 +790,7 @@
}
.offcanvas-toggle-bottom {
bottom: 80px; /* Above the dock */
bottom: calc(80px + env(safe-area-inset-bottom, 0px)); /* Above the dock */
left: 50%;
transform: translateX(-50%);
}
@ -432,7 +803,10 @@
transform: translateX(-50%) scale(0.95);
}
/* Bottom Dock — white card style with blue-strong accent */
/* Bottom Dock — white card style with blue-strong accent.
env(safe-area-inset-bottom) adds padding on devices with a
home indicator / gesture bar (e.g. iPhone notch models).
The value is 0 on devices without an inset. */
.bottom-dock {
position: absolute;
bottom: 0;
@ -441,7 +815,7 @@
z-index: 600;
background-color: var(--card);
border-top: 3px solid var(--primary);
padding: 8px 16px;
padding: 8px 16px calc(8px + env(safe-area-inset-bottom, 0px));
display: flex;
justify-content: space-around;
align-items: center;
@ -503,6 +877,13 @@
justify-content: center;
}
/* Snap-guides toggle — highlighted when active */
.ol-snap-toggle.ol-active button {
background: var(--primary) !important;
color: var(--primary-foreground, #fff) !important;
border-radius: 3px;
}
/* Touch-friendly improvements for forms and buttons */
.form-control, .form-select {
min-height: 44px;
@ -541,6 +922,18 @@
color: var(--foreground);
}
/* Message log in the right panel */
.message-log {
max-height: 260px;
overflow-y: auto;
}
.message-log-entry {
font-size: 0.82rem;
border-color: var(--border, #eee) !important;
background: transparent;
}
/* ol-ext GeolocationButton styling */
.ol-geobt {
top: auto !important;
@ -830,6 +1223,7 @@
/* Locations list in offcanvas - can be taller now without form */
.offcanvas-end .locations-list {
max-height: calc(100vh - 280px);
max-height: calc(100dvh - 280px);
overflow-y: auto;
}
@ -921,17 +1315,32 @@
font-size: 18px;
}
/* ScaleLine - position above the bottom dock */
.ol-scale-line {
bottom: 76px !important;
/* ScaleBar - position above the bottom dock with 4px gap */
.ol-scale-bar {
bottom: calc(85px + env(safe-area-inset-bottom, 0px)) !important;
left: 10px !important;
}
.ol-scale-line-inner {
border-color: var(--foreground) !important;
.ol-scale-bar .ol-scale-step-text {
color: var(--foreground) !important;
font-family: var(--font-body) !important;
font-size: 11px !important;
text-shadow: 0 0 3px var(--background), 0 0 6px var(--background) !important;
}
.ol-scale-bar .ol-scale-text {
color: var(--foreground) !important;
font-family: var(--font-body) !important;
font-size: 11px !important;
text-shadow: 0 0 3px var(--background), 0 0 6px var(--background) !important;
}
.ol-scale-bar .ol-scale-singlebar-even {
background-color: var(--foreground) !important;
}
.ol-scale-bar .ol-scale-singlebar-odd {
background-color: var(--muted-foreground) !important;
}
/* ol-ext Bar overrides */
@ -950,12 +1359,12 @@
gap: 2px;
}
</style>
<script type="module" crossorigin src="/assets/index-2WHoRhxp.js"></script>
<script type="module" crossorigin src="/assets/index-B4XzHtZX.js"></script>
<link rel="modulepreload" crossorigin href="/assets/openlayers-CUDtI0S3.js">
<link rel="modulepreload" crossorigin href="/assets/bootstrap-D1-uvFxm.js">
<link rel="modulepreload" crossorigin href="/assets/openlayers-D2I-bVN2.js">
<link rel="modulepreload" crossorigin href="/assets/ol-ext-DytxBANR.js">
<link rel="stylesheet" crossorigin href="/assets/bootstrap-BtmJYOxZ.css">
<link rel="modulepreload" crossorigin href="/assets/ol-ext-CSk2UikI.js">
<link rel="stylesheet" crossorigin href="/assets/openlayers-BtPuoxOl.css">
<link rel="stylesheet" crossorigin href="/assets/bootstrap-BtmJYOxZ.css">
<link rel="stylesheet" crossorigin href="/assets/ol-ext-BgKrOIxx.css">
<link rel="stylesheet" crossorigin href="/assets/index-BnwqsTiD.css">
</head>
@ -988,6 +1397,7 @@
</span>
</div>
<div id="map"></div>
<div class="map-drop-overlay"><span><i class="bi bi-file-earmark-arrow-up me-2"></i>Drop file to import (.shp .geojson .kml)</span></div>
<!-- Offcanvas toggle buttons -->
<button class="offcanvas-toggle offcanvas-toggle-left"
@ -1112,17 +1522,40 @@
<button type="button" class="btn btn-outline-primary w-100 mb-3" id="local-data-btn">
<i class="bi bi-database me-2"></i>Local Data
</button>
<button type="button" class="btn btn-outline-primary w-100 mb-3" id="import-shp-btn">
<i class="bi bi-file-earmark-arrow-up me-2"></i>Import .shp
</button>
<input type="file" id="shp-file-input" accept=".zip,.shp,.dbf,.shx,.prj" multiple class="d-none">
<button type="button" class="btn btn-outline-primary w-100 mb-3" id="import-geojson-btn">
<i class="bi bi-file-earmark-arrow-up me-2"></i>Import GeoJSON
</button>
<input type="file" id="geojson-file-input" accept=".geojson,.json" class="d-none">
<button type="button" class="btn btn-outline-primary w-100 mb-3" id="import-kml-btn">
<i class="bi bi-file-earmark-arrow-up me-2"></i>Import KML
</button>
<input type="file" id="kml-file-input" accept=".kml,.kmz" class="d-none">
<div id="file-import-alert" class="alert alert-danger alert-dismissible fade show d-none mb-3" role="alert">
<small class="message-text"></small>
<button type="button" class="btn-close btn-close-sm" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div id="imported-layers-info" class="d-none mb-3"></div>
<div id="local-data-stats" class="d-none">
<div class="card">
<div class="card-header bg-primary py-2">
<div class="card-header bg-primary py-2 d-flex justify-content-between align-items-center">
<h6 class="mb-0"><i class="bi bi-database me-2"></i>Local Database Tables</h6>
<button type="button" class="btn btn-sm btn-outline-light"
id="clear-all-cached-btn"
title="Delete all cached map layers. They will be re-downloaded on next app start.">
<i class="bi bi-arrow-clockwise me-1"></i>Refresh cached layers
</button>
</div>
<div class="card-body p-0">
<table class="table table-sm table-striped mb-0">
<thead>
<tr>
<th class="ps-3">Table</th>
<th class="text-end pe-3">Records</th>
<th class="text-end">Records</th>
<th class="text-end pe-3" style="width:3rem;"></th>
</tr>
</thead>
<tbody id="local-data-tbody">
@ -1150,6 +1583,10 @@
<span class="message-text"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div id="warning-message" class="alert alert-warning alert-dismissible fade show d-none" role="alert">
<span class="message-text"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<!-- Tip -->
<div class="alert alert-light border-start border-4 border-primary py-2 mb-3" role="alert">
@ -1189,20 +1626,268 @@
</div>
</div>
</div>
<!-- Message Log -->
<div class="card mt-3" id="message-log-card">
<div class="card-header bg-transparent py-2 d-flex justify-content-between align-items-center">
<h6 class="mb-0" style="font-family:var(--font-body);font-weight:700;"><i class="bi bi-journal-text me-1"></i> Messages</h6>
<button class="btn btn-sm btn-link text-muted p-0" id="clear-message-log" title="Clear messages">
<i class="bi bi-trash3"></i>
</button>
</div>
<div class="card-body p-0">
<div id="message-log" class="message-log list-group list-group-flush">
<div class="text-center text-muted py-3">
<small>No messages yet.</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bottom Offcanvas -->
<div class="offcanvas offcanvas-bottom" tabindex="-1" id="offcanvasBottom" aria-labelledby="offcanvasBottomLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasBottomLabel"><i class="bi bi-chevron-down me-2"></i>Bottom Panel</h5>
<h5 class="offcanvas-title" id="offcanvasBottomLabel"><i class="bi bi-gear me-2"></i>Settings</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
</div>
<div class="offcanvas-body">
<p>This is the bottom offcanvas panel.</p>
<p>You can add a data table, charts, or other wide content here.</p>
<div class="row g-3">
<!-- Fieldwork Mode -->
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="mb-1" style="font-family:var(--font-body);font-weight:700;">Fieldwork Mode</h6>
<small class="text-muted">High-contrast colours and larger touch targets for bright sunlight and field conditions.</small>
</div>
<div class="form-check form-switch ms-3">
<input class="form-check-input" type="checkbox" role="switch" id="fieldwork-mode-toggle" style="width:3rem;height:1.5rem;cursor:pointer;">
</div>
</div>
</div>
</div>
</div>
<!-- Dark Mode -->
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="mb-1" style="font-family:var(--font-body);font-weight:700;">Dark Mode</h6>
<small class="text-muted">Reduce glare and save battery with a dark colour scheme.</small>
</div>
<div class="form-check form-switch ms-3">
<input class="form-check-input" type="checkbox" role="switch" id="dark-mode-toggle" style="width:3rem;height:1.5rem;cursor:pointer;">
</div>
</div>
</div>
</div>
</div>
<!-- Measurement System -->
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="mb-1" style="font-family:var(--font-body);font-weight:700;">Measurement System</h6>
<small class="text-muted">Switch between Metric (m, km) and Imperial (ft, mi, acres) units.</small>
</div>
<div class="form-check form-switch ms-3">
<input class="form-check-input" type="checkbox" role="switch" id="measurement-system-toggle" style="width:3rem;height:1.5rem;cursor:pointer;">
<label class="form-check-label ms-1" id="measurement-system-label" for="measurement-system-toggle" style="font-size:0.8rem;font-weight:600;min-width:55px;">Metric</label>
</div>
</div>
</div>
</div>
</div>
<!-- Default Base Map -->
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div style="flex:1;min-width:0;">
<h6 class="mb-1" style="font-family:var(--font-body);font-weight:700;">Default Base Map</h6>
<small class="text-muted">Base map shown on app start. Saved on this device.</small>
</div>
<div class="ms-3" style="min-width:140px;">
<select class="form-select form-select-sm" id="default-basemap-select" aria-label="Default base map">
<option value="topo">Topographic</option>
<option value="osm">OpenStreetMap</option>
<option value="satellite">Satellite</option>
<option value="googlesat">Google Sat</option>
<option value="carto-light">Carto Light</option>
<option value="carto-dark">Carto Dark</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Offline Map Tiles -->
<div class="col-12 col-md-6 col-lg-8">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between mb-2">
<div style="flex:1;min-width:0;">
<h6 class="mb-1" style="font-family:var(--font-body);font-weight:700;">
<i class="bi bi-map me-1"></i>Offline Map Tiles
</h6>
<small class="text-muted">
Map tiles you've already viewed are cached on this device so they work offline.
Tiles are cached automatically as you browse, or you can pre-download a region.
</small>
</div>
<div class="ms-3 d-flex gap-2 flex-shrink-0">
<button type="button" class="btn btn-sm btn-primary"
id="download-tiles-btn" style="white-space:nowrap;">
<i class="bi bi-cloud-download me-1"></i>Download offline map
</button>
<button type="button" class="btn btn-sm btn-outline-danger"
id="clear-tiles-btn" style="white-space:nowrap;">
<i class="bi bi-trash3 me-1"></i>Clear cached tiles
</button>
</div>
</div>
<div id="tile-cache-stats" class="small">
<div class="text-muted fst-italic">Loading…</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Download Offline Map modal -->
<div class="modal fade" id="offline-download-modal" tabindex="-1" aria-labelledby="offline-download-title" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="offline-download-title">
<i class="bi bi-cloud-download me-2"></i>Download Offline Map
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" id="offline-download-close-btn"></button>
</div>
<!-- Form view (shown until Start is clicked) -->
<div class="modal-body" id="offline-download-form-view">
<p class="text-muted small mb-3">
Pre-fetch map tiles so they're available when you're offline.
Only the OpenStreetMap and Topographic base maps can be downloaded;
other providers don't permit bulk caching.
</p>
<!-- Base map -->
<div class="mb-3">
<label for="offline-basemap-select" class="form-label fw-bold">Base map</label>
<select class="form-select form-select-sm" id="offline-basemap-select">
<option value="topo">Topographic</option>
<option value="osm">OpenStreetMap</option>
</select>
</div>
<!-- Area -->
<div class="mb-3">
<label class="form-label fw-bold mb-1">Area to download</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="offline-area" id="offline-area-view" value="view" checked>
<label class="form-check-label" for="offline-area-view">
Current map view
<span class="text-muted small" id="offline-area-view-info"></span>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="offline-area" id="offline-area-district" value="district">
<label class="form-check-label" for="offline-area-district">
District boundary
<span class="text-muted small" id="offline-area-district-info"></span>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="offline-area" id="offline-area-ghana" value="ghana">
<label class="form-check-label" for="offline-area-ghana">
Entire Ghana <span class="text-muted small">(very large — only attempt over fast Wi-Fi)</span>
</label>
</div>
</div>
<!-- Zoom range -->
<div class="mb-3">
<label class="form-label fw-bold mb-1">Zoom levels</label>
<div class="row g-2 align-items-center">
<div class="col-auto"><label for="offline-min-zoom" class="form-label small mb-0">Min</label></div>
<div class="col-auto">
<input type="number" class="form-control form-control-sm" id="offline-min-zoom" min="6" max="19" value="10" style="width:5em;">
</div>
<div class="col-auto"><label for="offline-max-zoom" class="form-label small mb-0">Max</label></div>
<div class="col-auto">
<input type="number" class="form-control form-control-sm" id="offline-max-zoom" min="6" max="19" value="15" style="width:5em;">
</div>
<div class="col text-muted small">10 = regional · 13 = neighbourhood · 16 = building</div>
</div>
</div>
<!-- Estimate -->
<div class="alert alert-info py-2 px-3 mb-3" id="offline-estimate" style="font-size:0.9em;">
<strong>Estimated download:</strong>
<span id="offline-estimate-detail">Calculating…</span>
</div>
<!-- Acknowledgement -->
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="offline-ack-check">
<label class="form-check-label small" for="offline-ack-check">
I understand this counts against the tile provider's usage quota
and will use mobile data if I'm not on Wi-Fi.
</label>
</div>
</div>
<!-- Progress view (shown during download) -->
<div class="modal-body d-none" id="offline-download-progress-view">
<div class="text-center mb-3">
<div class="fs-5 fw-bold" id="offline-progress-percent">0%</div>
<div class="small text-muted" id="offline-progress-counts">0 of 0 tiles</div>
</div>
<div class="progress mb-3" style="height:1.25em;">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" id="offline-progress-bar"
aria-valuemin="0" aria-valuemax="100" style="width:0%;"></div>
</div>
<div class="row text-center small text-muted">
<div class="col"><div class="fw-bold text-body" id="offline-progress-ok">0</div>fetched</div>
<div class="col"><div class="fw-bold text-body" id="offline-progress-failed">0</div>failed</div>
<div class="col"><div class="fw-bold text-body" id="offline-progress-eta"></div>remaining</div>
</div>
</div>
<!-- Done view (shown after completion) -->
<div class="modal-body d-none" id="offline-download-done-view">
<div class="text-center py-3">
<i class="bi bi-check-circle-fill text-success" style="font-size:3rem;"></i>
<div class="fs-5 fw-bold mt-2" id="offline-done-title">Download complete</div>
<div class="text-muted" id="offline-done-detail"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="offline-download-cancel-btn">
Cancel
</button>
<button type="button" class="btn btn-primary" id="offline-download-start-btn" disabled>
<i class="bi bi-cloud-download me-1"></i>Start download
</button>
<button type="button" class="btn btn-primary d-none" id="offline-download-close-done-btn" data-bs-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
<!-- Polyfill crypto.randomUUID for non-secure contexts (HTTP) -->
<!-- Must run before module imports (SQLocal/coincident require it) -->

22
dist/manifest.json vendored
View File

@ -1,58 +1,58 @@
{
"name": "LUPMIS2 Drawing Tools",
"short_name": "LUPMIS",
"short_name": "LUPMIS2",
"description": "Map and GIS functions for Land Use Planning in Ghana",
"start_url": "/",
"scope": "/",
"start_url": "./",
"scope": "./",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#005eb8",
"orientation": "any",
"icons": [
{
"src": "/icons/icon-72.png",
"src": "./icons/luspa-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-96.png",
"src": "./icons/luspa-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-128.png",
"src": "./icons/luspa-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-144.png",
"src": "./icons/luspa-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-152.png",
"src": "./icons/luspa-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/luspa-192x192.png",
"src": "./icons/luspa-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-384.png",
"src": "./icons/luspa-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/luspa-512x512.png",
"src": "./icons/luspa-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"

367
dist/sw.js vendored
View File

@ -1,29 +1,81 @@
/**
* Service Worker
*
*
* Handles caching of:
* - App shell (HTML, CSS, JS)
* - Map tiles (runtime caching)
* - Map tiles (passive runtime caching, per-host buckets)
* - API responses (network-first)
*
*
* Note: Database operations are handled by the SharedWorker (shared-db-worker.js),
* NOT by this service worker. They serve different purposes:
* - Service Worker: Caching, offline asset serving, push notifications
* - SharedWorker: Shared database connection across tabs
*/
const CACHE_VERSION = 'v1';
const SHELL_CACHE = `shell-${CACHE_VERSION}`;
const TILES_CACHE = `tiles-${CACHE_VERSION}`;
// v3: lower per-cache limits (5000 → 1500) and counter-based eviction to
// prevent Safari memory-pressure reloads.
// v4: raise OSM and Topographic limits to 8000 to support active offline
// downloads (Phase 2). Other providers stay at 1500.
const CACHE_VERSION = 'v4';
const SHELL_CACHE = `shell-${CACHE_VERSION}`;
const MODULES_CACHE = `modules-${CACHE_VERSION}`;
const API_CACHE = `api-${CACHE_VERSION}`;
const API_CACHE = `api-${CACHE_VERSION}`;
// Maximum number of tiles to cache
const MAX_TILES = 500;
// ----------------------------------------------------------------------------
// Tile caches — one per provider so users can clear them independently.
// Limits are per-cache (not global). 5 000 tiles ≈ ~150 MB at ~30 KB/tile,
// which covers a Ghana district at zoom 1015 (typical field-work range).
// ----------------------------------------------------------------------------
const TILES_OSM = `tiles-osm-${CACHE_VERSION}`;
const TILES_TOPO = `tiles-topo-${CACHE_VERSION}`;
const TILES_SATELLITE = `tiles-satellite-${CACHE_VERSION}`;
const TILES_CARTO_LIGHT = `tiles-carto-light-${CACHE_VERSION}`;
const TILES_CARTO_DARK = `tiles-carto-dark-${CACHE_VERSION}`;
// App shell assets - precached on install
// Vite will generate hashed filenames, so we cache the entry points
// and let the browser handle the hashed assets
// Per-provider tile limits.
// • OSM and Topographic are the providers offered for active offline
// download (Phase 2 dialog), so they get a higher cap (~240 MB each at
// ~30 KB/tile) — enough for a typical Ghana district at zoom 1015.
// • The other providers serve passive caching only (whatever the user has
// already viewed), so 1 500 tiles ≈ 45 MB is plenty.
//
// Total max ≈ 5 × ~150 MB = ~750 MB on disk in the worst case, but only the
// two downloadable buckets are likely to fill. Eviction sweeps run every 100
// inserts (see EVICTION_CHECK_INTERVAL) so memory pressure stays bounded.
const TILE_LIMITS = {
[TILES_OSM]: 8000,
[TILES_TOPO]: 8000,
[TILES_SATELLITE]: 1500,
[TILES_CARTO_LIGHT]: 1500,
[TILES_CARTO_DARK]: 1500,
};
// Per-cache running insert counter, in memory. Avoids calling cache.keys()
// (which materialises every Request object in the cache) on every put — that
// was the cause of the Safari "reloaded due to memory pressure" failures.
//
// We only run a real eviction sweep every EVICTION_CHECK_INTERVAL inserts.
const _tileInsertCounters = new Map(); // cacheName → number of inserts since last eviction
const EVICTION_CHECK_INTERVAL = 100;
// Friendly name shown in the UI (matches Settings card labels)
const TILE_CACHE_LABELS = {
[TILES_OSM]: 'OpenStreetMap',
[TILES_TOPO]: 'Topographic',
[TILES_SATELLITE]: 'Satellite',
[TILES_CARTO_LIGHT]: 'Carto Light',
[TILES_CARTO_DARK]: 'Carto Dark',
};
const ALL_TILE_CACHES = Object.keys(TILE_LIMITS);
// Approximate average tile size — used for storage estimation.
// Real measurements: PNG tiles range 580 KB; 30 KB is a good middle ground.
const AVG_TILE_BYTES = 30 * 1024;
// ----------------------------------------------------------------------------
// App shell assets — precached on install.
// ----------------------------------------------------------------------------
const SHELL_ASSETS = [
'/',
'/index.html',
@ -37,7 +89,7 @@ const SHELL_ASSETS = [
self.addEventListener('install', (event) => {
console.log('[SW] Installing...');
event.waitUntil(
caches.open(SHELL_CACHE)
.then((cache) => {
@ -54,18 +106,26 @@ self.addEventListener('install', (event) => {
self.addEventListener('activate', (event) => {
console.log('[SW] Activating...');
event.waitUntil(
caches.keys()
.then((cacheNames) => {
// Build the set of caches that should remain
const keep = new Set([SHELL_CACHE, MODULES_CACHE, API_CACHE, ...ALL_TILE_CACHES]);
return Promise.all(
cacheNames
// Delete anything that:
// • belongs to one of our managed cache prefixes (shell-, tiles-, modules-, api-)
// • but is NOT in the current keep set
// This includes the legacy "tiles-v1" single bucket.
.filter((name) => {
// Delete old version caches
return (name.startsWith('shell-') && name !== SHELL_CACHE) ||
(name.startsWith('tiles-') && name !== TILES_CACHE) ||
(name.startsWith('modules-') && name !== MODULES_CACHE) ||
(name.startsWith('api-') && name !== API_CACHE);
const isOurs =
name.startsWith('shell-') ||
name.startsWith('tiles-') ||
name.startsWith('modules-') ||
name.startsWith('api-');
return isOurs && !keep.has(name);
})
.map((name) => {
console.log('[SW] Deleting old cache:', name);
@ -84,17 +144,28 @@ self.addEventListener('activate', (event) => {
self.addEventListener('fetch', (event) => {
const request = event.request;
const url = new URL(request.url);
// Only handle GET requests
if (request.method !== 'GET') return;
// Skip chrome-extension and other non-http(s) requests
if (!url.protocol.startsWith('http')) return;
// Route to appropriate caching strategy
if (isMapTile(url)) {
event.respondWith(cacheThenNetwork(request, TILES_CACHE, MAX_TILES));
} else if (isApiRequest(url)) {
// Skip worker files and Vite dev-server node_modules requests —
// intercepting these breaks module workers (e.g. SQLocal/SQLite).
if (url.pathname.includes('node_modules') ||
url.search.includes('worker_file') ||
request.destination === 'worker') return;
// ----- TILE REQUESTS — passive cache-then-network (per-host bucket) -----
const tileCache = getTileCacheName(url);
if (tileCache) {
event.respondWith(tileCacheThenNetwork(request, tileCache));
return;
}
// ----- OTHER ROUTES (unchanged) -----
if (isApiRequest(url)) {
event.respondWith(networkFirst(request, API_CACHE));
} else if (isModuleAsset(url)) {
event.respondWith(staleWhileRevalidate(request, MODULES_CACHE));
@ -108,15 +179,36 @@ self.addEventListener('fetch', (event) => {
// URL CLASSIFICATION
// ============================================================================
function isMapTile(url) {
// Common tile server patterns for all our base maps
return url.hostname.includes('tile.openstreetmap.org') ||
url.hostname.includes('opentopomap.org') ||
url.hostname.includes('arcgisonline.com') ||
url.hostname.includes('basemaps.cartocdn.com') ||
url.hostname.includes('tiles.') ||
url.pathname.match(/\/\d+\/\d+\/\d+\.(png|jpg|pbf)$/) ||
url.pathname.match(/\/tile\/\d+\/\d+\/\d+/);
/**
* Classify a URL into the appropriate tile cache.
* Returns `null` for non-tile requests, or for tile providers we deliberately
* do NOT cache (e.g. Google caching is forbidden by their ToS).
*/
function getTileCacheName(url) {
const host = url.hostname;
// OpenStreetMap — tile.openstreetmap.org and a/b/c subdomains
if (host.endsWith('tile.openstreetmap.org')) return TILES_OSM;
// OpenTopoMap — a/b/c.tile.opentopomap.org
if (host.endsWith('tile.opentopomap.org') || host.endsWith('opentopomap.org')) return TILES_TOPO;
// Carto Basemaps — light_all / dark_all distinguished by path
if (host.endsWith('basemaps.cartocdn.com')) {
if (url.pathname.includes('/light_all/')) return TILES_CARTO_LIGHT;
if (url.pathname.includes('/dark_all/')) return TILES_CARTO_DARK;
return null; // unknown Carto style
}
// Esri — server.arcgisonline.com
if (host.endsWith('arcgisonline.com')) return TILES_SATELLITE;
// Google — caching forbidden by ToS, do not store
if (host.endsWith('google.com') || host.endsWith('googleapis.com')) return null;
// Other tile providers (WMS endpoints, OWS, custom) — not cached at this layer
// (the user's "online only" toast handles those).
return null;
}
function isApiRequest(url) {
@ -129,7 +221,6 @@ function isModuleAsset(url) {
}
function isAppAsset(url) {
// Same origin, common asset extensions
return url.origin === self.location.origin &&
(url.pathname.endsWith('.html') ||
url.pathname.endsWith('.css') ||
@ -144,13 +235,13 @@ function isAppAsset(url) {
// ============================================================================
/**
* Cache First - Use cache, fallback to network
* Best for: App shell, static assets
* Cache First Use cache, fallback to network.
* Best for: App shell, static assets.
*/
async function cacheFirst(request, cacheName) {
const cached = await caches.match(request);
if (cached) return cached;
try {
const response = await fetch(request);
if (response.ok) {
@ -159,7 +250,6 @@ async function cacheFirst(request, cacheName) {
}
return response;
} catch (error) {
// Return offline page for navigation requests
if (request.mode === 'navigate') {
return caches.match('/offline.html');
}
@ -168,8 +258,8 @@ async function cacheFirst(request, cacheName) {
}
/**
* Network First - Try network, fallback to cache
* Best for: API requests, dynamic content
* Network First Try network, fallback to cache.
* Best for: API requests, dynamic content.
*/
async function networkFirst(request, cacheName) {
try {
@ -187,89 +277,141 @@ async function networkFirst(request, cacheName) {
}
/**
* Stale While Revalidate - Return cache immediately, update in background
* Best for: Module assets, frequently updated content
* Stale While Revalidate Return cache immediately, update in background.
* Best for: Module assets, frequently updated content.
*/
async function staleWhileRevalidate(request, cacheName) {
const cache = await caches.open(cacheName);
const cached = await cache.match(request);
const fetchPromise = fetch(request).then((response) => {
if (response.ok) {
cache.put(request, response.clone());
}
return response;
}).catch(() => cached);
return cached || fetchPromise;
}
/**
* Cache Then Network with limit - Cache tiles with size limit
* Best for: Map tiles
* Tile Cache then Network Per-host bucket with size limit.
* Cache first; on miss, fetch from network and store.
*
* Memory-conservative eviction:
* Increments an in-memory counter on every successful insert
* Only calls cache.keys() (which materialises all Request objects) every
* EVICTION_CHECK_INTERVAL inserts so the cost is amortised
* Eviction drops the oldest 10 % when over the per-host limit
*
* On network failure (offline), serves a 408 so the map renders a blank tile
* rather than throwing.
*/
async function cacheThenNetwork(request, cacheName, maxItems) {
async function tileCacheThenNetwork(request, cacheName) {
const cache = await caches.open(cacheName);
const cached = await cache.match(request);
if (cached) return cached;
try {
const response = await fetch(request);
if (response.ok) {
// Check cache size and trim if needed
const keys = await cache.keys();
if (keys.length >= maxItems) {
// Remove oldest entries (first 10%)
const toDelete = keys.slice(0, Math.ceil(maxItems * 0.1));
await Promise.all(toDelete.map(key => cache.delete(key)));
// Bump the counter; periodically run a real eviction sweep
const count = (_tileInsertCounters.get(cacheName) || 0) + 1;
_tileInsertCounters.set(cacheName, count);
if (count % EVICTION_CHECK_INTERVAL === 0) {
// Reset the counter — next sweep is another EVICTION_CHECK_INTERVAL away
_tileInsertCounters.set(cacheName, 0);
await maybeEvict(cache, cacheName);
}
cache.put(request, response.clone());
// Don't await put() — it can run after we return the response, keeping
// the fetch hot path lightweight.
cache.put(request, response.clone()).catch((err) => {
// QuotaExceededError → run an immediate eviction sweep and retry once
if (err && err.name === 'QuotaExceededError') {
maybeEvict(cache, cacheName, /* force */ true).catch(() => {});
}
});
}
return response;
} catch (error) {
// For tiles, just fail silently - map will show blank tile
// Offline — let the map renderer show a blank tile
return new Response('', { status: 408, statusText: 'Offline' });
}
}
/**
* Run an eviction sweep on a cache, dropping the oldest 10 % of entries
* when over the per-cache limit. Heavy: only call periodically.
*/
async function maybeEvict(cache, cacheName, force = false) {
try {
const limit = TILE_LIMITS[cacheName] || 1500;
const keys = await cache.keys();
if (force || keys.length >= limit) {
const drop = Math.max(1, Math.ceil(limit * 0.1));
const toDelete = keys.slice(0, drop);
await Promise.all(toDelete.map((k) => cache.delete(k)));
}
} catch (err) {
console.warn('[SW] eviction sweep failed for', cacheName, err);
}
}
// ============================================================================
// MESSAGE HANDLING
// ============================================================================
self.addEventListener('message', (event) => {
const { type, payload } = event.data || {};
switch (type) {
case 'SKIP_WAITING':
self.skipWaiting();
break;
case 'CACHE_MODULES':
cacheModules(payload.modules);
break;
case 'CLEAR_USER_CACHE':
clearUserCaches();
break;
case 'GET_CACHE_STATUS':
getCacheStatus().then(status => {
getCacheStatus().then((status) => {
event.source.postMessage({ type: 'CACHE_STATUS', status });
});
break;
// ----- Tile-cache management (Phase 1 offline maps) -----
case 'GET_TILE_STATS':
getTileStats().then((stats) => {
event.source.postMessage({ type: 'TILE_STATS', stats });
});
break;
case 'CLEAR_TILE_CACHES':
clearTileCaches().then(() => {
event.source.postMessage({ type: 'TILE_CACHES_CLEARED' });
});
break;
}
});
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Cache specific modules on demand
* Cache specific modules on demand.
*/
async function cacheModules(moduleNames) {
const cache = await caches.open(MODULES_CACHE);
for (const moduleName of moduleNames) {
try {
const moduleAssets = [
@ -277,9 +419,8 @@ async function cacheModules(moduleNames) {
`/modules/${moduleName}/index.css`,
`/modules/${moduleName}/index.html`
];
await cache.addAll(moduleAssets.filter(async (url) => {
// Only cache assets that exist
try {
const response = await fetch(url, { method: 'HEAD' });
return response.ok;
@ -287,7 +428,7 @@ async function cacheModules(moduleNames) {
return false;
}
}));
console.log('[SW] Cached module:', moduleName);
} catch (error) {
console.warn('[SW] Failed to cache module:', moduleName, error);
@ -296,7 +437,8 @@ async function cacheModules(moduleNames) {
}
/**
* Clear user-specific caches (call on logout)
* Clear user-specific caches (call on logout).
* Tile caches are NOT cleared here those belong to the device, not the user.
*/
async function clearUserCaches() {
await caches.delete(API_CACHE);
@ -305,17 +447,90 @@ async function clearUserCaches() {
}
/**
* Get cache status information
* Get summary status of all caches (count of entries in each).
*/
async function getCacheStatus() {
const cacheNames = await caches.keys();
const status = {};
for (const name of cacheNames) {
const cache = await caches.open(name);
const keys = await cache.keys();
status[name] = keys.length;
}
return status;
}
/**
* Get per-provider tile cache statistics.
*
* Returns shape:
* {
* totals: { count, estBytes },
* byProvider: [{ key, label, count, limit, estBytes }, ...]
* }
*
* estBytes is an approximation (count × AVG_TILE_BYTES). For an exact size,
* the caller can use navigator.storage.estimate() on the page side.
*
* Result is cached for STATS_TTL_MS so rapid re-queries (e.g. multiple
* Settings opens) don't re-enumerate every cache.
*/
const STATS_TTL_MS = 10 * 1000;
let _cachedStats = null;
let _cachedStatsAt = 0;
async function getTileStats({ force = false } = {}) {
const now = Date.now();
if (!force && _cachedStats && (now - _cachedStatsAt) < STATS_TTL_MS) {
return _cachedStats;
}
const byProvider = [];
let totalCount = 0;
for (const cacheName of ALL_TILE_CACHES) {
let count = 0;
if (await caches.has(cacheName)) {
const cache = await caches.open(cacheName);
// matchAll returns a smaller payload than keys() on Safari, but neither
// is free. Done at most once per STATS_TTL_MS thanks to the cache above.
const keys = await cache.keys();
count = keys.length;
}
byProvider.push({
key: cacheName,
label: TILE_CACHE_LABELS[cacheName] || cacheName,
count,
limit: TILE_LIMITS[cacheName] || 0,
estBytes: count * AVG_TILE_BYTES,
});
totalCount += count;
}
_cachedStats = {
totals: {
count: totalCount,
estBytes: totalCount * AVG_TILE_BYTES,
},
byProvider,
};
_cachedStatsAt = now;
return _cachedStats;
}
/**
* Delete every tile cache. Frees the device storage used by cached map tiles.
* Does not affect app-shell, modules, or API caches.
*/
async function clearTileCaches() {
const results = await Promise.all(
ALL_TILE_CACHES.map((name) => caches.delete(name))
);
// Reset counters and invalidate stats cache
_tileInsertCounters.clear();
_cachedStats = null;
_cachedStatsAt = 0;
console.log('[SW] Cleared tile caches:', ALL_TILE_CACHES.filter((_, i) => results[i]));
}

View File

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 217 KiB

View File

@ -2,19 +2,64 @@
<html lang="en" data-bs-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#005eb8">
<meta name="description" content="LUPMIS2 Drawing Tools">
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json">
<link rel="apple-touch-icon" href="/icons/luspa.icon">
<link rel="icon" href="/icons/luspa.icon">
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" sizes="192x192" href="icons/luspa-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/luspa-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/luspa-16x16.png">
<!-- LUSPA Design System Fonts: Bebas Neue (display) + Exo (body) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Exo:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- LUSPA Design System Fonts: Bebas Neue (display) + Exo (body) — self-hosted -->
<style>
/* Bebas Neue 400 — latin-ext */
@font-face {
font-family: 'Bebas Neue';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/bebas-neue-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Bebas Neue 400 — latin */
@font-face {
font-family: 'Bebas Neue';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/bebas-neue-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Exo 300-800 — vietnamese */
@font-face {
font-family: 'Exo';
font-style: normal;
font-weight: 300 800;
font-display: swap;
src: url('/fonts/exo-vietnamese.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* Exo 300-800 — latin-ext */
@font-face {
font-family: 'Exo';
font-style: normal;
font-weight: 300 800;
font-display: swap;
src: url('/fonts/exo-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Exo 300-800 — latin */
@font-face {
font-family: 'Exo';
font-style: normal;
font-weight: 300 800;
font-display: swap;
src: url('/fonts/exo-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
</style>
<title>LUPMIS2 Drawing Tools</title>
@ -106,6 +151,295 @@
--radius-2xl: 1rem;
}
/* ─── Fieldwork Mode ─── high-contrast + larger touch targets ─── */
.fieldwork-mode {
--foreground: #000;
--background: #fff;
--card: #fff;
--card-foreground: #000;
--primary: #0044aa;
--primary-foreground: #fff;
--primary-hover: #003080;
--muted: #e0e0e0;
--muted-foreground: #333;
--accent: #cce0ff;
--accent-foreground: #000;
--border: rgba(0,0,0,0.25);
--success: #005a00;
--success-foreground: #fff;
--warning: #b36b00;
--warning-foreground: #000;
--destructive: #b80000;
--destructive-foreground: #fff;
--ring: #0044aa;
--bs-body-color: #000;
}
/* Fieldwork: larger dock buttons */
.fieldwork-mode .dock-btn {
min-width: 72px;
min-height: 58px;
font-size: 1.6rem;
border-width: 2px;
}
.fieldwork-mode .dock-btn-label {
font-size: 0.75rem;
font-weight: 600;
}
/* Fieldwork: bolder navbar */
.fieldwork-mode .navbar {
border-bottom-width: 4px;
}
.fieldwork-mode .navbar .navbar-brand {
font-size: 1.6rem;
}
/* Fieldwork: larger offcanvas toggle buttons */
.fieldwork-mode .offcanvas-toggle {
width: 44px;
height: 44px;
font-size: 1.2rem;
}
/* Fieldwork: thicker bottom dock border */
.fieldwork-mode .bottom-dock {
border-top-width: 4px;
}
/* Fieldwork: larger text in cards / lists */
.fieldwork-mode .card-header h6 {
font-size: 1rem;
}
.fieldwork-mode .list-group-item {
font-size: 0.95rem;
padding: 0.65rem 1rem;
}
/* Fieldwork: larger buttons globally */
.fieldwork-mode .btn {
font-size: 0.95rem;
padding: 0.5rem 1rem;
font-weight: 600;
}
.fieldwork-mode .btn-sm {
font-size: 0.85rem;
padding: 0.4rem 0.75rem;
}
/* Fieldwork: stronger borders on inputs / form controls */
.fieldwork-mode .form-control,
.fieldwork-mode .form-select {
border-width: 2px;
border-color: #555;
font-size: 1rem;
}
/* Fieldwork: bolder map controls (ol-ext) */
.fieldwork-mode .ol-control button {
font-size: 1.3rem;
width: 2.2em;
height: 2.2em;
}
/* Fieldwork: scale bar text legibility */
.fieldwork-mode .ol-scale-bar .ol-scale-step-text,
.fieldwork-mode .ol-scale-bar .ol-scale-text {
font-size: 12px;
font-weight: 700;
text-shadow: 0 0 4px #fff, 0 0 8px #fff;
}
/* ─── Dark Mode ─── reversed colour scheme ─── */
.dark-mode {
--foreground: #e0dff0;
--background: #131325;
--card: #1e1e38;
--card-foreground: #e0dff0;
--primary: #4d9de6;
--primary-foreground: #fff;
--primary-hover: #6fb3f0;
--muted: #272745;
--muted-foreground: #9594a8;
--accent: #1e3a5f;
--accent-foreground: #e0dff0;
--border: rgba(255,255,255,0.12);
--ring: #4d9de6;
--success: #2dd46a;
--success-foreground: #131325;
--warning: #ffb84d;
--warning-foreground: #131325;
--destructive: #f04040;
--destructive-foreground: #fff;
--bs-body-color: #e0dff0;
--bs-body-bg: #131325;
--bs-tertiary-bg: #1e1e38;
color-scheme: dark;
}
/* Dark: navbar */
.dark-mode .navbar {
background-color: #1a1a30 !important;
box-shadow: 0 1px 6px rgba(0,0,0,0.4);
}
/* Dark: bottom dock */
.dark-mode .bottom-dock {
background-color: #1a1a30;
box-shadow: 0 -2px 10px rgba(0,0,0,0.3);
}
.dark-mode .dock-btn {
border-color: var(--primary);
color: var(--foreground);
}
.dark-mode .dock-btn:hover {
background-color: var(--muted);
}
.dark-mode .dock-btn.active {
background-color: var(--primary);
color: var(--primary-foreground);
}
/* Dark: offcanvas panels */
.dark-mode .offcanvas {
background-color: var(--background) !important;
color: var(--foreground) !important;
}
.dark-mode .offcanvas-header {
border-bottom-color: var(--border) !important;
}
.dark-mode .btn-close {
filter: invert(1) grayscale(100%) brightness(200%);
}
/* Dark: cards */
.dark-mode .card {
background-color: var(--card) !important;
color: var(--card-foreground) !important;
border-color: var(--border) !important;
}
/* Dark: offcanvas toggle buttons */
.dark-mode .offcanvas-toggle {
background-color: var(--card);
color: var(--foreground);
border-color: var(--border);
}
.dark-mode .offcanvas-toggle:hover {
background-color: var(--primary);
color: var(--primary-foreground);
}
/* Dark: form controls */
.dark-mode .form-control,
.dark-mode .form-select {
background-color: var(--muted) !important;
color: var(--foreground) !important;
border-color: var(--border) !important;
}
.dark-mode .form-check-input {
background-color: var(--muted);
border-color: var(--muted-foreground);
}
.dark-mode .form-check-input:checked {
background-color: var(--primary);
border-color: var(--primary);
}
/* Dark: list groups */
.dark-mode .list-group-item {
background-color: var(--card) !important;
color: var(--card-foreground) !important;
border-color: var(--border) !important;
}
/* Dark: buttons */
.dark-mode .btn-outline-primary {
color: var(--primary);
border-color: var(--primary);
}
.dark-mode .btn-outline-danger {
color: var(--destructive);
border-color: var(--destructive);
}
/* Dark: text utilities */
.dark-mode .text-muted {
color: var(--muted-foreground) !important;
}
/* Dark: measurement tooltips */
.dark-mode .measure-tooltip {
background: rgba(30, 30, 56, 0.95);
color: var(--foreground);
border-color: var(--primary);
}
.dark-mode .measure-tooltip::before {
border-right-color: var(--primary);
}
/* Dark: OL controls */
.dark-mode .ol-control button {
background-color: var(--card) !important;
color: var(--foreground) !important;
}
.dark-mode .ol-control button:hover {
background-color: var(--primary) !important;
color: var(--primary-foreground) !important;
}
.dark-mode .ol-attribution,
.dark-mode .ol-attribution a {
color: var(--muted-foreground) !important;
}
/* Dark: scale bar */
.dark-mode .ol-scale-bar .ol-scale-step-text,
.dark-mode .ol-scale-bar .ol-scale-text {
color: #fff !important;
text-shadow: 0 0 4px #000, 0 0 8px #000 !important;
}
.dark-mode .ol-scale-bar .ol-scale-singlebar-even {
background-color: #fff !important;
}
.dark-mode .ol-scale-bar .ol-scale-singlebar-odd {
background-color: #999 !important;
}
/* Dark: map drop overlay */
.dark-mode .map-drop-overlay {
background: rgba(19, 19, 37, 0.85);
border-color: var(--primary);
color: var(--foreground);
}
/* Dark: ol-ext LayerSwitcher */
.dark-mode .ol-layerswitcher {
background-color: var(--card) !important;
}
.dark-mode .ol-layerswitcher .panel {
background-color: var(--card) !important;
color: var(--foreground) !important;
}
.dark-mode .ol-layerswitcher .panel li {
color: var(--foreground);
}
.dark-mode .ol-layerswitcher .ol-switchertopdiv,
.dark-mode .ol-layerswitcher .ol-switcherbottomdiv {
background: var(--card) !important;
}
/* Dark: alert boxes */
.dark-mode .alert-danger {
background-color: rgba(240, 64, 64, 0.15) !important;
color: var(--destructive) !important;
border-color: var(--destructive) !important;
}
.dark-mode .alert-success {
background-color: rgba(45, 212, 106, 0.15) !important;
color: var(--success) !important;
border-color: var(--success) !important;
}
/* Full height layout */
html, body {
height: 100%;
@ -214,9 +548,12 @@
font-family: var(--font-body);
}
/* Main container - full height */
/* Main container - full height.
100dvh accounts for mobile browser chrome and OS nav bars.
Falls back to 100vh for older browsers. */
.app-container {
height: 100vh;
height: 100dvh;
display: flex;
flex-direction: column;
}
@ -244,6 +581,35 @@
height: 100%;
}
/* Drag-and-drop overlay shown when files are dragged over the map */
.map-drop-overlay {
position: absolute;
inset: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 94, 184, 0.15);
border: 3px dashed var(--primary, #005eb8);
border-radius: 8px;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s ease;
}
.map-container.drag-over .map-drop-overlay {
opacity: 1;
}
.map-drop-overlay span {
font-family: var(--font-body, 'Exo', sans-serif);
font-size: 1.15rem;
font-weight: 600;
color: var(--primary, #005eb8);
background: var(--card, #fff);
padding: 0.6rem 1.4rem;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.12);
}
/* Offline indicator */
#offline-indicator {
display: none;
@ -301,9 +667,14 @@
z-index: 1050;
}
/* Fix ol-ext LayerSwitcher z-index */
.ol-layerswitcher {
z-index: 100;
/* OL controls stacking context fix — OpenLayers sets z-index:0 on
.ol-overlaycontainer-stopevent, trapping all controls below the
offcanvas-toggle buttons (z-index:500). Raising the container
to 501 lets the LayerSwitcher dropdown render above the toggles.
pointer-events:none on the container still lets clicks through
to the toggle buttons underneath. */
.ol-overlaycontainer-stopevent {
z-index: 501 !important;
}
/* Alert hint box */
@ -419,7 +790,7 @@
}
.offcanvas-toggle-bottom {
bottom: 80px; /* Above the dock */
bottom: calc(80px + env(safe-area-inset-bottom, 0px)); /* Above the dock */
left: 50%;
transform: translateX(-50%);
}
@ -432,7 +803,10 @@
transform: translateX(-50%) scale(0.95);
}
/* Bottom Dock — white card style with blue-strong accent */
/* Bottom Dock — white card style with blue-strong accent.
env(safe-area-inset-bottom) adds padding on devices with a
home indicator / gesture bar (e.g. iPhone notch models).
The value is 0 on devices without an inset. */
.bottom-dock {
position: absolute;
bottom: 0;
@ -441,7 +815,7 @@
z-index: 600;
background-color: var(--card);
border-top: 3px solid var(--primary);
padding: 8px 16px;
padding: 8px 16px calc(8px + env(safe-area-inset-bottom, 0px));
display: flex;
justify-content: space-around;
align-items: center;
@ -503,6 +877,13 @@
justify-content: center;
}
/* Snap-guides toggle — highlighted when active */
.ol-snap-toggle.ol-active button {
background: var(--primary) !important;
color: var(--primary-foreground, #fff) !important;
border-radius: 3px;
}
/* Touch-friendly improvements for forms and buttons */
.form-control, .form-select {
min-height: 44px;
@ -541,6 +922,18 @@
color: var(--foreground);
}
/* Message log in the right panel */
.message-log {
max-height: 260px;
overflow-y: auto;
}
.message-log-entry {
font-size: 0.82rem;
border-color: var(--border, #eee) !important;
background: transparent;
}
/* ol-ext GeolocationButton styling */
.ol-geobt {
top: auto !important;
@ -830,6 +1223,7 @@
/* Locations list in offcanvas - can be taller now without form */
.offcanvas-end .locations-list {
max-height: calc(100vh - 280px);
max-height: calc(100dvh - 280px);
overflow-y: auto;
}
@ -921,17 +1315,32 @@
font-size: 18px;
}
/* ScaleLine - position above the bottom dock */
.ol-scale-line {
bottom: 76px !important;
/* ScaleBar - position above the bottom dock with 4px gap */
.ol-scale-bar {
bottom: calc(85px + env(safe-area-inset-bottom, 0px)) !important;
left: 10px !important;
}
.ol-scale-line-inner {
border-color: var(--foreground) !important;
.ol-scale-bar .ol-scale-step-text {
color: var(--foreground) !important;
font-family: var(--font-body) !important;
font-size: 11px !important;
text-shadow: 0 0 3px var(--background), 0 0 6px var(--background) !important;
}
.ol-scale-bar .ol-scale-text {
color: var(--foreground) !important;
font-family: var(--font-body) !important;
font-size: 11px !important;
text-shadow: 0 0 3px var(--background), 0 0 6px var(--background) !important;
}
.ol-scale-bar .ol-scale-singlebar-even {
background-color: var(--foreground) !important;
}
.ol-scale-bar .ol-scale-singlebar-odd {
background-color: var(--muted-foreground) !important;
}
/* ol-ext Bar overrides */
@ -980,6 +1389,7 @@
</span>
</div>
<div id="map"></div>
<div class="map-drop-overlay"><span><i class="bi bi-file-earmark-arrow-up me-2"></i>Drop file to import (.shp .geojson .kml)</span></div>
<!-- Offcanvas toggle buttons -->
<button class="offcanvas-toggle offcanvas-toggle-left"
@ -1104,17 +1514,40 @@
<button type="button" class="btn btn-outline-primary w-100 mb-3" id="local-data-btn">
<i class="bi bi-database me-2"></i>Local Data
</button>
<button type="button" class="btn btn-outline-primary w-100 mb-3" id="import-shp-btn">
<i class="bi bi-file-earmark-arrow-up me-2"></i>Import .shp
</button>
<input type="file" id="shp-file-input" accept=".zip,.shp,.dbf,.shx,.prj" multiple class="d-none">
<button type="button" class="btn btn-outline-primary w-100 mb-3" id="import-geojson-btn">
<i class="bi bi-file-earmark-arrow-up me-2"></i>Import GeoJSON
</button>
<input type="file" id="geojson-file-input" accept=".geojson,.json" class="d-none">
<button type="button" class="btn btn-outline-primary w-100 mb-3" id="import-kml-btn">
<i class="bi bi-file-earmark-arrow-up me-2"></i>Import KML
</button>
<input type="file" id="kml-file-input" accept=".kml,.kmz" class="d-none">
<div id="file-import-alert" class="alert alert-danger alert-dismissible fade show d-none mb-3" role="alert">
<small class="message-text"></small>
<button type="button" class="btn-close btn-close-sm" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div id="imported-layers-info" class="d-none mb-3"></div>
<div id="local-data-stats" class="d-none">
<div class="card">
<div class="card-header bg-primary py-2">
<div class="card-header bg-primary py-2 d-flex justify-content-between align-items-center">
<h6 class="mb-0"><i class="bi bi-database me-2"></i>Local Database Tables</h6>
<button type="button" class="btn btn-sm btn-outline-light"
id="clear-all-cached-btn"
title="Delete all cached map layers. They will be re-downloaded on next app start.">
<i class="bi bi-arrow-clockwise me-1"></i>Refresh cached layers
</button>
</div>
<div class="card-body p-0">
<table class="table table-sm table-striped mb-0">
<thead>
<tr>
<th class="ps-3">Table</th>
<th class="text-end pe-3">Records</th>
<th class="text-end">Records</th>
<th class="text-end pe-3" style="width:3rem;"></th>
</tr>
</thead>
<tbody id="local-data-tbody">
@ -1142,6 +1575,10 @@
<span class="message-text"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div id="warning-message" class="alert alert-warning alert-dismissible fade show d-none" role="alert">
<span class="message-text"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<!-- Tip -->
<div class="alert alert-light border-start border-4 border-primary py-2 mb-3" role="alert">
@ -1181,20 +1618,268 @@
</div>
</div>
</div>
<!-- Message Log -->
<div class="card mt-3" id="message-log-card">
<div class="card-header bg-transparent py-2 d-flex justify-content-between align-items-center">
<h6 class="mb-0" style="font-family:var(--font-body);font-weight:700;"><i class="bi bi-journal-text me-1"></i> Messages</h6>
<button class="btn btn-sm btn-link text-muted p-0" id="clear-message-log" title="Clear messages">
<i class="bi bi-trash3"></i>
</button>
</div>
<div class="card-body p-0">
<div id="message-log" class="message-log list-group list-group-flush">
<div class="text-center text-muted py-3">
<small>No messages yet.</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bottom Offcanvas -->
<div class="offcanvas offcanvas-bottom" tabindex="-1" id="offcanvasBottom" aria-labelledby="offcanvasBottomLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasBottomLabel"><i class="bi bi-chevron-down me-2"></i>Bottom Panel</h5>
<h5 class="offcanvas-title" id="offcanvasBottomLabel"><i class="bi bi-gear me-2"></i>Settings</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
</div>
<div class="offcanvas-body">
<p>This is the bottom offcanvas panel.</p>
<p>You can add a data table, charts, or other wide content here.</p>
<div class="row g-3">
<!-- Fieldwork Mode -->
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="mb-1" style="font-family:var(--font-body);font-weight:700;">Fieldwork Mode</h6>
<small class="text-muted">High-contrast colours and larger touch targets for bright sunlight and field conditions.</small>
</div>
<div class="form-check form-switch ms-3">
<input class="form-check-input" type="checkbox" role="switch" id="fieldwork-mode-toggle" style="width:3rem;height:1.5rem;cursor:pointer;">
</div>
</div>
</div>
</div>
</div>
<!-- Dark Mode -->
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="mb-1" style="font-family:var(--font-body);font-weight:700;">Dark Mode</h6>
<small class="text-muted">Reduce glare and save battery with a dark colour scheme.</small>
</div>
<div class="form-check form-switch ms-3">
<input class="form-check-input" type="checkbox" role="switch" id="dark-mode-toggle" style="width:3rem;height:1.5rem;cursor:pointer;">
</div>
</div>
</div>
</div>
</div>
<!-- Measurement System -->
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h6 class="mb-1" style="font-family:var(--font-body);font-weight:700;">Measurement System</h6>
<small class="text-muted">Switch between Metric (m, km) and Imperial (ft, mi, acres) units.</small>
</div>
<div class="form-check form-switch ms-3">
<input class="form-check-input" type="checkbox" role="switch" id="measurement-system-toggle" style="width:3rem;height:1.5rem;cursor:pointer;">
<label class="form-check-label ms-1" id="measurement-system-label" for="measurement-system-toggle" style="font-size:0.8rem;font-weight:600;min-width:55px;">Metric</label>
</div>
</div>
</div>
</div>
</div>
<!-- Default Base Map -->
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div style="flex:1;min-width:0;">
<h6 class="mb-1" style="font-family:var(--font-body);font-weight:700;">Default Base Map</h6>
<small class="text-muted">Base map shown on app start. Saved on this device.</small>
</div>
<div class="ms-3" style="min-width:140px;">
<select class="form-select form-select-sm" id="default-basemap-select" aria-label="Default base map">
<option value="topo">Topographic</option>
<option value="osm">OpenStreetMap</option>
<option value="satellite">Satellite</option>
<option value="googlesat">Google Sat</option>
<option value="carto-light">Carto Light</option>
<option value="carto-dark">Carto Dark</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Offline Map Tiles -->
<div class="col-12 col-md-6 col-lg-8">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-start justify-content-between mb-2">
<div style="flex:1;min-width:0;">
<h6 class="mb-1" style="font-family:var(--font-body);font-weight:700;">
<i class="bi bi-map me-1"></i>Offline Map Tiles
</h6>
<small class="text-muted">
Map tiles you've already viewed are cached on this device so they work offline.
Tiles are cached automatically as you browse, or you can pre-download a region.
</small>
</div>
<div class="ms-3 d-flex gap-2 flex-shrink-0">
<button type="button" class="btn btn-sm btn-primary"
id="download-tiles-btn" style="white-space:nowrap;">
<i class="bi bi-cloud-download me-1"></i>Download offline map
</button>
<button type="button" class="btn btn-sm btn-outline-danger"
id="clear-tiles-btn" style="white-space:nowrap;">
<i class="bi bi-trash3 me-1"></i>Clear cached tiles
</button>
</div>
</div>
<div id="tile-cache-stats" class="small">
<div class="text-muted fst-italic">Loading…</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Download Offline Map modal -->
<div class="modal fade" id="offline-download-modal" tabindex="-1" aria-labelledby="offline-download-title" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="offline-download-title">
<i class="bi bi-cloud-download me-2"></i>Download Offline Map
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" id="offline-download-close-btn"></button>
</div>
<!-- Form view (shown until Start is clicked) -->
<div class="modal-body" id="offline-download-form-view">
<p class="text-muted small mb-3">
Pre-fetch map tiles so they're available when you're offline.
Only the OpenStreetMap and Topographic base maps can be downloaded;
other providers don't permit bulk caching.
</p>
<!-- Base map -->
<div class="mb-3">
<label for="offline-basemap-select" class="form-label fw-bold">Base map</label>
<select class="form-select form-select-sm" id="offline-basemap-select">
<option value="topo">Topographic</option>
<option value="osm">OpenStreetMap</option>
</select>
</div>
<!-- Area -->
<div class="mb-3">
<label class="form-label fw-bold mb-1">Area to download</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="offline-area" id="offline-area-view" value="view" checked>
<label class="form-check-label" for="offline-area-view">
Current map view
<span class="text-muted small" id="offline-area-view-info"></span>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="offline-area" id="offline-area-district" value="district">
<label class="form-check-label" for="offline-area-district">
District boundary
<span class="text-muted small" id="offline-area-district-info"></span>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="offline-area" id="offline-area-ghana" value="ghana">
<label class="form-check-label" for="offline-area-ghana">
Entire Ghana <span class="text-muted small">(very large — only attempt over fast Wi-Fi)</span>
</label>
</div>
</div>
<!-- Zoom range -->
<div class="mb-3">
<label class="form-label fw-bold mb-1">Zoom levels</label>
<div class="row g-2 align-items-center">
<div class="col-auto"><label for="offline-min-zoom" class="form-label small mb-0">Min</label></div>
<div class="col-auto">
<input type="number" class="form-control form-control-sm" id="offline-min-zoom" min="6" max="19" value="10" style="width:5em;">
</div>
<div class="col-auto"><label for="offline-max-zoom" class="form-label small mb-0">Max</label></div>
<div class="col-auto">
<input type="number" class="form-control form-control-sm" id="offline-max-zoom" min="6" max="19" value="15" style="width:5em;">
</div>
<div class="col text-muted small">10 = regional · 13 = neighbourhood · 16 = building</div>
</div>
</div>
<!-- Estimate -->
<div class="alert alert-info py-2 px-3 mb-3" id="offline-estimate" style="font-size:0.9em;">
<strong>Estimated download:</strong>
<span id="offline-estimate-detail">Calculating…</span>
</div>
<!-- Acknowledgement -->
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="offline-ack-check">
<label class="form-check-label small" for="offline-ack-check">
I understand this counts against the tile provider's usage quota
and will use mobile data if I'm not on Wi-Fi.
</label>
</div>
</div>
<!-- Progress view (shown during download) -->
<div class="modal-body d-none" id="offline-download-progress-view">
<div class="text-center mb-3">
<div class="fs-5 fw-bold" id="offline-progress-percent">0%</div>
<div class="small text-muted" id="offline-progress-counts">0 of 0 tiles</div>
</div>
<div class="progress mb-3" style="height:1.25em;">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" id="offline-progress-bar"
aria-valuemin="0" aria-valuemax="100" style="width:0%;"></div>
</div>
<div class="row text-center small text-muted">
<div class="col"><div class="fw-bold text-body" id="offline-progress-ok">0</div>fetched</div>
<div class="col"><div class="fw-bold text-body" id="offline-progress-failed">0</div>failed</div>
<div class="col"><div class="fw-bold text-body" id="offline-progress-eta"></div>remaining</div>
</div>
</div>
<!-- Done view (shown after completion) -->
<div class="modal-body d-none" id="offline-download-done-view">
<div class="text-center py-3">
<i class="bi bi-check-circle-fill text-success" style="font-size:3rem;"></i>
<div class="fs-5 fw-bold mt-2" id="offline-done-title">Download complete</div>
<div class="text-muted" id="offline-done-detail"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="offline-download-cancel-btn">
Cancel
</button>
<button type="button" class="btn btn-primary" id="offline-download-start-btn" disabled>
<i class="bi bi-cloud-download me-1"></i>Start download
</button>
<button type="button" class="btn btn-primary d-none" id="offline-download-close-done-btn" data-bs-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
<!-- Polyfill crypto.randomUUID for non-secure contexts (HTTP) -->
<!-- Must run before module imports (SQLocal/coincident require it) -->

1531
main.js

File diff suppressed because it is too large Load Diff

1
node_modules/.bin/esbuild generated vendored
View File

@ -1 +0,0 @@
../esbuild/bin/esbuild

1
node_modules/.bin/nanoid generated vendored
View File

@ -1 +0,0 @@
../nanoid/bin/nanoid.cjs

1
node_modules/.bin/pbf generated vendored
View File

@ -1 +0,0 @@
../pbf/bin/pbf

1
node_modules/.bin/rollup generated vendored
View File

@ -1 +0,0 @@
../rollup/dist/bin/rollup

1
node_modules/.bin/sqlite-wasm generated vendored
View File

@ -1 +0,0 @@
../@sqlite.org/sqlite-wasm/bin/index.js

1
node_modules/.bin/vite generated vendored
View File

@ -1 +0,0 @@
../vite/bin/vite.js

643
node_modules/.package-lock.json generated vendored
View File

@ -1,643 +0,0 @@
{
"name": "lupmis-pwa",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@petamoriken/float16": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz",
"integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==",
"license": "MIT"
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.55.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.3.tgz",
"integrity": "sha512-1ht2SpGIjEl2igJ9AbNpPIKzb1B5goXOcmtD0RFxnwNuMxqkR6AUaaErZz+4o+FKmzxcSNBOLrzsICZVNYa1Rw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@sqlite.org/sqlite-wasm": {
"version": "3.50.4-build1",
"resolved": "https://registry.npmjs.org/@sqlite.org/sqlite-wasm/-/sqlite-wasm-3.50.4-build1.tgz",
"integrity": "sha512-Qig2Wso7gPkU1PtXwFzndh+CTRzrIFxVGqv6eCetjU7YqxlHItj+GvQYwYTppCRgAPawtRN/4AJcEgB9xDHGug==",
"license": "Apache-2.0",
"bin": {
"sqlite-wasm": "bin/index.js"
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"devOptional": true,
"license": "MIT"
},
"node_modules/@types/rbush": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-4.0.0.tgz",
"integrity": "sha512-+N+2H39P8X+Hy1I5mC6awlTX54k3FhiUmvt7HWzGJZvF+syUAAxP/stwppS8JE84YHqFgRMv6fCy31202CMFxQ==",
"license": "MIT"
},
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"license": "ISC"
},
"node_modules/@ungap/with-resolvers": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@ungap/with-resolvers/-/with-resolvers-0.1.0.tgz",
"integrity": "sha512-g7f0IkJdPW2xhY7H4iE72DAsIyfuwEFc6JWc2tYFwKDMWWAF699vGjrM348cwQuOXgHpe1gWFe+Eiyjx/ewvvw==",
"license": "ISC"
},
"node_modules/bootstrap": {
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
"integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bootstrap-icons": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
"integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT"
},
"node_modules/coincident": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/coincident/-/coincident-1.2.3.tgz",
"integrity": "sha512-Uxz3BMTWIslzeWjuQnizGWVg0j6khbvHUQ8+5BdM7WuJEm4ALXwq3wluYoB+uF68uPBz/oUOeJnYURKyfjexlA==",
"license": "ISC",
"dependencies": {
"@ungap/structured-clone": "^1.2.0",
"@ungap/with-resolvers": "^0.1.0",
"gc-hook": "^0.3.1",
"proxy-target": "^3.0.2"
},
"optionalDependencies": {
"ws": "^8.16.0"
}
},
"node_modules/earcut": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz",
"integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==",
"license": "ISC"
},
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"devOptional": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/gc-hook": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/gc-hook/-/gc-hook-0.3.1.tgz",
"integrity": "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A==",
"license": "ISC"
},
"node_modules/geotiff": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/geotiff/-/geotiff-2.1.3.tgz",
"integrity": "sha512-PT6uoF5a1+kbC3tHmZSUsLHBp2QJlHasxxxxPW47QIY1VBKpFB+FcDvX+MxER6UzgLQZ0xDzJ9s48B9JbOCTqA==",
"license": "MIT",
"dependencies": {
"@petamoriken/float16": "^3.4.7",
"lerc": "^3.0.0",
"pako": "^2.0.4",
"parse-headers": "^2.0.2",
"quick-lru": "^6.1.1",
"web-worker": "^1.2.0",
"xml-utils": "^1.0.2",
"zstddec": "^0.1.0"
},
"engines": {
"node": ">=10.19"
}
},
"node_modules/lerc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz",
"integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==",
"license": "Apache-2.0"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"devOptional": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/ol": {
"version": "10.7.0",
"resolved": "https://registry.npmjs.org/ol/-/ol-10.7.0.tgz",
"integrity": "sha512-122U5gamPqNgLpLOkogFJhgpywvd/5en2kETIDW+Ubfi9lPnZ0G9HWRdG+CX0oP8od2d6u6ky3eewIYYlrVczw==",
"license": "BSD-2-Clause",
"dependencies": {
"@types/rbush": "4.0.0",
"earcut": "^3.0.0",
"geotiff": "^2.1.3",
"pbf": "4.0.1",
"rbush": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/openlayers"
}
},
"node_modules/ol-ext": {
"version": "4.0.37",
"resolved": "https://registry.npmjs.org/ol-ext/-/ol-ext-4.0.37.tgz",
"integrity": "sha512-RxzdgMWnNBDP9VZCza3oS3rl1+OCl+1SJLMjt7ATyDDLZl/zzrsQELfJ25WAL6HIWgjkQ2vYDh3nnHFupxOH4w==",
"license": "BSD-3-Clause",
"peerDependencies": {
"ol": ">= 5.3.0"
}
},
"node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/parse-headers": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz",
"integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==",
"license": "MIT"
},
"node_modules/pbf": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz",
"integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==",
"license": "BSD-3-Clause",
"dependencies": {
"resolve-protobuf-schema": "^2.1.0"
},
"bin": {
"pbf": "bin/pbf"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"devOptional": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"devOptional": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/protocol-buffers-schema": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==",
"license": "MIT"
},
"node_modules/proxy-target": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/proxy-target/-/proxy-target-3.0.2.tgz",
"integrity": "sha512-FFE1XNwXX/FNC3/P8HiKaJSy/Qk68RitG/QEcLy/bVnTAPlgTAWPZKh0pARLAnpfXQPKyalBhk009NRTgsk8vQ==",
"license": "MIT"
},
"node_modules/quick-lru": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz",
"integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/quickselect": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
"integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==",
"license": "ISC"
},
"node_modules/rbush": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/rbush/-/rbush-4.0.1.tgz",
"integrity": "sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==",
"license": "MIT",
"dependencies": {
"quickselect": "^3.0.0"
}
},
"node_modules/resolve-protobuf-schema": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
"integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
"license": "MIT",
"dependencies": {
"protocol-buffers-schema": "^3.3.1"
}
},
"node_modules/rollup": {
"version": "4.55.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.3.tgz",
"integrity": "sha512-y9yUpfQvetAjiDLtNMf1hL9NXchIJgWt6zIKeoB+tCd3npX08Eqfzg60V9DhIGVMtQ0AlMkFw5xa+AQ37zxnAA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.55.3",
"@rollup/rollup-android-arm64": "4.55.3",
"@rollup/rollup-darwin-arm64": "4.55.3",
"@rollup/rollup-darwin-x64": "4.55.3",
"@rollup/rollup-freebsd-arm64": "4.55.3",
"@rollup/rollup-freebsd-x64": "4.55.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.55.3",
"@rollup/rollup-linux-arm-musleabihf": "4.55.3",
"@rollup/rollup-linux-arm64-gnu": "4.55.3",
"@rollup/rollup-linux-arm64-musl": "4.55.3",
"@rollup/rollup-linux-loong64-gnu": "4.55.3",
"@rollup/rollup-linux-loong64-musl": "4.55.3",
"@rollup/rollup-linux-ppc64-gnu": "4.55.3",
"@rollup/rollup-linux-ppc64-musl": "4.55.3",
"@rollup/rollup-linux-riscv64-gnu": "4.55.3",
"@rollup/rollup-linux-riscv64-musl": "4.55.3",
"@rollup/rollup-linux-s390x-gnu": "4.55.3",
"@rollup/rollup-linux-x64-gnu": "4.55.3",
"@rollup/rollup-linux-x64-musl": "4.55.3",
"@rollup/rollup-openbsd-x64": "4.55.3",
"@rollup/rollup-openharmony-arm64": "4.55.3",
"@rollup/rollup-win32-arm64-msvc": "4.55.3",
"@rollup/rollup-win32-ia32-msvc": "4.55.3",
"@rollup/rollup-win32-x64-gnu": "4.55.3",
"@rollup/rollup-win32-x64-msvc": "4.55.3",
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"devOptional": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sqlocal": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/sqlocal/-/sqlocal-0.16.0.tgz",
"integrity": "sha512-iK9IAnPGW+98Pw0dWvhPZlapEZ9NaAKMEhRsbY1XlXPpAnRXblF6hP3NGtfLcW2dErWRJ79xzX3tAVZ2jNwqCg==",
"license": "MIT",
"dependencies": {
"@sqlite.org/sqlite-wasm": "^3.50.4-build1",
"coincident": "^1.2.3"
},
"funding": {
"type": "paypal",
"url": "https://www.paypal.com/biz/fund?id=U3ZNM2Q26WJY8"
},
"peerDependencies": {
"@angular/core": ">=17.0.0",
"drizzle-orm": "*",
"kysely": "*",
"react": ">=18.0.0",
"vite": ">=4.0.0",
"vue": ">=3.0.0"
},
"peerDependenciesMeta": {
"@angular/core": {
"optional": true
},
"drizzle-orm": {
"optional": true
},
"kysely": {
"optional": true
},
"react": {
"optional": true
},
"vite": {
"optional": true
},
"vue": {
"optional": true
}
}
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/vite": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
"picomatch": "^4.0.2",
"postcss": "^8.5.3",
"rollup": "^4.34.9",
"tinyglobby": "^0.2.13"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"jiti": ">=1.21.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
},
"node_modules/web-worker": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz",
"integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==",
"license": "Apache-2.0"
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml-utils": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz",
"integrity": "sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==",
"license": "CC0-1.0"
},
"node_modules/zstddec": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.1.0.tgz",
"integrity": "sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg==",
"license": "MIT AND BSD-3-Clause"
}
}
}

View File

@ -1,439 +0,0 @@
{
"hash": "5fa9d27f",
"configHash": "cab165a2",
"lockfileHash": "c2087766",
"browserHash": "c156faea",
"optimized": {
"bootstrap": {
"src": "../../bootstrap/dist/js/bootstrap.esm.js",
"file": "bootstrap.js",
"fileHash": "fd6ceaaa",
"needsInterop": false
},
"ol/Map": {
"src": "../../ol/Map.js",
"file": "ol_Map.js",
"fileHash": "b0e367b7",
"needsInterop": false
},
"ol/View": {
"src": "../../ol/View.js",
"file": "ol_View.js",
"fileHash": "355ac42e",
"needsInterop": false
},
"ol/Overlay": {
"src": "../../ol/Overlay.js",
"file": "ol_Overlay.js",
"fileHash": "fe4fef22",
"needsInterop": false
},
"ol/layer/Tile": {
"src": "../../ol/layer/Tile.js",
"file": "ol_layer_Tile.js",
"fileHash": "e6defa38",
"needsInterop": false
},
"ol/layer/Group": {
"src": "../../ol/layer/Group.js",
"file": "ol_layer_Group.js",
"fileHash": "b538c107",
"needsInterop": false
},
"ol/layer/Vector": {
"src": "../../ol/layer/Vector.js",
"file": "ol_layer_Vector.js",
"fileHash": "88b2fe3b",
"needsInterop": false
},
"ol/source/Vector": {
"src": "../../ol/source/Vector.js",
"file": "ol_source_Vector.js",
"fileHash": "f625949b",
"needsInterop": false
},
"ol/source/OSM": {
"src": "../../ol/source/OSM.js",
"file": "ol_source_OSM.js",
"fileHash": "3b97b715",
"needsInterop": false
},
"ol/source/XYZ": {
"src": "../../ol/source/XYZ.js",
"file": "ol_source_XYZ.js",
"fileHash": "7d8f852b",
"needsInterop": false
},
"ol/proj": {
"src": "../../ol/proj.js",
"file": "ol_proj.js",
"fileHash": "bde13aba",
"needsInterop": false
},
"ol/geom": {
"src": "../../ol/geom.js",
"file": "ol_geom.js",
"fileHash": "e4ded8a3",
"needsInterop": false
},
"ol/Feature": {
"src": "../../ol/Feature.js",
"file": "ol_Feature.js",
"fileHash": "8ad86d7d",
"needsInterop": false
},
"ol/style": {
"src": "../../ol/style.js",
"file": "ol_style.js",
"fileHash": "6029528e",
"needsInterop": false
},
"ol-ext/control/LayerSwitcher": {
"src": "../../ol-ext/control/LayerSwitcher.js",
"file": "ol-ext_control_LayerSwitcher.js",
"fileHash": "ba3fcba4",
"needsInterop": false
},
"ol-ext/control/GeolocationButton": {
"src": "../../ol-ext/control/GeolocationButton.js",
"file": "ol-ext_control_GeolocationButton.js",
"fileHash": "6e1e8c81",
"needsInterop": false
},
"ol-ext/control/SearchNominatim": {
"src": "../../ol-ext/control/SearchNominatim.js",
"file": "ol-ext_control_SearchNominatim.js",
"fileHash": "49a8e3f6",
"needsInterop": false
},
"ol/interaction": {
"src": "../../ol/interaction.js",
"file": "ol_interaction.js",
"fileHash": "3b0cba52",
"needsInterop": false
},
"ol/layer": {
"src": "../../ol/layer.js",
"file": "ol_layer.js",
"fileHash": "2f7ece85",
"needsInterop": false
},
"ol/source": {
"src": "../../ol/source.js",
"file": "ol_source.js",
"fileHash": "55b243b7",
"needsInterop": false
},
"ol/sphere": {
"src": "../../ol/sphere.js",
"file": "ol_sphere.js",
"fileHash": "17c5b65f",
"needsInterop": false
},
"ol/Observable": {
"src": "../../ol/Observable.js",
"file": "ol_Observable.js",
"fileHash": "a1d95e20",
"needsInterop": false
},
"ol-ext/control/Bar": {
"src": "../../ol-ext/control/Bar.js",
"file": "ol-ext_control_Bar.js",
"fileHash": "9961a97b",
"needsInterop": false
},
"ol-ext/control/Toggle": {
"src": "../../ol-ext/control/Toggle.js",
"file": "ol-ext_control_Toggle.js",
"fileHash": "3716a205",
"needsInterop": false
},
"ol-ext/control/Button": {
"src": "../../ol-ext/control/Button.js",
"file": "ol-ext_control_Button.js",
"fileHash": "94db9999",
"needsInterop": false
},
"ol/format/GeoJSON": {
"src": "../../ol/format/GeoJSON.js",
"file": "ol_format_GeoJSON.js",
"fileHash": "6ca0d734",
"needsInterop": false
},
"ol-ext/control/EditBar": {
"src": "../../ol-ext/control/EditBar.js",
"file": "ol-ext_control_EditBar.js",
"fileHash": "eaa0bfea",
"needsInterop": false
},
"ol-ext/interaction/TouchCursor": {
"src": "../../ol-ext/interaction/TouchCursor.js",
"file": "ol-ext_interaction_TouchCursor.js",
"fileHash": "a53deca0",
"needsInterop": false
},
"ol-ext/interaction/ModifyFeature": {
"src": "../../ol-ext/interaction/ModifyFeature.js",
"file": "ol-ext_interaction_ModifyFeature.js",
"fileHash": "50e7de35",
"needsInterop": false
},
"ol/interaction/Select": {
"src": "../../ol/interaction/Select.js",
"file": "ol_interaction_Select.js",
"fileHash": "e3c3f813",
"needsInterop": false
},
"ol/events/condition": {
"src": "../../ol/events/condition.js",
"file": "ol_events_condition.js",
"fileHash": "bdeed76d",
"needsInterop": false
},
"ol-ext/interaction/UndoRedo": {
"src": "../../ol-ext/interaction/UndoRedo.js",
"file": "ol-ext_interaction_UndoRedo.js",
"fileHash": "906faad5",
"needsInterop": false
},
"ol/geom/Polygon": {
"src": "../../ol/geom/Polygon.js",
"file": "ol_geom_Polygon.js",
"fileHash": "c61e8553",
"needsInterop": false
},
"ol/control/ScaleLine": {
"src": "../../ol/control/ScaleLine.js",
"file": "ol_control_ScaleLine.js",
"fileHash": "8cff3002",
"needsInterop": false
},
"ol/format/WKT": {
"src": "../../ol/format/WKT.js",
"file": "ol_format_WKT.js",
"fileHash": "c6bcdd8f",
"needsInterop": false
}
},
"chunks": {
"lerc-YJMC4I3X": {
"file": "lerc-YJMC4I3X.js"
},
"webimage-T4PTOJUP": {
"file": "webimage-T4PTOJUP.js"
},
"decoder-FUDNTCPN": {
"file": "decoder-FUDNTCPN.js"
},
"raw-UKC26CDE": {
"file": "raw-UKC26CDE.js"
},
"lzw-7DRJSDK5": {
"file": "lzw-7DRJSDK5.js"
},
"jpeg-HGEGG7HT": {
"file": "jpeg-HGEGG7HT.js"
},
"deflate-IME5YE3D": {
"file": "deflate-IME5YE3D.js"
},
"chunk-OVHVPML2": {
"file": "chunk-OVHVPML2.js"
},
"packbits-F6QMWCFA": {
"file": "packbits-F6QMWCFA.js"
},
"chunk-YNX27GDF": {
"file": "chunk-YNX27GDF.js"
},
"chunk-3UNEODO2": {
"file": "chunk-3UNEODO2.js"
},
"chunk-MEQVYVYE": {
"file": "chunk-MEQVYVYE.js"
},
"chunk-6DXBPPKF": {
"file": "chunk-6DXBPPKF.js"
},
"chunk-NNBJMTCH": {
"file": "chunk-NNBJMTCH.js"
},
"chunk-QTABLK4X": {
"file": "chunk-QTABLK4X.js"
},
"chunk-C5KGH6RQ": {
"file": "chunk-C5KGH6RQ.js"
},
"chunk-3TN6D4MD": {
"file": "chunk-3TN6D4MD.js"
},
"chunk-VCBXDRBT": {
"file": "chunk-VCBXDRBT.js"
},
"chunk-NMUIRNIP": {
"file": "chunk-NMUIRNIP.js"
},
"chunk-43GYE2V5": {
"file": "chunk-43GYE2V5.js"
},
"chunk-PD2E5XZ4": {
"file": "chunk-PD2E5XZ4.js"
},
"chunk-YUMATXXX": {
"file": "chunk-YUMATXXX.js"
},
"chunk-E7S7Q7VV": {
"file": "chunk-E7S7Q7VV.js"
},
"chunk-S3QBQTEW": {
"file": "chunk-S3QBQTEW.js"
},
"chunk-V7WRBSQ6": {
"file": "chunk-V7WRBSQ6.js"
},
"chunk-56VFHHUN": {
"file": "chunk-56VFHHUN.js"
},
"chunk-W7BDJOQY": {
"file": "chunk-W7BDJOQY.js"
},
"chunk-7JXPN73Q": {
"file": "chunk-7JXPN73Q.js"
},
"chunk-E53S5GN6": {
"file": "chunk-E53S5GN6.js"
},
"chunk-UNDFRJ2M": {
"file": "chunk-UNDFRJ2M.js"
},
"chunk-T3TT2KJN": {
"file": "chunk-T3TT2KJN.js"
},
"chunk-HM3IY3H4": {
"file": "chunk-HM3IY3H4.js"
},
"chunk-JFXZSSOM": {
"file": "chunk-JFXZSSOM.js"
},
"chunk-ZUI5NXIU": {
"file": "chunk-ZUI5NXIU.js"
},
"chunk-I4Q72WOW": {
"file": "chunk-I4Q72WOW.js"
},
"chunk-RTVPCGIJ": {
"file": "chunk-RTVPCGIJ.js"
},
"chunk-MSWSBYBR": {
"file": "chunk-MSWSBYBR.js"
},
"chunk-QCJTGAWF": {
"file": "chunk-QCJTGAWF.js"
},
"chunk-CAVOO5JW": {
"file": "chunk-CAVOO5JW.js"
},
"chunk-VRTURNK3": {
"file": "chunk-VRTURNK3.js"
},
"chunk-2C73OZ6M": {
"file": "chunk-2C73OZ6M.js"
},
"chunk-M5TTSD4C": {
"file": "chunk-M5TTSD4C.js"
},
"chunk-ZCRXKB7J": {
"file": "chunk-ZCRXKB7J.js"
},
"chunk-RW3V7S4F": {
"file": "chunk-RW3V7S4F.js"
},
"chunk-PAB2HIXK": {
"file": "chunk-PAB2HIXK.js"
},
"chunk-I6K7MRGV": {
"file": "chunk-I6K7MRGV.js"
},
"chunk-PGWX4545": {
"file": "chunk-PGWX4545.js"
},
"chunk-AYBYZSAV": {
"file": "chunk-AYBYZSAV.js"
},
"chunk-YLJGUH5Z": {
"file": "chunk-YLJGUH5Z.js"
},
"chunk-AZGMK675": {
"file": "chunk-AZGMK675.js"
},
"chunk-C6SRSVJF": {
"file": "chunk-C6SRSVJF.js"
},
"chunk-BHVDQB66": {
"file": "chunk-BHVDQB66.js"
},
"chunk-6EWLK2BW": {
"file": "chunk-6EWLK2BW.js"
},
"chunk-3ZDRPUXW": {
"file": "chunk-3ZDRPUXW.js"
},
"chunk-6Y7C6NBJ": {
"file": "chunk-6Y7C6NBJ.js"
},
"chunk-7XMWB3J4": {
"file": "chunk-7XMWB3J4.js"
},
"chunk-5D2XPBR2": {
"file": "chunk-5D2XPBR2.js"
},
"chunk-SHUBVYN4": {
"file": "chunk-SHUBVYN4.js"
},
"chunk-FM44FOIC": {
"file": "chunk-FM44FOIC.js"
},
"chunk-LMC3RO5P": {
"file": "chunk-LMC3RO5P.js"
},
"chunk-X52LGBOS": {
"file": "chunk-X52LGBOS.js"
},
"chunk-QFCIXVZ3": {
"file": "chunk-QFCIXVZ3.js"
},
"chunk-A3RXLHYB": {
"file": "chunk-A3RXLHYB.js"
},
"chunk-ZLPTRF2L": {
"file": "chunk-ZLPTRF2L.js"
},
"chunk-54BTDBAD": {
"file": "chunk-54BTDBAD.js"
},
"chunk-UPTVWZ45": {
"file": "chunk-UPTVWZ45.js"
},
"chunk-5XHD7RSF": {
"file": "chunk-5XHD7RSF.js"
},
"chunk-Q5ZULJHM": {
"file": "chunk-Q5ZULJHM.js"
},
"chunk-NGFXCWUF": {
"file": "chunk-NGFXCWUF.js"
},
"chunk-K25ZO44T": {
"file": "chunk-K25ZO44T.js"
},
"chunk-SRXHWJOY": {
"file": "chunk-SRXHWJOY.js"
},
"chunk-5RHQVMYD": {
"file": "chunk-5RHQVMYD.js"
},
"chunk-DC5AMYBS": {
"file": "chunk-DC5AMYBS.js"
}
}
}

5186
node_modules/.vite/deps/bootstrap.js generated vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,297 +0,0 @@
import {
CollectionEventType_default,
Collection_default
} from "./chunk-M5TTSD4C.js";
import {
Base_default
} from "./chunk-AYBYZSAV.js";
import {
assert
} from "./chunk-QFCIXVZ3.js";
import {
ObjectEventType_default,
getUid
} from "./chunk-Q5ZULJHM.js";
import {
Event_default,
listen,
unlistenByKey
} from "./chunk-NGFXCWUF.js";
import {
EventType_default
} from "./chunk-K25ZO44T.js";
import {
getIntersection
} from "./chunk-SRXHWJOY.js";
import {
clear
} from "./chunk-5RHQVMYD.js";
// node_modules/ol/layer/Group.js
var GroupEventType = {
/**
* Triggered when a layer is added
* @event GroupEvent#addlayer
* @api
*/
ADDLAYER: "addlayer",
/**
* Triggered when a layer is removed
* @event GroupEvent#removelayer
* @api
*/
REMOVELAYER: "removelayer"
};
var GroupEvent = class extends Event_default {
/**
* @param {GroupEventType} type The event type.
* @param {BaseLayer} layer The layer.
*/
constructor(type, layer) {
super(type);
this.layer = layer;
}
};
var Property = {
LAYERS: "layers"
};
var LayerGroup = class _LayerGroup extends Base_default {
/**
* @param {Options} [options] Layer options.
*/
constructor(options) {
options = options || {};
const baseOptions = (
/** @type {Options} */
Object.assign({}, options)
);
delete baseOptions.layers;
let layers = options.layers;
super(baseOptions);
this.on;
this.once;
this.un;
this.layersListenerKeys_ = [];
this.listenerKeys_ = {};
this.addChangeListener(Property.LAYERS, this.handleLayersChanged_);
if (layers) {
if (Array.isArray(layers)) {
layers = new Collection_default(layers.slice(), { unique: true });
} else {
assert(
typeof /** @type {?} */
layers.getArray === "function",
"Expected `layers` to be an array or a `Collection`"
);
}
} else {
layers = new Collection_default(void 0, { unique: true });
}
this.setLayers(layers);
}
/**
* @private
*/
handleLayerChange_() {
this.changed();
}
/**
* @private
*/
handleLayersChanged_() {
this.layersListenerKeys_.forEach(unlistenByKey);
this.layersListenerKeys_.length = 0;
const layers = this.getLayers();
this.layersListenerKeys_.push(
listen(layers, CollectionEventType_default.ADD, this.handleLayersAdd_, this),
listen(
layers,
CollectionEventType_default.REMOVE,
this.handleLayersRemove_,
this
)
);
for (const id in this.listenerKeys_) {
this.listenerKeys_[id].forEach(unlistenByKey);
}
clear(this.listenerKeys_);
const layersArray = layers.getArray();
for (let i = 0, ii = layersArray.length; i < ii; i++) {
const layer = layersArray[i];
this.registerLayerListeners_(layer);
this.dispatchEvent(new GroupEvent(GroupEventType.ADDLAYER, layer));
}
this.changed();
}
/**
* @param {BaseLayer} layer The layer.
*/
registerLayerListeners_(layer) {
const listenerKeys = [
listen(
layer,
ObjectEventType_default.PROPERTYCHANGE,
this.handleLayerChange_,
this
),
listen(layer, EventType_default.CHANGE, this.handleLayerChange_, this)
];
if (layer instanceof _LayerGroup) {
listenerKeys.push(
listen(layer, GroupEventType.ADDLAYER, this.handleLayerGroupAdd_, this),
listen(
layer,
GroupEventType.REMOVELAYER,
this.handleLayerGroupRemove_,
this
)
);
}
this.listenerKeys_[getUid(layer)] = listenerKeys;
}
/**
* @param {GroupEvent} event The layer group event.
*/
handleLayerGroupAdd_(event) {
this.dispatchEvent(new GroupEvent(GroupEventType.ADDLAYER, event.layer));
}
/**
* @param {GroupEvent} event The layer group event.
*/
handleLayerGroupRemove_(event) {
this.dispatchEvent(new GroupEvent(GroupEventType.REMOVELAYER, event.layer));
}
/**
* @param {import("../Collection.js").CollectionEvent<import("./Base.js").default>} collectionEvent CollectionEvent.
* @private
*/
handleLayersAdd_(collectionEvent) {
const layer = collectionEvent.element;
this.registerLayerListeners_(layer);
this.dispatchEvent(new GroupEvent(GroupEventType.ADDLAYER, layer));
this.changed();
}
/**
* @param {import("../Collection.js").CollectionEvent<import("./Base.js").default>} collectionEvent CollectionEvent.
* @private
*/
handleLayersRemove_(collectionEvent) {
const layer = collectionEvent.element;
const key = getUid(layer);
this.listenerKeys_[key].forEach(unlistenByKey);
delete this.listenerKeys_[key];
this.dispatchEvent(new GroupEvent(GroupEventType.REMOVELAYER, layer));
this.changed();
}
/**
* Returns the {@link module:ol/Collection~Collection collection} of {@link module:ol/layer/Layer~Layer layers}
* in this group.
* @return {!Collection<import("./Base.js").default>} Collection of
* {@link module:ol/layer/Base~BaseLayer layers} that are part of this group.
* @observable
* @api
*/
getLayers() {
return (
/** @type {!Collection<import("./Base.js").default>} */
this.get(Property.LAYERS)
);
}
/**
* Set the {@link module:ol/Collection~Collection collection} of {@link module:ol/layer/Layer~Layer layers}
* in this group.
* @param {!Collection<import("./Base.js").default>} layers Collection of
* {@link module:ol/layer/Base~BaseLayer layers} that are part of this group.
* @observable
* @api
*/
setLayers(layers) {
const collection = this.getLayers();
if (collection) {
const currentLayers = collection.getArray();
for (let i = 0, ii = currentLayers.length; i < ii; ++i) {
this.dispatchEvent(
new GroupEvent(GroupEventType.REMOVELAYER, currentLayers[i])
);
}
}
this.set(Property.LAYERS, layers);
}
/**
* @param {Array<import("./Layer.js").default>} [array] Array of layers (to be modified in place).
* @return {Array<import("./Layer.js").default>} Array of layers.
* @override
*/
getLayersArray(array) {
array = array !== void 0 ? array : [];
this.getLayers().forEach(function(layer) {
layer.getLayersArray(array);
});
return array;
}
/**
* Get the layer states list and use this groups z-index as the default
* for all layers in this and nested groups, if it is unset at this point.
* If dest is not provided and this group's z-index is undefined
* 0 is used a the default z-index.
* @param {Array<import("./Layer.js").State>} [dest] Optional list
* of layer states (to be modified in place).
* @return {Array<import("./Layer.js").State>} List of layer states.
* @override
*/
getLayerStatesArray(dest) {
const states = dest !== void 0 ? dest : [];
const pos = states.length;
this.getLayers().forEach(function(layer) {
layer.getLayerStatesArray(states);
});
const ownLayerState = this.getLayerState();
let defaultZIndex = ownLayerState.zIndex;
if (!dest && ownLayerState.zIndex === void 0) {
defaultZIndex = 0;
}
for (let i = pos, ii = states.length; i < ii; i++) {
const layerState = states[i];
layerState.opacity *= ownLayerState.opacity;
layerState.visible = layerState.visible && ownLayerState.visible;
layerState.maxResolution = Math.min(
layerState.maxResolution,
ownLayerState.maxResolution
);
layerState.minResolution = Math.max(
layerState.minResolution,
ownLayerState.minResolution
);
layerState.minZoom = Math.max(layerState.minZoom, ownLayerState.minZoom);
layerState.maxZoom = Math.min(layerState.maxZoom, ownLayerState.maxZoom);
if (ownLayerState.extent !== void 0) {
if (layerState.extent !== void 0) {
layerState.extent = getIntersection(
layerState.extent,
ownLayerState.extent
);
} else {
layerState.extent = ownLayerState.extent;
}
}
if (layerState.zIndex === void 0) {
layerState.zIndex = defaultZIndex;
}
}
return states;
}
/**
* @return {import("../source/Source.js").State} Source state.
* @override
*/
getSourceState() {
return "ready";
}
};
var Group_default = LayerGroup;
export {
GroupEvent,
Group_default
};
//# sourceMappingURL=chunk-2C73OZ6M.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,77 +0,0 @@
import {
ImageState_default,
Image_default
} from "./chunk-SHUBVYN4.js";
// node_modules/ol/ImageCanvas.js
var ImageCanvas = class extends Image_default {
/**
* @param {import("./extent.js").Extent} extent Extent.
* @param {number} resolution Resolution.
* @param {number} pixelRatio Pixel ratio.
* @param {HTMLCanvasElement|OffscreenCanvas} canvas Canvas.
* @param {Loader} [loader] Optional loader function to
* support asynchronous canvas drawing.
*/
constructor(extent, resolution, pixelRatio, canvas, loader) {
const state = loader !== void 0 ? ImageState_default.IDLE : ImageState_default.LOADED;
super(extent, resolution, pixelRatio, state);
this.loader_ = loader !== void 0 ? loader : null;
this.canvas_ = canvas;
this.error_ = null;
}
/**
* Get any error associated with asynchronous rendering.
* @return {?Error} Any error that occurred during rendering.
*/
getError() {
return this.error_;
}
/**
* Handle async drawing complete.
* @param {Error} [err] Any error during drawing.
* @private
*/
handleLoad_(err) {
if (err) {
this.error_ = err;
this.state = ImageState_default.ERROR;
} else {
this.state = ImageState_default.LOADED;
}
this.changed();
}
/**
* Load not yet loaded URI.
* @override
*/
load() {
if (this.state == ImageState_default.IDLE) {
this.state = ImageState_default.LOADING;
this.changed();
this.loader_(this.handleLoad_.bind(this));
}
}
/**
* @return {HTMLCanvasElement|OffscreenCanvas} Canvas element.
* @override
*/
getImage() {
return this.canvas_;
}
};
var ImageCanvas_default = ImageCanvas;
// node_modules/ol/resolution.js
function fromResolutionLike(resolution) {
if (Array.isArray(resolution)) {
return Math.min(...resolution);
}
return resolution;
}
export {
ImageCanvas_default,
fromResolutionLike
};
//# sourceMappingURL=chunk-3TN6D4MD.js.map

View File

@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../ol/ImageCanvas.js", "../../ol/resolution.js"],
"sourcesContent": ["/**\n * @module ol/ImageCanvas\n */\nimport ImageWrapper from './Image.js';\nimport ImageState from './ImageState.js';\n\n/**\n * A function that is called to trigger asynchronous canvas drawing. It is\n * called with a \"done\" callback that should be called when drawing is done.\n * If any error occurs during drawing, the \"done\" callback should be called with\n * that error.\n *\n * @typedef {function(function(Error=): void): void} Loader\n */\n\nclass ImageCanvas extends ImageWrapper {\n /**\n * @param {import(\"./extent.js\").Extent} extent Extent.\n * @param {number} resolution Resolution.\n * @param {number} pixelRatio Pixel ratio.\n * @param {HTMLCanvasElement|OffscreenCanvas} canvas Canvas.\n * @param {Loader} [loader] Optional loader function to\n * support asynchronous canvas drawing.\n */\n constructor(extent, resolution, pixelRatio, canvas, loader) {\n const state = loader !== undefined ? ImageState.IDLE : ImageState.LOADED;\n\n super(extent, resolution, pixelRatio, state);\n\n /**\n * Optional canvas loader function.\n * @type {?Loader}\n * @private\n */\n this.loader_ = loader !== undefined ? loader : null;\n\n /**\n * @private\n * @type {HTMLCanvasElement|OffscreenCanvas}\n */\n this.canvas_ = canvas;\n\n /**\n * @private\n * @type {?Error}\n */\n this.error_ = null;\n }\n\n /**\n * Get any error associated with asynchronous rendering.\n * @return {?Error} Any error that occurred during rendering.\n */\n getError() {\n return this.error_;\n }\n\n /**\n * Handle async drawing complete.\n * @param {Error} [err] Any error during drawing.\n * @private\n */\n handleLoad_(err) {\n if (err) {\n this.error_ = err;\n this.state = ImageState.ERROR;\n } else {\n this.state = ImageState.LOADED;\n }\n this.changed();\n }\n\n /**\n * Load not yet loaded URI.\n * @override\n */\n load() {\n if (this.state == ImageState.IDLE) {\n this.state = ImageState.LOADING;\n this.changed();\n this.loader_(this.handleLoad_.bind(this));\n }\n }\n\n /**\n * @return {HTMLCanvasElement|OffscreenCanvas} Canvas element.\n * @override\n */\n getImage() {\n return this.canvas_;\n }\n}\n\nexport default ImageCanvas;\n", "/**\n * @module ol/resolution\n */\n\n/**\n * @typedef {number|Array<number>} ResolutionLike\n */\n\n/**\n * @param {ResolutionLike} resolution Resolution.\n * @return {number} Resolution.\n */\nexport function fromResolutionLike(resolution) {\n if (Array.isArray(resolution)) {\n return Math.min(...resolution);\n }\n return resolution;\n}\n"],
"mappings": ";;;;;;AAeA,IAAM,cAAN,cAA0B,cAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASrC,YAAY,QAAQ,YAAY,YAAY,QAAQ,QAAQ;AAC1D,UAAM,QAAQ,WAAW,SAAY,mBAAW,OAAO,mBAAW;AAElE,UAAM,QAAQ,YAAY,YAAY,KAAK;AAO3C,SAAK,UAAU,WAAW,SAAY,SAAS;AAM/C,SAAK,UAAU;AAMf,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW;AACT,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,KAAK;AACf,QAAI,KAAK;AACP,WAAK,SAAS;AACd,WAAK,QAAQ,mBAAW;AAAA,IAC1B,OAAO;AACL,WAAK,QAAQ,mBAAW;AAAA,IAC1B;AACA,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO;AACL,QAAI,KAAK,SAAS,mBAAW,MAAM;AACjC,WAAK,QAAQ,mBAAW;AACxB,WAAK,QAAQ;AACb,WAAK,QAAQ,KAAK,YAAY,KAAK,IAAI,CAAC;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW;AACT,WAAO,KAAK;AAAA,EACd;AACF;AAEA,IAAO,sBAAQ;;;ACjFR,SAAS,mBAAmB,YAAY;AAC7C,MAAI,MAAM,QAAQ,UAAU,GAAG;AAC7B,WAAO,KAAK,IAAI,GAAG,UAAU;AAAA,EAC/B;AACA,SAAO;AACT;",
"names": []
}

View File

@ -1,270 +0,0 @@
import {
Circle_default,
LineString_default,
MultiLineString_default,
MultiPolygon_default
} from "./chunk-7JXPN73Q.js";
import {
Polygon_default
} from "./chunk-AZGMK675.js";
import {
buffer
} from "./chunk-SRXHWJOY.js";
// node_modules/ol-ext/geom/GeomUtils.js
var ol_coordinate_dist2d = function(p1, p2) {
var dx = p1[0] - p2[0];
var dy = p1[1] - p2[1];
return Math.sqrt(dx * dx + dy * dy);
};
var ol_coordinate_equal = function(p1, p2) {
return p1[0] == p2[0] && p1[1] == p2[1];
};
var ol_coordinate_offsetCoords = function(coords, offset) {
var path = [];
var N = coords.length - 1;
var max = N;
var mi, mi1, li, li1, ri, ri1, si, si1, Xi1, Yi1;
var p0, p1, p2;
var isClosed = ol_coordinate_equal(coords[0], coords[N]);
if (!isClosed) {
p0 = coords[0];
p1 = coords[1];
p2 = [
p0[0] + (p1[1] - p0[1]) / ol_coordinate_dist2d(p0, p1) * offset,
p0[1] - (p1[0] - p0[0]) / ol_coordinate_dist2d(p0, p1) * offset
];
path.push(p2);
coords.push(coords[N]);
N++;
max--;
}
for (var i = 0; i < max; i++) {
p0 = coords[i];
p1 = coords[(i + 1) % N];
p2 = coords[(i + 2) % N];
mi = (p1[1] - p0[1]) / (p1[0] - p0[0]);
mi1 = (p2[1] - p1[1]) / (p2[0] - p1[0]);
if (Math.abs(mi - mi1) > 1e-10) {
li = Math.sqrt((p1[0] - p0[0]) * (p1[0] - p0[0]) + (p1[1] - p0[1]) * (p1[1] - p0[1]));
li1 = Math.sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]));
ri = p0[0] + offset * (p1[1] - p0[1]) / li;
ri1 = p1[0] + offset * (p2[1] - p1[1]) / li1;
si = p0[1] - offset * (p1[0] - p0[0]) / li;
si1 = p1[1] - offset * (p2[0] - p1[0]) / li1;
Xi1 = (mi1 * ri1 - mi * ri + si - si1) / (mi1 - mi);
Yi1 = (mi * mi1 * (ri1 - ri) + mi1 * si - mi * si1) / (mi1 - mi);
if (p1[0] - p0[0] == 0) {
Xi1 = p1[0] + offset * (p1[1] - p0[1]) / Math.abs(p1[1] - p0[1]);
Yi1 = mi1 * Xi1 - mi1 * ri1 + si1;
}
if (p2[0] - p1[0] == 0) {
Xi1 = p2[0] + offset * (p2[1] - p1[1]) / Math.abs(p2[1] - p1[1]);
Yi1 = mi * Xi1 - mi * ri + si;
}
path.push([Xi1, Yi1]);
}
}
if (isClosed) {
path.push(path[0]);
} else {
coords.pop();
p0 = coords[coords.length - 1];
p1 = coords[coords.length - 2];
p2 = [
p0[0] - (p1[1] - p0[1]) / ol_coordinate_dist2d(p0, p1) * offset,
p0[1] + (p1[0] - p0[0]) / ol_coordinate_dist2d(p0, p1) * offset
];
path.push(p2);
}
return path;
};
var ol_coordinate_findSegment = function(pt, coords) {
for (var i = 0; i < coords.length - 1; i++) {
var p0 = coords[i];
var p1 = coords[i + 1];
if (ol_coordinate_equal(pt, p0) || ol_coordinate_equal(pt, p1)) {
return { index: 1, segment: [p0, p1] };
} else {
var d0 = ol_coordinate_dist2d(p0, p1);
var v0 = [(p1[0] - p0[0]) / d0, (p1[1] - p0[1]) / d0];
var d1 = ol_coordinate_dist2d(p0, pt);
var v1 = [(pt[0] - p0[0]) / d1, (pt[1] - p0[1]) / d1];
if (Math.abs(v0[0] * v1[1] - v0[1] * v1[0]) < 1e-10) {
return { index: 1, segment: [p0, p1] };
}
}
}
return { index: -1 };
};
var ol_extent_intersection;
(function() {
function splitX(pts, x) {
var pt;
for (let i = pts.length - 1; i > 0; i--) {
if (pts[i][0] > x && pts[i - 1][0] < x || pts[i][0] < x && pts[i - 1][0] > x) {
pt = [x, (x - pts[i][0]) / (pts[i - 1][0] - pts[i][0]) * (pts[i - 1][1] - pts[i][1]) + pts[i][1]];
pts.splice(i, 0, pt);
}
}
}
function splitY(pts, y) {
var pt;
for (let i = pts.length - 1; i > 0; i--) {
if (pts[i][1] > y && pts[i - 1][1] < y || pts[i][1] < y && pts[i - 1][1] > y) {
pt = [(y - pts[i][1]) / (pts[i - 1][1] - pts[i][1]) * (pts[i - 1][0] - pts[i][0]) + pts[i][0], y];
pts.splice(i, 0, pt);
}
}
}
ol_extent_intersection = function(extent, polygon) {
var poly = polygon.getType() === "Polygon";
if (!poly && polygon.getType() !== "MultiPolygon") return null;
var geom = polygon.getCoordinates();
if (poly) geom = [geom];
geom.forEach(function(g) {
g.forEach(function(c) {
splitX(c, extent[0]);
splitX(c, extent[2]);
splitY(c, extent[1]);
splitY(c, extent[3]);
});
});
geom.forEach(function(g) {
g.forEach(function(c) {
c.forEach(function(p) {
if (p[0] < extent[0]) p[0] = extent[0];
else if (p[0] > extent[2]) p[0] = extent[2];
if (p[1] < extent[1]) p[1] = extent[1];
else if (p[1] > extent[3]) p[1] = extent[3];
});
});
});
if (poly) {
return new Polygon_default(geom[0]);
} else {
return new MultiPolygon_default(geom);
}
};
})();
var ol_coordinate_sampleAt = function(p1, p2, d, start) {
var pts = [];
if (start !== false) pts.push(p1);
var dl = ol_coordinate_dist2d(p1, p2);
if (dl) {
var nb = Math.round(dl / d);
if (nb > 1) {
var dx = (p2[0] - p1[0]) / nb;
var dy = (p2[1] - p1[1]) / nb;
for (var i = 1; i < nb; i++) {
pts.push([p1[0] + dx * i, p1[1] + dy * i]);
}
}
}
pts.push(p2);
return pts;
};
LineString_default.prototype.sampleAt = function(d) {
var line = this.getCoordinates();
var result = [];
for (var i = 1; i < line.length; i++) {
result = result.concat(ol_coordinate_sampleAt(line[i - 1], line[i], d, i === 1));
}
return new LineString_default(result);
};
MultiLineString_default.prototype.sampleAt = function(d) {
var lines = this.getCoordinates();
var result = [];
lines.forEach(function(p) {
var l = [];
for (var i = 1; i < p.length; i++) {
l = l.concat(ol_coordinate_sampleAt(p[i - 1], p[i], d, i === 1));
}
result.push(l);
});
return new MultiLineString_default(result);
};
Polygon_default.prototype.sampleAt = function(res) {
var poly = this.getCoordinates();
var result = [];
poly.forEach(function(p) {
var l = [];
for (var i = 1; i < p.length; i++) {
l = l.concat(ol_coordinate_sampleAt(p[i - 1], p[i], res, i === 1));
}
result.push(l);
});
return new Polygon_default(result);
};
MultiPolygon_default.prototype.sampleAt = function(res) {
var mpoly = this.getCoordinates();
var result = [];
mpoly.forEach(function(poly) {
var a = [];
result.push(a);
poly.forEach(function(p) {
var l = [];
for (var i = 1; i < p.length; i++) {
l = l.concat(ol_coordinate_sampleAt(p[i - 1], p[i], res, i === 1));
}
a.push(l);
});
});
return new MultiPolygon_default(result);
};
Circle_default.prototype.intersection = function(geom, resolution) {
if (geom.sampleAt) {
var ext = buffer(this.getCenter().concat(this.getCenter()), this.getRadius());
geom = ol_extent_intersection(ext, geom);
geom = geom.simplify(resolution);
var c = this.getCenter();
var r = this.getRadius();
var g = geom.sampleAt(resolution).getCoordinates();
switch (geom.getType()) {
case "Polygon":
g = [g];
// fallthrough
case "MultiPolygon": {
var hasout = false;
var result = [];
g.forEach(function(poly) {
var a = [];
result.push(a);
poly.forEach(function(ring) {
var l = [];
a.push(l);
ring.forEach(function(p) {
var d = ol_coordinate_dist2d(c, p);
if (d > r) {
hasout = true;
l.push([
c[0] + r / d * (p[0] - c[0]),
c[1] + r / d * (p[1] - c[1])
]);
} else {
l.push(p);
}
});
});
});
if (!hasout) return geom;
if (geom.getType() === "Polygon") {
return new Polygon_default(result[0]);
} else {
return new MultiPolygon_default(result);
}
}
}
} else {
console.warn("[ol/geom/Circle~intersection] Unsupported geometry type: " + geom.getType());
}
return geom;
};
export {
ol_coordinate_dist2d,
ol_coordinate_equal,
ol_coordinate_offsetCoords,
ol_coordinate_findSegment
};
//# sourceMappingURL=chunk-3UNEODO2.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,59 +0,0 @@
import {
XYZ_default,
defaultTileLoadFunction
} from "./chunk-3ZDRPUXW.js";
import {
WORKER_OFFSCREEN_CANVAS
} from "./chunk-5XHD7RSF.js";
// node_modules/ol/source/OSM.js
var ATTRIBUTION = '&#169; <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors.';
var OSM = class extends XYZ_default {
/**
* @param {Options} [options] Open Street Map options.
*/
constructor(options) {
options = options || {};
let attributions;
if (options.attributions !== void 0) {
attributions = options.attributions;
} else {
attributions = [ATTRIBUTION];
}
const crossOrigin = options.crossOrigin !== void 0 ? options.crossOrigin : "anonymous";
const url = options.url !== void 0 ? options.url : "https://tile.openstreetmap.org/{z}/{x}/{y}.png";
super({
attributions,
attributionsCollapsible: false,
cacheSize: options.cacheSize,
crossOrigin,
interpolate: options.interpolate,
maxZoom: options.maxZoom !== void 0 ? options.maxZoom : 19,
reprojectionErrorThreshold: options.reprojectionErrorThreshold,
tileLoadFunction: (
/**
* @param {import("../ImageTile.js").default} tile Image tile
* @param {string} src Image src
*/
(tile, src) => {
const image = tile.getImage();
if (!WORKER_OFFSCREEN_CANVAS && image instanceof HTMLImageElement) {
image.referrerPolicy = "origin-when-cross-origin";
}
(options.tileLoadFunction || defaultTileLoadFunction)(tile, src);
}
),
transition: options.transition,
url,
wrapX: options.wrapX,
zDirection: options.zDirection
});
}
};
var OSM_default = OSM;
export {
ATTRIBUTION,
OSM_default
};
//# sourceMappingURL=chunk-43GYE2V5.js.map

View File

@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../ol/source/OSM.js"],
"sourcesContent": ["/**\n * @module ol/source/OSM\n */\n\nimport {WORKER_OFFSCREEN_CANVAS} from '../has.js';\nimport {defaultTileLoadFunction} from './TileImage.js';\nimport XYZ from './XYZ.js';\n\n/**\n * The attribution containing a link to the OpenStreetMap Copyright and License\n * page.\n * @const\n * @type {string}\n * @api\n */\nexport const ATTRIBUTION =\n '&#169; ' +\n '<a href=\"https://www.openstreetmap.org/copyright\" target=\"_blank\">OpenStreetMap</a> ' +\n 'contributors.';\n\n/**\n * @typedef {Object} Options\n * @property {import(\"./Source.js\").AttributionLike} [attributions] Attributions.\n * @property {number} [cacheSize] Deprecated. Use the cacheSize option on the layer instead.\n * @property {null|string} [crossOrigin='anonymous'] The `crossOrigin` attribute for loaded images. Note that\n * you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.\n * See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.\n * @property {boolean} [interpolate=true] Use interpolated values when resampling. By default,\n * linear interpolation is used when resampling. Set to false to use the nearest neighbor instead.\n * @property {number} [maxZoom=19] Max zoom.\n * @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels).\n * Higher values can increase reprojection performance, but decrease precision.\n * @property {import(\"../Tile.js\").LoadFunction} [tileLoadFunction] Optional function to load a tile given a URL. The default is\n * ```js\n * function(imageTile, src) {\n * imageTile.getImage().src = src;\n * };\n * ```\n * @property {number} [transition=250] Duration of the opacity transition for rendering.\n * To disable the opacity transition, pass `transition: 0`.\n * @property {string} [url='https://tile.openstreetmap.org/{z}/{x}/{y}.png'] URL template.\n * Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders.\n * @property {boolean} [wrapX=true] Whether to wrap the world horizontally.\n * @property {number|import(\"../array.js\").NearestDirectionFunction} [zDirection=0]\n * Choose whether to use tiles with a higher or lower zoom level when between integer\n * zoom levels. See {@link module:ol/tilegrid/TileGrid~TileGrid#getZForResolution}.\n */\n\n/**\n * @classdesc\n * Layer source for the OpenStreetMap tile server.\n * @api\n */\nclass OSM extends XYZ {\n /**\n * @param {Options} [options] Open Street Map options.\n */\n constructor(options) {\n options = options || {};\n\n let attributions;\n if (options.attributions !== undefined) {\n attributions = options.attributions;\n } else {\n attributions = [ATTRIBUTION];\n }\n\n const crossOrigin =\n options.crossOrigin !== undefined ? options.crossOrigin : 'anonymous';\n\n const url =\n options.url !== undefined\n ? options.url\n : 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';\n\n super({\n attributions: attributions,\n attributionsCollapsible: false,\n cacheSize: options.cacheSize,\n crossOrigin: crossOrigin,\n interpolate: options.interpolate,\n maxZoom: options.maxZoom !== undefined ? options.maxZoom : 19,\n reprojectionErrorThreshold: options.reprojectionErrorThreshold,\n tileLoadFunction:\n /**\n * @param {import(\"../ImageTile.js\").default} tile Image tile\n * @param {string} src Image src\n */\n (tile, src) => {\n const image = tile.getImage();\n // FIXME referrer policy for worker fetch requests\n if (!WORKER_OFFSCREEN_CANVAS && image instanceof HTMLImageElement) {\n image.referrerPolicy = 'origin-when-cross-origin';\n }\n (options.tileLoadFunction || defaultTileLoadFunction)(tile, src);\n },\n transition: options.transition,\n url: url,\n wrapX: options.wrapX,\n zDirection: options.zDirection,\n });\n }\n}\n\nexport default OSM;\n"],
"mappings": ";;;;;;;;;AAeO,IAAM,cACX;AAqCF,IAAM,MAAN,cAAkB,YAAI;AAAA;AAAA;AAAA;AAAA,EAIpB,YAAY,SAAS;AACnB,cAAU,WAAW,CAAC;AAEtB,QAAI;AACJ,QAAI,QAAQ,iBAAiB,QAAW;AACtC,qBAAe,QAAQ;AAAA,IACzB,OAAO;AACL,qBAAe,CAAC,WAAW;AAAA,IAC7B;AAEA,UAAM,cACJ,QAAQ,gBAAgB,SAAY,QAAQ,cAAc;AAE5D,UAAM,MACJ,QAAQ,QAAQ,SACZ,QAAQ,MACR;AAEN,UAAM;AAAA,MACJ;AAAA,MACA,yBAAyB;AAAA,MACzB,WAAW,QAAQ;AAAA,MACnB;AAAA,MACA,aAAa,QAAQ;AAAA,MACrB,SAAS,QAAQ,YAAY,SAAY,QAAQ,UAAU;AAAA,MAC3D,4BAA4B,QAAQ;AAAA,MACpC;AAAA;AAAA;AAAA;AAAA;AAAA,QAKE,CAAC,MAAM,QAAQ;AACb,gBAAM,QAAQ,KAAK,SAAS;AAE5B,cAAI,CAAC,2BAA2B,iBAAiB,kBAAkB;AACjE,kBAAM,iBAAiB;AAAA,UACzB;AACA,WAAC,QAAQ,oBAAoB,yBAAyB,MAAM,GAAG;AAAA,QACjE;AAAA;AAAA,MACF,YAAY,QAAQ;AAAA,MACpB;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,YAAY,QAAQ;AAAA,IACtB,CAAC;AAAA,EACH;AACF;AAEA,IAAO,cAAQ;",
"names": []
}

View File

@ -1,112 +0,0 @@
// node_modules/ol/math.js
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function squaredSegmentDistance(x, y, x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
if (dx !== 0 || dy !== 0) {
const t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x1 = x2;
y1 = y2;
} else if (t > 0) {
x1 += dx * t;
y1 += dy * t;
}
}
return squaredDistance(x, y, x1, y1);
}
function squaredDistance(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
return dx * dx + dy * dy;
}
function solveLinearSystem(mat) {
const n = mat.length;
for (let i = 0; i < n; i++) {
let maxRow = i;
let maxEl = Math.abs(mat[i][i]);
for (let r = i + 1; r < n; r++) {
const absValue = Math.abs(mat[r][i]);
if (absValue > maxEl) {
maxEl = absValue;
maxRow = r;
}
}
if (maxEl === 0) {
return null;
}
const tmp = mat[maxRow];
mat[maxRow] = mat[i];
mat[i] = tmp;
for (let j = i + 1; j < n; j++) {
const coef = -mat[j][i] / mat[i][i];
for (let k = i; k < n + 1; k++) {
if (i == k) {
mat[j][k] = 0;
} else {
mat[j][k] += coef * mat[i][k];
}
}
}
}
const x = new Array(n);
for (let l = n - 1; l >= 0; l--) {
x[l] = mat[l][n] / mat[l][l];
for (let m = l - 1; m >= 0; m--) {
mat[m][n] -= mat[m][l] * x[l];
}
}
return x;
}
function toDegrees(angleInRadians) {
return angleInRadians * 180 / Math.PI;
}
function toRadians(angleInDegrees) {
return angleInDegrees * Math.PI / 180;
}
function modulo(a, b) {
const r = a % b;
return r * b < 0 ? r + b : r;
}
function lerp(a, b, x) {
return a + x * (b - a);
}
function toFixed(n, decimals) {
const factor = Math.pow(10, decimals);
return Math.round(n * factor) / factor;
}
function round(n, decimals) {
return Math.round(toFixed(n, decimals));
}
function floor(n, decimals) {
return Math.floor(toFixed(n, decimals));
}
function ceil(n, decimals) {
return Math.ceil(toFixed(n, decimals));
}
function wrap(n, min, max) {
if (n >= min && n < max) {
return n;
}
const range = max - min;
return ((n - min) % range + range) % range + min;
}
export {
clamp,
squaredSegmentDistance,
squaredDistance,
solveLinearSystem,
toDegrees,
toRadians,
modulo,
lerp,
toFixed,
round,
floor,
ceil,
wrap
};
//# sourceMappingURL=chunk-54BTDBAD.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,276 +0,0 @@
import {
Feature_default as Feature_default2
} from "./chunk-W7BDJOQY.js";
import {
GeometryCollection_default,
LineString_default,
MultiLineString_default,
MultiPoint_default,
MultiPolygon_default
} from "./chunk-7JXPN73Q.js";
import {
Feature_default
} from "./chunk-E53S5GN6.js";
import {
Point_default,
Polygon_default,
linearRingsAreOriented,
linearRingssAreOriented,
orientLinearRings,
orientLinearRingsArray
} from "./chunk-AZGMK675.js";
import {
equivalent,
get,
getTransform
} from "./chunk-A3RXLHYB.js";
import {
abstract
} from "./chunk-Q5ZULJHM.js";
// node_modules/ol/format/Feature.js
var FeatureFormat = class {
constructor() {
this.dataProjection = void 0;
this.defaultFeatureProjection = void 0;
this.featureClass = /** @type {FeatureToFeatureClass<FeatureType>} */
Feature_default;
this.supportedMediaTypes = null;
}
/**
* Adds the data projection to the read options.
* @param {Document|Element|Object|string} source Source.
* @param {ReadOptions} [options] Options.
* @return {ReadOptions|undefined} Options.
* @protected
*/
getReadOptions(source, options) {
if (options) {
let dataProjection = options.dataProjection ? get(options.dataProjection) : this.readProjection(source);
if (options.extent && dataProjection && dataProjection.getUnits() === "tile-pixels") {
dataProjection = get(dataProjection);
dataProjection.setWorldExtent(options.extent);
}
options = {
dataProjection,
featureProjection: options.featureProjection
};
}
return this.adaptOptions(options);
}
/**
* Sets the `dataProjection` on the options, if no `dataProjection`
* is set.
* @param {WriteOptions|ReadOptions|undefined} options
* Options.
* @protected
* @return {WriteOptions|ReadOptions|undefined}
* Updated options.
*/
adaptOptions(options) {
return Object.assign(
{
dataProjection: this.dataProjection,
featureProjection: this.defaultFeatureProjection,
featureClass: this.featureClass
},
options
);
}
/**
* @abstract
* @return {Type} The format type.
*/
getType() {
return abstract();
}
/**
* Read a single feature from a source.
*
* @abstract
* @param {Document|Element|Object|string} source Source.
* @param {ReadOptions} [options] Read options.
* @return {FeatureType|Array<FeatureType>} Feature.
*/
readFeature(source, options) {
return abstract();
}
/**
* Read all features from a source.
*
* @abstract
* @param {Document|Element|ArrayBuffer|Object|string} source Source.
* @param {ReadOptions} [options] Read options.
* @return {Array<FeatureType>} Features.
*/
readFeatures(source, options) {
return abstract();
}
/**
* Read a single geometry from a source.
*
* @abstract
* @param {Document|Element|Object|string} source Source.
* @param {ReadOptions} [options] Read options.
* @return {import("../geom/Geometry.js").default} Geometry.
*/
readGeometry(source, options) {
return abstract();
}
/**
* Read the projection from a source.
*
* @abstract
* @param {Document|Element|Object|string} source Source.
* @return {import("../proj/Projection.js").default|undefined} Projection.
*/
readProjection(source) {
return abstract();
}
/**
* Encode a feature in this format.
*
* @abstract
* @param {Feature} feature Feature.
* @param {WriteOptions} [options] Write options.
* @return {string|ArrayBuffer} Result.
*/
writeFeature(feature, options) {
return abstract();
}
/**
* Encode an array of features in this format.
*
* @abstract
* @param {Array<Feature>} features Features.
* @param {WriteOptions} [options] Write options.
* @return {string|ArrayBuffer} Result.
*/
writeFeatures(features, options) {
return abstract();
}
/**
* Write a single geometry in this format.
*
* @abstract
* @param {import("../geom/Geometry.js").default} geometry Geometry.
* @param {WriteOptions} [options] Write options.
* @return {string|ArrayBuffer} Result.
*/
writeGeometry(geometry, options) {
return abstract();
}
};
var Feature_default3 = FeatureFormat;
function transformGeometryWithOptions(geometry, write, options) {
const featureProjection = options ? get(options.featureProjection) : null;
const dataProjection = options ? get(options.dataProjection) : null;
let transformed = geometry;
if (featureProjection && dataProjection && !equivalent(featureProjection, dataProjection)) {
if (write) {
transformed = /** @type {T} */
geometry.clone();
}
const fromProjection = write ? featureProjection : dataProjection;
const toProjection = write ? dataProjection : featureProjection;
if (fromProjection.getUnits() === "tile-pixels") {
transformed.transform(fromProjection, toProjection);
} else {
transformed.applyTransform(getTransform(fromProjection, toProjection));
}
}
if (write && options && /** @type {WriteOptions} */
options.decimals !== void 0) {
const power = Math.pow(
10,
/** @type {WriteOptions} */
options.decimals
);
const transform = function(coordinates) {
for (let i = 0, ii = coordinates.length; i < ii; ++i) {
coordinates[i] = Math.round(coordinates[i] * power) / power;
}
return coordinates;
};
if (transformed === geometry) {
transformed = /** @type {T} */
geometry.clone();
}
transformed.applyTransform(transform);
}
return transformed;
}
var GeometryConstructor = {
Point: Point_default,
LineString: LineString_default,
Polygon: Polygon_default,
MultiPoint: MultiPoint_default,
MultiLineString: MultiLineString_default,
MultiPolygon: MultiPolygon_default
};
function orientFlatCoordinates(flatCoordinates, ends, stride) {
if (Array.isArray(ends[0])) {
if (!linearRingssAreOriented(flatCoordinates, 0, ends, stride)) {
flatCoordinates = flatCoordinates.slice();
orientLinearRingsArray(flatCoordinates, 0, ends, stride);
}
return flatCoordinates;
}
if (!linearRingsAreOriented(flatCoordinates, 0, ends, stride)) {
flatCoordinates = flatCoordinates.slice();
orientLinearRings(flatCoordinates, 0, ends, stride);
}
return flatCoordinates;
}
function createRenderFeature(object, options) {
var _a;
const geometry = object.geometry;
if (!geometry) {
return [];
}
if (Array.isArray(geometry)) {
return geometry.map((geometry2) => createRenderFeature({ ...object, geometry: geometry2 })).flat();
}
const geometryType = geometry.type === "MultiPolygon" ? "Polygon" : geometry.type;
if (geometryType === "GeometryCollection" || geometryType === "Circle") {
throw new Error("Unsupported geometry type: " + geometryType);
}
const stride = geometry.layout.length;
return transformGeometryWithOptions(
new Feature_default2(
geometryType,
geometryType === "Polygon" ? orientFlatCoordinates(geometry.flatCoordinates, geometry.ends, stride) : geometry.flatCoordinates,
(_a = geometry.ends) == null ? void 0 : _a.flat(),
stride,
object.properties || {},
object.id
).enableSimplifyTransformed(),
false,
options
);
}
function createGeometry(object, options) {
if (!object) {
return null;
}
if (Array.isArray(object)) {
const geometries = object.map(
(geometry) => createGeometry(geometry, options)
);
return new GeometryCollection_default(geometries);
}
const Geometry = GeometryConstructor[object.type];
return transformGeometryWithOptions(
new Geometry(object.flatCoordinates, object.layout || "XY", object.ends),
false,
options
);
}
export {
Feature_default3 as Feature_default,
transformGeometryWithOptions,
createRenderFeature,
createGeometry
};
//# sourceMappingURL=chunk-56VFHHUN.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,17 +0,0 @@
// node_modules/ol/TileState.js
var TileState_default = {
IDLE: 0,
LOADING: 1,
LOADED: 2,
/**
* Indicates that tile loading failed
* @type {number}
*/
ERROR: 3,
EMPTY: 4
};
export {
TileState_default
};
//# sourceMappingURL=chunk-5D2XPBR2.js.map

View File

@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../ol/TileState.js"],
"sourcesContent": ["/**\n * @module ol/TileState\n */\n\n/**\n * @enum {number}\n */\nexport default {\n IDLE: 0,\n LOADING: 1,\n LOADED: 2,\n /**\n * Indicates that tile loading failed\n * @type {number}\n */\n ERROR: 3,\n EMPTY: 4,\n};\n"],
"mappings": ";AAOA,IAAO,oBAAQ;AAAA,EACb,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,EAKR,OAAO;AAAA,EACP,OAAO;AACT;",
"names": []
}

View File

@ -1,19 +0,0 @@
// node_modules/ol/obj.js
function clear(object) {
for (const property in object) {
delete object[property];
}
}
function isEmpty(object) {
let property;
for (property in object) {
return false;
}
return !property;
}
export {
clear,
isEmpty
};
//# sourceMappingURL=chunk-5RHQVMYD.js.map

View File

@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../ol/obj.js"],
"sourcesContent": ["/**\n * @module ol/obj\n */\n\n/**\n * Removes all properties from an object.\n * @param {Object<string, unknown>} object The object to clear.\n */\nexport function clear(object) {\n for (const property in object) {\n delete object[property];\n }\n}\n\n/**\n * Determine if an object has any properties.\n * @param {Object} object The object to check.\n * @return {boolean} The object is empty.\n */\nexport function isEmpty(object) {\n let property;\n for (property in object) {\n return false;\n }\n return !property;\n}\n"],
"mappings": ";AAQO,SAAS,MAAM,QAAQ;AAC5B,aAAW,YAAY,QAAQ;AAC7B,WAAO,OAAO,QAAQ;AAAA,EACxB;AACF;AAOO,SAAS,QAAQ,QAAQ;AAC9B,MAAI;AACJ,OAAK,YAAY,QAAQ;AACvB,WAAO;AAAA,EACT;AACA,SAAO,CAAC;AACV;",
"names": []
}

View File

@ -1,36 +0,0 @@
// node_modules/ol/has.js
var ua = typeof navigator !== "undefined" && typeof navigator.userAgent !== "undefined" ? navigator.userAgent.toLowerCase() : "";
var SAFARI = ua.includes("safari") && !ua.includes("chrom");
var SAFARI_BUG_237906 = SAFARI && (ua.includes("version/15.4") || /cpu (os|iphone os) 15_4 like mac os x/.test(ua));
var WEBKIT = ua.includes("webkit") && !ua.includes("edge");
var MAC = ua.includes("macintosh");
var DEVICE_PIXEL_RATIO = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
var WORKER_OFFSCREEN_CANVAS = typeof WorkerGlobalScope !== "undefined" && typeof OffscreenCanvas !== "undefined" && self instanceof WorkerGlobalScope;
var IMAGE_DECODE = typeof Image !== "undefined" && Image.prototype.decode;
var CREATE_IMAGE_BITMAP = typeof createImageBitmap === "function";
var PASSIVE_EVENT_LISTENERS = (function() {
let passive = false;
try {
const options = Object.defineProperty({}, "passive", {
get: function() {
passive = true;
}
});
window.addEventListener("_", null, options);
window.removeEventListener("_", null, options);
} catch {
}
return passive;
})();
export {
SAFARI_BUG_237906,
WEBKIT,
MAC,
DEVICE_PIXEL_RATIO,
WORKER_OFFSCREEN_CANVAS,
IMAGE_DECODE,
CREATE_IMAGE_BITMAP,
PASSIVE_EVENT_LISTENERS
};
//# sourceMappingURL=chunk-5XHD7RSF.js.map

View File

@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../ol/has.js"],
"sourcesContent": ["/**\n * @module ol/has\n */\n\nconst ua =\n typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined'\n ? navigator.userAgent.toLowerCase()\n : '';\n\n/**\n * User agent string says we are dealing with Safari as browser.\n * @type {boolean}\n */\nexport const SAFARI = ua.includes('safari') && !ua.includes('chrom');\n\n/**\n * https://bugs.webkit.org/show_bug.cgi?id=237906\n * @type {boolean}\n */\nexport const SAFARI_BUG_237906 =\n SAFARI &&\n (ua.includes('version/15.4') ||\n /cpu (os|iphone os) 15_4 like mac os x/.test(ua));\n\n/**\n * User agent string says we are dealing with a WebKit engine.\n * @type {boolean}\n */\nexport const WEBKIT = ua.includes('webkit') && !ua.includes('edge');\n\n/**\n * User agent string says we are dealing with a Mac as platform.\n * @type {boolean}\n */\nexport const MAC = ua.includes('macintosh');\n\n/**\n * The ratio between physical pixels and device-independent pixels\n * (dips) on the device (`window.devicePixelRatio`).\n * @const\n * @type {number}\n * @api\n */\nexport const DEVICE_PIXEL_RATIO =\n typeof devicePixelRatio !== 'undefined' ? devicePixelRatio : 1;\n\n/**\n * The execution context is a worker with OffscreenCanvas available.\n * @const\n * @type {boolean}\n */\nexport const WORKER_OFFSCREEN_CANVAS =\n typeof WorkerGlobalScope !== 'undefined' &&\n typeof OffscreenCanvas !== 'undefined' &&\n self instanceof WorkerGlobalScope; //eslint-disable-line\n\n/**\n * Image.prototype.decode() is supported.\n * @type {boolean}\n */\nexport const IMAGE_DECODE =\n typeof Image !== 'undefined' && Image.prototype.decode;\n\n/**\n * createImageBitmap() is supported.\n * @type {boolean}\n */\nexport const CREATE_IMAGE_BITMAP = typeof createImageBitmap === 'function';\n\n/**\n * @type {boolean}\n */\nexport const PASSIVE_EVENT_LISTENERS = (function () {\n let passive = false;\n try {\n const options = Object.defineProperty({}, 'passive', {\n get: function () {\n passive = true;\n },\n });\n\n // @ts-ignore Ignore invalid event type '_'\n window.addEventListener('_', null, options);\n // @ts-ignore Ignore invalid event type '_'\n window.removeEventListener('_', null, options);\n } catch {\n // passive not supported\n }\n return passive;\n})();\n"],
"mappings": ";AAIA,IAAM,KACJ,OAAO,cAAc,eAAe,OAAO,UAAU,cAAc,cAC/D,UAAU,UAAU,YAAY,IAChC;AAMC,IAAM,SAAS,GAAG,SAAS,QAAQ,KAAK,CAAC,GAAG,SAAS,OAAO;AAM5D,IAAM,oBACX,WACC,GAAG,SAAS,cAAc,KACzB,wCAAwC,KAAK,EAAE;AAM5C,IAAM,SAAS,GAAG,SAAS,QAAQ,KAAK,CAAC,GAAG,SAAS,MAAM;AAM3D,IAAM,MAAM,GAAG,SAAS,WAAW;AASnC,IAAM,qBACX,OAAO,qBAAqB,cAAc,mBAAmB;AAOxD,IAAM,0BACX,OAAO,sBAAsB,eAC7B,OAAO,oBAAoB,eAC3B,gBAAgB;AAMX,IAAM,eACX,OAAO,UAAU,eAAe,MAAM,UAAU;AAM3C,IAAM,sBAAsB,OAAO,sBAAsB;AAKzD,IAAM,2BAA2B,WAAY;AAClD,MAAI,UAAU;AACd,MAAI;AACF,UAAM,UAAU,OAAO,eAAe,CAAC,GAAG,WAAW;AAAA,MACnD,KAAK,WAAY;AACf,kBAAU;AAAA,MACZ;AAAA,IACF,CAAC;AAGD,WAAO,iBAAiB,KAAK,MAAM,OAAO;AAE1C,WAAO,oBAAoB,KAAK,MAAM,OAAO;AAAA,EAC/C,QAAQ;AAAA,EAER;AACA,SAAO;AACT,GAAG;",
"names": []
}

View File

@ -1,465 +0,0 @@
import {
Feature_default
} from "./chunk-E53S5GN6.js";
import {
Vector_default
} from "./chunk-T3TT2KJN.js";
import {
Interaction_default
} from "./chunk-MSWSBYBR.js";
import {
never,
shiftKeyOnly,
singleClick
} from "./chunk-QCJTGAWF.js";
import {
CollectionEventType_default,
Collection_default
} from "./chunk-M5TTSD4C.js";
import {
createEditingStyle
} from "./chunk-PAB2HIXK.js";
import {
getUid
} from "./chunk-Q5ZULJHM.js";
import {
Event_default
} from "./chunk-NGFXCWUF.js";
import {
TRUE,
extend
} from "./chunk-K25ZO44T.js";
import {
clear
} from "./chunk-5RHQVMYD.js";
// node_modules/ol/interaction/Select.js
var SelectEventType = {
/**
* Triggered when feature(s) has been (de)selected.
* @event SelectEvent#select
* @api
*/
SELECT: "select"
};
var SelectEvent = class extends Event_default {
/**
* @param {SelectEventType} type The event type.
* @param {Array<import("../Feature.js").default>} selected Selected features.
* @param {Array<import("../Feature.js").default>} deselected Deselected features.
* @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Associated
* {@link module:ol/MapBrowserEvent~MapBrowserEvent}.
*/
constructor(type, selected, deselected, mapBrowserEvent) {
super(type);
this.selected = selected;
this.deselected = deselected;
this.mapBrowserEvent = mapBrowserEvent;
}
};
var originalFeatureStyles = {};
var Select = class _Select extends Interaction_default {
/**
* @param {Options} [options] Options.
*/
constructor(options) {
super();
this.on;
this.once;
this.un;
options = options ? options : {};
this.boundAddFeature_ = this.addFeature_.bind(this);
this.boundRemoveFeature_ = this.removeFeature_.bind(this);
this.condition_ = options.condition ? options.condition : singleClick;
this.addCondition_ = options.addCondition ? options.addCondition : never;
this.removeCondition_ = options.removeCondition ? options.removeCondition : never;
this.toggleCondition_ = options.toggleCondition ? options.toggleCondition : shiftKeyOnly;
this.multi_ = options.multi ? options.multi : false;
this.filter_ = options.filter ? options.filter : TRUE;
this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0;
this.style_ = options.style !== void 0 ? options.style : getDefaultStyleFunction();
this.features_ = options.features || new Collection_default();
let layerFilter;
if (options.layers) {
if (typeof options.layers === "function") {
layerFilter = options.layers;
} else {
const layers = options.layers;
layerFilter = function(layer) {
return layers.includes(layer);
};
}
} else {
layerFilter = TRUE;
}
this.layerFilter_ = layerFilter;
this.featureLayerAssociation_ = {};
}
/**
* @param {import("../Feature.js").default} feature Feature.
* @param {import("../layer/Layer.js").default} layer Layer.
* @private
*/
addFeatureLayerAssociation_(feature, layer) {
this.featureLayerAssociation_[getUid(feature)] = layer;
}
/**
* Get the selected features.
* @return {Collection<Feature>} Features collection.
* @api
*/
getFeatures() {
return this.features_;
}
/**
* Returns the Hit-detection tolerance.
* @return {number} Hit tolerance in pixels.
* @api
*/
getHitTolerance() {
return this.hitTolerance_;
}
/**
* Returns the associated {@link module:ol/layer/Vector~VectorLayer vector layer} of
* a selected feature.
* @param {import("../Feature.js").default} feature Feature
* @return {import('../layer/Vector.js').default} Layer.
* @api
*/
getLayer(feature) {
return (
/** @type {import('../layer/Vector.js').default} */
this.featureLayerAssociation_[getUid(feature)]
);
}
/**
* Hit-detection tolerance. Pixels inside the radius around the given position
* will be checked for features.
* @param {number} hitTolerance Hit tolerance in pixels.
* @api
*/
setHitTolerance(hitTolerance) {
this.hitTolerance_ = hitTolerance;
}
/**
* Remove the interaction from its current map, if any, and attach it to a new
* map, if any. Pass `null` to just remove the interaction from the current map.
* @param {import("../Map.js").default|null} map Map.
* @api
* @override
*/
setMap(map) {
const currentMap = this.getMap();
if (currentMap && this.style_) {
this.features_.forEach(this.restorePreviousStyle_.bind(this));
}
super.setMap(map);
if (map) {
this.features_.addEventListener(
CollectionEventType_default.ADD,
this.boundAddFeature_
);
this.features_.addEventListener(
CollectionEventType_default.REMOVE,
this.boundRemoveFeature_
);
if (this.style_) {
this.features_.forEach(this.applySelectedStyle_.bind(this));
}
} else {
this.features_.removeEventListener(
CollectionEventType_default.ADD,
this.boundAddFeature_
);
this.features_.removeEventListener(
CollectionEventType_default.REMOVE,
this.boundRemoveFeature_
);
}
}
/**
* @param {import("../Collection.js").CollectionEvent<Feature>} evt Event.
* @private
*/
addFeature_(evt) {
const feature = evt.element;
if (this.style_) {
this.applySelectedStyle_(feature);
}
if (!this.getLayer(feature)) {
const layer = this.findLayerOfFeature_(feature);
if (layer) {
this.addFeatureLayerAssociation_(feature, layer);
}
}
}
/**
* @param {import("../Collection.js").CollectionEvent<Feature>} evt Event.
* @private
*/
removeFeature_(evt) {
if (this.style_) {
this.restorePreviousStyle_(evt.element);
}
}
/**
* @param {Feature} feature Feature of which to get the layer
* @return {VectorLayer} layer, if one was found.
* @private
*/
findLayerOfFeature_(feature) {
const layer = (
/** @type {VectorLayer} */
this.getMap().getAllLayers().find(function(layer2) {
if (layer2 instanceof Vector_default && layer2.getSource() && layer2.getSource().hasFeature(feature)) {
return layer2;
}
})
);
return layer;
}
/**
* @return {import("../style/Style.js").StyleLike|null} Select style.
*/
getStyle() {
return this.style_;
}
/**
* @param {Feature} feature Feature
* @private
*/
applySelectedStyle_(feature) {
const key = getUid(feature);
if (!(key in originalFeatureStyles)) {
originalFeatureStyles[key] = feature.getStyle();
}
feature.setStyle(this.style_);
}
/**
* @param {Feature} feature Feature
* @private
*/
restorePreviousStyle_(feature) {
const interactions = this.getMap().getInteractions().getArray();
for (let i = interactions.length - 1; i >= 0; --i) {
const interaction = interactions[i];
if (interaction !== this && interaction instanceof _Select && interaction.getStyle() && interaction.getFeatures().getArray().lastIndexOf(feature) !== -1) {
feature.setStyle(interaction.getStyle());
return;
}
}
const key = getUid(feature);
feature.setStyle(originalFeatureStyles[key]);
delete originalFeatureStyles[key];
}
/**
* @param {Feature} feature Feature.
* @private
*/
removeFeatureLayerAssociation_(feature) {
delete this.featureLayerAssociation_[getUid(feature)];
}
/**
* @param {import("../Feature.js").FeatureLike} feature The feature to select
* @param {import("../layer/Layer.js").default} layer Optional layer containing this feature
* @param {Array<Feature>} [selected] optional array to which selected features will be added
* @return {Feature|undefined} The feature, if it got selected.
* @private
*/
selectFeatureInternal_(feature, layer, selected) {
if (!(feature instanceof Feature_default)) {
return;
}
if (!this.filter_(feature, layer)) {
return;
}
const features = this.getFeatures();
if (!features.getArray().includes(feature)) {
this.addFeatureLayerAssociation_(feature, layer);
features.push(feature);
selected == null ? void 0 : selected.push(feature);
}
return feature;
}
/**
* Try to select a feature as if it was clicked and `addCondition` evaluated to True.
* Unlike modifying `select.getFeatures()` directly, this respects the `filter` and `layers` options (except `multi`, which is ignored).
* The {@link module:ol/interaction/Select~SelectEvent} fired by this won't have a mapBrowserEvent property
* @param {Feature} feature The feature to select
* @return {boolean} True if the feature was selected
*/
selectFeature(feature) {
const layer = this.findLayerOfFeature_(feature);
if (!this.layerFilter_(layer)) {
return false;
}
const selected = this.selectFeatureInternal_(feature, layer);
if (selected) {
this.dispatchEvent(
new SelectEvent(SelectEventType.SELECT, [selected], [], void 0)
);
}
return !!selected;
}
/**
* Deselects a feature if it was previously selected. Also removes layer association.
* @param {import("../Feature.js").FeatureLike} feature The feature to deselect
* @param {Array<Feature>} [deselected] optional array to which deselected features will be added
* @return {Feature|undefined} The feature, if it was previously selected.
* @private
*/
removeFeatureInternal_(feature, deselected) {
const features = this.getFeatures();
if (!(feature instanceof Feature_default) || !features.getArray().includes(feature)) {
return;
}
features.remove(feature);
this.removeFeatureLayerAssociation_(feature);
deselected == null ? void 0 : deselected.push(feature);
return feature;
}
/**
* Try to deselect a feature as if it was clicked.
* Compared to `select.getFeatures().remove(feature)` this causes a SelectEvent.
* The {@link module:ol/interaction/Select~SelectEvent} fired by this won't have a mapBrowserEvent property
* @param {Feature} feature The feature to deselect
* @return {boolean} True if the feature was deselected
*/
deselectFeature(feature) {
const deselected = this.removeFeatureInternal_(feature);
if (deselected) {
this.dispatchEvent(
new SelectEvent(SelectEventType.SELECT, [], [deselected], void 0)
);
}
return !!deselected;
}
/**
* Try to toggle a feature as if it was clicked and `toggleCondition` was True.
* Unlike modifying `select.getFeatures()` directly, this respects the `filter` and `layers` options (except `multi`, which is ignored).
* The {@link module:ol/interaction/Select~SelectEvent} fired by this won't have a mapBrowserEvent property
* @param {Feature} feature The feature to deselect
*/
toggleFeature(feature) {
if (!this.deselectFeature(feature)) {
this.selectFeature(feature);
}
}
/**
* Deselect all features as if a user deselected them.
* Compared to `select.getFeatures().clear()` this causes a SelectEvent.
* The {@link module:ol/interaction/Select~SelectEvent} fired by this won't have a mapBrowserEvent property
*/
clearSelection() {
clear(this.featureLayerAssociation_);
const features = this.getFeatures();
const deselected = features.getArray().slice();
features.clear();
if (deselected.length !== 0) {
this.dispatchEvent(
new SelectEvent(SelectEventType.SELECT, [], deselected, void 0)
);
}
}
/**
* Handles the {@link module:ol/MapBrowserEvent~MapBrowserEvent map browser event} and may change the
* selected state of features.
* @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event.
* @return {boolean} `false` to stop event propagation.
* @override
*/
handleEvent(mapBrowserEvent) {
if (!this.condition_(mapBrowserEvent)) {
return true;
}
const add = this.addCondition_(mapBrowserEvent);
const remove = this.removeCondition_(mapBrowserEvent);
const toggle = this.toggleCondition_(mapBrowserEvent);
const set = !add && !remove && !toggle;
const map = mapBrowserEvent.map;
const features = this.getFeatures();
const deselected = [];
const selected = [];
if (set) {
let foundAtCursor = false;
map.forEachFeatureAtPixel(
mapBrowserEvent.pixel,
(feature, layer) => {
foundAtCursor = true;
if (!this.selectFeatureInternal_(feature, layer, selected)) {
return;
}
return !this.multi_;
},
{
layerFilter: this.layerFilter_,
hitTolerance: this.hitTolerance_
}
);
for (let i = features.getLength() - 1; i >= 0; --i) {
const feature = features.item(i);
if (
// remove all but selected, if there were any selected
selected.length > 0 && !selected.includes(feature) || // remove all, if click outside of layer
!foundAtCursor
) {
this.removeFeatureInternal_(feature, deselected);
}
}
} else {
map.forEachFeatureAtPixel(
mapBrowserEvent.pixel,
(feature, layer) => {
let modifiedFeature;
if (remove || toggle) {
modifiedFeature = this.removeFeatureInternal_(feature, deselected);
}
if ((add || toggle) && !modifiedFeature) {
modifiedFeature = this.selectFeatureInternal_(
feature,
layer,
selected
);
}
if (!modifiedFeature) {
return;
}
return !this.multi_;
},
{
layerFilter: this.layerFilter_,
hitTolerance: this.hitTolerance_
}
);
}
if (selected.length > 0 || deselected.length > 0) {
this.dispatchEvent(
new SelectEvent(
SelectEventType.SELECT,
selected,
deselected,
mapBrowserEvent
)
);
}
return true;
}
};
function getDefaultStyleFunction() {
const styles = createEditingStyle();
extend(styles["Polygon"], styles["LineString"]);
extend(styles["GeometryCollection"], styles["LineString"]);
return function(feature) {
if (!feature.getGeometry()) {
return null;
}
return styles[feature.getGeometry().getType()];
};
}
var Select_default = Select;
export {
SelectEvent,
Select_default
};
//# sourceMappingURL=chunk-6DXBPPKF.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,68 +0,0 @@
// node_modules/ol/css.js
var CLASS_HIDDEN = "ol-hidden";
var CLASS_SELECTABLE = "ol-selectable";
var CLASS_UNSELECTABLE = "ol-unselectable";
var CLASS_CONTROL = "ol-control";
var CLASS_COLLAPSED = "ol-collapsed";
var fontRegEx = new RegExp(
[
"^\\s*(?=(?:(?:[-a-z]+\\s*){0,2}(italic|oblique))?)",
"(?=(?:(?:[-a-z]+\\s*){0,2}(small-caps))?)",
"(?=(?:(?:[-a-z]+\\s*){0,2}(bold(?:er)?|lighter|[1-9]00 ))?)",
"(?:(?:normal|\\1|\\2|\\3)\\s*){0,3}((?:xx?-)?",
"(?:small|large)|medium|smaller|larger|[\\.\\d]+(?:\\%|in|[cem]m|ex|p[ctx]))",
"(?:\\s*\\/\\s*(normal|[\\.\\d]+(?:\\%|in|[cem]m|ex|p[ctx])?))",
`?\\s*([-,\\"\\'\\sa-z0-9]+?)\\s*$`
].join(""),
"i"
);
var fontRegExMatchIndex = [
"style",
"variant",
"weight",
"size",
"lineHeight",
"family"
];
var fontWeights = {
normal: 400,
bold: 700
};
var getFontParameters = function(fontSpec) {
const match = fontSpec.match(fontRegEx);
if (!match) {
return null;
}
const style = (
/** @type {FontParameters} */
{
lineHeight: "normal",
size: "1.2em",
style: "normal",
weight: "400",
variant: "normal"
}
);
for (let i = 0, ii = fontRegExMatchIndex.length; i < ii; ++i) {
const value = match[i + 1];
if (value !== void 0) {
style[fontRegExMatchIndex[i]] = typeof value === "string" ? value.trim() : value;
}
}
if (isNaN(Number(style.weight)) && style.weight in fontWeights) {
style.weight = fontWeights[style.weight];
}
style.families = style.family.split(/,\s?/).map((f) => f.trim().replace(/^['"]|['"]$/g, ""));
return style;
};
export {
CLASS_HIDDEN,
CLASS_SELECTABLE,
CLASS_UNSELECTABLE,
CLASS_CONTROL,
CLASS_COLLAPSED,
fontWeights,
getFontParameters
};
//# sourceMappingURL=chunk-6EWLK2BW.js.map

View File

@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../ol/css.js"],
"sourcesContent": ["/**\n * @module ol/css\n */\n\n/**\n * @typedef {Object} FontParameters\n * @property {string} style Style.\n * @property {string} variant Variant.\n * @property {string} weight Weight.\n * @property {string} size Size.\n * @property {string} lineHeight LineHeight.\n * @property {string} family Family.\n * @property {Array<string>} families Families.\n */\n\n/**\n * The CSS class for hidden feature.\n *\n * @const\n * @type {string}\n */\nexport const CLASS_HIDDEN = 'ol-hidden';\n\n/**\n * The CSS class that we'll give the DOM elements to have them selectable.\n *\n * @const\n * @type {string}\n */\nexport const CLASS_SELECTABLE = 'ol-selectable';\n\n/**\n * The CSS class that we'll give the DOM elements to have them unselectable.\n *\n * @const\n * @type {string}\n */\nexport const CLASS_UNSELECTABLE = 'ol-unselectable';\n\n/**\n * The CSS class for unsupported feature.\n *\n * @const\n * @type {string}\n */\nexport const CLASS_UNSUPPORTED = 'ol-unsupported';\n\n/**\n * The CSS class for controls.\n *\n * @const\n * @type {string}\n */\nexport const CLASS_CONTROL = 'ol-control';\n\n/**\n * The CSS class that we'll give the DOM elements that are collapsed, i.e.\n * to those elements which usually can be expanded.\n *\n * @const\n * @type {string}\n */\nexport const CLASS_COLLAPSED = 'ol-collapsed';\n\n/**\n * From https://stackoverflow.com/questions/10135697/regex-to-parse-any-css-font\n * @type {RegExp}\n */\nconst fontRegEx = new RegExp(\n [\n '^\\\\s*(?=(?:(?:[-a-z]+\\\\s*){0,2}(italic|oblique))?)',\n '(?=(?:(?:[-a-z]+\\\\s*){0,2}(small-caps))?)',\n '(?=(?:(?:[-a-z]+\\\\s*){0,2}(bold(?:er)?|lighter|[1-9]00 ))?)',\n '(?:(?:normal|\\\\1|\\\\2|\\\\3)\\\\s*){0,3}((?:xx?-)?',\n '(?:small|large)|medium|smaller|larger|[\\\\.\\\\d]+(?:\\\\%|in|[cem]m|ex|p[ctx]))',\n '(?:\\\\s*\\\\/\\\\s*(normal|[\\\\.\\\\d]+(?:\\\\%|in|[cem]m|ex|p[ctx])?))',\n '?\\\\s*([-,\\\\\"\\\\\\'\\\\sa-z0-9]+?)\\\\s*$',\n ].join(''),\n 'i',\n);\n/** @type {Array<'style'|'variant'|'weight'|'size'|'lineHeight'|'family'>} */\nconst fontRegExMatchIndex = [\n 'style',\n 'variant',\n 'weight',\n 'size',\n 'lineHeight',\n 'family',\n];\n\n/** @type {Object<string|number, number>} */\nexport const fontWeights = {\n normal: 400,\n bold: 700,\n};\n\n/**\n * Get the list of font families from a font spec. Note that this doesn't work\n * for font families that have commas in them.\n * @param {string} fontSpec The CSS font property.\n * @return {FontParameters|null} The font parameters (or null if the input spec is invalid).\n */\nexport const getFontParameters = function (fontSpec) {\n const match = fontSpec.match(fontRegEx);\n if (!match) {\n return null;\n }\n const style = /** @type {FontParameters} */ ({\n lineHeight: 'normal',\n size: '1.2em',\n style: 'normal',\n weight: '400',\n variant: 'normal',\n });\n for (let i = 0, ii = fontRegExMatchIndex.length; i < ii; ++i) {\n const value = match[i + 1];\n if (value !== undefined) {\n style[fontRegExMatchIndex[i]] =\n typeof value === 'string' ? value.trim() : value;\n }\n }\n if (isNaN(Number(style.weight)) && style.weight in fontWeights) {\n style.weight = fontWeights[style.weight];\n }\n style.families = style.family\n .split(/,\\s?/)\n .map((f) => f.trim().replace(/^['\"]|['\"]$/g, ''));\n return style;\n};\n"],
"mappings": ";AAqBO,IAAM,eAAe;AAQrB,IAAM,mBAAmB;AAQzB,IAAM,qBAAqB;AAgB3B,IAAM,gBAAgB;AAStB,IAAM,kBAAkB;AAM/B,IAAM,YAAY,IAAI;AAAA,EACpB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,EAAE;AAAA,EACT;AACF;AAEA,IAAM,sBAAsB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,cAAc;AAAA,EACzB,QAAQ;AAAA,EACR,MAAM;AACR;AAQO,IAAM,oBAAoB,SAAU,UAAU;AACnD,QAAM,QAAQ,SAAS,MAAM,SAAS;AACtC,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AACA,QAAM;AAAA;AAAA,IAAuC;AAAA,MAC3C,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA;AACA,WAAS,IAAI,GAAG,KAAK,oBAAoB,QAAQ,IAAI,IAAI,EAAE,GAAG;AAC5D,UAAM,QAAQ,MAAM,IAAI,CAAC;AACzB,QAAI,UAAU,QAAW;AACvB,YAAM,oBAAoB,CAAC,CAAC,IAC1B,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI;AAAA,IAC/C;AAAA,EACF;AACA,MAAI,MAAM,OAAO,MAAM,MAAM,CAAC,KAAK,MAAM,UAAU,aAAa;AAC9D,UAAM,SAAS,YAAY,MAAM,MAAM;AAAA,EACzC;AACA,QAAM,WAAW,MAAM,OACpB,MAAM,MAAM,EACZ,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,gBAAgB,EAAE,CAAC;AAClD,SAAO;AACT;",
"names": []
}

View File

@ -1,130 +0,0 @@
import {
get
} from "./chunk-A3RXLHYB.js";
import {
Object_default
} from "./chunk-Q5ZULJHM.js";
// node_modules/ol/source/Source.js
var Source = class extends Object_default {
/**
* @param {Options} options Source options.
*/
constructor(options) {
super();
this.projection = get(options.projection);
this.attributions_ = adaptAttributions(options.attributions);
this.attributionsCollapsible_ = options.attributionsCollapsible ?? true;
this.loading = false;
this.state_ = options.state !== void 0 ? options.state : "ready";
this.wrapX_ = options.wrapX !== void 0 ? options.wrapX : false;
this.interpolate_ = !!options.interpolate;
this.viewResolver = null;
this.viewRejector = null;
const self = this;
this.viewPromise_ = new Promise(function(resolve, reject) {
self.viewResolver = resolve;
self.viewRejector = reject;
});
}
/**
* Get the attribution function for the source.
* @return {?Attribution} Attribution function.
* @api
*/
getAttributions() {
return this.attributions_;
}
/**
* @return {boolean} Attributions are collapsible.
* @api
*/
getAttributionsCollapsible() {
return this.attributionsCollapsible_;
}
/**
* Get the projection of the source.
* @return {import("../proj/Projection.js").default|null} Projection.
* @api
*/
getProjection() {
return this.projection;
}
/**
* @param {import("../proj/Projection").default} [projection] Projection.
* @return {Array<number>|null} Resolutions.
*/
getResolutions(projection) {
return null;
}
/**
* @return {Promise<import("../View.js").ViewOptions>} A promise for view-related properties.
*/
getView() {
return this.viewPromise_;
}
/**
* Get the state of the source, see {@link import("./Source.js").State} for possible states.
* @return {import("./Source.js").State} State.
* @api
*/
getState() {
return this.state_;
}
/**
* @return {boolean|undefined} Wrap X.
*/
getWrapX() {
return this.wrapX_;
}
/**
* @return {boolean} Use linear interpolation when resampling.
*/
getInterpolate() {
return this.interpolate_;
}
/**
* Refreshes the source. The source will be cleared, and data from the server will be reloaded.
* @api
*/
refresh() {
this.changed();
}
/**
* Set the attributions of the source.
* @param {AttributionLike|undefined} attributions Attributions.
* Can be passed as `string`, `Array<string>`, {@link module:ol/source/Source~Attribution},
* or `undefined`.
* @api
*/
setAttributions(attributions) {
this.attributions_ = adaptAttributions(attributions);
this.changed();
}
/**
* Set the state of the source.
* @param {import("./Source.js").State} state State.
*/
setState(state) {
this.state_ = state;
this.changed();
}
};
function adaptAttributions(attributionLike) {
if (!attributionLike) {
return null;
}
if (typeof attributionLike === "function") {
return attributionLike;
}
if (!Array.isArray(attributionLike)) {
attributionLike = [attributionLike];
}
return (frameState) => attributionLike;
}
var Source_default = Source;
export {
Source_default
};
//# sourceMappingURL=chunk-6Y7C6NBJ.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,329 +0,0 @@
import {
assert
} from "./chunk-QFCIXVZ3.js";
import {
clamp
} from "./chunk-54BTDBAD.js";
import {
Object_default,
abstract
} from "./chunk-Q5ZULJHM.js";
// node_modules/ol/layer/Property.js
var Property_default = {
OPACITY: "opacity",
VISIBLE: "visible",
EXTENT: "extent",
Z_INDEX: "zIndex",
MAX_RESOLUTION: "maxResolution",
MIN_RESOLUTION: "minResolution",
MAX_ZOOM: "maxZoom",
MIN_ZOOM: "minZoom",
SOURCE: "source",
MAP: "map"
};
// node_modules/ol/layer/Base.js
var BaseLayer = class extends Object_default {
/**
* @param {Options} options Layer options.
*/
constructor(options) {
super();
this.on;
this.once;
this.un;
this.background_ = options.background;
const properties = Object.assign({}, options);
if (typeof options.properties === "object") {
delete properties.properties;
Object.assign(properties, options.properties);
}
properties[Property_default.OPACITY] = options.opacity !== void 0 ? options.opacity : 1;
assert(
typeof properties[Property_default.OPACITY] === "number",
"Layer opacity must be a number"
);
properties[Property_default.VISIBLE] = options.visible !== void 0 ? options.visible : true;
properties[Property_default.Z_INDEX] = options.zIndex;
properties[Property_default.MAX_RESOLUTION] = options.maxResolution !== void 0 ? options.maxResolution : Infinity;
properties[Property_default.MIN_RESOLUTION] = options.minResolution !== void 0 ? options.minResolution : 0;
properties[Property_default.MIN_ZOOM] = options.minZoom !== void 0 ? options.minZoom : -Infinity;
properties[Property_default.MAX_ZOOM] = options.maxZoom !== void 0 ? options.maxZoom : Infinity;
this.className_ = properties.className !== void 0 ? properties.className : "ol-layer";
delete properties.className;
this.setProperties(properties);
this.state_ = null;
}
/**
* Get the background for this layer.
* @return {BackgroundColor|false} Layer background.
*/
getBackground() {
return this.background_;
}
/**
* @return {string} CSS class name.
*/
getClassName() {
return this.className_;
}
/**
* This method is not meant to be called by layers or layer renderers because the state
* is incorrect if the layer is included in a layer group.
*
* @param {boolean} [managed] Layer is managed.
* @return {import("./Layer.js").State} Layer state.
*/
getLayerState(managed) {
const state = this.state_ || /** @type {?} */
{
layer: this,
managed: managed === void 0 ? true : managed
};
const zIndex = this.getZIndex();
state.opacity = clamp(Math.round(this.getOpacity() * 100) / 100, 0, 1);
state.visible = this.getVisible();
state.extent = this.getExtent();
state.zIndex = zIndex === void 0 && !state.managed ? Infinity : zIndex;
state.maxResolution = this.getMaxResolution();
state.minResolution = Math.max(this.getMinResolution(), 0);
state.minZoom = this.getMinZoom();
state.maxZoom = this.getMaxZoom();
this.state_ = state;
return state;
}
/**
* @abstract
* @param {Array<import("./Layer.js").default>} [array] Array of layers (to be
* modified in place).
* @return {Array<import("./Layer.js").default>} Array of layers.
*/
getLayersArray(array) {
return abstract();
}
/**
* @abstract
* @param {Array<import("./Layer.js").State>} [states] Optional list of layer
* states (to be modified in place).
* @return {Array<import("./Layer.js").State>} List of layer states.
*/
getLayerStatesArray(states) {
return abstract();
}
/**
* Return the {@link module:ol/extent~Extent extent} of the layer or `undefined` if it
* will be visible regardless of extent.
* @return {import("../extent.js").Extent|undefined} The layer extent.
* @observable
* @api
*/
getExtent() {
return (
/** @type {import("../extent.js").Extent|undefined} */
this.get(Property_default.EXTENT)
);
}
/**
* Return the maximum resolution of the layer. Returns Infinity if
* the layer has no maximum resolution set.
* @return {number} The maximum resolution of the layer.
* @observable
* @api
*/
getMaxResolution() {
return (
/** @type {number} */
this.get(Property_default.MAX_RESOLUTION)
);
}
/**
* Return the minimum resolution of the layer. Returns 0 if
* the layer has no minimum resolution set.
* @return {number} The minimum resolution of the layer.
* @observable
* @api
*/
getMinResolution() {
return (
/** @type {number} */
this.get(Property_default.MIN_RESOLUTION)
);
}
/**
* Return the minimum zoom level of the layer. Returns -Infinity if
* the layer has no minimum zoom set.
* @return {number} The minimum zoom level of the layer.
* @observable
* @api
*/
getMinZoom() {
return (
/** @type {number} */
this.get(Property_default.MIN_ZOOM)
);
}
/**
* Return the maximum zoom level of the layer. Returns Infinity if
* the layer has no maximum zoom set.
* @return {number} The maximum zoom level of the layer.
* @observable
* @api
*/
getMaxZoom() {
return (
/** @type {number} */
this.get(Property_default.MAX_ZOOM)
);
}
/**
* Return the opacity of the layer (between 0 and 1).
* @return {number} The opacity of the layer.
* @observable
* @api
*/
getOpacity() {
return (
/** @type {number} */
this.get(Property_default.OPACITY)
);
}
/**
* @abstract
* @return {import("../source/Source.js").State} Source state.
*/
getSourceState() {
return abstract();
}
/**
* Return the value of this layer's `visible` property. To find out whether the layer
* is visible on a map, use `isVisible()` instead.
* @return {boolean} The value of the `visible` property of the layer.
* @observable
* @api
*/
getVisible() {
return (
/** @type {boolean} */
this.get(Property_default.VISIBLE)
);
}
/**
* Return the Z-index of the layer, which is used to order layers before
* rendering. Returns undefined if the layer is unmanaged.
* @return {number|undefined} The Z-index of the layer.
* @observable
* @api
*/
getZIndex() {
return (
/** @type {number|undefined} */
this.get(Property_default.Z_INDEX)
);
}
/**
* Sets the background color.
* @param {BackgroundColor} [background] Background color.
*/
setBackground(background) {
this.background_ = background;
this.changed();
}
/**
* Set the extent at which the layer is visible. If `undefined`, the layer
* will be visible at all extents.
* @param {import("../extent.js").Extent|undefined} extent The extent of the layer.
* @observable
* @api
*/
setExtent(extent) {
this.set(Property_default.EXTENT, extent);
}
/**
* Set the maximum resolution at which the layer is visible.
* @param {number} maxResolution The maximum resolution of the layer.
* @observable
* @api
*/
setMaxResolution(maxResolution) {
this.set(Property_default.MAX_RESOLUTION, maxResolution);
}
/**
* Set the minimum resolution at which the layer is visible.
* @param {number} minResolution The minimum resolution of the layer.
* @observable
* @api
*/
setMinResolution(minResolution) {
this.set(Property_default.MIN_RESOLUTION, minResolution);
}
/**
* Set the maximum zoom (exclusive) at which the layer is visible.
* Note that the zoom levels for layer visibility are based on the
* view zoom level, which may be different from a tile source zoom level.
* @param {number} maxZoom The maximum zoom of the layer.
* @observable
* @api
*/
setMaxZoom(maxZoom) {
this.set(Property_default.MAX_ZOOM, maxZoom);
}
/**
* Set the minimum zoom (inclusive) at which the layer is visible.
* Note that the zoom levels for layer visibility are based on the
* view zoom level, which may be different from a tile source zoom level.
* @param {number} minZoom The minimum zoom of the layer.
* @observable
* @api
*/
setMinZoom(minZoom) {
this.set(Property_default.MIN_ZOOM, minZoom);
}
/**
* Set the opacity of the layer, allowed values range from 0 to 1.
* @param {number} opacity The opacity of the layer.
* @observable
* @api
*/
setOpacity(opacity) {
assert(typeof opacity === "number", "Layer opacity must be a number");
this.set(Property_default.OPACITY, opacity);
}
/**
* Set the visibility of the layer (`true` or `false`).
* @param {boolean} visible The visibility of the layer.
* @observable
* @api
*/
setVisible(visible) {
this.set(Property_default.VISIBLE, visible);
}
/**
* Set Z-index of the layer, which is used to order layers before rendering.
* The default Z-index is 0.
* @param {number} zindex The z-index of the layer.
* @observable
* @api
*/
setZIndex(zindex) {
this.set(Property_default.Z_INDEX, zindex);
}
/**
* Clean up.
* @override
*/
disposeInternal() {
if (this.state_) {
this.state_.layer = null;
this.state_ = null;
}
super.disposeInternal();
}
};
var Base_default = BaseLayer;
export {
Property_default,
Base_default
};
//# sourceMappingURL=chunk-AYBYZSAV.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,38 +0,0 @@
// node_modules/ol/MapEventType.js
var MapEventType_default = {
/**
* Triggered after a map frame is rendered.
* @event module:ol/MapEvent~MapEvent#postrender
* @api
*/
POSTRENDER: "postrender",
/**
* Triggered when the map starts moving.
* @event module:ol/MapEvent~MapEvent#movestart
* @api
*/
MOVESTART: "movestart",
/**
* Triggered after the map is moved.
* @event module:ol/MapEvent~MapEvent#moveend
* @api
*/
MOVEEND: "moveend",
/**
* Triggered when loading of additional map data (tiles, images, features) starts.
* @event module:ol/MapEvent~MapEvent#loadstart
* @api
*/
LOADSTART: "loadstart",
/**
* Triggered when loading of additional map data has completed.
* @event module:ol/MapEvent~MapEvent#loadend
* @api
*/
LOADEND: "loadend"
};
export {
MapEventType_default
};
//# sourceMappingURL=chunk-BHVDQB66.js.map

View File

@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../ol/MapEventType.js"],
"sourcesContent": ["/**\n * @module ol/MapEventType\n */\n\n/**\n * @enum {string}\n */\nexport default {\n /**\n * Triggered after a map frame is rendered.\n * @event module:ol/MapEvent~MapEvent#postrender\n * @api\n */\n POSTRENDER: 'postrender',\n\n /**\n * Triggered when the map starts moving.\n * @event module:ol/MapEvent~MapEvent#movestart\n * @api\n */\n MOVESTART: 'movestart',\n\n /**\n * Triggered after the map is moved.\n * @event module:ol/MapEvent~MapEvent#moveend\n * @api\n */\n MOVEEND: 'moveend',\n\n /**\n * Triggered when loading of additional map data (tiles, images, features) starts.\n * @event module:ol/MapEvent~MapEvent#loadstart\n * @api\n */\n LOADSTART: 'loadstart',\n\n /**\n * Triggered when loading of additional map data has completed.\n * @event module:ol/MapEvent~MapEvent#loadend\n * @api\n */\n LOADEND: 'loadend',\n};\n\n/***\n * @typedef {'postrender'|'movestart'|'moveend'|'loadstart'|'loadend'} Types\n */\n"],
"mappings": ";AAOA,IAAO,uBAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMb,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOZ,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOX,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOX,SAAS;AACX;",
"names": []
}

View File

@ -1,114 +0,0 @@
// node_modules/geotiff/dist-module/predictor.js
function decodeRowAcc(row, stride) {
let length = row.length - stride;
let offset = 0;
do {
for (let i = stride; i > 0; i--) {
row[offset + stride] += row[offset];
offset++;
}
length -= stride;
} while (length > 0);
}
function decodeRowFloatingPoint(row, stride, bytesPerSample) {
let index = 0;
let count = row.length;
const wc = count / bytesPerSample;
while (count > stride) {
for (let i = stride; i > 0; --i) {
row[index + stride] += row[index];
++index;
}
count -= stride;
}
const copy = row.slice();
for (let i = 0; i < wc; ++i) {
for (let b = 0; b < bytesPerSample; ++b) {
row[bytesPerSample * i + b] = copy[(bytesPerSample - b - 1) * wc + i];
}
}
}
function applyPredictor(block, predictor, width, height, bitsPerSample, planarConfiguration) {
if (!predictor || predictor === 1) {
return block;
}
for (let i = 0; i < bitsPerSample.length; ++i) {
if (bitsPerSample[i] % 8 !== 0) {
throw new Error("When decoding with predictor, only multiple of 8 bits are supported.");
}
if (bitsPerSample[i] !== bitsPerSample[0]) {
throw new Error("When decoding with predictor, all samples must have the same size.");
}
}
const bytesPerSample = bitsPerSample[0] / 8;
const stride = planarConfiguration === 2 ? 1 : bitsPerSample.length;
for (let i = 0; i < height; ++i) {
if (i * stride * width * bytesPerSample >= block.byteLength) {
break;
}
let row;
if (predictor === 2) {
switch (bitsPerSample[0]) {
case 8:
row = new Uint8Array(
block,
i * stride * width * bytesPerSample,
stride * width * bytesPerSample
);
break;
case 16:
row = new Uint16Array(
block,
i * stride * width * bytesPerSample,
stride * width * bytesPerSample / 2
);
break;
case 32:
row = new Uint32Array(
block,
i * stride * width * bytesPerSample,
stride * width * bytesPerSample / 4
);
break;
default:
throw new Error(`Predictor 2 not allowed with ${bitsPerSample[0]} bits per sample.`);
}
decodeRowAcc(row, stride, bytesPerSample);
} else if (predictor === 3) {
row = new Uint8Array(
block,
i * stride * width * bytesPerSample,
stride * width * bytesPerSample
);
decodeRowFloatingPoint(row, stride, bytesPerSample);
}
}
return block;
}
// node_modules/geotiff/dist-module/compression/basedecoder.js
var BaseDecoder = class {
async decode(fileDirectory, buffer) {
const decoded = await this.decodeBlock(buffer);
const predictor = fileDirectory.Predictor || 1;
if (predictor !== 1) {
const isTiled = !fileDirectory.StripOffsets;
const tileWidth = isTiled ? fileDirectory.TileWidth : fileDirectory.ImageWidth;
const tileHeight = isTiled ? fileDirectory.TileLength : fileDirectory.RowsPerStrip || fileDirectory.ImageLength;
return applyPredictor(
decoded,
predictor,
tileWidth,
tileHeight,
fileDirectory.BitsPerSample,
fileDirectory.PlanarConfiguration
);
}
return decoded;
}
};
export {
BaseDecoder
};
//# sourceMappingURL=chunk-C5KGH6RQ.js.map

View File

@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../geotiff/dist-module/predictor.js", "../../geotiff/dist-module/compression/basedecoder.js"],
"sourcesContent": ["function decodeRowAcc(row, stride) {\n let length = row.length - stride;\n let offset = 0;\n do {\n for (let i = stride; i > 0; i--) {\n row[offset + stride] += row[offset];\n offset++;\n }\n\n length -= stride;\n } while (length > 0);\n}\n\nfunction decodeRowFloatingPoint(row, stride, bytesPerSample) {\n let index = 0;\n let count = row.length;\n const wc = count / bytesPerSample;\n\n while (count > stride) {\n for (let i = stride; i > 0; --i) {\n row[index + stride] += row[index];\n ++index;\n }\n count -= stride;\n }\n\n const copy = row.slice();\n for (let i = 0; i < wc; ++i) {\n for (let b = 0; b < bytesPerSample; ++b) {\n row[(bytesPerSample * i) + b] = copy[((bytesPerSample - b - 1) * wc) + i];\n }\n }\n}\n\nexport function applyPredictor(block, predictor, width, height, bitsPerSample,\n planarConfiguration) {\n if (!predictor || predictor === 1) {\n return block;\n }\n\n for (let i = 0; i < bitsPerSample.length; ++i) {\n if (bitsPerSample[i] % 8 !== 0) {\n throw new Error('When decoding with predictor, only multiple of 8 bits are supported.');\n }\n if (bitsPerSample[i] !== bitsPerSample[0]) {\n throw new Error('When decoding with predictor, all samples must have the same size.');\n }\n }\n\n const bytesPerSample = bitsPerSample[0] / 8;\n const stride = planarConfiguration === 2 ? 1 : bitsPerSample.length;\n\n for (let i = 0; i < height; ++i) {\n // Last strip will be truncated if height % stripHeight != 0\n if (i * stride * width * bytesPerSample >= block.byteLength) {\n break;\n }\n let row;\n if (predictor === 2) { // horizontal prediction\n switch (bitsPerSample[0]) {\n case 8:\n row = new Uint8Array(\n block, i * stride * width * bytesPerSample, stride * width * bytesPerSample,\n );\n break;\n case 16:\n row = new Uint16Array(\n block, i * stride * width * bytesPerSample, stride * width * bytesPerSample / 2,\n );\n break;\n case 32:\n row = new Uint32Array(\n block, i * stride * width * bytesPerSample, stride * width * bytesPerSample / 4,\n );\n break;\n default:\n throw new Error(`Predictor 2 not allowed with ${bitsPerSample[0]} bits per sample.`);\n }\n decodeRowAcc(row, stride, bytesPerSample);\n } else if (predictor === 3) { // horizontal floating point\n row = new Uint8Array(\n block, i * stride * width * bytesPerSample, stride * width * bytesPerSample,\n );\n decodeRowFloatingPoint(row, stride, bytesPerSample);\n }\n }\n return block;\n}\n", "import { applyPredictor } from '../predictor.js';\n\nexport default class BaseDecoder {\n async decode(fileDirectory, buffer) {\n const decoded = await this.decodeBlock(buffer);\n const predictor = fileDirectory.Predictor || 1;\n if (predictor !== 1) {\n const isTiled = !fileDirectory.StripOffsets;\n const tileWidth = isTiled ? fileDirectory.TileWidth : fileDirectory.ImageWidth;\n const tileHeight = isTiled ? fileDirectory.TileLength : (\n fileDirectory.RowsPerStrip || fileDirectory.ImageLength\n );\n return applyPredictor(\n decoded, predictor, tileWidth, tileHeight, fileDirectory.BitsPerSample,\n fileDirectory.PlanarConfiguration,\n );\n }\n return decoded;\n }\n}\n"],
"mappings": ";AAAA,SAAS,aAAa,KAAK,QAAQ;AACjC,MAAI,SAAS,IAAI,SAAS;AAC1B,MAAI,SAAS;AACb,KAAG;AACD,aAAS,IAAI,QAAQ,IAAI,GAAG,KAAK;AAC/B,UAAI,SAAS,MAAM,KAAK,IAAI,MAAM;AAClC;AAAA,IACF;AAEA,cAAU;AAAA,EACZ,SAAS,SAAS;AACpB;AAEA,SAAS,uBAAuB,KAAK,QAAQ,gBAAgB;AAC3D,MAAI,QAAQ;AACZ,MAAI,QAAQ,IAAI;AAChB,QAAM,KAAK,QAAQ;AAEnB,SAAO,QAAQ,QAAQ;AACrB,aAAS,IAAI,QAAQ,IAAI,GAAG,EAAE,GAAG;AAC/B,UAAI,QAAQ,MAAM,KAAK,IAAI,KAAK;AAChC,QAAE;AAAA,IACJ;AACA,aAAS;AAAA,EACX;AAEA,QAAM,OAAO,IAAI,MAAM;AACvB,WAAS,IAAI,GAAG,IAAI,IAAI,EAAE,GAAG;AAC3B,aAAS,IAAI,GAAG,IAAI,gBAAgB,EAAE,GAAG;AACvC,UAAK,iBAAiB,IAAK,CAAC,IAAI,MAAO,iBAAiB,IAAI,KAAK,KAAM,CAAC;AAAA,IAC1E;AAAA,EACF;AACF;AAEO,SAAS,eAAe,OAAO,WAAW,OAAO,QAAQ,eAC9D,qBAAqB;AACrB,MAAI,CAAC,aAAa,cAAc,GAAG;AACjC,WAAO;AAAA,EACT;AAEA,WAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,EAAE,GAAG;AAC7C,QAAI,cAAc,CAAC,IAAI,MAAM,GAAG;AAC9B,YAAM,IAAI,MAAM,sEAAsE;AAAA,IACxF;AACA,QAAI,cAAc,CAAC,MAAM,cAAc,CAAC,GAAG;AACzC,YAAM,IAAI,MAAM,oEAAoE;AAAA,IACtF;AAAA,EACF;AAEA,QAAM,iBAAiB,cAAc,CAAC,IAAI;AAC1C,QAAM,SAAS,wBAAwB,IAAI,IAAI,cAAc;AAE7D,WAAS,IAAI,GAAG,IAAI,QAAQ,EAAE,GAAG;AAE/B,QAAI,IAAI,SAAS,QAAQ,kBAAkB,MAAM,YAAY;AAC3D;AAAA,IACF;AACA,QAAI;AACJ,QAAI,cAAc,GAAG;AACnB,cAAQ,cAAc,CAAC,GAAG;AAAA,QACxB,KAAK;AACH,gBAAM,IAAI;AAAA,YACR;AAAA,YAAO,IAAI,SAAS,QAAQ;AAAA,YAAgB,SAAS,QAAQ;AAAA,UAC/D;AACA;AAAA,QACF,KAAK;AACH,gBAAM,IAAI;AAAA,YACR;AAAA,YAAO,IAAI,SAAS,QAAQ;AAAA,YAAgB,SAAS,QAAQ,iBAAiB;AAAA,UAChF;AACA;AAAA,QACF,KAAK;AACH,gBAAM,IAAI;AAAA,YACR;AAAA,YAAO,IAAI,SAAS,QAAQ;AAAA,YAAgB,SAAS,QAAQ,iBAAiB;AAAA,UAChF;AACA;AAAA,QACF;AACE,gBAAM,IAAI,MAAM,gCAAgC,cAAc,CAAC,CAAC,mBAAmB;AAAA,MACvF;AACA,mBAAa,KAAK,QAAQ,cAAc;AAAA,IAC1C,WAAW,cAAc,GAAG;AAC1B,YAAM,IAAI;AAAA,QACR;AAAA,QAAO,IAAI,SAAS,QAAQ;AAAA,QAAgB,SAAS,QAAQ;AAAA,MAC/D;AACA,6BAAuB,KAAK,QAAQ,cAAc;AAAA,IACpD;AAAA,EACF;AACA,SAAO;AACT;;;ACrFA,IAAqB,cAArB,MAAiC;AAAA,EAC/B,MAAM,OAAO,eAAe,QAAQ;AAClC,UAAM,UAAU,MAAM,KAAK,YAAY,MAAM;AAC7C,UAAM,YAAY,cAAc,aAAa;AAC7C,QAAI,cAAc,GAAG;AACnB,YAAM,UAAU,CAAC,cAAc;AAC/B,YAAM,YAAY,UAAU,cAAc,YAAY,cAAc;AACpE,YAAM,aAAa,UAAU,cAAc,aACzC,cAAc,gBAAgB,cAAc;AAE9C,aAAO;AAAA,QACL;AAAA,QAAS;AAAA,QAAW;AAAA,QAAW;AAAA,QAAY,cAAc;AAAA,QACzD,cAAc;AAAA,MAChB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;",
"names": []
}

View File

@ -1,397 +0,0 @@
import {
MapEventType_default
} from "./chunk-BHVDQB66.js";
import {
CLASS_SELECTABLE
} from "./chunk-6EWLK2BW.js";
import {
outerHeight,
outerWidth,
removeChildren
} from "./chunk-UPTVWZ45.js";
import {
Object_default
} from "./chunk-Q5ZULJHM.js";
import {
listen,
unlistenByKey
} from "./chunk-NGFXCWUF.js";
import {
containsExtent
} from "./chunk-SRXHWJOY.js";
// node_modules/ol/Overlay.js
var Property = {
ELEMENT: "element",
MAP: "map",
OFFSET: "offset",
POSITION: "position",
POSITIONING: "positioning"
};
var Overlay = class extends Object_default {
/**
* @param {Options} options Overlay options.
*/
constructor(options) {
super();
this.on;
this.once;
this.un;
this.options = options;
this.id = options.id;
this.insertFirst = options.insertFirst !== void 0 ? options.insertFirst : true;
this.stopEvent = options.stopEvent !== void 0 ? options.stopEvent : true;
this.element = document.createElement("div");
this.element.className = options.className !== void 0 ? options.className : "ol-overlay-container " + CLASS_SELECTABLE;
this.element.style.position = "absolute";
this.element.style.pointerEvents = "auto";
this.autoPan = options.autoPan === true ? {} : options.autoPan || void 0;
this.rendered = {
transform_: "",
visible: true
};
this.mapPostrenderListenerKey = null;
this.addChangeListener(Property.ELEMENT, this.handleElementChanged);
this.addChangeListener(Property.MAP, this.handleMapChanged);
this.addChangeListener(Property.OFFSET, this.handleOffsetChanged);
this.addChangeListener(Property.POSITION, this.handlePositionChanged);
this.addChangeListener(Property.POSITIONING, this.handlePositioningChanged);
if (options.element !== void 0) {
this.setElement(options.element);
}
this.setOffset(options.offset !== void 0 ? options.offset : [0, 0]);
this.setPositioning(options.positioning || "top-left");
if (options.position !== void 0) {
this.setPosition(options.position);
}
}
/**
* Get the DOM element of this overlay.
* @return {HTMLElement|undefined} The Element containing the overlay.
* @observable
* @api
*/
getElement() {
return (
/** @type {HTMLElement|undefined} */
this.get(Property.ELEMENT)
);
}
/**
* Get the overlay identifier which is set on constructor.
* @return {number|string|undefined} Id.
* @api
*/
getId() {
return this.id;
}
/**
* Get the map associated with this overlay.
* @return {import("./Map.js").default|null} The map that the
* overlay is part of.
* @observable
* @api
*/
getMap() {
return (
/** @type {import("./Map.js").default|null} */
this.get(Property.MAP) || null
);
}
/**
* Get the offset of this overlay.
* @return {Array<number>} The offset.
* @observable
* @api
*/
getOffset() {
return (
/** @type {Array<number>} */
this.get(Property.OFFSET)
);
}
/**
* Get the current position of this overlay.
* @return {import("./coordinate.js").Coordinate|undefined} The spatial point that the overlay is
* anchored at.
* @observable
* @api
*/
getPosition() {
return (
/** @type {import("./coordinate.js").Coordinate|undefined} */
this.get(Property.POSITION)
);
}
/**
* Get the current positioning of this overlay.
* @return {Positioning} How the overlay is positioned
* relative to its point on the map.
* @observable
* @api
*/
getPositioning() {
return (
/** @type {Positioning} */
this.get(Property.POSITIONING)
);
}
/**
* @protected
*/
handleElementChanged() {
removeChildren(this.element);
const element = this.getElement();
if (element) {
this.element.appendChild(element);
}
}
/**
* @protected
*/
handleMapChanged() {
var _a;
if (this.mapPostrenderListenerKey) {
(_a = this.element) == null ? void 0 : _a.remove();
unlistenByKey(this.mapPostrenderListenerKey);
this.mapPostrenderListenerKey = null;
}
const map = this.getMap();
if (map) {
this.mapPostrenderListenerKey = listen(
map,
MapEventType_default.POSTRENDER,
this.render,
this
);
this.updatePixelPosition();
const container = this.stopEvent ? map.getOverlayContainerStopEvent() : map.getOverlayContainer();
if (this.insertFirst) {
container.insertBefore(this.element, container.childNodes[0] || null);
} else {
container.appendChild(this.element);
}
this.performAutoPan();
}
}
/**
* @protected
*/
render() {
this.updatePixelPosition();
}
/**
* @protected
*/
handleOffsetChanged() {
this.updatePixelPosition();
}
/**
* @protected
*/
handlePositionChanged() {
this.updatePixelPosition();
this.performAutoPan();
}
/**
* @protected
*/
handlePositioningChanged() {
this.updatePixelPosition();
}
/**
* Set the DOM element to be associated with this overlay.
* @param {HTMLElement|undefined} element The Element containing the overlay.
* @observable
* @api
*/
setElement(element) {
this.set(Property.ELEMENT, element);
}
/**
* Set the map to be associated with this overlay.
* @param {import("./Map.js").default|null} map The map that the
* overlay is part of. Pass `null` to just remove the overlay from the current map.
* @observable
* @api
*/
setMap(map) {
this.set(Property.MAP, map);
}
/**
* Set the offset for this overlay.
* @param {Array<number>} offset Offset.
* @observable
* @api
*/
setOffset(offset) {
this.set(Property.OFFSET, offset);
}
/**
* Set the position for this overlay. If the position is `undefined` the
* overlay is hidden.
* @param {import("./coordinate.js").Coordinate|undefined} position The spatial point that the overlay
* is anchored at.
* @observable
* @api
*/
setPosition(position) {
this.set(Property.POSITION, position);
}
/**
* Pan the map so that the overlay is entirely visible in the current viewport
* (if necessary) using the configured autoPan parameters
* @protected
*/
performAutoPan() {
if (this.autoPan) {
this.panIntoView(this.autoPan);
}
}
/**
* Pan the map so that the overlay is entirely visible in the current viewport
* (if necessary).
* @param {PanIntoViewOptions} [panIntoViewOptions] Options for the pan action
* @api
*/
panIntoView(panIntoViewOptions) {
const map = this.getMap();
if (!map || !map.getTargetElement() || !this.get(Property.POSITION)) {
return;
}
const mapRect = this.getRect(map.getTargetElement(), map.getSize());
const element = this.getElement();
const overlayRect = this.getRect(element, [
outerWidth(element),
outerHeight(element)
]);
panIntoViewOptions = panIntoViewOptions || {};
const myMargin = panIntoViewOptions.margin === void 0 ? 20 : panIntoViewOptions.margin;
if (!containsExtent(mapRect, overlayRect)) {
const offsetLeft = overlayRect[0] - mapRect[0];
const offsetRight = mapRect[2] - overlayRect[2];
const offsetTop = overlayRect[1] - mapRect[1];
const offsetBottom = mapRect[3] - overlayRect[3];
const delta = [0, 0];
if (offsetLeft < 0) {
delta[0] = offsetLeft - myMargin;
} else if (offsetRight < 0) {
delta[0] = Math.abs(offsetRight) + myMargin;
}
if (offsetTop < 0) {
delta[1] = offsetTop - myMargin;
} else if (offsetBottom < 0) {
delta[1] = Math.abs(offsetBottom) + myMargin;
}
if (delta[0] !== 0 || delta[1] !== 0) {
const center = (
/** @type {import("./coordinate.js").Coordinate} */
map.getView().getCenterInternal()
);
const centerPx = map.getPixelFromCoordinateInternal(center);
if (!centerPx) {
return;
}
const newCenterPx = [centerPx[0] + delta[0], centerPx[1] + delta[1]];
const panOptions = panIntoViewOptions.animation || {};
map.getView().animateInternal({
center: map.getCoordinateFromPixelInternal(newCenterPx),
duration: panOptions.duration,
easing: panOptions.easing
});
}
}
}
/**
* Get the extent of an element relative to the document
* @param {HTMLElement} element The element.
* @param {import("./size.js").Size} size The size of the element.
* @return {import("./extent.js").Extent} The extent.
* @protected
*/
getRect(element, size) {
const box = element.getBoundingClientRect();
const offsetX = box.left + window.pageXOffset;
const offsetY = box.top + window.pageYOffset;
return [offsetX, offsetY, offsetX + size[0], offsetY + size[1]];
}
/**
* Set the positioning for this overlay.
* @param {Positioning} positioning how the overlay is
* positioned relative to its point on the map.
* @observable
* @api
*/
setPositioning(positioning) {
this.set(Property.POSITIONING, positioning);
}
/**
* Modify the visibility of the element.
* @param {boolean} visible Element visibility.
* @protected
*/
setVisible(visible) {
if (this.rendered.visible !== visible) {
this.element.style.display = visible ? "" : "none";
this.rendered.visible = visible;
}
}
/**
* Update pixel position.
* @protected
*/
updatePixelPosition() {
const map = this.getMap();
const position = this.getPosition();
if (!map || !map.isRendered() || !position) {
this.setVisible(false);
return;
}
const pixel = map.getPixelFromCoordinate(position);
const mapSize = map.getSize();
this.updateRenderedPosition(pixel, mapSize);
}
/**
* @param {import("./pixel.js").Pixel} pixel The pixel location.
* @param {import("./size.js").Size|undefined} mapSize The map size.
* @protected
*/
updateRenderedPosition(pixel, mapSize) {
const style = this.element.style;
const offset = this.getOffset();
const positioning = this.getPositioning();
this.setVisible(true);
const x = `${pixel[0] + offset[0]}px`;
const y = `${pixel[1] + offset[1]}px`;
let posX = "0%";
let posY = "0%";
if (positioning == "bottom-right" || positioning == "center-right" || positioning == "top-right") {
posX = "-100%";
} else if (positioning == "bottom-center" || positioning == "center-center" || positioning == "top-center") {
posX = "-50%";
}
if (positioning == "bottom-left" || positioning == "bottom-center" || positioning == "bottom-right") {
posY = "-100%";
} else if (positioning == "center-left" || positioning == "center-center" || positioning == "center-right") {
posY = "-50%";
}
const transform = `translate(${posX}, ${posY}) translate(${x}, ${y})`;
if (this.rendered.transform_ != transform) {
this.rendered.transform_ = transform;
style.transform = transform;
}
}
/**
* returns the options this Overlay has been created with
* @return {Options} overlay options
*/
getOptions() {
return this.options;
}
};
var Overlay_default = Overlay;
export {
Overlay_default
};
//# sourceMappingURL=chunk-C6SRSVJF.js.map

Some files were not shown because too many files have changed in this diff Show More