Reorganize map tools and improve KML sheet filtering
This commit is contained in:
1 parent
c8b8f21d8d
commit
7933b2e86a
6 files changed
+331
-173
No files matched your search
+87
-1
@@ -292,6 +292,43 @@ input[type="checkbox"] {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.map-measure-tools {
|
||||
position: absolute;
|
||||
top: 72px;
|
||||
right: 12px;
|
||||
z-index: 860;
|
||||
display: grid;
|
||||
grid-template-columns: auto repeat(3, minmax(76px, auto));
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.7);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.2);
|
||||
transition: top 140ms ease;
|
||||
}
|
||||
|
||||
.map-measure-tools > span {
|
||||
padding: 0 4px;
|
||||
color: var(--muted);
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.map-measure-tools button {
|
||||
min-height: 38px;
|
||||
padding: 0 10px;
|
||||
background: #fff3e8;
|
||||
color: #8a3a08;
|
||||
border: 1px solid #e4b58f;
|
||||
}
|
||||
|
||||
.map-measure-tools.search-open {
|
||||
top: 140px;
|
||||
}
|
||||
|
||||
.tool-sheet {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -358,6 +395,29 @@ h2 {
|
||||
padding: 15px 16px 24px;
|
||||
}
|
||||
|
||||
.panel-section {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: 0 0 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.panel-section:last-child {
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.panel-section h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.field-help {
|
||||
margin-top: -7px;
|
||||
color: var(--muted);
|
||||
font-size: 10px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
@@ -637,7 +697,7 @@ label {
|
||||
left: 0;
|
||||
z-index: 1200;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
height: var(--nav-height);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
border-top: 1px solid #bcc7cd;
|
||||
@@ -695,6 +755,16 @@ footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 701px) and (max-width: 900px) {
|
||||
.map-measure-tools {
|
||||
top: 126px;
|
||||
}
|
||||
|
||||
.map-measure-tools.search-open {
|
||||
top: 194px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.map-toolbar {
|
||||
top: 8px;
|
||||
@@ -738,6 +808,22 @@ footer {
|
||||
top: 136px;
|
||||
}
|
||||
|
||||
.map-measure-tools,
|
||||
.map-measure-tools.search-open {
|
||||
top: 126px;
|
||||
right: 8px;
|
||||
left: 8px;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.map-measure-tools.search-open {
|
||||
top: 194px;
|
||||
}
|
||||
|
||||
.map-measure-tools > span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gps-main,
|
||||
.gps-center {
|
||||
padding: 0 8px;
|
||||
|
||||
+81
-47
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user