Add structured multi-project storage
This commit is contained in:
1 parent
ab43c5ca30
commit
ff2d4bccb6
8 files changed
+550
-40
No files matched your search
@@ -459,6 +459,19 @@ label {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.project-save-option {
|
||||
padding: 10px 11px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.project-archive-state {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.map-info {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
|
||||
+128
-25
@@ -43,8 +43,10 @@ window.lmeMap = {
|
||||
addProjectMarker,
|
||||
setDrawToolsVisible,
|
||||
clearDrawings,
|
||||
saveWorkspace,
|
||||
loadWorkspace,
|
||||
createProjectSnapshot,
|
||||
loadProjectSnapshot,
|
||||
getLegacyProjectSnapshot,
|
||||
clearLegacyProjectSnapshot,
|
||||
setBaseLayer,
|
||||
configureGoogleMaps,
|
||||
configureAzureMaps,
|
||||
@@ -197,7 +199,7 @@ function displayOfficialParcel(xmlText, latitude, longitude, parcel, label, shou
|
||||
}
|
||||
|
||||
const existingLayer = normalizedParcel
|
||||
? targetGroup.getLayers().find(item => item.lmeParcelReference === ref)
|
||||
? findParcelLayerByReference(targetGroup, ref)
|
||||
: null;
|
||||
if (existingLayer) {
|
||||
if (shouldFit) {
|
||||
@@ -235,6 +237,23 @@ function normalizeParcel(parcel) {
|
||||
return String(parcel || "").trim().toUpperCase();
|
||||
}
|
||||
|
||||
function findParcelLayerByReference(group, reference) {
|
||||
let match = null;
|
||||
group.eachLayer(layer => {
|
||||
if (match) {
|
||||
return;
|
||||
}
|
||||
if (layer.lmeParcelReference === reference) {
|
||||
match = layer;
|
||||
return;
|
||||
}
|
||||
if (typeof layer.eachLayer === "function") {
|
||||
match = findParcelLayerByReference(layer, reference);
|
||||
}
|
||||
});
|
||||
return match;
|
||||
}
|
||||
|
||||
function resize() {
|
||||
if (!lmeState.map) {
|
||||
return;
|
||||
@@ -459,6 +478,10 @@ function addProjectMarker(name) {
|
||||
const center = lmeState.map.getCenter();
|
||||
const label = (name || "Nuovo POI").trim();
|
||||
const marker = L.marker(center, { draggable: true }).addTo(lmeState.drawings);
|
||||
marker.feature = {
|
||||
type: "Feature",
|
||||
properties: { name: label }
|
||||
};
|
||||
marker.bindPopup(`<strong>${escapeHtml(label)}</strong><br>${center.lat.toFixed(6)}, ${center.lng.toFixed(6)}`).openPopup();
|
||||
setInfo(`POI aggiunto: ${label}`);
|
||||
}
|
||||
@@ -528,33 +551,113 @@ function clearDrawings() {
|
||||
setInfo("Disegni e POI eliminati dalla mappa.");
|
||||
}
|
||||
|
||||
function saveWorkspace() {
|
||||
const data = {
|
||||
function createProjectSnapshot(includeCadastralParcels) {
|
||||
const center = lmeState.map.getCenter();
|
||||
return JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
view: {
|
||||
latitude: center.lat,
|
||||
longitude: center.lng,
|
||||
zoom: lmeState.map.getZoom()
|
||||
},
|
||||
drawings: lmeState.drawings.toGeoJSON(),
|
||||
imported: lmeState.imported.toGeoJSON(),
|
||||
trail: lmeState.gpsTrailPoints
|
||||
};
|
||||
localStorage.setItem("lmeMapWorkspace", JSON.stringify(data));
|
||||
setInfo("Progetti, POI, file e percorso salvati sul dispositivo.");
|
||||
selectedParcels: includeCadastralParcels
|
||||
? lmeState.selectedParcelsLayer.toGeoJSON()
|
||||
: { type: "FeatureCollection", features: [] },
|
||||
trail: lmeState.gpsTrailPoints.map(point => [
|
||||
Number(point.lat ?? point[0]),
|
||||
Number(point.lng ?? point[1])
|
||||
])
|
||||
});
|
||||
}
|
||||
|
||||
function loadWorkspace() {
|
||||
const raw = localStorage.getItem("lmeMapWorkspace");
|
||||
if (!raw) {
|
||||
setInfo("Nessun progetto salvato trovato.");
|
||||
return;
|
||||
}
|
||||
function getLegacyProjectSnapshot() {
|
||||
return localStorage.getItem("lmeMapWorkspace");
|
||||
}
|
||||
|
||||
const data = JSON.parse(raw);
|
||||
lmeState.drawings.clearLayers();
|
||||
lmeState.imported.clearLayers();
|
||||
L.geoJSON(data.drawings, { style: { color: "#f97316", weight: 3, fillOpacity: 0.16 } }).addTo(lmeState.drawings);
|
||||
L.geoJSON(data.imported, { style: { color: "#22c55e", weight: 3, fillOpacity: 0.18 } }).addTo(lmeState.imported);
|
||||
lmeState.gpsTrailPoints = data.trail || [];
|
||||
lmeState.gpsTrail.setLatLngs(lmeState.gpsTrailPoints);
|
||||
fitLayer(L.featureGroup([lmeState.drawings, lmeState.imported]));
|
||||
updateGpsInsideState();
|
||||
setInfo("Progetto caricato.");
|
||||
function clearLegacyProjectSnapshot() {
|
||||
localStorage.removeItem("lmeMapWorkspace");
|
||||
}
|
||||
|
||||
function loadProjectSnapshot(raw, projectName) {
|
||||
try {
|
||||
const data = typeof raw === "string" ? JSON.parse(raw) : raw;
|
||||
if (!data || typeof data !== "object") {
|
||||
throw new Error("contenuto non valido");
|
||||
}
|
||||
|
||||
lmeState.drawings.clearLayers();
|
||||
lmeState.imported.clearLayers();
|
||||
lmeState.selectedParcelsLayer.clearLayers();
|
||||
lmeState.gpsParcelLayer.clearLayers();
|
||||
|
||||
L.geoJSON(data.drawings || emptyFeatureCollection(), {
|
||||
style: { color: "#f97316", weight: 3, fillOpacity: 0.16 },
|
||||
onEachFeature: bindStoredFeaturePopup
|
||||
}).addTo(lmeState.drawings);
|
||||
L.geoJSON(data.imported || emptyFeatureCollection(), {
|
||||
style: { color: "#22c55e", weight: 3, opacity: 0.95, fillOpacity: 0.18 },
|
||||
pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
|
||||
radius: 7,
|
||||
color: "#22c55e",
|
||||
fillColor: "#ffffff",
|
||||
fillOpacity: 0.9
|
||||
}),
|
||||
onEachFeature: bindStoredFeaturePopup
|
||||
}).addTo(lmeState.imported);
|
||||
L.geoJSON(data.selectedParcels || emptyFeatureCollection(), {
|
||||
style: { color: "#ef4444", weight: 4, opacity: 1, fillColor: "#fbbf24", fillOpacity: 0.26 },
|
||||
onEachFeature: (feature, layer) => {
|
||||
const properties = feature?.properties || {};
|
||||
const reference = properties.NATIONALCADASTRALREFERENCE
|
||||
|| properties.INSPIREID_LOCALID
|
||||
|| properties.LABEL
|
||||
|| "Particella catastale";
|
||||
layer.lmeParcelReference = reference;
|
||||
layer.bindPopup(`<strong>${escapeHtml(reference)}</strong>`);
|
||||
}
|
||||
}).addTo(lmeState.selectedParcelsLayer);
|
||||
|
||||
lmeState.gpsTrailPoints = Array.isArray(data.trail) ? data.trail : [];
|
||||
lmeState.gpsTrail.setLatLngs(lmeState.gpsTrailPoints);
|
||||
|
||||
const projectLayers = L.featureGroup([
|
||||
lmeState.drawings,
|
||||
lmeState.imported,
|
||||
lmeState.selectedParcelsLayer,
|
||||
lmeState.gpsTrail
|
||||
]);
|
||||
if (projectLayers.getBounds().isValid()) {
|
||||
fitLayer(projectLayers);
|
||||
} else if (Number.isFinite(data.view?.latitude)
|
||||
&& Number.isFinite(data.view?.longitude)
|
||||
&& Number.isFinite(data.view?.zoom)) {
|
||||
lmeState.map.setView([data.view.latitude, data.view.longitude], data.view.zoom);
|
||||
}
|
||||
|
||||
const parcelCount = lmeState.selectedParcelsLayer.toGeoJSON().features?.length || 0;
|
||||
setText("parcelState", parcelCount
|
||||
? `${parcelCount} particelle del progetto`
|
||||
: "Nessuna particella selezionata");
|
||||
updateGpsInsideState();
|
||||
setInfo(`Progetto caricato: ${projectName || "senza nome"}.`);
|
||||
} catch (error) {
|
||||
setInfo(`Progetto non leggibile: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function emptyFeatureCollection() {
|
||||
return { type: "FeatureCollection", features: [] };
|
||||
}
|
||||
|
||||
function bindStoredFeaturePopup(feature, layer) {
|
||||
const properties = feature?.properties || {};
|
||||
const label = properties.name || properties.Name || properties.label || properties.LABEL;
|
||||
if (label) {
|
||||
layer.bindPopup(`<strong>${escapeHtml(label)}</strong>`);
|
||||
}
|
||||
}
|
||||
|
||||
function setBaseLayer(name) {
|
||||
|
||||
Reference in new issue
Block a user