/** * GIS export modal controller. * * openExportGisModal({ title, kind, parcelFeatures, zoneFeatures, * otherByLayer, clipGeometry }) * * Opens the modal seeded with: * - a default filename ('area_analysis' / 'circle_analysis'), * - the union of all source-attribute keys across the intersecting * features, each pre-filled with its own name in the rename column. * * On Export: gathers the renamed map and calls exportFeaturesToGis(). * * Symmetric to src/import-modal.js — same Bootstrap modal pattern. */ import { Modal } from 'bootstrap'; import { exportFeaturesToGis } from './gis-export.js'; const els = {}; let modal = null; let state = null; function cacheEls() { if (els.root) return; els.root = document.getElementById('exportGisModal'); els.summary = document.getElementById('export-gis-summary'); els.filename = document.getElementById('export-gis-filename'); els.tbody = document.getElementById('export-gis-fields-tbody'); els.fmtHint = document.getElementById('export-gis-format-hint'); els.btnGo = document.getElementById('export-gis-go'); els.fmtInputs = Array.from(document.querySelectorAll('input[name="export-gis-format"]')); // Wire format-change handler so the SHP warning + filename suggestion react. if (!els.root.dataset.wired) { els.root.dataset.wired = '1'; els.fmtInputs.forEach((r) => r.addEventListener('change', onFormatChange)); els.btnGo.addEventListener('click', onExportClick); } } // --------------------------------------------------------------------------- // State helpers // --------------------------------------------------------------------------- function collectFeatures(ctx) { const out = []; for (const f of ctx.parcelFeatures || []) out.push(tagWithSource(f, 'Parcels')); for (const f of ctx.zoneFeatures || []) out.push(tagWithSource(f, 'Zones')); for (const [layerTitle, arr] of Object.entries(ctx.otherByLayer || {})) { for (const f of arr) out.push(tagWithSource(f, layerTitle)); } return out; } /** * Add a `_source` property to a cloned feature so the export carries layer * provenance. We clone so the source feature is never mutated. */ function tagWithSource(feature, sourceLabel) { const clone = feature.clone(); clone.set('_source', sourceLabel); return clone; } /** Union of source-attribute keys across all features (skipping internals). */ function unionAttributeKeys(features) { const skip = new Set(['geometry', '_layerType']); const seen = new Map(); for (const f of features) { for (const k of Object.keys(f.getProperties() || {})) { if (skip.has(k)) continue; if (!seen.has(k)) seen.set(k, true); } } // Ensure _source is always last so it shows up at the bottom of the table. seen.delete('_source'); const keys = Array.from(seen.keys()); keys.push('_source'); return keys; } // --------------------------------------------------------------------------- // Rendering // --------------------------------------------------------------------------- function renderFieldsTable() { const fmt = currentFormat(); els.tbody.innerHTML = state.keys.map((src) => { const current = state.rename[src] ?? src; const overLen = fmt === 'shp' && current.length > 10; const warn = overLen ? `
${escapeHtml(src)}