Reorganize map tools and improve KML sheet filtering

This commit is contained in:
livio committed 2026-09-21 11:01:12 +02:00
1 parent c8b8f21d8d
commit 7933b2e86a
6 files changed
+331 -173

No files matched your search

+81 -47
View File
@@ -13,7 +13,7 @@ const lmeState = {
drawControl: null,
activeDrawHandler: null,
drawEventsBound: false,
importParcelOnly: true,
includeKmlSheetLayer: false,
showImportedMeasurements: false,
showDrawingMeasurements: true,
measurementRefreshTimer: null,
@@ -47,7 +47,7 @@ window.lmeMap = {
clearMapElements,
fitImported,
clearImported,
setImportParcelOnly,
setIncludeKmlSheetLayer,
setImportedMeasurementsVisible,
setDrawingMeasurementsVisible,
addProjectMarker,
@@ -447,7 +447,7 @@ function bindFileImport() {
refreshImportedMeasurements();
const featureCount = geojson.features.length;
const ignored = result.ignoredCount
? `; esclusi ${result.ignoredCount} elementi non particella`
? `; esclusi ${result.ignoredCount} elementi del foglio o non particella`
: "";
const reprojected = result.reprojected ? "; coordinate Web Mercator convertite" : "";
setInfo(`Caricato ${file.name}: ${featureCount} elementi${ignored}${reprojected}.`);
@@ -652,13 +652,14 @@ function normalizeKmlFiles(files) {
throw new Error("KML senza geometrie utilizzabili");
}
const selected = lmeState.importParcelOnly ? filterKmlParcelFeatures(features) : features;
const polygonParts = splitKmlPolygonFeatures(features);
const selected = lmeState.includeKmlSheetLayer ? features : filterKmlParcelFeatures(polygonParts);
if (!selected.length) {
throw new Error("nessuna particella trovata; disattiva 'solo particelle' per importare tutti gli elementi");
throw new Error("nessuna particella trovata; attiva 'includi layer del foglio' per importare tutti gli elementi");
}
return {
geojson: { type: "FeatureCollection", features: selected },
ignoredCount: features.length - selected.length,
ignoredCount: lmeState.includeKmlSheetLayer ? 0 : Math.max(0, polygonParts.length - selected.length),
reprojected: false
};
}
@@ -690,10 +691,7 @@ function kmlTextToGeoJson(text, sourceName) {
}
function filterKmlParcelFeatures(features) {
const polygons = features
.map(feature => ({ ...feature, geometry: polygonOnlyGeometry(feature.geometry) }))
.filter(feature => feature.geometry);
const filtered = polygons.filter(feature => {
const filtered = features.filter(feature => {
const properties = feature.properties || {};
if (properties["@geometry-type"] === "groundoverlay") {
return false;
@@ -706,45 +704,81 @@ function filterKmlParcelFeatures(features) {
const folder = String(properties._lmeFolder || "").toLowerCase();
const isParcel = /particell|mappal|parcel/.test(`${identity} ${folder}`)
|| /^[a-z]?\s*\d+[a-z]?(?:[\/. -]\d+[a-z]?)?$/i.test(name);
const isSheet = /\bfoglio\b|\bsheet\b|quadro[ _-]?d[' ]?unione|perimetro[ _-]?foglio/.test(identity);
const isSheet = /\bfoglio\b|\bsheet\b|quadro[ _-]?d[' ]?unione|(?:perimetro|bordo|limite|contorno)[ _-]?(?:del[ _-]?)?foglio|sheet[ _-]?boundary/.test(identity);
return isParcel || !isSheet;
});
return removeLikelySheetOutline(filtered);
return removeLikelySheetOutlines(filtered);
}
function polygonOnlyGeometry(geometry) {
if (!geometry) {
return null;
}
if (geometry.type === "Polygon" || geometry.type === "MultiPolygon") {
return geometry;
}
if (geometry.type === "GeometryCollection") {
const geometries = (geometry.geometries || []).filter(item =>
item.type === "Polygon" || item.type === "MultiPolygon");
return geometries.length ? { type: "GeometryCollection", geometries } : null;
}
return null;
function splitKmlPolygonFeatures(features) {
const result = [];
const addGeometry = (feature, geometry, partIndex) => {
if (!geometry) {
return;
}
if (geometry.type === "Polygon") {
result.push({
...feature,
properties: { ...(feature.properties || {}), _lmePart: partIndex },
geometry
});
return;
}
if (geometry.type === "MultiPolygon") {
(geometry.coordinates || []).forEach((coordinates, index) => addGeometry(
feature,
{ type: "Polygon", coordinates },
`${partIndex}.${index}`));
return;
}
if (geometry.type === "GeometryCollection") {
(geometry.geometries || []).forEach((item, index) => addGeometry(feature, item, `${partIndex}.${index}`));
}
};
features.forEach((feature, index) => addGeometry(feature, feature.geometry, index));
return result;
}
function removeLikelySheetOutline(features) {
if (features.length < 3) {
return features;
function removeLikelySheetOutlines(features) {
let remaining = [...features];
while (remaining.length >= 2) {
const candidates = remaining
.map(feature => ({ feature, bounds: geoJsonBounds(feature.geometry) }))
.filter(item => item.bounds);
if (candidates.length < 2) {
break;
}
const largest = candidates.reduce((current, item) =>
boundsArea(item.bounds) > boundsArea(current.bounds) ? item : current);
if (isStrongParcelFeature(largest.feature)) {
break;
}
const others = candidates.filter(item => item !== largest);
const comparisonAreas = others.map(item => boundsArea(item.bounds)).sort((a, b) => a - b);
const referenceArea = comparisonAreas[Math.floor(comparisonAreas.length / 2)];
const contained = others.filter(item => boundsContains(largest.bounds, boundsCenter(item.bounds))).length;
const minimumContained = candidates.length === 2 ? 1 : Math.ceil(others.length * 0.6);
const areaFactor = 2.5;
const looksLikeSheet = referenceArea > 0
&& boundsArea(largest.bounds) > referenceArea * areaFactor
&& contained >= minimumContained;
if (!looksLikeSheet) {
break;
}
remaining = remaining.filter(feature => feature !== largest.feature);
}
const candidates = features.map(feature => ({ feature, bounds: geoJsonBounds(feature.geometry) }));
const valid = candidates.filter(item => item.bounds);
if (valid.length < 3) {
return features;
}
const areas = valid.map(item => boundsArea(item.bounds)).sort((a, b) => a - b);
const median = areas[Math.floor(areas.length / 2)];
const largest = valid.reduce((current, item) =>
boundsArea(item.bounds) > boundsArea(current.bounds) ? item : current);
const others = valid.filter(item => item !== largest);
const contained = others.filter(item => boundsContains(largest.bounds, boundsCenter(item.bounds))).length;
const looksLikeSheet = boundsArea(largest.bounds) > median * 8
&& contained >= Math.ceil(others.length * 0.75);
return looksLikeSheet ? features.filter(feature => feature !== largest.feature) : features;
return remaining;
}
function isStrongParcelFeature(feature) {
const properties = feature?.properties || {};
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(" ");
return /particell|mappal|parcel/i.test(context)
|| /^[a-z]?\s*\d+[a-z]?(?:[\/. -]\d+[a-z]?)?$/i.test(name);
}
function geoJsonBounds(geometry) {
@@ -774,11 +808,11 @@ function boundsContains(bounds, point) {
&& point[1] >= bounds.south && point[1] <= bounds.north;
}
function setImportParcelOnly(enabled) {
lmeState.importParcelOnly = Boolean(enabled);
setInfo(lmeState.importParcelOnly
? "Importazione KML/KMZ: verranno caricate solo le particelle."
: "Importazione KML/KMZ: verranno caricati tutti gli elementi.");
function setIncludeKmlSheetLayer(enabled) {
lmeState.includeKmlSheetLayer = Boolean(enabled);
setInfo(lmeState.includeKmlSheetLayer
? "Importazione KML/KMZ: verranno inclusi anche foglio e altri elementi."
: "Importazione KML/KMZ: verranno caricate solo le particelle riconosciute.");
}
function setImportedMeasurementsVisible(enabled) {