Fix KMZ overlays and add map export

This commit is contained in:
livio committed 2026-09-22 08:43:23 +02:00
1 parent 32a36c98ec
commit 78af860493
8 files changed
+495 -29

No files matched your search

+155 -21
View File
@@ -61,6 +61,7 @@ window.lmeMap = {
startMeasurementDrawing,
setDrawToolsVisible,
clearDrawings,
createMapExportGeoJson,
createProjectSnapshot,
loadProjectSnapshot,
getLegacyProjectSnapshot,
@@ -752,10 +753,10 @@ function kmlTextToGeoJson(text, sourceName) {
function filterKmlParcelFeatures(features) {
const filtered = features.filter(feature => {
const properties = feature.properties || {};
if (properties["@geometry-type"] === "groundoverlay") {
if (isSheetOrOverlayFeature(feature)) {
return false;
}
const properties = feature.properties || {};
const name = String(properties.name || properties.Name || properties.LABEL || "").trim();
const identity = [name, properties.layer, properties.Layer, properties.type, properties.Tipo]
.filter(Boolean)
@@ -770,6 +771,28 @@ function filterKmlParcelFeatures(features) {
return removeLikelySheetOutlines(filtered);
}
function isSheetOrOverlayFeature(feature) {
const properties = feature?.properties || {};
const geometryType = String(properties["@geometry-type"] || "").toLowerCase();
const name = String(properties.name || properties.Name || properties.LABEL || "").trim();
const context = [
name,
properties.layer,
properties.Layer,
properties.type,
properties.Tipo,
properties._lmeFolder
].filter(Boolean).join(" ").toLowerCase();
if (geometryType === "groundoverlay" || /^doc[_ -]?\d+$/i.test(name)) {
return true;
}
const hasParcelNumber = /^[a-z]?\s*\d+[a-z]?(?:[\/. -]\d+[a-z]?)?$/i.test(name);
return !hasParcelNumber
&& /\bfoglio\b|\bsheet\b|quadro[ _-]?d[' ]?unione|(?:perimetro|bordo|limite|contorno)[ _-]?(?:del[ _-]?)?foglio|sheet[ _-]?boundary/.test(context);
}
function splitKmlPolygonFeatures(features) {
const result = [];
const addGeometry = (feature, geometry, partIndex) => {
@@ -876,6 +899,7 @@ function setIncludeKmlSheetLayer(enabled) {
}
function setImportedSheetLayerVisible(enabled) {
migrateMisclassifiedImportedSheets();
lmeState.importedSheetsVisible = Boolean(enabled);
applyImportedSheetVisibility();
const count = lmeState.importedSheets?.toGeoJSON()?.features?.length || 0;
@@ -884,6 +908,50 @@ function setImportedSheetLayerVisible(enabled) {
: "Layer del foglio importato nascosto; le particelle restano visibili.");
}
function migrateMisclassifiedImportedSheets() {
if (!lmeState.imported || !lmeState.importedSheets) {
return 0;
}
const current = lmeState.imported.toGeoJSON();
const misplaced = (current.features || []).filter(isSheetOrOverlayFeature);
if (!misplaced.length) {
return 0;
}
const retained = (current.features || []).filter(feature => !isSheetOrOverlayFeature(feature));
lmeState.imported.clearLayers();
addImportedFeatures({ type: "FeatureCollection", features: retained });
addImportedSheetFeatures({ type: "FeatureCollection", features: misplaced });
refreshImportedMeasurements();
return misplaced.length;
}
function addImportedFeatures(geojson) {
return L.geoJSON(geojson || 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);
}
function addImportedSheetFeatures(geojson) {
return L.geoJSON(geojson || emptyFeatureCollection(), {
style: { color: "#15803d", weight: 3, opacity: 0.85, fillOpacity: 0.04, dashArray: "9 7" },
pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
radius: 6,
color: "#15803d",
fillOpacity: 0.25
}),
onEachFeature: bindStoredFeaturePopup
}).addTo(lmeState.importedSheets);
}
function applyImportedSheetVisibility() {
if (!lmeState.map || !lmeState.importedSheets) {
return;
@@ -1355,6 +1423,85 @@ function formatNumber(value, digits) {
}).format(value);
}
function createMapExportGeoJson(options) {
migrateMisclassifiedImportedSheets();
const requested = options || {};
const features = [];
if (requested.includeImported !== false) {
appendExportFeatures(features, lmeState.imported?.toGeoJSON(), "imported");
}
if (requested.includeImportedSheets !== false) {
appendExportFeatures(features, lmeState.importedSheets?.toGeoJSON(), "imported-sheet");
}
if (requested.includeCadastral !== false) {
appendExportFeatures(features, lmeState.selectedParcelsLayer?.toGeoJSON(), "cadastral");
appendExportFeatures(features, lmeState.gpsParcelLayer?.toGeoJSON(), "cadastral-gps");
}
const drawings = lmeState.drawings?.toGeoJSON() || emptyFeatureCollection();
if (requested.includeDrawings !== false) {
appendExportFeatures(features, drawings, "drawing", feature => !isPoiFeature(feature));
}
if (requested.includePoi !== false) {
appendExportFeatures(features, drawings, "poi", isPoiFeature);
}
if (requested.includeTrail !== false && lmeState.gpsTrailPoints.length >= 2) {
features.push({
type: "Feature",
properties: { name: "Traccia GPS", _lmeCategory: "gps-trail" },
geometry: {
type: "LineString",
coordinates: lmeState.gpsTrailPoints.map(point => [
Number(point.lng ?? point[1]),
Number(point.lat ?? point[0])
])
}
});
}
return JSON.stringify({ type: "FeatureCollection", features });
}
function appendExportFeatures(target, collection, category, predicate = () => true) {
for (const sourceFeature of collection?.features || []) {
if (!sourceFeature?.geometry || !predicate(sourceFeature)) {
continue;
}
const feature = JSON.parse(JSON.stringify(sourceFeature));
feature.properties = { ...(feature.properties || {}), _lmeCategory: category };
if (feature.properties._lmeShape === "circle"
&& feature.geometry.type === "Point"
&& Number(feature.properties._lmeRadius) > 0) {
feature.geometry = circleToPolygon(feature.geometry.coordinates, Number(feature.properties._lmeRadius));
}
target.push(feature);
}
}
function isPoiFeature(feature) {
return feature?.geometry?.type === "Point"
&& feature?.properties?._lmeShape !== "circle";
}
function circleToPolygon(center, radiusMeters) {
const longitude = Number(center?.[0]);
const latitude = Number(center?.[1]);
const earthRadius = 6378137;
const latitudeRadians = latitude * Math.PI / 180;
const ring = [];
for (let index = 0; index <= 64; index += 1) {
const angle = index / 64 * Math.PI * 2;
const north = Math.sin(angle) * radiusMeters;
const east = Math.cos(angle) * radiusMeters;
ring.push([
longitude + east / (earthRadius * Math.cos(latitudeRadians)) * 180 / Math.PI,
latitude + north / earthRadius * 180 / Math.PI
]);
}
return { type: "Polygon", coordinates: [ring] };
}
function createProjectSnapshot(includeCadastralParcels) {
const center = lmeState.map.getCenter();
return JSON.stringify({
@@ -1423,25 +1570,8 @@ function loadProjectSnapshot(raw, projectName) {
const importedParts = partitionStoredImportedFeatures(
data.imported || emptyFeatureCollection(),
data.importedSheets || emptyFeatureCollection());
L.geoJSON(importedParts.parcels, {
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(importedParts.sheets, {
style: { color: "#15803d", weight: 3, opacity: 0.85, fillOpacity: 0.04, dashArray: "9 7" },
pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
radius: 6,
color: "#15803d",
fillOpacity: 0.25
}),
onEachFeature: bindStoredFeaturePopup
}).addTo(lmeState.importedSheets);
addImportedFeatures(importedParts.parcels);
addImportedSheetFeatures(importedParts.sheets);
const projectDisplayOptions = data.schemaVersion >= 3 && data.displayOptions
? data.displayOptions
: null;
@@ -1557,6 +1687,10 @@ function partitionStoredImportedFeatures(imported, storedSheets) {
const sourceGroups = new Map();
(imported?.features || []).forEach(feature => {
if (isSheetOrOverlayFeature(feature)) {
sheetFeatures.push(feature);
return;
}
const source = String(feature?.properties?._lmeSource || "");
if (!/\.(kml|kmz)$/i.test(source)) {
parcelFeatures.push(feature);