Aggiungi gestore gerarchico livelli mappa
This commit is contained in:
1 parent
708f539c64
commit
f1bbd53634
8 files changed
+772
-50
No files matched your search
@@ -61,10 +61,24 @@
|
||||
<label class="map-layer-option">
|
||||
<input type="checkbox" @bind="ShowSelectedParcels" @bind:after="ApplySelectedParcelsVisibility" />
|
||||
<span>
|
||||
<strong>Particelle selezionate</strong>
|
||||
<small>Nascondi o mostra senza eliminare i dati</small>
|
||||
<strong>Particelle catastali evidenziate</strong>
|
||||
<small>Contorni rossi ottenuti da ricerca o GPS</small>
|
||||
</span>
|
||||
</label>
|
||||
<label class="map-layer-option">
|
||||
<input type="checkbox" @bind="ShowImportedData" @bind:after="ApplyImportedDataVisibility" />
|
||||
<span>
|
||||
<strong>Dati importati</strong>
|
||||
<small>Particelle e geometrie verdi di file e progetti</small>
|
||||
</span>
|
||||
</label>
|
||||
<section class="layer-tree-section">
|
||||
<header>
|
||||
<strong>Ordine livelli e oggetti</strong>
|
||||
<small>Trascina dall'alto verso il basso</small>
|
||||
</header>
|
||||
<div id="layerManagerTree" class="layer-manager-tree"></div>
|
||||
</section>
|
||||
<div class="layer-panel-actions">
|
||||
<button class="danger-command" @onclick="ClearSelectedParcels">Elimina definitivamente le particelle selezionate</button>
|
||||
<button class="danger-command" @onclick="ClearMapElements">Pulisci elementi mappa</button>
|
||||
@@ -176,6 +190,10 @@
|
||||
? "Attivato: importa foglio, particelle e altri elementi KML/KMZ."
|
||||
: "Disattivato: importa solo i poligoni delle particelle riconosciute.")
|
||||
</small>
|
||||
<label class="toggle-line project-save-option">
|
||||
<input type="checkbox" @bind="ShowImportedData" @bind:after="ApplyImportedDataVisibility" />
|
||||
<span>Mostra particelle/dati importati in verde</span>
|
||||
</label>
|
||||
<label class="toggle-line project-save-option">
|
||||
<input type="checkbox" @bind="ShowImportedSheetLayer" @bind:after="ApplyImportedSheetLayerVisibility" />
|
||||
<span>Mostra cornice/layer del foglio importato</span>
|
||||
@@ -301,6 +319,10 @@
|
||||
|
||||
<section class="panel-section">
|
||||
<h2>Visualizzazione progetto</h2>
|
||||
<label class="toggle-line project-save-option">
|
||||
<input type="checkbox" @bind="ShowImportedData" @bind:after="ApplyImportedDataVisibility" />
|
||||
<span>Mostra particelle/dati importati in verde</span>
|
||||
</label>
|
||||
<label class="toggle-line project-save-option">
|
||||
<input type="checkbox" @bind="ShowImportedSheetLayer" @bind:after="ApplyImportedSheetLayerVisibility" />
|
||||
<span>Mostra cornice/layer del foglio importato</span>
|
||||
@@ -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;
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
<ApplicationId>com.companyname.lmemappecatastaliapp</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>0.2.17</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>21</ApplicationVersion>
|
||||
<ApplicationDisplayVersion>0.2.18</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>22</ApplicationVersion>
|
||||
|
||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#075E54" />
|
||||
|
||||
<!-- Splash Screen -->
|
||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="456" height="456" fill="#075E54" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 187 B |
@@ -1,8 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="456" height="456" viewBox="0 0 456 456" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M105 150L183 119L273 151L351 120V307L273 338L183 306L105 337Z"
|
||||
fill="#F7FAF5" stroke="#E1ECE5" stroke-width="10" />
|
||||
<path d="M183 119V306M273 151V338" fill="none" stroke="#83B6A5" stroke-width="10" />
|
||||
<path d="M125 190L162 175M124 274L160 260M293 196L330 181M295 282L330 268"
|
||||
fill="none" stroke="#A9CDBE" stroke-width="9" />
|
||||
<path d="M228 104C188 104 157 135 157 175C157 225 228 294 228 294C228 294 299 225 299 175C299 135 268 104 228 104Z"
|
||||
fill="#F97316" stroke="#FFFFFF" stroke-width="12" />
|
||||
<circle cx="228" cy="175" r="24" fill="#FFFFFF" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 834 B |
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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<NetworkLocationSnapshot?> GetApproximateLocationAsync(
|
||||
|
||||
@@ -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;
|
||||
|
||||
+616
-33
@@ -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 = `<summary><button type="button" class="layer-drag-handle" aria-label="Trascina gruppo">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra ${escapeHtml(category.label)}"><span class="layer-manager-title">${escapeHtml(category.label)}</span><span class="layer-manager-count">${records.length}</span></summary><div class="layer-manager-children"></div>`;
|
||||
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 = `<summary><button type="button" class="layer-drag-handle" aria-label="Trascina blocco">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra blocco"><span class="layer-manager-title">${escapeHtml(source)}</span><span class="layer-manager-count">${records.length}</span></summary><div class="layer-manager-children"></div>`;
|
||||
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 = `<summary><button type="button" class="layer-drag-handle" aria-label="Trascina oggetto">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra oggetto"><span class="layer-manager-title">${escapeHtml(managedObjectLabel(record, index))}</span><span class="layer-manager-count">›</span></summary><div class="layer-manager-details"></div>`;
|
||||
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 => `<span>${escapeHtml(value)}</span>`).join("")
|
||||
: "<span>Nessuna misura disponibile</span>";
|
||||
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(`<strong>${escapeHtml(label)}</strong><br>${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: `<span>${html}</span>`,
|
||||
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,
|
||||
|
||||
Reference in new issue
Block a user