Add contextual fiducial point downloads

This commit is contained in:
livio committed 2026-10-04 19:28:31 +02:00
1 parent 85e1c93d8a
commit ce355f820c
5 files changed
+262 -28

No files matched your search

+140 -15
View File
@@ -5,11 +5,11 @@ const lmeState = {
currentBaseName: "satellite",
layerOrder: [
"gpsPosition",
"drawings",
"gpsTrail",
"fiducials",
"drawings",
"gpsParcels",
"selectedParcels",
"fiducials",
"importedSheets",
"imported",
"cadastre"
@@ -36,6 +36,7 @@ const lmeState = {
drawingDeleteButton: null,
drawingScopeMenu: null,
geometryToolsControl: null,
fiducialDownloadButton: null,
ignoreMapClickUntil: 0,
activeDrawHandler: null,
drawEventsBound: false,
@@ -225,6 +226,7 @@ function setLocalization(options = {}) {
function setEntitlements(options = {}) {
lmeState.entitlements = { ...lmeState.entitlements, ...options };
updateFiducialDownloadButton();
}
function requireProFeature(feature) {
@@ -1379,7 +1381,8 @@ function restoreLayerOrder() {
try {
const saved = JSON.parse(localStorage.getItem("lmeMapLayerOrder") || "null");
if (Array.isArray(saved)) {
lmeState.layerOrder = saved;
const remaining = saved.filter(key => !["gpsPosition", "gpsTrail", "fiducials"].includes(key));
lmeState.layerOrder = ["gpsPosition", "gpsTrail", "fiducials", ...remaining];
}
} catch {
}
@@ -1437,7 +1440,7 @@ function layerManagerCategories() {
{ 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: "fiducials", label: "Punti fiduciali", roots: [lmeState.fiducials].filter(Boolean) },
{ key: "fiducials", label: "Punti fiduciali", roots: [lmeState.fiducials].filter(Boolean), blocks: true },
{ key: "importedSheets", label: "Cornici e fogli importati", roots: [lmeState.importedSheets].filter(Boolean), blocks: true },
{ key: "imported", label: "Progetti e dati importati", roots: [lmeState.imported].filter(Boolean), blocks: true },
{ key: "cadastre", label: "Layer catastali AdE", roots: [lmeState.cadastre].filter(Boolean) }
@@ -1625,11 +1628,11 @@ async function renameManagedBlock(category, source, records) {
function managedCategoryCanRemove(category, records) {
return records.length > 0
&& ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels"].includes(category.key);
&& ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels", "fiducials"].includes(category.key);
}
function managedObjectCanRemove(record) {
return ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels"].includes(record.categoryKey);
return ["drawings", "imported", "importedSheets", "selectedParcels", "gpsParcels", "fiducials"].includes(record.categoryKey);
}
function managedCategoryCanMove(categoryKey) {
@@ -1891,6 +1894,7 @@ function managedObjectDetails(layer) {
}
function refreshLayerManager() {
updateFiducialDownloadButton();
const container = document.getElementById("layerManagerTree");
if (!container || !lmeState.map) {
return;
@@ -2026,8 +2030,12 @@ function renderLayerManagerBlock(category, source, records) {
const blockName = managedBlockName(source, records);
const rowActions = layerManagerRowActions(
`<button type="button" class="layer-rename-button" aria-label="Rinomina gruppo" title="Rinomina">Rinomina</button>`,
`<button type="button" class="layer-membership-button" aria-label="Sposta gruppo in un altro gruppo" title="Sposta nel gruppo">Sposta nel gruppo</button>`,
`<button type="button" class="layer-remove-button" aria-label="Rimuovi gruppo dal progetto" title="Rimuovi dal progetto">Rimuovi</button>`);
managedCategoryCanMove(category.key)
? `<button type="button" class="layer-membership-button" aria-label="Sposta gruppo in un altro gruppo" title="Sposta nel gruppo">Sposta nel gruppo</button>`
: "",
managedObjectCanRemove(records[0])
? `<button type="button" class="layer-remove-button" aria-label="Rimuovi gruppo dal progetto" title="Rimuovi dal progetto">Rimuovi</button>`
: "");
block.classList.add("layer-manager-with-actions");
const blockLocked = records.length > 0 && records.every(record => Boolean(record.layer?.feature?.properties?._lmeLocked));
block.innerHTML = `<summary>${layerExpandButton(block.open, blockName)}<button type="button" class="layer-drag-handle" aria-label="Trascina blocco">☰</button><input type="checkbox" ${visible ? "checked" : ""} aria-label="Mostra ${escapeHtml(blockName)}"><span class="layer-manager-title">${escapeHtml(blockName)}</span><button type="button" class="layer-inline-lock ${blockLocked ? "is-locked" : ""}" aria-label="${blockLocked ? "Sblocca" : "Blocca"} gruppo" title="${blockLocked ? "Sblocca" : "Blocca"}">${blockLocked ? "🔒" : "🔓"}</button>${rowActions}${layerManagerMoveButtons("blocco")}<span class="layer-manager-count">${records.length}</span></summary><div class="layer-manager-children"></div>`;
@@ -2048,12 +2056,12 @@ function renderLayerManagerBlock(category, source, records) {
event.stopPropagation();
renameManagedBlock(category, source, records);
});
block.querySelector(".layer-remove-button").addEventListener("click", event => {
block.querySelector(".layer-remove-button")?.addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
removeManagedRecords(records, `il gruppo '${blockName}'`);
});
block.querySelector(".layer-membership-button").addEventListener("click", event => {
block.querySelector(".layer-membership-button")?.addEventListener("click", event => {
event.preventDefault();
event.stopPropagation();
showMoveManagedRecordsDialog(records, `il gruppo '${blockName}'`);
@@ -2943,6 +2951,7 @@ function showLeafletToolbarHelp() {
<div><dt>✓</dt><dd><strong>Controlla e ripara</strong>Verifica gli oggetti selezionati e prova a correggere coordinate duplicate o poligoni non validi. Esempio: usalo quando un contorno importato non si esporta o si riempie male; al termine compare un riepilogo.</dd></div>
<div><dt>◉</dt><dd><strong>Crea foro</strong>Seleziona esattamente due poligoni: prima l'area esterna, poi quella interna. Esempio: sottrai il cortile dal perimetro di un fabbricato o un'isola esclusa da un'area di lavoro.</dd></div>
<div><dt>⇄</dt><dd><strong>Confronta due confini</strong>Seleziona esattamente due poligoni. L'app crea il solo primo in rosso, il solo secondo in blu e la sovrapposizione in verde, mostrando le rispettive aree.</dd></div>
<div><dt>PF</dt><dd><strong>Scarica Punti Fiduciali</strong>Seleziona una particella o un foglio catastale; in alternativa usa la particella rilevata dal GPS. I PF vengono aggiunti come sottogruppo del relativo foglio.</dd></div>
<div><dt>?</dt><dd><strong>Guida</strong>Riapre questa descrizione. I risultati di riparazione e confronto vengono mostrati anche in una finestra, così l'operazione non resta senza riscontro.</dd></div>
</dl>
</div>`);
@@ -2958,6 +2967,7 @@ function addGeometryToolsControl() {
{ icon: "✓", title: "Controlla e ripara geometrie", run: validateAndRepairSelected },
{ icon: "◉", title: "Crea foro da due poligoni", run: createHoleFromSelected },
{ icon: "⇄", title: "Confronta due confini", run: compareSelectedGeometries },
{ icon: "PF", title: "Scarica PF del foglio o della particella", run: downloadFiducialsForMapContext, fiducials: true },
{ icon: "?", title: "Guida strumenti mappa", run: showLeafletToolbarHelp }
];
actions.forEach(action => {
@@ -2967,6 +2977,10 @@ function addGeometryToolsControl() {
button.setAttribute("role", "button");
button.setAttribute("aria-label", action.title);
button.textContent = action.icon;
if (action.fiducials) {
button.classList.add("lme-fiducial-download");
lmeState.fiducialDownloadButton = button;
}
L.DomEvent.on(button, "click", event => { L.DomEvent.stop(event); action.run(); });
});
L.DomEvent.disableClickPropagation(container);
@@ -2974,6 +2988,88 @@ function addGeometryToolsControl() {
}
});
lmeState.geometryToolsControl = new GeometryTools().addTo(lmeState.map);
updateFiducialDownloadButton();
}
function downloadFiducialsForMapContext() {
if (!requireProFeature("fiducialPoints")) return false;
const context = currentFiducialMapContext();
if (!context) {
setInfo("Seleziona una particella o un foglio catastale. Se il GPS è attivo puoi usare anche la particella corrente.");
return false;
}
if (!lmeState.dotNetReference) {
setInfo("Servizio PF non ancora pronto. Riprova tra poco.");
return false;
}
void lmeState.dotNetReference.invokeMethodAsync(
"DownloadFiducialsForMapSelection",
context.municipalityCode,
context.sheet,
context.label);
return true;
}
function currentFiducialMapContext() {
const selected = selectedManagedRecords();
if (!selected.length && lmeState.managerHighlightedObjectId) {
const highlighted = lmeState.managerRecords?.get?.(lmeState.managerHighlightedObjectId);
if (highlighted) selected.push(highlighted);
}
const preferred = selected.filter(record => ["selectedParcels", "gpsParcels", "importedSheets", "imported"].includes(record.categoryKey));
for (const record of preferred) {
const context = fiducialContextFromLayer(record.layer);
if (context) return context;
}
const gpsLayers = managedRecordsForCategory(layerManagerCategories().find(category => category.key === "gpsParcels"));
for (const record of gpsLayers) {
const context = fiducialContextFromLayer(record.layer);
if (context) return context;
}
return null;
}
function fiducialContextFromLayer(layer) {
const properties = layer?.feature?.properties || {};
const municipalityCode = String(properties._lmeMunicipalityCode || "").trim().toUpperCase();
const sheet = normalizeFiducialSheet(properties._lmeSheet || properties.foglio || properties.FOGLIO || properties.sheet || properties.SHEET);
if (/^[A-Z][0-9A-Z]{3}$/.test(municipalityCode) && sheet) {
return { municipalityCode, sheet, label: `Comune ${municipalityCode}, foglio ${sheet}` };
}
const references = [
properties.NATIONALCADASTRALREFERENCE,
properties.NATIONALCADASTRALZONINGREFERENCE,
properties.INSPIREID_LOCALID,
properties._lmeParcelReference,
properties.LABEL
].filter(Boolean).map(value => String(value).toUpperCase());
for (const reference of references) {
const match = reference.match(/(?:^|\.)([A-Z][0-9A-Z]{3})[_-](\d{4})(?:\d{2})?(?:[._-]|$)/);
if (match) {
const parsedSheet = normalizeFiducialSheet(match[2]);
return { municipalityCode: match[1], sheet: parsedSheet, label: `Comune ${match[1]}, foglio ${parsedSheet}` };
}
}
return null;
}
function normalizeFiducialSheet(value) {
const digits = String(value ?? "").match(/\d+/)?.[0] || "";
if (!digits) return "";
return String(Number.parseInt(digits, 10));
}
function updateFiducialDownloadButton() {
const button = lmeState.fiducialDownloadButton;
if (!button) return;
const enabled = Boolean(lmeState.entitlements?.fiducialPoints && currentFiducialMapContext());
button.classList.toggle("is-disabled", !enabled);
button.setAttribute("aria-disabled", String(!enabled));
button.title = enabled
? "Scarica PF del foglio o della particella"
: "Seleziona una particella o un foglio per scaricare i PF";
}
async function numericEditManagedRecord(record) {
@@ -3086,7 +3182,8 @@ function restoreLastTrashItem() {
if (lmeState.readOnly) { setInfo("Il progetto e' in sola lettura."); return false; }
const entry = lmeState.trash.pop();
if (!entry) { setInfo("Il cestino e' vuoto."); return false; }
const drawingsToRestore = entry.items.filter(item => !["imported", "importedSheets"].includes(item.categoryKey)).length;
const drawingsToRestore = entry.items.filter(item =>
!["imported", "importedSheets", "fiducials"].includes(item.categoryKey)).length;
if (!canAddDrawingObjects(drawingsToRestore)) {
lmeState.trash.push(entry);
return false;
@@ -3095,6 +3192,12 @@ function restoreLastTrashItem() {
entry.items.forEach(item => {
if (item.categoryKey === "imported") addImportedFeatures({ type: "FeatureCollection", features: [item.feature] });
else if (item.categoryKey === "importedSheets") addImportedSheetFeatures({ type: "FeatureCollection", features: [item.feature] });
else if (item.categoryKey === "fiducials") {
L.geoJSON(item.feature, {
pointToLayer: (feature, latlng) => createFiducialMarker(feature, latlng),
onEachFeature: (feature, layer) => bindFiducialPopup(feature, layer)
}).eachLayer(layer => lmeState.fiducials.addLayer(layer));
}
else addDrawingFeature(item.feature);
});
refreshLayerManager();
@@ -5663,12 +5766,30 @@ function activeCadastreLayers() {
return drawOrder.filter(layer => lmeState.cadastreLayers.includes(layer));
}
function setFiducialPoints(points, color, visible = true) {
function setFiducialPoints(points, color, visible = true, keepPrevious = false, municipalityCode = "", sheet = "", municipalityName = "") {
if (!lmeState.fiducials) return;
const source = Array.isArray(points) ? points : [];
if (source.length || lmeState.fiducials.getLayers().length) rememberMapChange();
lmeState.fiducials.clearLayers();
clearManagedCategoryState("fiducials");
const normalizedMunicipality = String(municipalityCode || "").trim().toUpperCase();
const normalizedSheet = normalizeFiducialSheet(sheet);
const sheetKey = normalizedMunicipality && normalizedSheet
? `${normalizedMunicipality}:${normalizedSheet}`
: "fiducials-current";
const sheetLabel = `Foglio ${normalizedSheet || sheet}${municipalityName ? ` · ${municipalityName}` : ""}`;
if (!keepPrevious) {
managedRecordsForCategory(layerManagerCategories().find(category => category.key === "fiducials"))
.forEach(record => lmeState.managerSelectedObjectIds.delete(record.id));
lmeState.fiducials.clearLayers();
clearManagedCategoryState("fiducials");
} else {
managedRecordsForCategory(layerManagerCategories().find(category => category.key === "fiducials"))
.filter(record => record.layer?.feature?.properties?._lmeFiducialSheetKey === sheetKey)
.forEach(record => {
record.parent?.removeLayer?.(record.layer);
clearManagedObjectState(record.id);
lmeState.managerSelectedObjectIds.delete(record.id);
});
}
if (typeof color === "string" && /^#[0-9a-f]{6}$/i.test(color)) {
lmeState.fiducialColor = color;
}
@@ -5682,7 +5803,11 @@ function setFiducialPoints(points, color, visible = true) {
properties: {
name: String(point.identifier ?? point.Identifier ?? `PF ${index + 1}`),
_lmeDisplayName: String(point.identifier ?? point.Identifier ?? `PF ${index + 1}`),
_lmeSource: "Punti fiduciali georiferiti",
_lmeSource: sheetKey,
_lmeGroupName: sheetLabel,
_lmeFiducialSheetKey: sheetKey,
_lmeMunicipalityCode: normalizedMunicipality,
_lmeSheet: normalizedSheet,
_lmeColor: lmeState.fiducialColor,
_lmeFillColor: lmeState.fiducialColor,
_lmeWeight: 3,