diff --git a/Components/Pages/Home.razor b/Components/Pages/Home.razor
index e242d6e..fe7b920 100644
--- a/Components/Pages/Home.razor
+++ b/Components/Pages/Home.razor
@@ -61,10 +61,24 @@
- Particelle selezionate
- Nascondi o mostra senza eliminare i dati
+ Particelle catastali evidenziate
+ Contorni rossi ottenuti da ricerca o GPS
+
+
+
+ Dati importati
+ Particelle e geometrie verdi di file e progetti
+
+
+
+
+ Ordine livelli e oggetti
+ Trascina dall'alto verso il basso
+
+
+
Elimina definitivamente le particelle selezionate
Pulisci elementi mappa
@@ -176,6 +190,10 @@
? "Attivato: importa foglio, particelle e altri elementi KML/KMZ."
: "Disattivato: importa solo i poligoni delle particelle riconosciute.")
+
+
+ Mostra particelle/dati importati in verde
+
Mostra cornice/layer del foglio importato
@@ -301,6 +319,10 @@
Visualizzazione progetto
+
+
+ Mostra particelle/dati importati in verde
+
Mostra cornice/layer del foglio importato
@@ -446,6 +468,7 @@
private bool GpsActive { get; set; }
private bool ShowGpsPosition { get; set; } = true;
private bool ShowSelectedParcels { get; set; } = true;
+ private bool ShowImportedData { get; set; } = true;
private bool DrawToolsVisible { get; set; }
private bool TrailEnabled { get; set; }
private bool LayerPanelOpen { get; set; }
@@ -681,6 +704,10 @@
}
await Task.Delay(30);
await JS.InvokeVoidAsync("lmeMap.resize");
+ if (LayerPanelOpen)
+ {
+ await JS.InvokeVoidAsync("lmeMap.refreshLayerManager");
+ }
}
private async Task ToggleLayerPanel()
@@ -693,6 +720,10 @@
await Task.Delay(30);
await JS.InvokeVoidAsync("lmeMap.resize");
+ if (LayerPanelOpen)
+ {
+ await JS.InvokeVoidAsync("lmeMap.refreshLayerManager");
+ }
}
private async Task SearchLocation()
@@ -1175,6 +1206,7 @@
SelectedBaseLayer = options.BaseLayer;
CadastralOpacity = options.CadastreOpacity;
IncludeKmlSheetLayer = options.IncludeKmlSheetLayer;
+ ShowImportedData = options.ImportedVisible;
ShowImportedSheetLayer = options.ImportedSheetsVisible;
ShowImportedMeasurements = options.ShowImportedMeasurements;
ShowDrawingMeasurements = options.ShowDrawingMeasurements;
@@ -1336,6 +1368,7 @@
{
IncludeCadastralParcels = GetFlagPreference("include_cadastral_parcels", true);
IncludeKmlSheetLayer = GetFlagPreference("include_kml_sheet_layer", false);
+ ShowImportedData = GetFlagPreference("show_imported_data", true);
ShowImportedSheetLayer = GetFlagPreference("show_imported_sheet_layer", false);
ShowImportedMeasurements = GetFlagPreference("show_imported_measurements", false);
ShowDrawingMeasurements = GetFlagPreference("show_drawing_measurements", true);
@@ -1360,6 +1393,7 @@
{
SetFlagPreference("include_cadastral_parcels", IncludeCadastralParcels);
SetFlagPreference("include_kml_sheet_layer", IncludeKmlSheetLayer);
+ SetFlagPreference("show_imported_data", ShowImportedData);
SetFlagPreference("show_imported_sheet_layer", ShowImportedSheetLayer);
SetFlagPreference("show_imported_measurements", ShowImportedMeasurements);
SetFlagPreference("show_drawing_measurements", ShowDrawingMeasurements);
@@ -1383,6 +1417,7 @@
private async Task ApplyFlagPreferencesToMapAsync()
{
await JS.InvokeVoidAsync("lmeMap.setIncludeKmlSheetLayer", IncludeKmlSheetLayer);
+ await JS.InvokeVoidAsync("lmeMap.setImportedLayerVisible", ShowImportedData);
await JS.InvokeVoidAsync("lmeMap.setImportedSheetLayerVisible", ShowImportedSheetLayer);
await JS.InvokeVoidAsync("lmeMap.setImportedMeasurementsVisible", ShowImportedMeasurements);
await JS.InvokeVoidAsync("lmeMap.setDrawingMeasurementsVisible", ShowDrawingMeasurements);
@@ -1409,6 +1444,12 @@
await JS.InvokeVoidAsync("lmeMap.setSelectedParcelsVisible", ShowSelectedParcels);
}
+ private async Task ApplyImportedDataVisibility()
+ {
+ SaveFlagPreferences();
+ await JS.InvokeVoidAsync("lmeMap.setImportedLayerVisible", ShowImportedData);
+ }
+
private async Task ExportCadastralImage()
{
ActiveTab = "map";
@@ -1442,6 +1483,7 @@
public double CadastreOpacity { get; init; } = 0.75;
public string[] CadastreLayers { get; init; } = [];
public bool IncludeKmlSheetLayer { get; init; }
+ public bool ImportedVisible { get; init; } = true;
public bool ImportedSheetsVisible { get; init; }
public bool ShowImportedMeasurements { get; init; }
public bool ShowDrawingMeasurements { get; init; } = true;
diff --git a/LMEMappeCatastaliApp.csproj b/LMEMappeCatastaliApp.csproj
index 7f14ed6..242c53d 100644
--- a/LMEMappeCatastaliApp.csproj
+++ b/LMEMappeCatastaliApp.csproj
@@ -28,8 +28,8 @@
com.companyname.lmemappecatastaliapp
- 0.2.17
- 21
+ 0.2.18
+ 22
None
@@ -44,7 +44,7 @@
-
+
diff --git a/Resources/AppIcon/appicon.svg b/Resources/AppIcon/appicon.svg
index 9d63b65..95a1bc4 100644
--- a/Resources/AppIcon/appicon.svg
+++ b/Resources/AppIcon/appicon.svg
@@ -1,4 +1,4 @@
-
-
-
-
\ No newline at end of file
+
+
+
+
diff --git a/Resources/AppIcon/appiconfg.svg b/Resources/AppIcon/appiconfg.svg
index 21dfb25..c886dba 100644
--- a/Resources/AppIcon/appiconfg.svg
+++ b/Resources/AppIcon/appiconfg.svg
@@ -1,8 +1,13 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
diff --git a/Services/AzureMapsService.cs b/Services/AzureMapsService.cs
index 4ff176f..4ad80f3 100644
--- a/Services/AzureMapsService.cs
+++ b/Services/AzureMapsService.cs
@@ -13,7 +13,7 @@ public sealed class AzureMapsService
public AzureMapsService()
{
- _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.17");
+ _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.18");
_httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/json");
}
diff --git a/Services/NetworkLocationService.cs b/Services/NetworkLocationService.cs
index 2e9e312..f573df6 100644
--- a/Services/NetworkLocationService.cs
+++ b/Services/NetworkLocationService.cs
@@ -13,7 +13,7 @@ public sealed class NetworkLocationService
public NetworkLocationService()
{
- httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.17");
+ httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2.18");
}
public async Task GetApproximateLocationAsync(
diff --git a/wwwroot/css/app.css b/wwwroot/css/app.css
index 299ce25..c5f0cb0 100644
--- a/wwwroot/css/app.css
+++ b/wwwroot/css/app.css
@@ -239,6 +239,98 @@ input[type="checkbox"] {
padding-top: 9px;
}
+.layer-tree-section {
+ display: grid;
+ gap: 6px;
+ padding-top: 9px;
+}
+
+.layer-tree-section > header {
+ display: grid;
+ gap: 2px;
+ padding: 0 4px;
+}
+
+.layer-tree-section > header small {
+ color: var(--muted);
+ font-size: 10px;
+}
+
+.layer-manager-tree,
+.layer-manager-children {
+ display: grid;
+ gap: 4px;
+}
+
+.layer-manager-group,
+.layer-manager-block,
+.layer-manager-object {
+ border: 1px solid #dce3e6;
+ border-radius: 6px;
+ background: #fff;
+}
+
+.layer-manager-group > summary,
+.layer-manager-block > summary,
+.layer-manager-object > summary {
+ display: grid;
+ grid-template-columns: 28px 20px minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 6px;
+ min-height: 38px;
+ padding: 4px 6px;
+ cursor: pointer;
+ list-style: none;
+}
+
+.layer-manager-group > summary::-webkit-details-marker,
+.layer-manager-block > summary::-webkit-details-marker,
+.layer-manager-object > summary::-webkit-details-marker {
+ display: none;
+}
+
+.layer-drag-handle {
+ width: 28px;
+ height: 28px;
+ border: 0;
+ border-radius: 4px;
+ background: #e9eef0;
+ color: var(--ink);
+ font-size: 18px;
+ line-height: 1;
+ touch-action: none;
+ cursor: grab;
+}
+
+.layer-manager-title {
+ min-width: 0;
+ overflow-wrap: anywhere;
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.layer-manager-count {
+ color: var(--muted);
+ font-size: 10px;
+}
+
+.layer-manager-children {
+ padding: 0 5px 5px 18px;
+}
+
+.layer-manager-details {
+ display: grid;
+ gap: 2px;
+ padding: 0 8px 7px 40px;
+ color: var(--muted);
+ font-size: 10px;
+}
+
+.layer-manager-dragging {
+ opacity: 0.55;
+ outline: 2px solid #2563eb;
+}
+
.gps-overlay {
position: absolute;
top: 72px;
diff --git a/wwwroot/js/lme-map.js b/wwwroot/js/lme-map.js
index 1f8964a..5db60b4 100644
--- a/wwwroot/js/lme-map.js
+++ b/wwwroot/js/lme-map.js
@@ -3,10 +3,21 @@ const lmeState = {
baseLayers: {},
currentBase: null,
currentBaseName: "satellite",
+ layerOrder: [
+ "gpsPosition",
+ "drawings",
+ "gpsTrail",
+ "gpsParcels",
+ "selectedParcels",
+ "importedSheets",
+ "imported",
+ "cadastre"
+ ],
cadastre: null,
cadastreLayers: ["CP.CadastralParcel", "codice_plla", "CP.CadastralZoning", "fabbricati"],
cadastreOpacity: 0.75,
imported: null,
+ importedVisible: true,
importedSheets: null,
importedSheetsVisible: false,
importedMeasurements: null,
@@ -36,7 +47,12 @@ const lmeState = {
resizeBound: false,
googleMapsLoading: null,
azureMapsKey: null,
- azureAttribution: null
+ azureAttribution: null,
+ managerHighlight: null,
+ managerHiddenObjects: new Map(),
+ managerHiddenObjectIds: new Set(),
+ managerHiddenCategories: new Set(),
+ managerObjectOrder: {}
};
// AdE publishes the cadastral WMS in ETRS89. WMS 1.1.1 keeps the longitude,
@@ -56,7 +72,10 @@ window.lmeMap = {
fitImported,
clearImported,
setIncludeKmlSheetLayer,
+ setImportedLayerVisible,
setImportedSheetLayerVisible,
+ setLayerOrder,
+ refreshLayerManager,
setImportedMeasurementsVisible,
setDrawingMeasurementsVisible,
addProjectMarker,
@@ -134,8 +153,15 @@ function init() {
});
lmeState.currentBase = lmeState.baseLayers.satellite.addTo(lmeState.map);
- const importedSheetPane = lmeState.map.createPane("importedSheetPane");
- importedSheetPane.style.zIndex = "520";
+ restoreLayerOrder();
+ Object.values(managedLayerPanes()).forEach(paneName => {
+ if (!lmeState.map.getPane(paneName)) {
+ lmeState.map.createPane(paneName);
+ }
+ });
+ const managerHighlightPane = lmeState.map.createPane("managerHighlightPane");
+ managerHighlightPane.style.zIndex = "650";
+ setLayerOrder(lmeState.layerOrder);
lmeState.imported = L.featureGroup().addTo(lmeState.map);
lmeState.importedSheets = L.featureGroup();
lmeState.drawings = L.featureGroup().addTo(lmeState.map);
@@ -143,7 +169,13 @@ function init() {
lmeState.drawingMeasurements = L.layerGroup().addTo(lmeState.map);
lmeState.selectedParcelsLayer = L.featureGroup().addTo(lmeState.map);
lmeState.gpsParcelLayer = L.featureGroup().addTo(lmeState.map);
- lmeState.gpsTrail = L.polyline([], { color: "#f97316", weight: 4, opacity: 0.85 }).addTo(lmeState.map);
+ lmeState.gpsTrail = L.polyline([], {
+ pane: "gpsTrailPane",
+ color: "#f97316",
+ weight: 4,
+ opacity: 0.85
+ }).addTo(lmeState.map);
+ lmeState.managerHighlight = L.layerGroup().addTo(lmeState.map);
refreshCadastreLayer();
bindFileImport();
@@ -265,6 +297,7 @@ function displayOfficialParcel(xmlText, latitude, longitude, parcel, label, shou
}
const layer = L.geoJSON(feature, {
+ pane: normalizedParcel ? "selectedParcelsPane" : "gpsParcelsPane",
style: normalizedParcel
? { color: "#ef4444", weight: 4, opacity: 1, fillColor: "#fbbf24", fillOpacity: 0.26 }
: { color: "#1d4ed8", weight: 4, opacity: 1, fillColor: "#38bdf8", fillOpacity: 0.2 }
@@ -282,6 +315,7 @@ function displayOfficialParcel(xmlText, latitude, longitude, parcel, label, shou
} else {
setText("parcelState", `Dentro particella AdE: ${ref}`);
}
+ refreshLayerManager();
} catch (error) {
setInfo(`Geometria catastale non leggibile: ${error.message}`);
}
@@ -490,6 +524,7 @@ function bindFileImport() {
const reprojected = result.reprojected ? "; coordinate Web Mercator convertite" : "";
setInfo(`Caricato ${file.name}: ${featureCount} particelle/elementi${includedSheets}${ignored}${reprojected}.`);
updateGpsInsideState();
+ refreshLayerManager();
} catch (error) {
setInfo(`File non leggibile: ${error.message}`);
} finally {
@@ -888,6 +923,458 @@ function setIncludeKmlSheetLayer(enabled) {
: "Importazione KML/KMZ: verranno caricate solo le particelle riconosciute.");
}
+function setImportedLayerVisible(enabled) {
+ lmeState.importedVisible = Boolean(enabled);
+ if (!lmeState.map || !lmeState.imported) {
+ return;
+ }
+
+ if (lmeState.importedVisible) {
+ if (!lmeState.map.hasLayer(lmeState.imported)) {
+ lmeState.imported.addTo(lmeState.map);
+ }
+ lmeState.imported.bringToFront();
+ const count = lmeState.imported.toGeoJSON().features?.length || 0;
+ setInfo(count
+ ? `${count} particelle o geometrie importate visibili.`
+ : "Nessun dato importato da mostrare.");
+ return;
+ }
+
+ if (lmeState.map.hasLayer(lmeState.imported)) {
+ lmeState.map.removeLayer(lmeState.imported);
+ }
+ setInfo("Dati importati nascosti; restano disponibili nel file o progetto.");
+}
+
+function managedLayerPanes() {
+ return {
+ gpsPosition: "gpsPositionPane",
+ drawings: "drawingsPane",
+ gpsTrail: "gpsTrailPane",
+ gpsParcels: "gpsParcelsPane",
+ selectedParcels: "selectedParcelsPane",
+ importedSheets: "importedSheetPane",
+ imported: "importedPane",
+ cadastre: "cadastrePane"
+ };
+}
+
+function setLayerOrder(order) {
+ const panes = managedLayerPanes();
+ const requested = Array.isArray(order) ? order.filter(key => panes[key]) : [];
+ const normalized = [...new Set([...requested, ...Object.keys(panes)])];
+ lmeState.layerOrder = normalized;
+ if (!lmeState.map) {
+ return;
+ }
+ normalized.forEach((key, index) => {
+ const pane = lmeState.map.getPane(panes[key]);
+ if (pane) {
+ pane.style.zIndex = String(590 - index * 20);
+ }
+ });
+ try {
+ localStorage.setItem("lmeMapLayerOrder", JSON.stringify(normalized));
+ } catch {
+ }
+}
+
+function restoreLayerOrder() {
+ try {
+ const saved = JSON.parse(localStorage.getItem("lmeMapLayerOrder") || "null");
+ if (Array.isArray(saved)) {
+ lmeState.layerOrder = saved;
+ }
+ } catch {
+ }
+}
+
+function saveLayerManagerState() {
+ // Object order and visibility are serialized in the project snapshot.
+}
+
+function applyStoredLayerManagerVisibility() {
+ layerManagerCategories().forEach(category => {
+ if (lmeState.managerHiddenCategories.has(category.key)) {
+ category.roots.forEach(layer => {
+ if (layer && lmeState.map.hasLayer(layer)) {
+ lmeState.map.removeLayer(layer);
+ }
+ });
+ }
+ managedRecordsForCategory(category).forEach(record => {
+ if (lmeState.managerHiddenObjectIds.has(record.id)
+ && !lmeState.managerHiddenObjects.has(record.id)) {
+ lmeState.managerHiddenObjects.set(record.id, record);
+ record.parent.removeLayer(record.layer);
+ }
+ });
+ });
+}
+
+function managedGeoJsonLabel(geojson, fallback) {
+ const features = geojson?.features || [];
+ const source = features.map(feature => feature?.properties?._lmeSource).find(Boolean);
+ return `${source || fallback} (${features.length})`;
+}
+
+function drawingLayerLabel(layer, layerType) {
+ const properties = layer?.feature?.properties || {};
+ if (properties.name) {
+ return `POI: ${properties.name}`;
+ }
+ const shape = layerType || properties._lmeShape || "disegno";
+ const labels = {
+ polygon: "Area",
+ rectangle: "Rettangolo",
+ polyline: "Linea",
+ circle: "Cerchio",
+ marker: "POI"
+ };
+ return labels[shape] || "Disegno";
+}
+
+function layerManagerCategories() {
+ return [
+ { key: "gpsPosition", label: "Posizione GPS", roots: [lmeState.gpsMarker, lmeState.gpsAccuracy].filter(Boolean) },
+ { key: "drawings", label: "Disegni e POI", roots: [lmeState.drawings].filter(Boolean) },
+ { key: "gpsTrail", label: "Traccia GPS", roots: [lmeState.gpsTrail].filter(Boolean) },
+ { key: "gpsParcels", label: "Particella rilevata dal GPS", roots: [lmeState.gpsParcelLayer].filter(Boolean) },
+ { key: "selectedParcels", label: "Particelle catastali evidenziate", roots: [lmeState.selectedParcelsLayer].filter(Boolean) },
+ { key: "importedSheets", label: "Cornici e fogli importati", roots: [lmeState.importedSheets].filter(Boolean), blocks: true },
+ { key: "imported", label: "Dati importati", roots: [lmeState.imported].filter(Boolean), blocks: true },
+ { key: "cadastre", label: "Layer catastali AdE", roots: [lmeState.cadastre].filter(Boolean) }
+ ];
+}
+
+function managedLayerId(layer, categoryKey) {
+ const properties = layer?.feature?.properties;
+ if (properties) {
+ properties._lmeManagerId ||= `${categoryKey}-${L.stamp(layer)}`;
+ return properties._lmeManagerId;
+ }
+ layer.lmeManagerId ||= `${categoryKey}-${L.stamp(layer)}`;
+ return layer.lmeManagerId;
+}
+
+function collectManagedLeafLayers(layer, categoryKey, parent, records) {
+ if (!layer) {
+ return;
+ }
+ if (typeof layer.getLayers === "function") {
+ const children = layer.getLayers();
+ children.forEach(child => collectManagedLeafLayers(child, categoryKey, layer, records));
+ return;
+ }
+ if (layer instanceof L.Polyline && !lineLatLngGroups(layer.getLatLngs()).some(line => line.length)) {
+ return;
+ }
+ const id = managedLayerId(layer, categoryKey);
+ records.push({
+ id,
+ categoryKey,
+ layer,
+ parent,
+ source: String(layer?.feature?.properties?._lmeSource || "Progetto corrente")
+ });
+}
+
+function managedRecordsForCategory(category) {
+ const records = [];
+ category.roots.forEach(root => collectManagedLeafLayers(root, category.key, lmeState.map, records));
+ lmeState.managerHiddenObjects.forEach(record => {
+ if (record.categoryKey === category.key && !records.some(item => item.id === record.id)) {
+ records.push(record);
+ }
+ });
+ return records;
+}
+
+function managedObjectLabel(record, index) {
+ const layer = record.layer;
+ const properties = layer?.feature?.properties || {};
+ const reference = properties.NATIONALCADASTRALREFERENCE
+ || properties.INSPIREID_LOCALID
+ || properties.LABEL
+ || properties.name
+ || properties.Name;
+ if (reference) {
+ return String(reference);
+ }
+ if (layer.lmeManagerLabel) {
+ return layer.lmeManagerLabel;
+ }
+ if (layer instanceof L.Circle) {
+ return `Cerchio ${index + 1}`;
+ }
+ if (layer instanceof L.Polygon) {
+ return `Area/particella ${index + 1}`;
+ }
+ if (layer instanceof L.Polyline) {
+ return `Linea ${index + 1}`;
+ }
+ if (layer instanceof L.Marker) {
+ return `POI ${index + 1}`;
+ }
+ return `Oggetto ${index + 1}`;
+}
+
+function managedObjectDetails(layer) {
+ const details = [];
+ const properties = layer?.feature?.properties || {};
+ if (properties.name || properties.Name) {
+ details.push(`Nome: ${properties.name || properties.Name}`);
+ }
+ if (layer instanceof L.Circle) {
+ const radius = layer.getRadius();
+ details.push(`Raggio: ${formatDistance(radius)}`);
+ details.push(`Superficie: ${formatArea(Math.PI * radius * radius)}`);
+ details.push(`Circonferenza: ${formatDistance(2 * Math.PI * radius)}`);
+ } else if (layer instanceof L.Polygon) {
+ polygonLatLngGroups(layer.getLatLngs()).forEach((polygon, polygonIndex) => {
+ const outer = polygon[0] || [];
+ const area = polygon.reduce((sum, ring, ringIndex) => {
+ const ringArea = Math.abs(L.GeometryUtil.geodesicArea(ring));
+ return sum + (ringIndex === 0 ? ringArea : -ringArea);
+ }, 0);
+ details.push(`${polygonIndex ? `Area ${polygonIndex + 1}` : "Superficie"}: ${formatArea(Math.abs(area))}`);
+ details.push(`${polygonIndex ? `Perimetro ${polygonIndex + 1}` : "Perimetro"}: ${formatDistance(latLngPathLength(outer, true))}`);
+ outer.forEach((point, index) => {
+ const next = outer[(index + 1) % outer.length];
+ if (next && !point.equals(next)) {
+ details.push(`Lato ${index + 1}: ${formatDistance(lmeState.map.distance(point, next))}`);
+ }
+ });
+ });
+ } else if (layer instanceof L.Polyline) {
+ const lines = lineLatLngGroups(layer.getLatLngs());
+ details.push(`Lunghezza: ${formatDistance(lines.reduce((sum, line) => sum + latLngPathLength(line, false), 0))}`);
+ } else if (typeof layer.getLatLng === "function") {
+ const point = layer.getLatLng();
+ details.push(`Coordinate: ${point.lat.toFixed(6)}, ${point.lng.toFixed(6)}`);
+ }
+ return details;
+}
+
+function refreshLayerManager() {
+ const container = document.getElementById("layerManagerTree");
+ if (!container || !lmeState.map) {
+ return;
+ }
+ applyStoredLayerManagerVisibility();
+ container.innerHTML = "";
+ lmeState.managerRecords = new Map();
+ const categoriesByKey = new Map(layerManagerCategories().map(category => [category.key, category]));
+ lmeState.layerOrder.forEach(categoryKey => {
+ const category = categoriesByKey.get(categoryKey);
+ if (!category) {
+ return;
+ }
+ const records = managedRecordsForCategory(category);
+ const savedOrder = lmeState.managerObjectOrder[category.key] || [];
+ const positions = new Map(savedOrder.map((id, index) => [id, index]));
+ records.sort((left, right) =>
+ (positions.get(left.id) ?? Number.MAX_SAFE_INTEGER) - (positions.get(right.id) ?? Number.MAX_SAFE_INTEGER));
+ records.forEach(record => lmeState.managerRecords.set(record.id, record));
+ container.appendChild(renderLayerManagerCategory(category, records));
+ });
+ applyManagedVisualOrder(container);
+ bindLayerManagerDrag(container);
+}
+
+function renderLayerManagerCategory(category, records) {
+ const group = document.createElement("details");
+ group.className = "layer-manager-group";
+ group.dataset.dragType = "category";
+ group.dataset.categoryKey = category.key;
+ const visible = !lmeState.managerHiddenCategories.has(category.key)
+ && (!category.roots.length || category.roots.every(layer => lmeState.map.hasLayer(layer)));
+ group.innerHTML = `☰ ${escapeHtml(category.label)} ${records.length}
`;
+ const checkbox = group.querySelector("input");
+ checkbox.addEventListener("click", event => event.stopPropagation());
+ checkbox.addEventListener("change", () => setManagedCategoryVisible(category, checkbox.checked));
+ const children = group.querySelector(".layer-manager-children");
+ if (category.blocks) {
+ const blocks = new Map();
+ records.forEach(record => {
+ const list = blocks.get(record.source) || [];
+ list.push(record);
+ blocks.set(record.source, list);
+ });
+ blocks.forEach((blockRecords, source) => children.appendChild(renderLayerManagerBlock(category, source, blockRecords)));
+ } else {
+ records.forEach((record, index) => children.appendChild(renderLayerManagerObject(record, index)));
+ }
+ return group;
+}
+
+function renderLayerManagerBlock(category, source, records) {
+ const block = document.createElement("details");
+ block.className = "layer-manager-block";
+ block.dataset.dragType = "block";
+ block.dataset.categoryKey = category.key;
+ block.dataset.blockKey = source;
+ const visibleCount = records.filter(record => !lmeState.managerHiddenObjectIds.has(record.id)).length;
+ const visible = visibleCount === records.length;
+ block.innerHTML = `☰ ${escapeHtml(source)} ${records.length}
`;
+ const checkbox = block.querySelector("input");
+ checkbox.indeterminate = visibleCount > 0 && !visible;
+ checkbox.addEventListener("click", event => event.stopPropagation());
+ checkbox.addEventListener("change", () => {
+ records.forEach(record => setManagedObjectVisible(record, checkbox.checked, false));
+ refreshLayerManager();
+ });
+ const children = block.querySelector(".layer-manager-children");
+ records.forEach((record, index) => children.appendChild(renderLayerManagerObject(record, index)));
+ return block;
+}
+
+function renderLayerManagerObject(record, index) {
+ const object = document.createElement("details");
+ object.className = "layer-manager-object";
+ object.dataset.dragType = "object";
+ object.dataset.objectId = record.id;
+ const visible = !lmeState.managerHiddenObjectIds.has(record.id);
+ object.innerHTML = `☰ ${escapeHtml(managedObjectLabel(record, index))} ›
`;
+ const checkbox = object.querySelector("input");
+ checkbox.addEventListener("click", event => event.stopPropagation());
+ checkbox.addEventListener("change", () => setManagedObjectVisible(record, checkbox.checked, true));
+ object.querySelector(".layer-manager-title").addEventListener("click", event => {
+ event.preventDefault();
+ event.stopPropagation();
+ object.open = true;
+ highlightManagedObject(record);
+ });
+ const details = object.querySelector(".layer-manager-details");
+ const rows = managedObjectDetails(record.layer);
+ details.innerHTML = rows.length
+ ? rows.map(value => `${escapeHtml(value)} `).join("")
+ : "Nessuna misura disponibile ";
+ return object;
+}
+
+function setManagedCategoryVisible(category, visible) {
+ if (category.key === "imported") {
+ lmeState.importedVisible = visible;
+ } else if (category.key === "importedSheets") {
+ lmeState.importedSheetsVisible = visible;
+ } else if (category.key === "selectedParcels") {
+ lmeState.selectedParcelsVisible = visible;
+ } else if (category.key === "gpsPosition") {
+ lmeState.gpsPositionVisible = visible;
+ }
+ if (visible) {
+ lmeState.managerHiddenCategories.delete(category.key);
+ category.roots.forEach(layer => {
+ if (layer && !lmeState.map.hasLayer(layer)) {
+ layer.addTo(lmeState.map);
+ }
+ });
+ } else {
+ lmeState.managerHiddenCategories.add(category.key);
+ category.roots.forEach(layer => {
+ if (layer && lmeState.map.hasLayer(layer)) {
+ lmeState.map.removeLayer(layer);
+ }
+ });
+ }
+ saveLayerManagerState();
+}
+
+function setManagedObjectVisible(record, visible, refresh = true) {
+ if (visible) {
+ const hidden = lmeState.managerHiddenObjects.get(record.id);
+ if (hidden) {
+ hidden.parent.addLayer(hidden.layer);
+ lmeState.managerHiddenObjects.delete(record.id);
+ }
+ lmeState.managerHiddenObjectIds.delete(record.id);
+ } else if (!lmeState.managerHiddenObjects.has(record.id)) {
+ lmeState.managerHiddenObjects.set(record.id, record);
+ lmeState.managerHiddenObjectIds.add(record.id);
+ record.parent.removeLayer(record.layer);
+ }
+ saveLayerManagerState();
+ refreshImportedMeasurements();
+ refreshDrawingMeasurements();
+ if (refresh) {
+ refreshLayerManager();
+ }
+}
+
+function highlightManagedObject(record) {
+ lmeState.managerHighlight.clearLayers();
+ const geojson = record.layer.toGeoJSON?.();
+ if (geojson) {
+ L.geoJSON(geojson, {
+ pane: "managerHighlightPane",
+ interactive: false,
+ style: { color: "#ff00d4", weight: 8, opacity: 1, fillColor: "#00fff0", fillOpacity: 0.16 },
+ pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
+ pane: "managerHighlightPane",
+ radius: 12,
+ color: "#ff00d4",
+ weight: 5,
+ fillColor: "#00fff0",
+ fillOpacity: 0.9,
+ interactive: false
+ })
+ }).addTo(lmeState.managerHighlight);
+ }
+ fitLayer(record.layer);
+}
+
+function bindLayerManagerDrag(container) {
+ container.querySelectorAll(".layer-drag-handle").forEach(handle => {
+ handle.addEventListener("pointerdown", event => {
+ event.preventDefault();
+ event.stopPropagation();
+ const row = handle.closest("[data-drag-type]");
+ const list = row.parentElement;
+ row.classList.add("layer-manager-dragging");
+ handle.setPointerCapture?.(event.pointerId);
+ const move = moveEvent => {
+ const target = document.elementFromPoint(moveEvent.clientX, moveEvent.clientY)?.closest(`[data-drag-type="${row.dataset.dragType}"]`);
+ if (!target || target === row || target.parentElement !== list) {
+ return;
+ }
+ const targetRect = target.getBoundingClientRect();
+ list.insertBefore(row, moveEvent.clientY < targetRect.top + targetRect.height / 2 ? target : target.nextSibling);
+ };
+ const end = () => {
+ row.classList.remove("layer-manager-dragging");
+ handle.removeEventListener("pointermove", move);
+ handle.removeEventListener("pointerup", end);
+ handle.removeEventListener("pointercancel", end);
+ applyLayerManagerDomOrder(container);
+ };
+ handle.addEventListener("pointermove", move);
+ handle.addEventListener("pointerup", end);
+ handle.addEventListener("pointercancel", end);
+ });
+ });
+}
+
+function applyLayerManagerDomOrder(container) {
+ const categoryKeys = [...container.children].map(row => row.dataset.categoryKey).filter(Boolean);
+ setLayerOrder(categoryKeys);
+ container.querySelectorAll(":scope > .layer-manager-group").forEach(group => {
+ const objectIds = [...group.querySelectorAll(".layer-manager-object")].map(row => row.dataset.objectId).filter(Boolean);
+ lmeState.managerObjectOrder[group.dataset.categoryKey] = objectIds;
+ });
+ applyManagedVisualOrder(container);
+ saveLayerManagerState();
+}
+
+function applyManagedVisualOrder(container) {
+ container.querySelectorAll(":scope > .layer-manager-group").forEach(group => {
+ const objectIds = [...group.querySelectorAll(".layer-manager-object")].map(row => row.dataset.objectId).filter(Boolean);
+ [...objectIds].reverse().forEach(id => lmeState.managerRecords.get(id)?.layer?.bringToFront?.());
+ objectIds.forEach((id, index) => lmeState.managerRecords.get(id)?.layer?.setZIndexOffset?.(1000 - index));
+ });
+}
+
function setImportedSheetLayerVisible(enabled) {
migrateMisclassifiedImportedSheets();
lmeState.importedSheetsVisible = Boolean(enabled);
@@ -981,9 +1468,11 @@ function migrateMisclassifiedImportedSheets() {
}
function addImportedFeatures(geojson) {
- return L.geoJSON(geojson || emptyFeatureCollection(), {
+ const layer = L.geoJSON(geojson || emptyFeatureCollection(), {
+ pane: "importedPane",
style: { color: "#22c55e", weight: 3, opacity: 0.95, fillOpacity: 0.18 },
pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
+ pane: "importedPane",
radius: 7,
color: "#22c55e",
fillColor: "#ffffff",
@@ -991,10 +1480,12 @@ function addImportedFeatures(geojson) {
}),
onEachFeature: bindStoredFeaturePopup
}).addTo(lmeState.imported);
+ layer.lmeManagerLabel = managedGeoJsonLabel(geojson, "Dati importati");
+ return layer;
}
function addImportedSheetFeatures(geojson) {
- return L.geoJSON(geojson || emptyFeatureCollection(), {
+ const layer = L.geoJSON(geojson || emptyFeatureCollection(), {
pane: "importedSheetPane",
style: { color: "#f59e0b", weight: 4, opacity: 1, fillOpacity: 0.03, dashArray: "10 6" },
pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
@@ -1006,6 +1497,8 @@ function addImportedSheetFeatures(geojson) {
}),
onEachFeature: bindStoredFeaturePopup
}).addTo(lmeState.importedSheets);
+ layer.lmeManagerLabel = managedGeoJsonLabel(geojson, "Cornice o foglio");
+ return layer;
}
function applyImportedSheetVisibility() {
@@ -1049,6 +1542,8 @@ function fitImported() {
}
function clearImported() {
+ clearManagedCategoryState("imported");
+ clearManagedCategoryState("importedSheets");
lmeState.imported.clearLayers();
lmeState.importedSheets.clearLayers();
lmeState.importedSheetsVisible = false;
@@ -1056,12 +1551,14 @@ function clearImported() {
lmeState.importedMeasurements.clearLayers();
setInfo("File GIS rimossi.");
updateGpsInsideState();
+ refreshLayerManager();
}
function addProjectMarker(name) {
const center = lmeState.map.getCenter();
const label = (name || "Nuovo POI").trim();
- const marker = L.marker(center, { draggable: true }).addTo(lmeState.drawings);
+ const marker = L.marker(center, { pane: "drawingsPane", draggable: true }).addTo(lmeState.drawings);
+ marker.lmeManagerLabel = `POI: ${label}`;
marker.feature = {
type: "Feature",
properties: { name: label }
@@ -1069,6 +1566,7 @@ function addProjectMarker(name) {
marker.bindPopup(`${escapeHtml(label)} ${center.lat.toFixed(6)}, ${center.lng.toFixed(6)}`).openPopup();
refreshDrawingMeasurements();
setInfo(`POI aggiunto: ${label}`);
+ refreshLayerManager();
}
function configureDrawLabels() {
@@ -1105,15 +1603,18 @@ function bindDrawingEvents() {
marker: "Punto aggiunto alla mappa."
};
setInfo(completedLabels[event.layerType] || "Disegno completato.");
+ refreshLayerManager();
});
lmeState.map.on(L.Draw.Event.EDITED, event => {
event.layers.eachLayer(layer => annotateDrawingLayer(layer));
refreshDrawingMeasurements();
updateGpsInsideState();
+ refreshLayerManager();
});
lmeState.map.on(L.Draw.Event.DELETED, () => {
refreshDrawingMeasurements();
updateGpsInsideState();
+ refreshLayerManager();
});
lmeState.map.on(L.Draw.Event.DRAWSTOP, () => {
lmeState.activeDrawHandler = null;
@@ -1128,14 +1629,14 @@ function enableDrawTools() {
}
configureDrawLabels();
- const orangeArea = { color: "#f97316", weight: 4, fillColor: "#fb923c", fillOpacity: 0.2 };
+ const orangeArea = { pane: "drawingsPane", color: "#f97316", weight: 4, fillColor: "#fb923c", fillOpacity: 0.2 };
lmeState.drawControl = new L.Control.Draw({
position: "topright",
edit: { featureGroup: lmeState.drawings },
draw: {
circle: { metric: true, feet: false, showRadius: true, shapeOptions: orangeArea },
circlemarker: false,
- marker: true,
+ marker: { zIndexOffset: 0 },
polygon: {
metric: true,
feet: false,
@@ -1147,7 +1648,7 @@ function enableDrawTools() {
metric: true,
feet: false,
showLength: true,
- shapeOptions: { color: "#f97316", weight: 4, opacity: 0.95 }
+ shapeOptions: { pane: "drawingsPane", color: "#f97316", weight: 4, opacity: 0.95 }
},
rectangle: { metric: true, shapeOptions: orangeArea }
}
@@ -1172,19 +1673,19 @@ function startMeasurementDrawing(mode) {
showArea: true,
metric: true,
feet: false,
- shapeOptions: { color: "#f97316", weight: 4, fillColor: "#fb923c", fillOpacity: 0.2 }
+ shapeOptions: { pane: "drawingsPane", color: "#f97316", weight: 4, fillColor: "#fb923c", fillOpacity: 0.2 }
}),
distance: () => new L.Draw.Polyline(lmeState.map, {
metric: true,
feet: false,
showLength: true,
- shapeOptions: { color: "#f97316", weight: 4, opacity: 0.95 }
+ shapeOptions: { pane: "drawingsPane", color: "#f97316", weight: 4, opacity: 0.95 }
}),
circle: () => new L.Draw.Circle(lmeState.map, {
metric: true,
feet: false,
showRadius: true,
- shapeOptions: { color: "#f97316", weight: 4, fillColor: "#fb923c", fillOpacity: 0.2 }
+ shapeOptions: { pane: "drawingsPane", color: "#f97316", weight: 4, fillColor: "#fb923c", fillOpacity: 0.2 }
})
};
const createHandler = handlers[mode];
@@ -1204,6 +1705,7 @@ function startMeasurementDrawing(mode) {
}
function annotateDrawingLayer(layer, layerType) {
+ layer.options.pane = "drawingsPane";
layer.feature = layer.feature || { type: "Feature", properties: {} };
layer.feature.properties = layer.feature.properties || {};
if (layer instanceof L.Circle) {
@@ -1212,6 +1714,7 @@ function annotateDrawingLayer(layer, layerType) {
} else if (layerType) {
layer.feature.properties._lmeShape = layerType;
}
+ layer.lmeManagerLabel = drawingLayerLabel(layer, layerType);
}
function setDrawToolsVisible(visible) {
@@ -1235,7 +1738,7 @@ function setDrawToolsVisible(visible) {
}
function clearDrawings() {
- const count = lmeState.drawings.getLayers().length;
+ const count = managedGroupGeoJson(lmeState.drawings, "drawings").features.length;
if (!count) {
setInfo("Non ci sono disegni o POI da eliminare.");
return;
@@ -1246,9 +1749,11 @@ function clearDrawings() {
}
lmeState.drawings.clearLayers();
+ clearManagedCategoryState("drawings");
lmeState.drawingMeasurements.clearLayers();
updateGpsInsideState();
setInfo("Disegni e POI eliminati dalla mappa.");
+ refreshLayerManager();
}
function scheduleMeasurementRefresh() {
@@ -1446,6 +1951,7 @@ function addMeasurementLabel(targetGroup, latLng, html, labelType, context) {
return;
}
L.marker(latLng, {
+ pane: context.sourceType === "drawing" ? "drawingsPane" : "importedPane",
interactive: false,
keyboard: false,
icon: L.divIcon({
@@ -1453,7 +1959,7 @@ function addMeasurementLabel(targetGroup, latLng, html, labelType, context) {
html: `${html} `,
iconSize: null
})
- }).addTo(targetGroup);
+ }).addTo(targetGroup).lmeManagerLabel = html.replace(/<[^>]+>/g, "");
context.count += 1;
}
@@ -1484,17 +1990,17 @@ function createMapExportGeoJson(options) {
const features = [];
if (requested.includeImported !== false) {
- appendExportFeatures(features, lmeState.imported?.toGeoJSON(), "imported");
+ appendExportFeatures(features, managedGroupGeoJson(lmeState.imported, "imported"), "imported");
}
if (requested.includeImportedSheets !== false) {
- appendExportFeatures(features, lmeState.importedSheets?.toGeoJSON(), "imported-sheet");
+ appendExportFeatures(features, managedGroupGeoJson(lmeState.importedSheets, "importedSheets"), "imported-sheet");
}
if (requested.includeCadastral !== false) {
- appendExportFeatures(features, lmeState.selectedParcelsLayer?.toGeoJSON(), "cadastral");
- appendExportFeatures(features, lmeState.gpsParcelLayer?.toGeoJSON(), "cadastral-gps");
+ appendExportFeatures(features, managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels"), "cadastral");
+ appendExportFeatures(features, managedGroupGeoJson(lmeState.gpsParcelLayer, "gpsParcels"), "cadastral-gps");
}
- const drawings = lmeState.drawings?.toGeoJSON() || emptyFeatureCollection();
+ const drawings = managedGroupGeoJson(lmeState.drawings, "drawings");
if (requested.includeDrawings !== false) {
appendExportFeatures(features, drawings, "drawing", feature => !isPoiFeature(feature));
}
@@ -1566,13 +2072,13 @@ function createProjectSnapshot(includeCadastralParcels) {
longitude: center.lng,
zoom: lmeState.map.getZoom()
},
- drawings: lmeState.drawings.toGeoJSON(),
- imported: lmeState.imported.toGeoJSON(),
- importedSheets: lmeState.importedSheets.toGeoJSON(),
+ drawings: managedGroupGeoJson(lmeState.drawings, "drawings"),
+ imported: managedGroupGeoJson(lmeState.imported, "imported"),
+ importedSheets: managedGroupGeoJson(lmeState.importedSheets, "importedSheets"),
importedSheetsVisible: lmeState.importedSheetsVisible,
displayOptions: captureDisplayOptions(),
selectedParcels: includeCadastralParcels
- ? lmeState.selectedParcelsLayer.toGeoJSON()
+ ? managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels")
: { type: "FeatureCollection", features: [] },
trail: lmeState.gpsTrailPoints.map(point => [
Number(point.lat ?? point[0]),
@@ -1596,6 +2102,7 @@ function loadProjectSnapshot(raw, projectName) {
throw new Error("contenuto non valido");
}
+ resetLayerManagerProjectState();
lmeState.drawings.clearLayers();
lmeState.imported.clearLayers();
lmeState.importedSheets.clearLayers();
@@ -1605,11 +2112,13 @@ function loadProjectSnapshot(raw, projectName) {
lmeState.gpsParcelLayer.clearLayers();
const restoredDrawings = L.geoJSON(data.drawings || emptyFeatureCollection(), {
+ pane: "drawingsPane",
style: { color: "#f97316", weight: 3, fillOpacity: 0.16 },
pointToLayer: (feature, latlng) => {
const properties = feature?.properties || {};
if (properties._lmeShape === "circle" && Number(properties._lmeRadius) > 0) {
return L.circle(latlng, {
+ pane: "drawingsPane",
radius: Number(properties._lmeRadius),
color: "#f97316",
weight: 3,
@@ -1617,9 +2126,12 @@ function loadProjectSnapshot(raw, projectName) {
fillOpacity: 0.2
});
}
- return L.marker(latlng, { draggable: true });
+ return L.marker(latlng, { pane: "drawingsPane", draggable: true });
},
- onEachFeature: bindStoredFeaturePopup
+ onEachFeature: (feature, layer) => {
+ bindStoredFeaturePopup(feature, layer);
+ annotateDrawingLayer(layer, feature?.properties?._lmeShape);
+ }
});
restoredDrawings.eachLayer(layer => lmeState.drawings.addLayer(layer));
const importedParts = partitionStoredImportedFeatures(
@@ -1638,6 +2150,7 @@ function loadProjectSnapshot(raw, projectName) {
}
applyImportedSheetVisibility();
L.geoJSON(data.selectedParcels || emptyFeatureCollection(), {
+ pane: "selectedParcelsPane",
style: { color: "#ef4444", weight: 4, opacity: 1, fillColor: "#fbbf24", fillOpacity: 0.26 },
onEachFeature: (feature, layer) => {
const properties = feature?.properties || {};
@@ -1685,6 +2198,7 @@ function loadProjectSnapshot(raw, projectName) {
? ` ${importedParts.sheets.features.length} elementi del foglio sono nascosti.`
: "";
setInfo(`Progetto caricato: ${projectName || "senza nome"}.${hiddenSheets}`);
+ refreshLayerManager();
return captureDisplayOptions();
} catch (error) {
setInfo(`Progetto non leggibile: ${error.message}`);
@@ -1698,12 +2212,17 @@ function captureDisplayOptions() {
cadastreOpacity: lmeState.cadastreOpacity,
cadastreLayers: [...lmeState.cadastreLayers],
includeKmlSheetLayer: lmeState.includeKmlSheetLayer,
+ importedVisible: lmeState.importedVisible,
importedSheetsVisible: lmeState.importedSheetsVisible,
showImportedMeasurements: lmeState.showImportedMeasurements,
showDrawingMeasurements: lmeState.showDrawingMeasurements,
drawToolsVisible: Boolean(lmeState.drawControl),
gpsPositionVisible: lmeState.gpsPositionVisible,
selectedParcelsVisible: lmeState.selectedParcelsVisible,
+ layerOrder: [...lmeState.layerOrder],
+ layerObjectOrder: { ...lmeState.managerObjectOrder },
+ hiddenLayerObjects: [...lmeState.managerHiddenObjectIds],
+ hiddenLayerCategories: [...lmeState.managerHiddenCategories],
trailEnabled: lmeState.gpsTrailEnabled
};
}
@@ -1726,10 +2245,24 @@ function applyProjectDisplayOptions(options) {
lmeState.cadastreOpacity = Math.max(0, Math.min(1, opacity));
}
lmeState.includeKmlSheetLayer = Boolean(options.includeKmlSheetLayer);
+ lmeState.importedVisible = options.importedVisible !== false;
lmeState.importedSheetsVisible = Boolean(options.importedSheetsVisible);
lmeState.showImportedMeasurements = Boolean(options.showImportedMeasurements);
lmeState.showDrawingMeasurements = options.showDrawingMeasurements !== false;
+ if (Array.isArray(options.layerOrder)) {
+ setLayerOrder(options.layerOrder);
+ }
+ if (options.layerObjectOrder && typeof options.layerObjectOrder === "object") {
+ lmeState.managerObjectOrder = options.layerObjectOrder;
+ }
+ if (Array.isArray(options.hiddenLayerObjects)) {
+ lmeState.managerHiddenObjectIds = new Set(options.hiddenLayerObjects);
+ }
+ if (Array.isArray(options.hiddenLayerCategories)) {
+ lmeState.managerHiddenCategories = new Set(options.hiddenLayerCategories);
+ }
applyImportedSheetVisibility();
+ setImportedLayerVisible(lmeState.importedVisible);
refreshCadastreLayer();
if (typeof options.baseLayer === "string") {
@@ -1739,6 +2272,8 @@ function applyProjectDisplayOptions(options) {
setSelectedParcelsVisible(options.selectedParcelsVisible !== false);
toggleTrail(Boolean(options.trailEnabled));
setDrawToolsVisible(Boolean(options.drawToolsVisible));
+ applyStoredLayerManagerVisibility();
+ saveLayerManagerState();
}
function partitionStoredImportedFeatures(imported, storedSheets) {
@@ -1780,6 +2315,48 @@ function emptyFeatureCollection() {
return { type: "FeatureCollection", features: [] };
}
+function managedGroupGeoJson(group, categoryKey) {
+ const collection = group?.toGeoJSON?.() || emptyFeatureCollection();
+ const features = [...(collection.features || [])];
+ const existingIds = new Set(features.map(feature => feature?.properties?._lmeManagerId).filter(Boolean));
+ lmeState.managerHiddenObjects.forEach(record => {
+ if (record.categoryKey !== categoryKey) {
+ return;
+ }
+ const hidden = record.layer?.toGeoJSON?.();
+ const hiddenFeatures = hidden?.type === "FeatureCollection" ? hidden.features : hidden ? [hidden] : [];
+ hiddenFeatures.forEach(feature => {
+ const id = feature?.properties?._lmeManagerId;
+ if (!id || !existingIds.has(id)) {
+ features.push(feature);
+ if (id) {
+ existingIds.add(id);
+ }
+ }
+ });
+ });
+ return { type: "FeatureCollection", features };
+}
+
+function resetLayerManagerProjectState() {
+ lmeState.managerHiddenObjects.clear();
+ lmeState.managerHiddenObjectIds.clear();
+ lmeState.managerHiddenCategories.clear();
+ lmeState.managerObjectOrder = {};
+ lmeState.managerHighlight?.clearLayers();
+}
+
+function clearManagedCategoryState(categoryKey) {
+ [...lmeState.managerHiddenObjects.entries()].forEach(([id, record]) => {
+ if (record.categoryKey === categoryKey) {
+ lmeState.managerHiddenObjects.delete(id);
+ lmeState.managerHiddenObjectIds.delete(id);
+ }
+ });
+ lmeState.managerHiddenCategories.delete(categoryKey);
+ delete lmeState.managerObjectOrder[categoryKey];
+}
+
function bindStoredFeaturePopup(feature, layer) {
const properties = feature?.properties || {};
const label = properties.name || properties.Name || properties.label || properties.LABEL;
@@ -2025,6 +2602,7 @@ function refreshCadastreLayer() {
}
const cadastreLayer = L.tileLayer.wms(cadastreUrl(), {
+ pane: "cadastrePane",
layers: layers.join(","),
format: "image/png",
transparent: true,
@@ -2064,10 +2642,12 @@ function activeCadastreLayers() {
}
function clearSelectedParcels() {
- const count = lmeState.selectedParcelsLayer.getLayers().length;
+ const count = managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels").features.length;
lmeState.selectedParcelsLayer.clearLayers();
+ clearManagedCategoryState("selectedParcels");
updateGpsInsideState();
setInfo(count ? `${count} particelle selezionate rimosse.` : "Non ci sono particelle selezionate da rimuovere.");
+ refreshLayerManager();
}
function setSelectedParcelsVisible(enabled) {
@@ -2095,11 +2675,11 @@ function setSelectedParcelsVisible(enabled) {
}
function clearMapElements() {
- const count = lmeState.selectedParcelsLayer.getLayers().length
- + lmeState.gpsParcelLayer.getLayers().length
- + lmeState.imported.getLayers().length
- + lmeState.importedSheets.getLayers().length
- + lmeState.drawings.getLayers().length
+ const count = managedGroupGeoJson(lmeState.selectedParcelsLayer, "selectedParcels").features.length
+ + managedGroupGeoJson(lmeState.gpsParcelLayer, "gpsParcels").features.length
+ + managedGroupGeoJson(lmeState.imported, "imported").features.length
+ + managedGroupGeoJson(lmeState.importedSheets, "importedSheets").features.length
+ + managedGroupGeoJson(lmeState.drawings, "drawings").features.length
+ lmeState.gpsTrailPoints.length;
if (!count) {
setInfo("La mappa non contiene elementi da pulire.");
@@ -2109,6 +2689,7 @@ function clearMapElements() {
return;
}
+ resetLayerManagerProjectState();
lmeState.selectedParcelsLayer.clearLayers();
lmeState.gpsParcelLayer.clearLayers();
lmeState.imported.clearLayers();
@@ -2120,6 +2701,7 @@ function clearMapElements() {
lmeState.gpsTrail.setLatLngs([]);
updateGpsInsideState();
setInfo("Elementi della mappa rimossi. Sfondo e layer catastali restano attivi.");
+ refreshLayerManager();
}
function startGps() {
@@ -2231,8 +2813,9 @@ function updateGpsPositionLayers(latlng, accuracy) {
return;
}
if (!lmeState.gpsMarker) {
- lmeState.gpsMarker = L.marker(latlng, { title: "La tua posizione" });
+ lmeState.gpsMarker = L.marker(latlng, { pane: "gpsPositionPane", title: "La tua posizione" });
lmeState.gpsAccuracy = L.circle(latlng, {
+ pane: "gpsPositionPane",
radius: accuracy,
color: "#2563eb",
weight: 2,