Add draggable compass and restore project fiducials

This commit is contained in:
livio committed 2026-10-04 23:46:22 +02:00
1 parent aa389f7581
commit a15c8e0b36
4 files changed
+203 -31

No files matched your search

+6 -6
View File
@@ -680,8 +680,8 @@
<label> <label>
Orientamento bussola Orientamento bussola
<select @bind="CompassMode" @bind:after="ApplyCompassOptions" disabled="@(!ShowCompass)"> <select @bind="CompassMode" @bind:after="ApplyCompassOptions" disabled="@(!ShowCompass)">
<option value="heading">Navigatore: direzione del dispositivo in alto</option>
<option value="fixed">Nord fisso in alto</option> <option value="fixed">Nord fisso in alto</option>
<option value="heading">Navigatore: direzione del dispositivo in alto</option>
</select> </select>
</label> </label>
</section> </section>
@@ -889,7 +889,7 @@
private bool GpsOverlayCollapsed { get; set; } private bool GpsOverlayCollapsed { get; set; }
private bool ShowGpsPosition { get; set; } = true; private bool ShowGpsPosition { get; set; } = true;
private bool ShowCompass { get; set; } private bool ShowCompass { get; set; }
private string CompassMode { get; set; } = "heading"; private string CompassMode { get; set; } = "fixed";
private bool CompassSensorSubscribed { get; set; } private bool CompassSensorSubscribed { get; set; }
private bool ShowSelectedParcels { get; set; } = true; private bool ShowSelectedParcels { get; set; } = true;
private bool ShowFiducialPoints { get; set; } = true; private bool ShowFiducialPoints { get; set; } = true;
@@ -2322,7 +2322,7 @@
DrawToolsVisible = options.DrawToolsVisible; DrawToolsVisible = options.DrawToolsVisible;
ShowGpsPosition = options.GpsPositionVisible; ShowGpsPosition = options.GpsPositionVisible;
ShowCompass = options.CompassVisible; ShowCompass = options.CompassVisible;
CompassMode = options.CompassMode is "fixed" ? "fixed" : "heading"; CompassMode = options.CompassMode is "heading" ? "heading" : "fixed";
ShowSelectedParcels = options.SelectedParcelsVisible; ShowSelectedParcels = options.SelectedParcelsVisible;
TrailEnabled = options.TrailEnabled; TrailEnabled = options.TrailEnabled;
@@ -2745,7 +2745,7 @@
DrawToolsVisible = GetFlagPreference("draw_tools_visible", false); DrawToolsVisible = GetFlagPreference("draw_tools_visible", false);
ShowGpsPosition = GetFlagPreference("show_gps_position", true); ShowGpsPosition = GetFlagPreference("show_gps_position", true);
ShowCompass = GetFlagPreference("show_compass", false); ShowCompass = GetFlagPreference("show_compass", false);
CompassMode = Preferences.Default.Get($"{FlagPreferencePrefix}compass_mode", "heading") is "fixed" ? "fixed" : "heading"; CompassMode = Preferences.Default.Get($"{FlagPreferencePrefix}compass_mode", "fixed") is "heading" ? "heading" : "fixed";
ShowSelectedParcels = GetFlagPreference("show_selected_parcels", true); ShowSelectedParcels = GetFlagPreference("show_selected_parcels", true);
ShowFiducialPoints = GetFlagPreference("show_fiducial_points", true); ShowFiducialPoints = GetFlagPreference("show_fiducial_points", true);
KeepPreviousFiducialPoints = GetFlagPreference("keep_previous_fiducials", true); KeepPreviousFiducialPoints = GetFlagPreference("keep_previous_fiducials", true);
@@ -2853,7 +2853,7 @@
private async Task ApplyCompassOptions() private async Task ApplyCompassOptions()
{ {
CompassMode = CompassMode is "fixed" ? "fixed" : "heading"; CompassMode = CompassMode is "heading" ? "heading" : "fixed";
SaveFlagPreferences(); SaveFlagPreferences();
await JS.InvokeVoidAsync("lmeMap.setCompassOptions", ShowCompass, CompassMode); await JS.InvokeVoidAsync("lmeMap.setCompassOptions", ShowCompass, CompassMode);
await ConfigureCompassSensorAsync(); await ConfigureCompassSensorAsync();
@@ -2988,7 +2988,7 @@
public bool DrawToolsVisible { get; init; } public bool DrawToolsVisible { get; init; }
public bool GpsPositionVisible { get; init; } = true; public bool GpsPositionVisible { get; init; } = true;
public bool CompassVisible { get; init; } public bool CompassVisible { get; init; }
public string CompassMode { get; init; } = "heading"; public string CompassMode { get; init; } = "fixed";
public bool SelectedParcelsVisible { get; init; } = true; public bool SelectedParcelsVisible { get; init; } = true;
public bool TrailEnabled { get; init; } public bool TrailEnabled { get; init; }
} }
+2 -2
View File
@@ -29,8 +29,8 @@
<ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId> <ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>1.0.15</ApplicationDisplayVersion> <ApplicationDisplayVersion>1.0.16</ApplicationDisplayVersion>
<ApplicationVersion>115</ApplicationVersion> <ApplicationVersion>116</ApplicationVersion>
<Version>$(ApplicationDisplayVersion)</Version> <Version>$(ApplicationDisplayVersion)</Version>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged --> <!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
+48 -13
View File
@@ -1676,43 +1676,77 @@ label {
.map-compass { .map-compass {
--compass-rotation: 0deg; --compass-rotation: 0deg;
--compass-radius: -31px;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 12px; left: 12px;
z-index: 810; z-index: 810;
width: 54px; width: 82px;
height: 54px; height: 82px;
transform: translateY(-50%); transform: translateY(-50%);
border: 2px solid rgba(255, 255, 255, 0.92); border: 2px solid rgba(255, 255, 255, 0.92);
border-radius: 50%; border-radius: 50%;
background: rgba(22, 31, 38, 0.86); background: rgba(22, 31, 38, 0.86);
box-shadow: 0 2px 9px rgba(0, 0, 0, 0.3); box-shadow: 0 2px 9px rgba(0, 0, 0, 0.3);
color: #fff; color: #fff;
pointer-events: none; cursor: grab;
pointer-events: auto;
touch-action: none;
user-select: none;
} }
.map-compass-north { .map-compass.is-dragging {
cursor: grabbing;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.42);
}
.map-compass-rose {
position: absolute; position: absolute;
top: 2px; inset: 0;
transform: rotate(var(--compass-rotation));
transition: transform 120ms linear;
}
.map-compass-direction {
position: absolute;
top: 50%;
left: 50%; left: 50%;
transform: translateX(-50%); width: 20px;
height: 12px;
transform: translate(-50%, -50%) rotate(var(--direction-angle)) translateY(var(--compass-radius));
color: #f8fafc;
font-size: 9px;
font-weight: 800;
line-height: 1;
text-align: center;
}
.map-compass-direction > span {
display: block;
transform: rotate(var(--label-counter));
}
.map-compass-direction.is-intermediate {
color: #cbd5e1;
font-size: 7px;
}
.map-compass-direction.is-north {
color: #fca5a5; color: #fca5a5;
font-size: 11px; font-size: 11px;
font-weight: 900; font-weight: 900;
line-height: 1;
} }
.map-compass-needle { .map-compass-needle {
position: absolute; position: absolute;
bottom: 25px; bottom: 50%;
left: 50%; left: 50%;
width: 4px; width: 4px;
height: 18px; height: 23px;
transform: translateX(-50%) rotate(var(--compass-rotation)); transform: translateX(-50%);
transform-origin: 50% 100%; transform-origin: 50% 100%;
border-radius: 4px 4px 1px 1px; border-radius: 4px 4px 1px 1px;
background: #ef4444; background: #ef4444;
transition: transform 120ms linear;
} }
.map-compass-needle::after { .map-compass-needle::after {
@@ -2015,9 +2049,10 @@ footer {
} }
.map-compass { .map-compass {
--compass-radius: -27px;
left: 8px; left: 8px;
width: 48px; width: 72px;
height: 48px; height: 72px;
} }
.map-info-text { .map-info-text {
+147 -10
View File
@@ -65,8 +65,11 @@ const lmeState = {
compassLayer: null, compassLayer: null,
compassElement: null, compassElement: null,
compassVisible: false, compassVisible: false,
compassMode: "heading", compassMode: "fixed",
compassHeading: 0, compassHeading: 0,
compassPosition: null,
compassDragCleanup: null,
compassResizeHandler: null,
infoTimeoutSeconds: 3, infoTimeoutSeconds: 3,
infoTimer: null, infoTimer: null,
fileImportBound: false, fileImportBound: false,
@@ -321,7 +324,7 @@ function init(dotNetReference) {
restoreLayerOrder(); restoreLayerOrder();
Object.values(managedLayerPanes()).forEach(paneName => { Object.values(managedLayerPanes()).forEach(paneName => {
if (!lmeState.map.getPane(paneName)) { if (!lmeState.map.getPane(paneName)) {
const noRotation = paneName === "gpsPositionPane" || paneName === "fiducialsPane"; const noRotation = paneName === "gpsPositionPane";
lmeState.map.createPane(paneName, noRotation lmeState.map.createPane(paneName, noRotation
? (lmeState.map._norotatePane || lmeState.map._mapPane) ? (lmeState.map._norotatePane || lmeState.map._mapPane)
: (lmeState.map._rotatePane || lmeState.map._mapPane)); : (lmeState.map._rotatePane || lmeState.map._mapPane));
@@ -442,22 +445,150 @@ function createCompassLayer() {
const element = L.DomUtil.create("div", "map-compass"); const element = L.DomUtil.create("div", "map-compass");
element.setAttribute("role", "img"); element.setAttribute("role", "img");
element.setAttribute("aria-label", "Bussola: nord"); element.setAttribute("aria-label", "Bussola: nord");
element.innerHTML = `<span class="map-compass-north">N</span><span class="map-compass-needle" aria-hidden="true"></span><span class="map-compass-center" aria-hidden="true"></span>`; element.setAttribute("title", "Trascina per spostare la bussola");
element.innerHTML = `<span class="map-compass-rose" aria-hidden="true">
<span class="map-compass-direction is-north" style="--direction-angle:0deg;--label-counter:0deg"><span>N</span></span>
<span class="map-compass-direction is-intermediate" style="--direction-angle:45deg;--label-counter:-45deg"><span>NE</span></span>
<span class="map-compass-direction" style="--direction-angle:90deg;--label-counter:-90deg"><span>E</span></span>
<span class="map-compass-direction is-intermediate" style="--direction-angle:135deg;--label-counter:-135deg"><span>SE</span></span>
<span class="map-compass-direction" style="--direction-angle:180deg;--label-counter:-180deg"><span>S</span></span>
<span class="map-compass-direction is-intermediate" style="--direction-angle:225deg;--label-counter:-225deg"><span>SW</span></span>
<span class="map-compass-direction" style="--direction-angle:270deg;--label-counter:-270deg"><span>W</span></span>
<span class="map-compass-direction is-intermediate" style="--direction-angle:315deg;--label-counter:-315deg"><span>NW</span></span>
<span class="map-compass-needle"></span>
</span><span class="map-compass-center" aria-hidden="true"></span>`;
L.DomEvent.disableClickPropagation(element); L.DomEvent.disableClickPropagation(element);
L.DomEvent.disableScrollPropagation(element);
map.getContainer().appendChild(element); map.getContainer().appendChild(element);
lmeState.compassElement = element; lmeState.compassElement = element;
restoreCompassPosition();
bindCompassDragging(element);
lmeState.compassResizeHandler = () => applyCompassPosition();
map.on("resize", lmeState.compassResizeHandler);
requestAnimationFrame(() => applyCompassPosition());
updateCompassDisplay(); updateCompassDisplay();
}, },
onRemove() { onRemove(map) {
lmeState.compassDragCleanup?.();
lmeState.compassDragCleanup = null;
if (lmeState.compassResizeHandler) {
map.off("resize", lmeState.compassResizeHandler);
lmeState.compassResizeHandler = null;
}
lmeState.compassElement?.remove(); lmeState.compassElement?.remove();
lmeState.compassElement = null; lmeState.compassElement = null;
} }
}))(); }))();
} }
function setCompassOptions(visible, mode = "heading") { function restoreCompassPosition() {
if (lmeState.compassPosition) return;
try {
const saved = JSON.parse(localStorage.getItem("lmeCompassPosition") || "null");
const x = Number(saved?.x);
const y = Number(saved?.y);
if (Number.isFinite(x) && Number.isFinite(y)) {
lmeState.compassPosition = {
x: Math.max(0, Math.min(1, x)),
y: Math.max(0, Math.min(1, y))
};
}
} catch {
}
}
function applyCompassPosition() {
const element = lmeState.compassElement;
const container = lmeState.map?.getContainer();
if (!element || !container) return;
const width = container.clientWidth;
const height = container.clientHeight;
const elementWidth = element.offsetWidth;
const elementHeight = element.offsetHeight;
if (!width || !height || !elementWidth || !elementHeight) return;
const halfWidth = elementWidth / 2;
const halfHeight = elementHeight / 2;
const position = lmeState.compassPosition || {
x: (12 + halfWidth) / width,
y: 0.5
};
const centerX = Math.max(halfWidth + 4, Math.min(width - halfWidth - 4, position.x * width));
const centerY = Math.max(halfHeight + 4, Math.min(height - halfHeight - 4, position.y * height));
element.style.left = `${centerX - halfWidth}px`;
element.style.top = `${centerY - halfHeight}px`;
element.style.transform = "none";
}
function bindCompassDragging(element) {
let pointerId = null;
let offsetX = 0;
let offsetY = 0;
const move = event => {
if (event.pointerId !== pointerId) return;
event.preventDefault();
event.stopPropagation();
const container = lmeState.map?.getContainer();
if (!container) return;
const containerRect = container.getBoundingClientRect();
const elementWidth = element.offsetWidth;
const elementHeight = element.offsetHeight;
const left = event.clientX - containerRect.left - offsetX;
const top = event.clientY - containerRect.top - offsetY;
const centerX = Math.max(elementWidth / 2 + 4,
Math.min(containerRect.width - elementWidth / 2 - 4, left + elementWidth / 2));
const centerY = Math.max(elementHeight / 2 + 4,
Math.min(containerRect.height - elementHeight / 2 - 4, top + elementHeight / 2));
lmeState.compassPosition = {
x: centerX / containerRect.width,
y: centerY / containerRect.height
};
applyCompassPosition();
};
const finish = event => {
if (event.pointerId !== pointerId) return;
event.preventDefault();
event.stopPropagation();
element.classList.remove("is-dragging");
if (element.hasPointerCapture?.(pointerId)) {
element.releasePointerCapture(pointerId);
}
pointerId = null;
try {
localStorage.setItem("lmeCompassPosition", JSON.stringify(lmeState.compassPosition));
} catch {
}
};
const start = event => {
if (event.pointerType === "mouse" && event.button !== 0) return;
event.preventDefault();
event.stopPropagation();
const rect = element.getBoundingClientRect();
pointerId = event.pointerId;
offsetX = event.clientX - rect.left;
offsetY = event.clientY - rect.top;
element.classList.add("is-dragging");
element.setPointerCapture?.(pointerId);
};
element.addEventListener("pointerdown", start);
element.addEventListener("pointermove", move);
element.addEventListener("pointerup", finish);
element.addEventListener("pointercancel", finish);
lmeState.compassDragCleanup = () => {
element.removeEventListener("pointerdown", start);
element.removeEventListener("pointermove", move);
element.removeEventListener("pointerup", finish);
element.removeEventListener("pointercancel", finish);
};
}
function setCompassOptions(visible, mode = "fixed") {
lmeState.compassVisible = Boolean(visible); lmeState.compassVisible = Boolean(visible);
lmeState.compassMode = mode === "fixed" ? "fixed" : "heading"; lmeState.compassMode = mode === "heading" ? "heading" : "fixed";
if (!lmeState.map || !lmeState.compassLayer) return; if (!lmeState.map || !lmeState.compassLayer) return;
if (lmeState.compassVisible) { if (lmeState.compassVisible) {
lmeState.managerHiddenCategories.delete("compass"); lmeState.managerHiddenCategories.delete("compass");
@@ -5052,10 +5183,14 @@ function loadProjectSnapshot(raw, projectName) {
applyStyleFromProperties(layer); applyStyleFromProperties(layer);
} }
}).addTo(lmeState.selectedParcelsLayer); }).addTo(lmeState.selectedParcelsLayer);
L.geoJSON(data.fiducials || emptyFeatureCollection(), { const restoredFiducials = L.geoJSON(data.fiducials || emptyFeatureCollection(), {
pointToLayer: (feature, latlng) => createFiducialMarker(feature, latlng), pointToLayer: (feature, latlng) => createFiducialMarker(feature, latlng),
onEachFeature: (feature, layer) => bindFiducialPopup(feature, layer) onEachFeature: (feature, layer) => {
}).addTo(lmeState.fiducials); bindFiducialPopup(feature, layer);
bindManagedMapSelection(layer, "fiducials", lmeState.fiducials);
}
});
restoredFiducials.eachLayer(layer => lmeState.fiducials.addLayer(layer));
if (lmeState.importedSheetsVisible) { if (lmeState.importedSheetsVisible) {
void ensureImportedSheetOutlines().then(() => { void ensureImportedSheetOutlines().then(() => {
applyImportedSheetVisibility(); applyImportedSheetVisibility();
@@ -6003,7 +6138,9 @@ function setFiducialPointsVisible(enabled) {
function setFiducialColor(color) { function setFiducialColor(color) {
if (!/^#[0-9a-f]{6}$/i.test(String(color))) return; if (!/^#[0-9a-f]{6}$/i.test(String(color))) return;
lmeState.fiducialColor = color; lmeState.fiducialColor = color;
lmeState.fiducials?.eachLayer(layer => { const category = layerManagerCategories().find(item => item.key === "fiducials");
managedRecordsForCategory(category).forEach(record => {
const layer = record.layer;
if (layer.feature?.properties) { if (layer.feature?.properties) {
layer.feature.properties._lmeColor = color; layer.feature.properties._lmeColor = color;
layer.feature.properties._lmeFillColor = color; layer.feature.properties._lmeFillColor = color;