Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc1ee8f21a | ||
|
|
bb69b9fd0d | ||
|
|
260e0a3df5 | ||
|
|
a15c8e0b36 | ||
|
|
aa389f7581 | ||
|
|
812420b86a | ||
|
|
bc037a57d4 | ||
|
|
ce355f820c | ||
|
|
85e1c93d8a | ||
|
|
7b642afe2d | ||
|
|
342935d99d | ||
|
|
5ea424b46c | ||
|
|
29c3848a22 | ||
|
|
f27e47d83a | ||
|
|
bcc9e01179 |
No files matched your search
@@ -24,6 +24,7 @@
|
|||||||
{
|
{
|
||||||
"layers" => "Qui controlli visibilita, ordine, selezione e organizzazione di tutti gli oggetti presenti sulla mappa.",
|
"layers" => "Qui controlli visibilita, ordine, selezione e organizzazione di tutti gli oggetti presenti sulla mappa.",
|
||||||
"cadastre" => "Trova una particella nel catalogo catastale e mostrane il confine ufficiale sulla mappa.",
|
"cadastre" => "Trova una particella nel catalogo catastale e mostrane il confine ufficiale sulla mappa.",
|
||||||
|
"fiducials" => "Ricerca i Punti Fiduciali di un foglio, consultali sulla mappa e scarica le monografie disponibili.",
|
||||||
"data" => "Importa file GIS, crea disegni e misure oppure aggiungi punti di interesse.",
|
"data" => "Importa file GIS, crea disegni e misure oppure aggiungi punti di interesse.",
|
||||||
"save" => "Esporta gli elementi della mappa in un file compatibile con altre applicazioni GIS e GPS.",
|
"save" => "Esporta gli elementi della mappa in un file compatibile con altre applicazioni GIS e GPS.",
|
||||||
"projects" => "Salva il lavoro completo, riaprilo, organizzalo e recupera revisioni o copie di sicurezza.",
|
"projects" => "Salva il lavoro completo, riaprilo, organizzalo e recupera revisioni o copie di sicurezza.",
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
"Gerarchia: premi la freccia accanto alla maniglia per aprire un gruppo. Il rientro, il carattere e la tonalita indicano se una riga e figlia, nipote o di livello successivo.",
|
"Gerarchia: premi la freccia accanto alla maniglia per aprire un gruppo. Il rientro, il carattere e la tonalita indicano se una riga e figlia, nipote o di livello successivo.",
|
||||||
"Selezione multipla: attiva il flag sulle righe desiderate. Esempio: seleziona tre poligoni per esportare solo quelli oppure per bloccarli con una sola operazione.",
|
"Selezione multipla: attiva il flag sulle righe desiderate. Esempio: seleziona tre poligoni per esportare solo quelli oppure per bloccarli con una sola operazione.",
|
||||||
"Rinomina e attributi: Rinomina modifica il nome mostrato e salvato nei progetti e nei file esportati; Attributi apre un editor multilinea per consultare o correggere i dati associati.",
|
"Rinomina e attributi: Rinomina modifica il nome mostrato e salvato nei progetti e nei file esportati; Attributi apre un editor multilinea per consultare o correggere i dati associati.",
|
||||||
|
"Note: espandi un oggetto e premi Visualizza note per consultarne l'elenco. Con LME Pro, tramite abbonamento o licenza manuale, puoi aggiungere, modificare ed eliminare un numero qualsiasi di note; le vecchie note singole restano compatibili.",
|
||||||
"Colori: espandi un padre o un figlio e usa gli editor di linea e riempimento. Puoi scegliere il colore, prelevarlo con il contagocce se supportato, oppure inserire e copiare i valori HEX, RGB e HSL; sul padre viene applicato a tutto il gruppo.",
|
"Colori: espandi un padre o un figlio e usa gli editor di linea e riempimento. Puoi scegliere il colore, prelevarlo con il contagocce se supportato, oppure inserire e copiare i valori HEX, RGB e HSL; sul padre viene applicato a tutto il gruppo.",
|
||||||
"Blocco: il lucchetto impedisce modifiche e spostamenti involontari. Sblocca l'oggetto prima di usare la matita o gli strumenti geometrici.",
|
"Blocco: il lucchetto impedisce modifiche e spostamenti involontari. Sblocca l'oggetto prima di usare la matita o gli strumenti geometrici.",
|
||||||
"Sposta nel gruppo cambia l'appartenenza. Esempio: trasferisci un poligono da Disegni e POI a Progetti e dati importati, oppure in un nuovo sottogruppo chiamato Rilievo nord.",
|
"Sposta nel gruppo cambia l'appartenenza. Esempio: trasferisci un poligono da Disegni e POI a Progetti e dati importati, oppure in un nuovo sottogruppo chiamato Rilievo nord.",
|
||||||
@@ -56,6 +58,16 @@
|
|||||||
"Se un oggetto gia presente era nascosto, viene reso nuovamente visibile. La visibilita generale dei contorni evidenziati e dei dati importati resta controllabile dal pannello Layer.",
|
"Se un oggetto gia presente era nascosto, viene reso nuovamente visibile. La visibilita generale dei contorni evidenziati e dei dati importati resta controllabile dal pannello Layer.",
|
||||||
"Quando il servizio ufficiale non restituisce subito il confine, attendi il completamento indicato nel pannello e riprova mantenendo gli stessi dati selezionati."
|
"Quando il servizio ufficiale non restituisce subito il confine, attendi il completamento indicato nel pannello e riprova mantenendo gli stessi dati selezionati."
|
||||||
],
|
],
|
||||||
|
"fiducials" =>
|
||||||
|
[
|
||||||
|
"La funzione richiede LME Pro: sono validi sia l'abbonamento Google Play sia una licenza manuale attiva.",
|
||||||
|
"Scegli Provincia, Comune catastale e Foglio, quindi premi Cerca Punti Fiduciali. Puoi anche usare il comando PF sulla mappa dopo aver selezionato una particella o un foglio, oppure dalla particella individuata dal GPS.",
|
||||||
|
"Mantieni i PF dei fogli gia caricati conserva i risultati precedenti come sottogruppi distinti; disattivalo per sostituirli con il nuovo foglio.",
|
||||||
|
"Il flag Mostra i PF sulla mappa controlla la visibilita generale. Nel pannello Layer puoi inoltre cambiare colore, ordine, visibilita o rimuovere il singolo gruppo del foglio.",
|
||||||
|
"Tocca un punto rosso per vedere identificativo e coordinate. Quando disponibile, Scarica PDF monografia apre o salva il documento ufficiale dell'Agenzia delle Entrate.",
|
||||||
|
"I PF caricati vengono salvati nel progetto con gruppi, popup e collegamenti alle monografie; riaprendo il progetto tornano disponibili come gli altri oggetti.",
|
||||||
|
"Il posizionamento cartografico e sperimentale e non sostituisce un rilievo topografico o la verifica professionale sul posto."
|
||||||
|
],
|
||||||
"data" =>
|
"data" =>
|
||||||
[
|
[
|
||||||
"Importazione: premi Carica KML, KMZ o GeoJSON e scegli il file dal dispositivo. I nomi e gli attributi riconosciuti vengono mantenuti.",
|
"Importazione: premi Carica KML, KMZ o GeoJSON e scegli il file dal dispositivo. I nomi e gli attributi riconosciuti vengono mantenuti.",
|
||||||
@@ -75,11 +87,11 @@
|
|||||||
"Attiva solo le categorie da includere. Esempio: per consegnare disegni e particelle importate senza la traccia GPS, lascia attive quelle due voci e disattiva Traccia GPS.",
|
"Attiva solo le categorie da includere. Esempio: per consegnare disegni e particelle importate senza la traccia GPS, lascia attive quelle due voci e disattiva Traccia GPS.",
|
||||||
"Solo oggetti selezionati esporta esclusivamente le righe contrassegnate nel pannello Layer. Se non devi filtrare manualmente, lascia questa opzione disattivata.",
|
"Solo oggetti selezionati esporta esclusivamente le righe contrassegnate nel pannello Layer. Se non devi filtrare manualmente, lascia questa opzione disattivata.",
|
||||||
"Premi Esporta e salva file e scegli la cartella o l'app di destinazione proposta dal dispositivo.",
|
"Premi Esporta e salva file e scegli la cartella o l'app di destinazione proposta dal dispositivo.",
|
||||||
"Nomi rinominati e attributi vengono salvati in GeoJSON, KML e KMZ. Gli sfondi satellitari, stradali e WMS non vengono incorporati nei file vettoriali."
|
"Nomi rinominati, attributi e note compatibili vengono salvati nei formati che supportano metadati. Gli sfondi satellitari, stradali e WMS non vengono incorporati nei file vettoriali."
|
||||||
],
|
],
|
||||||
"projects" =>
|
"projects" =>
|
||||||
[
|
[
|
||||||
"Nuovo progetto: inserisci un nome, scegli se includere i contorni catastali evidenziati e premi Salva progetto. Vengono conservati oggetti, gruppi, nomi, attributi e opzioni di visualizzazione.",
|
"Nuovo progetto: inserisci un nome, scegli se includere i contorni catastali evidenziati e premi Salva progetto. Vengono conservati oggetti, gruppi, nomi, attributi, note, Punti Fiduciali caricati e opzioni di visualizzazione.",
|
||||||
"Aggiornamento: dopo aver aperto un progetto, usa di nuovo Salva progetto con lo stesso nome per aggiornarlo. Se nelle Impostazioni attivi Aggiorna automaticamente il progetto aperto, le modifiche vengono registrate dopo 3 secondi di inattivita.",
|
"Aggiornamento: dopo aver aperto un progetto, usa di nuovo Salva progetto con lo stesso nome per aggiornarlo. Se nelle Impostazioni attivi Aggiorna automaticamente il progetto aperto, le modifiche vengono registrate dopo 3 secondi di inattivita.",
|
||||||
"Cartelle: seleziona un progetto, scrivi per esempio Cantieri 2026 e premi Sposta per organizzarlo nell'archivio; questa operazione non sposta le geometrie sulla mappa.",
|
"Cartelle: seleziona un progetto, scrivi per esempio Cantieri 2026 e premi Sposta per organizzarlo nell'archivio; questa operazione non sposta le geometrie sulla mappa.",
|
||||||
"Rinomina cambia il nome del progetto selezionato senza alterarne contenuti e revisioni. Duplica crea invece una copia modificabile, utile prima di una variante importante o quando ricevi un progetto in sola lettura.",
|
"Rinomina cambia il nome del progetto selezionato senza alterarne contenuti e revisioni. Duplica crea invece una copia modificabile, utile prima di una variante importante o quando ricevi un progetto in sola lettura.",
|
||||||
@@ -91,6 +103,7 @@
|
|||||||
"tools" =>
|
"tools" =>
|
||||||
[
|
[
|
||||||
"Tabella oggetti: apre una vista ordinata di particelle, file importati e disegni. Usa i flag per selezionare piu righe, il lucchetto per proteggerle e la matita per modificare i metadati.",
|
"Tabella oggetti: apre una vista ordinata di particelle, file importati e disegni. Usa i flag per selezionare piu righe, il lucchetto per proteggerle e la matita per modificare i metadati.",
|
||||||
|
"Colonna Note: il pulsante Note e sempre attivo e apre tutte le note dell'oggetto. La consultazione resta disponibile anche senza Pro; aggiunta, modifica ed eliminazione richiedono abbonamento o licenza manuale.",
|
||||||
"Copia metadati: nella maschera Metadati oggetto scegli Copia da e l'oggetto sorgente. Attiva Importa anche i valori per copiare nomi e contenuti; disattivalo per creare soltanto gli stessi campi, lasciandoli vuoti o mantenendo i valori gia presenti.",
|
"Copia metadati: nella maschera Metadati oggetto scegli Copia da e l'oggetto sorgente. Attiva Importa anche i valori per copiare nomi e contenuti; disattivalo per creare soltanto gli stessi campi, lasciandoli vuoti o mantenendo i valori gia presenti.",
|
||||||
"Coordinate WGS84 e UTM: scegli un punto sulla mappa per leggere latitudine e longitudine oppure le coordinate metriche UTM, utili per rilievi e confronti tecnici.",
|
"Coordinate WGS84 e UTM: scegli un punto sulla mappa per leggere latitudine e longitudine oppure le coordinate metriche UTM, utili per rilievi e confronti tecnici.",
|
||||||
"Registro operazioni: mostra in ordine cronologico disegni, modifiche, rinomine, spostamenti ed eliminazioni, cosi puoi capire cosa e cambiato durante la sessione.",
|
"Registro operazioni: mostra in ordine cronologico disegni, modifiche, rinomine, spostamenti ed eliminazioni, cosi puoi capire cosa e cambiato durante la sessione.",
|
||||||
@@ -102,6 +115,8 @@
|
|||||||
"settings" =>
|
"settings" =>
|
||||||
[
|
[
|
||||||
"Mappa e sfondo: scegli la cartografia di base e regola Trasparenza catasto per leggere meglio confini e immagini sottostanti. Esempio: riducila quando le linee coprono dettagli del terreno.",
|
"Mappa e sfondo: scegli la cartografia di base e regola Trasparenza catasto per leggere meglio confini e immagini sottostanti. Esempio: riducila quando le linee coprono dettagli del terreno.",
|
||||||
|
"Bussola: attiva Mostra bussola per visualizzare N, NE, E, SE, S, SW, W e NW. Puoi trascinarla nella posizione preferita. Con nord fisso la mappa non ruota e la lancetta indica il nord; in modalita Navigatore la mappa segue l'orientamento del dispositivo.",
|
||||||
|
"Messaggi mappa: scegli dopo quanti secondi devono chiudersi automaticamente; imposta 0 per mantenerli finche non premi la X.",
|
||||||
"Autosalvataggio locale dopo ogni modifica: se attivo, ogni disegno, modifica, rinomina o eliminazione aggiorna immediatamente una copia di recupero nel dispositivo. Per usarla apri Dati e disegni e premi Recupera ultimo autosalvataggio.",
|
"Autosalvataggio locale dopo ogni modifica: se attivo, ogni disegno, modifica, rinomina o eliminazione aggiorna immediatamente una copia di recupero nel dispositivo. Per usarla apri Dati e disegni e premi Recupera ultimo autosalvataggio.",
|
||||||
"Aggiorna automaticamente il progetto aperto: funziona solo dopo aver aperto o salvato un progetto modificabile. Dopo una modifica attende 3 secondi senza altre operazioni e aggiorna quel progetto; un progetto in sola lettura non viene mai sovrascritto.",
|
"Aggiorna automaticamente il progetto aperto: funziona solo dopo aver aperto o salvato un progetto modificabile. Dopo una modifica attende 3 secondi senza altre operazioni e aggiorna quel progetto; un progetto in sola lettura non viene mai sovrascritto.",
|
||||||
"Backup automatico: scegli Giornaliero o Settimanale. Alla successiva pressione di Salva progetto, se e trascorso l'intervallo, viene creata una copia interna di tutti i progetti; l'app conserva le 10 copie automatiche piu recenti. Disattivato non crea nuove copie programmate.",
|
"Backup automatico: scegli Giornaliero o Settimanale. Alla successiva pressione di Salva progetto, se e trascorso l'intervallo, viene creata una copia interna di tutti i progetti; l'app conserva le 10 copie automatiche piu recenti. Disattivato non crea nuove copie programmate.",
|
||||||
@@ -109,7 +124,8 @@
|
|||||||
"Aggancio ai vertici: avvicinando un nuovo vertice a uno esistente, l'app lo allinea entro la distanza in pixel scelta. Aumenta la distanza su schermi piccoli; riducila quando ci sono molti vertici ravvicinati.",
|
"Aggancio ai vertici: avvicinando un nuovo vertice a uno esistente, l'app lo allinea entro la distanza in pixel scelta. Aumenta la distanza su schermi piccoli; riducila quando ci sono molti vertici ravvicinati.",
|
||||||
"Centra e zooma quando seleziono un oggetto: attivalo per inquadrare automaticamente ogni elemento scelto; disattivalo se vuoi mantenere ferma la vista mentre lavori dall'elenco Layer.",
|
"Centra e zooma quando seleziono un oggetto: attivalo per inquadrare automaticamente ogni elemento scelto; disattivalo se vuoi mantenere ferma la vista mentre lavori dall'elenco Layer.",
|
||||||
"WebDAV: inserisci indirizzo, utente e password applicazione, salva la configurazione, poi usa Invia al cloud per caricare il backup o Scarica dal cloud per ripristinare quello remoto.",
|
"WebDAV: inserisci indirizzo, utente e password applicazione, salva la configurazione, poi usa Invia al cloud per caricare il backup o Scarica dal cloud per ripristinare quello remoto.",
|
||||||
"GPS e percorso: Mostra punto visualizza posizione e precisione; Registra traccia memorizza il cammino; distanza e avvisi aiutano a controllare l'avvicinamento e l'attraversamento del confine selezionato.",
|
"GPS e percorso: Mostra punto visualizza posizione e precisione; Registra traccia memorizza il cammino; distanza e avvisi aiutano a controllare l'avvicinamento e l'attraversamento del confine selezionato. Avvia/Ferma GPS e Centra sono disposti verticalmente e il riquadro puo essere compresso.",
|
||||||
|
"Fogli catastali in navigazione: con Fogli catastali attivo nel pannello Layer, l'app carica il foglio anche senza particelle o progetti e lo mantiene visibile a zoom piu ampi senza duplicarlo se e gia presente.",
|
||||||
"Google Maps e Azure Maps richiedono le rispettive chiavi solo quando scegli quegli sfondi. Controlla nuova versione confronta la versione installata con l'ultima pubblicata e mostra le relative modifiche."
|
"Google Maps e Azure Maps richiedono le rispettive chiavi solo quando scegli quegli sfondi. Controlla nuova versione confronta la versione installata con l'ultima pubblicata e mostra le relative modifiche."
|
||||||
],
|
],
|
||||||
_ => []
|
_ => []
|
||||||
|
|||||||
+516
-5
@@ -3,6 +3,7 @@
|
|||||||
@inject PreciseLocationService LocationService
|
@inject PreciseLocationService LocationService
|
||||||
@inject CadastralCatalogService CatalogService
|
@inject CadastralCatalogService CatalogService
|
||||||
@inject OfficialCadastreService OfficialCadastreService
|
@inject OfficialCadastreService OfficialCadastreService
|
||||||
|
@inject FiducialPointService FiducialPointService
|
||||||
@inject LocationSearchService LocationSearchService
|
@inject LocationSearchService LocationSearchService
|
||||||
@inject NetworkLocationService NetworkLocationService
|
@inject NetworkLocationService NetworkLocationService
|
||||||
@inject AzureMapsService AzureMapsService
|
@inject AzureMapsService AzureMapsService
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
<button @onclick='() => SelectTab("map")'>@L("menu.map")</button>
|
<button @onclick='() => SelectTab("map")'>@L("menu.map")</button>
|
||||||
<button @onclick='() => SelectTab("search")'>@L("menu.search")</button>
|
<button @onclick='() => SelectTab("search")'>@L("menu.search")</button>
|
||||||
<button @onclick='() => SelectTab("cadastre")'>@L("menu.cadastre")</button>
|
<button @onclick='() => SelectTab("cadastre")'>@L("menu.cadastre")</button>
|
||||||
|
<button @onclick='() => SelectTab("fiducials")'>Punti fiduciali</button>
|
||||||
<button @onclick='() => SelectTab("data")'>@L("menu.data")</button>
|
<button @onclick='() => SelectTab("data")'>@L("menu.data")</button>
|
||||||
<button @onclick='() => SelectTab("projects")'>@L("menu.projects")</button>
|
<button @onclick='() => SelectTab("projects")'>@L("menu.projects")</button>
|
||||||
<button @onclick='() => SelectTab("save")'>@L("menu.export")</button>
|
<button @onclick='() => SelectTab("save")'>@L("menu.export")</button>
|
||||||
@@ -94,6 +96,16 @@
|
|||||||
<small>Particelle e geometrie verdi di file e progetti</small>
|
<small>Particelle e geometrie verdi di file e progetti</small>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
@if (CanUse(AppFeatures.FiducialPoints))
|
||||||
|
{
|
||||||
|
<label class="map-layer-option">
|
||||||
|
<input type="checkbox" @bind="ShowFiducialPoints" @bind:after="ApplyFiducialVisibility" />
|
||||||
|
<span>
|
||||||
|
<strong>Punti fiduciali</strong>
|
||||||
|
<small>PF georiferiti caricati nella ricerca Pro</small>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
<section class="layer-tree-section">
|
<section class="layer-tree-section">
|
||||||
<header>
|
<header>
|
||||||
<strong>Ordine livelli e oggetti</strong>
|
<strong>Ordine livelli e oggetti</strong>
|
||||||
@@ -109,13 +121,18 @@
|
|||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="gps-overlay @(ActiveTab == "search" ? "search-open" : null)">
|
<div class="gps-overlay @(ActiveTab == "search" ? "search-open" : null) @(GpsOverlayCollapsed ? "is-collapsed" : null)">
|
||||||
<div class="gps-state">
|
<div class="gps-state">
|
||||||
<span id="gpsState">GPS non attivo</span>
|
<span id="gpsState">GPS non attivo</span>
|
||||||
<strong id="parcelState">Nessuna particella selezionata</strong>
|
<strong id="parcelState">Nessuna particella selezionata</strong>
|
||||||
</div>
|
</div>
|
||||||
<button class="gps-main" @onclick="ToggleGps">@(GpsActive ? "Ferma GPS" : "Avvia GPS")</button>
|
<div class="gps-actions">
|
||||||
<button class="gps-center" @onclick="CenterOnGps" aria-label="Centra posizione">Centra</button>
|
<button class="gps-main" @onclick="ToggleGps">@(GpsActive ? "Ferma GPS" : "Avvia GPS")</button>
|
||||||
|
<button class="gps-center" @onclick="CenterOnGps" aria-label="Centra posizione GPS">Centra</button>
|
||||||
|
</div>
|
||||||
|
<button class="gps-collapse" @onclick="ToggleGpsOverlay" aria-label="@(GpsOverlayCollapsed ? "Espandi pannello GPS" : "Riduci pannello GPS")" title="@(GpsOverlayCollapsed ? "Espandi pannello GPS" : "Riduci pannello GPS")">
|
||||||
|
<span aria-hidden="true">@(GpsOverlayCollapsed ? "+" : "−")</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (ActiveTab is not "map" and not "search")
|
@if (ActiveTab is not "map" and not "search")
|
||||||
@@ -196,6 +213,105 @@
|
|||||||
<MenuHelp Topic="cadastre" />
|
<MenuHelp Topic="cadastre" />
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
else if (ActiveTab == "fiducials")
|
||||||
|
{
|
||||||
|
<section class="panel-content">
|
||||||
|
@if (!CanUse(AppFeatures.FiducialPoints))
|
||||||
|
{
|
||||||
|
<div class="service-note protected-service">
|
||||||
|
<strong>Funzione LME Pro</strong>
|
||||||
|
<p>La ricerca dei Punti Fiduciali e delle monografie ufficiali è disponibile con abbonamento Pro o chiave manuale valida.</p>
|
||||||
|
<button class="primary-command" @onclick='() => SelectTab("settings")'>Apri abbonamento Pro</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<label>
|
||||||
|
Provincia
|
||||||
|
<select value="@SelectedFiducialProvinceCode" @onchange="OnFiducialProvinceChanged" disabled="@FiducialBusy">
|
||||||
|
<option value="">Seleziona provincia</option>
|
||||||
|
@foreach (var province in FiducialProvinces)
|
||||||
|
{
|
||||||
|
<option value="@province.Code">@province.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Comune catastale
|
||||||
|
<select value="@SelectedFiducialMunicipalityCode" @onchange="OnFiducialMunicipalityChanged" disabled="@(FiducialMunicipalities.Count == 0 || FiducialBusy)">
|
||||||
|
<option value="">Seleziona comune</option>
|
||||||
|
@foreach (var municipality in FiducialMunicipalities)
|
||||||
|
{
|
||||||
|
<option value="@municipality.Code">@municipality.Name (@municipality.Code)</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Foglio
|
||||||
|
<select @bind="SelectedFiducialSheet" disabled="@(FiducialSheets.Count == 0 || FiducialBusy)">
|
||||||
|
<option value="">Seleziona foglio</option>
|
||||||
|
@foreach (var sheet in FiducialSheets)
|
||||||
|
{
|
||||||
|
<option value="@sheet.Code">@sheet.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button class="primary-command" @onclick="SearchFiducials" disabled="@(string.IsNullOrWhiteSpace(SelectedFiducialSheet) || FiducialBusy)">
|
||||||
|
@(FiducialBusy ? "Ricerca..." : "Cerca Punti Fiduciali")
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<label class="toggle-line">
|
||||||
|
<input type="checkbox" @bind="KeepPreviousFiducialPoints" @bind:after="ApplyKeepPreviousFiducials" />
|
||||||
|
<span>Mantieni i PF dei fogli già caricati</span>
|
||||||
|
</label>
|
||||||
|
<small class="field-help">Ogni foglio resta un sottogruppo separato nel pannello Layer, da cui puoi nasconderlo o rimuoverlo.</small>
|
||||||
|
|
||||||
|
<label class="toggle-line">
|
||||||
|
<input type="checkbox" @bind="ShowFiducialPoints" @bind:after="ApplyFiducialVisibility" />
|
||||||
|
<span>Mostra i PF sulla mappa</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Colore PF
|
||||||
|
<input type="color" @bind="FiducialColor" @bind:after="ApplyFiducialColor" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(FiducialMessage))
|
||||||
|
{
|
||||||
|
<div class="catalog-progress"><span>@FiducialMessage</span></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (MappedFiducialPoints.Count > 0)
|
||||||
|
{
|
||||||
|
<section class="panel-section">
|
||||||
|
<h2>Punti cartografici (@MappedFiducialPoints.Count)</h2>
|
||||||
|
<button class="secondary-command" @onclick="FitFiducialPoints">Inquadra tutti i PF</button>
|
||||||
|
<div class="fiducial-list">
|
||||||
|
@foreach (var point in MappedFiducialPoints)
|
||||||
|
{
|
||||||
|
<article class="fiducial-item">
|
||||||
|
<strong>@point.Identifier</strong>
|
||||||
|
<small>Nord/X: @FormatCoordinate(point.CadastralNorth) · Est/Y: @FormatCoordinate(point.CadastralEast)</small>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(point.MonographUrl))
|
||||||
|
{
|
||||||
|
<button class="secondary-command" @onclick="() => DownloadFiducialMonograph(point.MonographUrl)">Scarica PDF monografia</button>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="service-note">
|
||||||
|
<strong>Monografie ufficiali, posizione sperimentale</strong>
|
||||||
|
<p>Elenco e PDF provengono dall'Agenzia delle Entrate; il posizionamento sperimentale usa il servizio cartografico GloboGIS. Le coordinate sono un ausilio: verificale prima di qualsiasi uso topografico o professionale.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<MenuHelp Topic="fiducials" />
|
||||||
|
</section>
|
||||||
|
}
|
||||||
else if (ActiveTab == "data")
|
else if (ActiveTab == "data")
|
||||||
{
|
{
|
||||||
<section class="panel-content">
|
<section class="panel-content">
|
||||||
@@ -553,6 +669,22 @@
|
|||||||
Trasparenza catasto
|
Trasparenza catasto
|
||||||
<input type="range" min="0" max="1" step="0.05" value="@CadastralOpacity.ToString(System.Globalization.CultureInfo.InvariantCulture)" @onchange="ChangeCadastreOpacity" />
|
<input type="range" min="0" max="1" step="0.05" value="@CadastralOpacity.ToString(System.Globalization.CultureInfo.InvariantCulture)" @onchange="ChangeCadastreOpacity" />
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
Durata messaggi mappa (secondi)
|
||||||
|
<input type="number" min="0" max="30" step="1" @bind="MapMessageTimeoutSeconds" @bind:after="ApplyMapMessageOptions" />
|
||||||
|
</label>
|
||||||
|
<small class="field-help">Imposta 0 per lasciare i messaggi visibili fino alla chiusura manuale.</small>
|
||||||
|
<label class="toggle-line project-save-option">
|
||||||
|
<input type="checkbox" @bind="ShowCompass" @bind:after="ApplyCompassOptions" />
|
||||||
|
<span>Mostra bussola sulla mappa</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Orientamento bussola
|
||||||
|
<select @bind="CompassMode" @bind:after="ApplyCompassOptions" disabled="@(!ShowCompass)">
|
||||||
|
<option value="fixed">Mappa: nord fisso in alto</option>
|
||||||
|
<option value="heading">Navigatore: direzione del dispositivo in alto</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel-section">
|
<section class="panel-section">
|
||||||
@@ -689,7 +821,10 @@
|
|||||||
</aside>
|
</aside>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div id="mapInfo" class="map-info">Pronto</div>
|
<div id="mapInfo" class="map-info" role="status" aria-live="polite">
|
||||||
|
<span id="mapInfoText" class="map-info-text">Pronto</span>
|
||||||
|
<button type="button" class="map-info-close" onclick="window.lmeMap.hideInfo()" aria-label="Chiudi messaggio" title="Chiudi messaggio">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="wmsPreview" class="wms-preview" hidden>
|
<div id="wmsPreview" class="wms-preview" hidden>
|
||||||
<section class="wms-preview-dialog" role="dialog" aria-modal="true" aria-labelledby="wmsPreviewTitle">
|
<section class="wms-preview-dialog" role="dialog" aria-modal="true" aria-labelledby="wmsPreviewTitle">
|
||||||
@@ -732,6 +867,12 @@
|
|||||||
private string MonthlyPrice { get; set; } = string.Empty;
|
private string MonthlyPrice { get; set; } = string.Empty;
|
||||||
private string YearlyPrice { get; set; } = string.Empty;
|
private string YearlyPrice { get; set; } = string.Empty;
|
||||||
private bool BillingBusy { get; set; }
|
private bool BillingBusy { get; set; }
|
||||||
|
private bool FiducialBusy { get; set; }
|
||||||
|
private string SelectedFiducialProvinceCode { get; set; } = string.Empty;
|
||||||
|
private string SelectedFiducialMunicipalityCode { get; set; } = string.Empty;
|
||||||
|
private string SelectedFiducialSheet { get; set; } = string.Empty;
|
||||||
|
private string FiducialMessage { get; set; } = string.Empty;
|
||||||
|
private string FiducialColor { get; set; } = "#dc2626";
|
||||||
private string Status { get; set; } = "online";
|
private string Status { get; set; } = "online";
|
||||||
private string SelectedBaseLayer { get; set; } = "satellite";
|
private string SelectedBaseLayer { get; set; } = "satellite";
|
||||||
private string GoogleMapsApiKey { get; set; } = string.Empty;
|
private string GoogleMapsApiKey { get; set; } = string.Empty;
|
||||||
@@ -743,10 +884,17 @@
|
|||||||
private string CatalogMessage { get; set; } = string.Empty;
|
private string CatalogMessage { get; set; } = string.Empty;
|
||||||
private double? CatalogFraction { get; set; }
|
private double? CatalogFraction { get; set; }
|
||||||
private double CadastralOpacity { get; set; } = 0.75;
|
private double CadastralOpacity { get; set; } = 0.75;
|
||||||
|
private int MapMessageTimeoutSeconds { get; set; } = 3;
|
||||||
private bool CatalogBusy { get; set; }
|
private bool CatalogBusy { get; set; }
|
||||||
private bool GpsActive { get; set; }
|
private bool GpsActive { 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 string CompassMode { get; set; } = "fixed";
|
||||||
|
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 KeepPreviousFiducialPoints { get; set; } = true;
|
||||||
private bool ShowImportedData { get; set; } = true;
|
private bool ShowImportedData { get; set; } = true;
|
||||||
private bool DrawToolsVisible { get; set; }
|
private bool DrawToolsVisible { get; set; }
|
||||||
private bool TrailEnabled { get; set; }
|
private bool TrailEnabled { get; set; }
|
||||||
@@ -806,6 +954,11 @@
|
|||||||
private IReadOnlyList<MunicipalityOption> Municipalities { get; set; } = [];
|
private IReadOnlyList<MunicipalityOption> Municipalities { get; set; } = [];
|
||||||
private IReadOnlyList<string> Sheets { get; set; } = [];
|
private IReadOnlyList<string> Sheets { get; set; } = [];
|
||||||
private IReadOnlyList<ParcelOption> Parcels { get; set; } = [];
|
private IReadOnlyList<ParcelOption> Parcels { get; set; } = [];
|
||||||
|
private IReadOnlyList<FiducialAreaOption> FiducialProvinces { get; set; } = [];
|
||||||
|
private IReadOnlyList<FiducialAreaOption> FiducialMunicipalities { get; set; } = [];
|
||||||
|
private IReadOnlyList<FiducialAreaOption> FiducialSheets { get; set; } = [];
|
||||||
|
private IReadOnlyList<FiducialPointEntry> FiducialPoints { get; set; } = [];
|
||||||
|
private IReadOnlyList<MappedFiducialPoint> MappedFiducialPoints { get; set; } = [];
|
||||||
private IReadOnlyList<MapProjectSummary> Projects { get; set; } = [];
|
private IReadOnlyList<MapProjectSummary> Projects { get; set; } = [];
|
||||||
private IReadOnlyList<MapProjectVersion> ProjectVersions { get; set; } = [];
|
private IReadOnlyList<MapProjectVersion> ProjectVersions { get; set; } = [];
|
||||||
private MapProjectSummary? SelectedProject => Projects.FirstOrDefault(item => item.Id == SelectedProjectId);
|
private MapProjectSummary? SelectedProject => Projects.FirstOrDefault(item => item.Id == SelectedProjectId);
|
||||||
@@ -819,6 +972,7 @@
|
|||||||
private string PanelTitle => ActiveTab switch
|
private string PanelTitle => ActiveTab switch
|
||||||
{
|
{
|
||||||
"cadastre" => "Ricerca catastale",
|
"cadastre" => "Ricerca catastale",
|
||||||
|
"fiducials" => "Punti fiduciali",
|
||||||
"data" => "Dati e disegni",
|
"data" => "Dati e disegni",
|
||||||
"projects" => "Archivio progetti",
|
"projects" => "Archivio progetti",
|
||||||
"save" => "Salvataggio ed esportazione",
|
"save" => "Salvataggio ed esportazione",
|
||||||
@@ -830,6 +984,7 @@
|
|||||||
private string PanelEyebrow => ActiveTab switch
|
private string PanelEyebrow => ActiveTab switch
|
||||||
{
|
{
|
||||||
"cadastre" => "Regione, Comune, Foglio, Particella",
|
"cadastre" => "Regione, Comune, Foglio, Particella",
|
||||||
|
"fiducials" => "Monografie ufficiali AdE",
|
||||||
"data" => "File GIS, misure e POI",
|
"data" => "File GIS, misure e POI",
|
||||||
"projects" => "Salva, apri e visualizza i tuoi lavori",
|
"projects" => "Salva, apri e visualizza i tuoi lavori",
|
||||||
"save" => "Esporta file GIS",
|
"save" => "Esporta file GIS",
|
||||||
@@ -1041,6 +1196,10 @@
|
|||||||
{
|
{
|
||||||
await RefreshProjectsAsync();
|
await RefreshProjectsAsync();
|
||||||
}
|
}
|
||||||
|
if (tab == "fiducials" && CanUse(AppFeatures.FiducialPoints) && FiducialProvinces.Count == 0)
|
||||||
|
{
|
||||||
|
await LoadFiducialProvincesAsync();
|
||||||
|
}
|
||||||
await Task.Delay(30);
|
await Task.Delay(30);
|
||||||
await JS.InvokeVoidAsync("lmeMap.resize");
|
await JS.InvokeVoidAsync("lmeMap.resize");
|
||||||
if (LayerPanelOpen)
|
if (LayerPanelOpen)
|
||||||
@@ -1120,6 +1279,235 @@
|
|||||||
: await CatalogService.GetMunicipalitiesAsync(SelectedRegionFile);
|
: await CatalogService.GetMunicipalitiesAsync(SelectedRegionFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoadFiducialProvincesAsync()
|
||||||
|
{
|
||||||
|
FiducialBusy = true;
|
||||||
|
FiducialMessage = "Carico l'elenco delle province...";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FiducialProvinces = await FiducialPointService.GetProvincesAsync();
|
||||||
|
FiducialMessage = string.Empty;
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
FiducialMessage = $"Servizio monografie non disponibile: {error.GetBaseException().Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FiducialBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFiducialProvinceChanged(ChangeEventArgs args)
|
||||||
|
{
|
||||||
|
SelectedFiducialProvinceCode = args.Value?.ToString() ?? string.Empty;
|
||||||
|
SelectedFiducialMunicipalityCode = string.Empty;
|
||||||
|
SelectedFiducialSheet = string.Empty;
|
||||||
|
FiducialMunicipalities = [];
|
||||||
|
FiducialSheets = [];
|
||||||
|
FiducialPoints = [];
|
||||||
|
MappedFiducialPoints = [];
|
||||||
|
if (string.IsNullOrWhiteSpace(SelectedFiducialProvinceCode))
|
||||||
|
{
|
||||||
|
FiducialMessage = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FiducialBusy = true;
|
||||||
|
FiducialMessage = "Carico i Comuni catastali...";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FiducialMunicipalities = await FiducialPointService.GetMunicipalitiesAsync(SelectedFiducialProvinceCode);
|
||||||
|
FiducialMessage = string.Empty;
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
FiducialMessage = $"Comuni non disponibili: {error.GetBaseException().Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FiducialBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnFiducialMunicipalityChanged(ChangeEventArgs args)
|
||||||
|
{
|
||||||
|
SelectedFiducialMunicipalityCode = args.Value?.ToString() ?? string.Empty;
|
||||||
|
SelectedFiducialSheet = string.Empty;
|
||||||
|
FiducialSheets = [];
|
||||||
|
FiducialPoints = [];
|
||||||
|
MappedFiducialPoints = [];
|
||||||
|
if (string.IsNullOrWhiteSpace(SelectedFiducialMunicipalityCode))
|
||||||
|
{
|
||||||
|
FiducialMessage = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FiducialBusy = true;
|
||||||
|
FiducialMessage = "Carico i fogli disponibili...";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FiducialSheets = await FiducialPointService.GetSheetsAsync(
|
||||||
|
SelectedFiducialProvinceCode,
|
||||||
|
SelectedFiducialMunicipalityCode);
|
||||||
|
FiducialMessage = string.Empty;
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
FiducialMessage = $"Fogli non disponibili: {error.GetBaseException().Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FiducialBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SearchFiducials()
|
||||||
|
{
|
||||||
|
var municipality = FiducialMunicipalities.FirstOrDefault(item => item.Code == SelectedFiducialMunicipalityCode);
|
||||||
|
if (municipality is null || string.IsNullOrWhiteSpace(SelectedFiducialSheet))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await LoadFiducialsForSheetAsync(
|
||||||
|
municipality.Code,
|
||||||
|
SelectedFiducialSheet,
|
||||||
|
municipality.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadFiducialsForSheetAsync(
|
||||||
|
string municipalityCode,
|
||||||
|
string sheet,
|
||||||
|
string? municipalityName = null)
|
||||||
|
{
|
||||||
|
FiducialBusy = true;
|
||||||
|
FiducialPoints = [];
|
||||||
|
MappedFiducialPoints = [];
|
||||||
|
FiducialMessage = "Recupero monografie e coordinate dei PF...";
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string? monographError = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FiducialPoints = await FiducialPointService.GetFiducialsAsync(
|
||||||
|
municipalityCode,
|
||||||
|
sheet);
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
monographError = error.GetBaseException().Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapped = await FiducialPointService.GetMappedFiducialsAsync(
|
||||||
|
municipalityCode,
|
||||||
|
sheet);
|
||||||
|
var monographs = FiducialPoints.ToDictionary(
|
||||||
|
item => item.Identifier,
|
||||||
|
item => item.MonographUrl,
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
var currentMappedPoints = monographs.Count == 0
|
||||||
|
? mapped
|
||||||
|
: mapped.Where(point => monographs.ContainsKey(point.Identifier)).ToArray();
|
||||||
|
MappedFiducialPoints = currentMappedPoints
|
||||||
|
.Select(point => point with
|
||||||
|
{
|
||||||
|
MonographUrl = monographs.GetValueOrDefault(point.Identifier, string.Empty)
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
await JS.InvokeVoidAsync(
|
||||||
|
"lmeMap.setFiducialPoints",
|
||||||
|
MappedFiducialPoints,
|
||||||
|
FiducialColor,
|
||||||
|
ShowFiducialPoints,
|
||||||
|
KeepPreviousFiducialPoints,
|
||||||
|
municipalityCode,
|
||||||
|
sheet,
|
||||||
|
string.IsNullOrWhiteSpace(municipalityName) ? municipalityCode : municipalityName);
|
||||||
|
FiducialMessage = MappedFiducialPoints.Count == 0
|
||||||
|
? "Nessun Punto Fiduciale georiferito per il foglio selezionato."
|
||||||
|
: $"Caricati {MappedFiducialPoints.Count} PF del foglio {NormalizeSheetLabel(sheet)} e {FiducialPoints.Count} monografie ufficiali."
|
||||||
|
+ (monographError is null ? string.Empty : " Le monografie non sono al momento raggiungibili.");
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.showInfo", FiducialMessage);
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
FiducialMessage = $"Ricerca PF non riuscita: {error.GetBaseException().Message}";
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.showInfo", FiducialMessage);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FiducialBusy = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public async Task DownloadFiducialsForMapSelection(
|
||||||
|
string municipalityCode,
|
||||||
|
string sheet,
|
||||||
|
string contextLabel)
|
||||||
|
{
|
||||||
|
if (!CanUse(AppFeatures.FiducialPoints))
|
||||||
|
{
|
||||||
|
await ShowProRequiredAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FiducialBusy)
|
||||||
|
{
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.showInfo", "È già in corso una ricerca dei Punti Fiduciali.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.showInfo", $"Scarico i PF per {contextLabel}...");
|
||||||
|
var municipality = await CatalogService.FindMunicipalityAsync(municipalityCode);
|
||||||
|
await LoadFiducialsForSheetAsync(municipalityCode, sheet, municipality?.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeSheetLabel(string sheet) =>
|
||||||
|
int.TryParse(new string((sheet ?? string.Empty).Where(char.IsDigit).ToArray()), out var number)
|
||||||
|
? number.ToString(System.Globalization.CultureInfo.InvariantCulture)
|
||||||
|
: sheet ?? string.Empty;
|
||||||
|
|
||||||
|
private async Task DownloadFiducialMonograph(string monographUrl)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Launcher.Default.OpenAsync(new Uri(monographUrl));
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
FiducialMessage = $"Impossibile aprire la monografia: {error.GetBaseException().Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyFiducialVisibility()
|
||||||
|
{
|
||||||
|
SaveFlagPreferences();
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setFiducialPointsVisible", ShowFiducialPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyFiducialColor()
|
||||||
|
{
|
||||||
|
SaveFlagPreferences();
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setFiducialColor", FiducialColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ApplyKeepPreviousFiducials()
|
||||||
|
{
|
||||||
|
SaveFlagPreferences();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FitFiducialPoints() =>
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.fitFiducialPoints");
|
||||||
|
|
||||||
|
private static string FormatCoordinate(double? value) =>
|
||||||
|
value?.ToString("0.000", System.Globalization.CultureInfo.InvariantCulture) ?? "-";
|
||||||
|
|
||||||
private async Task OnMunicipalityChanged(ChangeEventArgs args)
|
private async Task OnMunicipalityChanged(ChangeEventArgs args)
|
||||||
{
|
{
|
||||||
SelectedMunicipalityCode = args.Value?.ToString() ?? string.Empty;
|
SelectedMunicipalityCode = args.Value?.ToString() ?? string.Empty;
|
||||||
@@ -1305,7 +1693,7 @@
|
|||||||
Interlocked.Exchange(ref LastGpsLookupAt, now);
|
Interlocked.Exchange(ref LastGpsLookupAt, now);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var radius = Math.Clamp(location.Accuracy ?? 5, 2, 25);
|
var radius = Math.Clamp(location.Accuracy ?? 12, 12, 30);
|
||||||
var xml = await OfficialCadastreService.GetParcelsNearAsync(
|
var xml = await OfficialCadastreService.GetParcelsNearAsync(
|
||||||
location.Latitude,
|
location.Latitude,
|
||||||
location.Longitude,
|
location.Longitude,
|
||||||
@@ -1415,6 +1803,15 @@
|
|||||||
return InvokeAsync(StateHasChanged);
|
return InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public async Task SetCompassFromLayerManager(bool enabled)
|
||||||
|
{
|
||||||
|
ShowCompass = enabled;
|
||||||
|
SaveFlagPreferences();
|
||||||
|
await ConfigureCompassSensorAsync();
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
public Task<string> GetOfficialCadastralSheetsNear(double latitude, double longitude) =>
|
public Task<string> GetOfficialCadastralSheetsNear(double latitude, double longitude) =>
|
||||||
OfficialCadastreService.GetSheetsNearAsync(latitude, longitude);
|
OfficialCadastreService.GetSheetsNearAsync(latitude, longitude);
|
||||||
@@ -1925,6 +2322,8 @@
|
|||||||
ShowDrawingMeasurements = options.ShowDrawingMeasurements;
|
ShowDrawingMeasurements = options.ShowDrawingMeasurements;
|
||||||
DrawToolsVisible = options.DrawToolsVisible;
|
DrawToolsVisible = options.DrawToolsVisible;
|
||||||
ShowGpsPosition = options.GpsPositionVisible;
|
ShowGpsPosition = options.GpsPositionVisible;
|
||||||
|
ShowCompass = options.CompassVisible;
|
||||||
|
CompassMode = options.CompassMode is "heading" ? "heading" : "fixed";
|
||||||
ShowSelectedParcels = options.SelectedParcelsVisible;
|
ShowSelectedParcels = options.SelectedParcelsVisible;
|
||||||
TrailEnabled = options.TrailEnabled;
|
TrailEnabled = options.TrailEnabled;
|
||||||
|
|
||||||
@@ -1934,6 +2333,7 @@
|
|||||||
layer.Enabled = activeLayers.Contains(layer.Key);
|
layer.Enabled = activeLayers.Contains(layer.Key);
|
||||||
}
|
}
|
||||||
SaveFlagPreferences();
|
SaveFlagPreferences();
|
||||||
|
_ = ConfigureCompassSensorAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteProject()
|
private async Task DeleteProject()
|
||||||
@@ -2094,6 +2494,12 @@
|
|||||||
|
|
||||||
private Task CenterOnGps() => JS.InvokeVoidAsync("lmeMap.centerOnGps").AsTask();
|
private Task CenterOnGps() => JS.InvokeVoidAsync("lmeMap.centerOnGps").AsTask();
|
||||||
|
|
||||||
|
private void ToggleGpsOverlay()
|
||||||
|
{
|
||||||
|
GpsOverlayCollapsed = !GpsOverlayCollapsed;
|
||||||
|
SaveFlagPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
private string L(string key) => L10n.T(key);
|
private string L(string key) => L10n.T(key);
|
||||||
|
|
||||||
private bool CanUse(string feature) => Entitlements.CanUse(feature);
|
private bool CanUse(string feature) => Entitlements.CanUse(feature);
|
||||||
@@ -2132,6 +2538,8 @@
|
|||||||
private Task ApplyEntitlementsToMapAsync() => JS.InvokeVoidAsync("lmeMap.setEntitlements", new
|
private Task ApplyEntitlementsToMapAsync() => JS.InvokeVoidAsync("lmeMap.setEntitlements", new
|
||||||
{
|
{
|
||||||
advancedGeometry = CanUse(AppFeatures.AdvancedGeometry),
|
advancedGeometry = CanUse(AppFeatures.AdvancedGeometry),
|
||||||
|
fiducialPoints = CanUse(AppFeatures.FiducialPoints),
|
||||||
|
multipleNotes = CanUse(AppFeatures.MultipleNotes),
|
||||||
maxDrawingObjects = Entitlements.HasProAccess ? (int?)null : EntitlementService.BaseDrawingObjectLimit,
|
maxDrawingObjects = Entitlements.HasProAccess ? (int?)null : EntitlementService.BaseDrawingObjectLimit,
|
||||||
drawingLimitMessage = L("drawings.base.limit.reached")
|
drawingLimitMessage = L("drawings.base.limit.reached")
|
||||||
}).AsTask();
|
}).AsTask();
|
||||||
@@ -2338,7 +2746,12 @@
|
|||||||
ShowDrawingMeasurements = GetFlagPreference("show_drawing_measurements", true);
|
ShowDrawingMeasurements = GetFlagPreference("show_drawing_measurements", true);
|
||||||
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);
|
||||||
|
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);
|
||||||
|
KeepPreviousFiducialPoints = GetFlagPreference("keep_previous_fiducials", true);
|
||||||
|
FiducialColor = Preferences.Default.Get($"{FlagPreferencePrefix}fiducial_color", "#dc2626");
|
||||||
TrailEnabled = GetFlagPreference("trail_enabled", false);
|
TrailEnabled = GetFlagPreference("trail_enabled", false);
|
||||||
ExportImported = GetFlagPreference("export_imported", true);
|
ExportImported = GetFlagPreference("export_imported", true);
|
||||||
ExportImportedSheets = GetFlagPreference("export_imported_sheets", true);
|
ExportImportedSheets = GetFlagPreference("export_imported_sheets", true);
|
||||||
@@ -2356,6 +2769,11 @@
|
|||||||
OfflineExtraZoomLevels = Preferences.Default.Get($"{FlagPreferencePrefix}offline_zoom_levels", 2);
|
OfflineExtraZoomLevels = Preferences.Default.Get($"{FlagPreferencePrefix}offline_zoom_levels", 2);
|
||||||
GpsProximityEnabled = GetFlagPreference("gps_proximity", true);
|
GpsProximityEnabled = GetFlagPreference("gps_proximity", true);
|
||||||
GpsBoundaryAlertEnabled = GetFlagPreference("gps_boundary_alert", false);
|
GpsBoundaryAlertEnabled = GetFlagPreference("gps_boundary_alert", false);
|
||||||
|
GpsOverlayCollapsed = GetFlagPreference("gps_overlay_collapsed", false);
|
||||||
|
MapMessageTimeoutSeconds = Math.Clamp(
|
||||||
|
Preferences.Default.Get($"{FlagPreferencePrefix}map_message_timeout", 3),
|
||||||
|
0,
|
||||||
|
30);
|
||||||
|
|
||||||
foreach (var layer in CadastralLayers)
|
foreach (var layer in CadastralLayers)
|
||||||
{
|
{
|
||||||
@@ -2373,7 +2791,12 @@
|
|||||||
SetFlagPreference("show_drawing_measurements", ShowDrawingMeasurements);
|
SetFlagPreference("show_drawing_measurements", ShowDrawingMeasurements);
|
||||||
SetFlagPreference("draw_tools_visible", DrawToolsVisible);
|
SetFlagPreference("draw_tools_visible", DrawToolsVisible);
|
||||||
SetFlagPreference("show_gps_position", ShowGpsPosition);
|
SetFlagPreference("show_gps_position", ShowGpsPosition);
|
||||||
|
SetFlagPreference("show_compass", ShowCompass);
|
||||||
|
Preferences.Default.Set($"{FlagPreferencePrefix}compass_mode", CompassMode);
|
||||||
SetFlagPreference("show_selected_parcels", ShowSelectedParcels);
|
SetFlagPreference("show_selected_parcels", ShowSelectedParcels);
|
||||||
|
SetFlagPreference("show_fiducial_points", ShowFiducialPoints);
|
||||||
|
SetFlagPreference("keep_previous_fiducials", KeepPreviousFiducialPoints);
|
||||||
|
Preferences.Default.Set($"{FlagPreferencePrefix}fiducial_color", FiducialColor);
|
||||||
SetFlagPreference("trail_enabled", TrailEnabled);
|
SetFlagPreference("trail_enabled", TrailEnabled);
|
||||||
SetFlagPreference("export_imported", ExportImported);
|
SetFlagPreference("export_imported", ExportImported);
|
||||||
SetFlagPreference("export_imported_sheets", ExportImportedSheets);
|
SetFlagPreference("export_imported_sheets", ExportImportedSheets);
|
||||||
@@ -2391,6 +2814,8 @@
|
|||||||
Preferences.Default.Set($"{FlagPreferencePrefix}offline_zoom_levels", OfflineExtraZoomLevels);
|
Preferences.Default.Set($"{FlagPreferencePrefix}offline_zoom_levels", OfflineExtraZoomLevels);
|
||||||
SetFlagPreference("gps_proximity", GpsProximityEnabled);
|
SetFlagPreference("gps_proximity", GpsProximityEnabled);
|
||||||
SetFlagPreference("gps_boundary_alert", GpsBoundaryAlertEnabled);
|
SetFlagPreference("gps_boundary_alert", GpsBoundaryAlertEnabled);
|
||||||
|
SetFlagPreference("gps_overlay_collapsed", GpsOverlayCollapsed);
|
||||||
|
Preferences.Default.Set($"{FlagPreferencePrefix}map_message_timeout", MapMessageTimeoutSeconds);
|
||||||
|
|
||||||
foreach (var layer in CadastralLayers)
|
foreach (var layer in CadastralLayers)
|
||||||
{
|
{
|
||||||
@@ -2400,6 +2825,7 @@
|
|||||||
|
|
||||||
private async Task ApplyFlagPreferencesToMapAsync()
|
private async Task ApplyFlagPreferencesToMapAsync()
|
||||||
{
|
{
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setInfoTimeout", MapMessageTimeoutSeconds);
|
||||||
await JS.InvokeVoidAsync("lmeMap.setIncludeKmlSheetLayer", IncludeKmlSheetLayer);
|
await JS.InvokeVoidAsync("lmeMap.setIncludeKmlSheetLayer", IncludeKmlSheetLayer);
|
||||||
await JS.InvokeVoidAsync("lmeMap.setImportedLayerVisible", ShowImportedData);
|
await JS.InvokeVoidAsync("lmeMap.setImportedLayerVisible", ShowImportedData);
|
||||||
await JS.InvokeVoidAsync("lmeMap.setImportedSheetLayerVisible", ShowImportedSheetLayer);
|
await JS.InvokeVoidAsync("lmeMap.setImportedSheetLayerVisible", ShowImportedSheetLayer);
|
||||||
@@ -2407,7 +2833,11 @@
|
|||||||
await JS.InvokeVoidAsync("lmeMap.setDrawingMeasurementsVisible", ShowDrawingMeasurements);
|
await JS.InvokeVoidAsync("lmeMap.setDrawingMeasurementsVisible", ShowDrawingMeasurements);
|
||||||
await JS.InvokeVoidAsync("lmeMap.setDrawToolsVisible", DrawToolsVisible);
|
await JS.InvokeVoidAsync("lmeMap.setDrawToolsVisible", DrawToolsVisible);
|
||||||
await JS.InvokeVoidAsync("lmeMap.setGpsPositionVisible", ShowGpsPosition);
|
await JS.InvokeVoidAsync("lmeMap.setGpsPositionVisible", ShowGpsPosition);
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setCompassOptions", ShowCompass, CompassMode);
|
||||||
|
await ConfigureCompassSensorAsync();
|
||||||
await JS.InvokeVoidAsync("lmeMap.setSelectedParcelsVisible", ShowSelectedParcels);
|
await JS.InvokeVoidAsync("lmeMap.setSelectedParcelsVisible", ShowSelectedParcels);
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setFiducialColor", FiducialColor);
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setFiducialPointsVisible", ShowFiducialPoints);
|
||||||
await JS.InvokeVoidAsync("lmeMap.toggleTrail", TrailEnabled);
|
await JS.InvokeVoidAsync("lmeMap.toggleTrail", TrailEnabled);
|
||||||
|
|
||||||
foreach (var layer in CadastralLayers)
|
foreach (var layer in CadastralLayers)
|
||||||
@@ -2416,6 +2846,75 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ApplyMapMessageOptions()
|
||||||
|
{
|
||||||
|
MapMessageTimeoutSeconds = Math.Clamp(MapMessageTimeoutSeconds, 0, 30);
|
||||||
|
SaveFlagPreferences();
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setInfoTimeout", MapMessageTimeoutSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyCompassOptions()
|
||||||
|
{
|
||||||
|
CompassMode = CompassMode is "heading" ? "heading" : "fixed";
|
||||||
|
SaveFlagPreferences();
|
||||||
|
await JS.InvokeVoidAsync("lmeMap.setCompassOptions", ShowCompass, CompassMode);
|
||||||
|
await ConfigureCompassSensorAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ConfigureCompassSensorAsync()
|
||||||
|
{
|
||||||
|
var compass = Microsoft.Maui.Devices.Sensors.Compass.Default;
|
||||||
|
var shouldMonitor = ShowCompass && compass.IsSupported;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (shouldMonitor && !CompassSensorSubscribed)
|
||||||
|
{
|
||||||
|
compass.ReadingChanged += OnCompassReadingChanged;
|
||||||
|
CompassSensorSubscribed = true;
|
||||||
|
compass.Start(Microsoft.Maui.Devices.Sensors.SensorSpeed.UI);
|
||||||
|
}
|
||||||
|
else if (!shouldMonitor && CompassSensorSubscribed)
|
||||||
|
{
|
||||||
|
compass.ReadingChanged -= OnCompassReadingChanged;
|
||||||
|
CompassSensorSubscribed = false;
|
||||||
|
if (compass.IsMonitoring)
|
||||||
|
{
|
||||||
|
compass.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (CompassSensorSubscribed)
|
||||||
|
{
|
||||||
|
compass.ReadingChanged -= OnCompassReadingChanged;
|
||||||
|
CompassSensorSubscribed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnCompassReadingChanged(
|
||||||
|
object? sender,
|
||||||
|
Microsoft.Maui.Devices.Sensors.CompassChangedEventArgs args)
|
||||||
|
{
|
||||||
|
if (!MapReady || !ShowCompass)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await InvokeAsync(() => JS.InvokeVoidAsync(
|
||||||
|
"lmeMap.setCompassHeading",
|
||||||
|
args.Reading.HeadingMagneticNorth).AsTask());
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ApplyAdvancedOptions()
|
private async Task ApplyAdvancedOptions()
|
||||||
{
|
{
|
||||||
SaveFlagPreferences();
|
SaveFlagPreferences();
|
||||||
@@ -2490,6 +2989,8 @@
|
|||||||
public bool ShowDrawingMeasurements { get; init; } = true;
|
public bool ShowDrawingMeasurements { get; init; } = true;
|
||||||
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 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; }
|
||||||
}
|
}
|
||||||
@@ -2519,6 +3020,16 @@
|
|||||||
GpsLookupCancellation?.Dispose();
|
GpsLookupCancellation?.Dispose();
|
||||||
ProjectAutoSaveCancellation?.Cancel();
|
ProjectAutoSaveCancellation?.Cancel();
|
||||||
ProjectAutoSaveCancellation?.Dispose();
|
ProjectAutoSaveCancellation?.Dispose();
|
||||||
|
var compass = Microsoft.Maui.Devices.Sensors.Compass.Default;
|
||||||
|
if (CompassSensorSubscribed)
|
||||||
|
{
|
||||||
|
compass.ReadingChanged -= OnCompassReadingChanged;
|
||||||
|
if (compass.IsMonitoring)
|
||||||
|
{
|
||||||
|
compass.Stop();
|
||||||
|
}
|
||||||
|
CompassSensorSubscribed = false;
|
||||||
|
}
|
||||||
MapCallbackReference?.Dispose();
|
MapCallbackReference?.Dispose();
|
||||||
return ValueTask.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<EnableDefaultCssItems>false</EnableDefaultCssItems>
|
<EnableDefaultCssItems>false</EnableDefaultCssItems>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);obj/**;bin/**;obj-api36*/**;artifacts/**</DefaultItemExcludes>
|
||||||
|
|
||||||
<!-- Display name -->
|
<!-- Display name -->
|
||||||
<ApplicationTitle>LME Mappe Catastali Pro</ApplicationTitle>
|
<ApplicationTitle>LME Mappe Catastali Pro</ApplicationTitle>
|
||||||
@@ -28,8 +29,8 @@
|
|||||||
<ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId>
|
<ApplicationId>it.liviomerola.lmemappecatastali.pro</ApplicationId>
|
||||||
|
|
||||||
<!-- Versions -->
|
<!-- Versions -->
|
||||||
<ApplicationDisplayVersion>1.0.6</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0.19</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>106</ApplicationVersion>
|
<ApplicationVersion>119</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 -->
|
||||||
@@ -61,6 +62,7 @@
|
|||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)' == 'Release' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
<PropertyGroup Condition="'$(Configuration)' == 'Release' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
||||||
<AndroidTargetSdkVersion>36</AndroidTargetSdkVersion>
|
<AndroidTargetSdkVersion>36</AndroidTargetSdkVersion>
|
||||||
|
<RuntimeIdentifiers>android-arm;android-arm64</RuntimeIdentifiers>
|
||||||
<AndroidPackageFormat>aab</AndroidPackageFormat>
|
<AndroidPackageFormat>aab</AndroidPackageFormat>
|
||||||
<AndroidUseAapt2>true</AndroidUseAapt2>
|
<AndroidUseAapt2>true</AndroidUseAapt2>
|
||||||
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
|
<AndroidCreatePackagePerAbi>false</AndroidCreatePackagePerAbi>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public static class MauiProgram
|
|||||||
builder.Services.AddSingleton<PreciseLocationService>();
|
builder.Services.AddSingleton<PreciseLocationService>();
|
||||||
builder.Services.AddSingleton<CadastralCatalogService>();
|
builder.Services.AddSingleton<CadastralCatalogService>();
|
||||||
builder.Services.AddSingleton<OfficialCadastreService>();
|
builder.Services.AddSingleton<OfficialCadastreService>();
|
||||||
|
builder.Services.AddSingleton<FiducialPointService>();
|
||||||
builder.Services.AddSingleton<LocationSearchService>();
|
builder.Services.AddSingleton<LocationSearchService>();
|
||||||
builder.Services.AddSingleton<NetworkLocationService>();
|
builder.Services.AddSingleton<NetworkLocationService>();
|
||||||
builder.Services.AddSingleton<AzureMapsService>();
|
builder.Services.AddSingleton<AzureMapsService>();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
- Nome: LME Mappe Catastali Pro.
|
- Nome: LME Mappe Catastali Pro.
|
||||||
- Package name Android: `it.liviomerola.lmemappecatastali.pro`.
|
- Package name Android: `it.liviomerola.lmemappecatastali.pro`.
|
||||||
- Versione corrente da pubblicare: `1.0.6` / version code `106`.
|
- Versione corrente da pubblicare: `1.0.11` / version code `111`.
|
||||||
- Formato Release Android: Android App Bundle (`.aab`).
|
- Formato Release Android: Android App Bundle (`.aab`).
|
||||||
- Target SDK Release: Android 16 / API 36.
|
- Target SDK Release: Android 16 / API 36.
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public sealed class AndroidPlayBillingService : Java.Lang.Object, IPlayBillingSe
|
|||||||
private readonly SemaphoreSlim _connectionLock = new(1, 1);
|
private readonly SemaphoreSlim _connectionLock = new(1, 1);
|
||||||
private readonly BillingClient _client;
|
private readonly BillingClient _client;
|
||||||
private TaskCompletionSource<PlayBillingResult>? _purchaseCompletion;
|
private TaskCompletionSource<PlayBillingResult>? _purchaseCompletion;
|
||||||
|
private string? _lastCatalogFailure;
|
||||||
|
|
||||||
public AndroidPlayBillingService()
|
public AndroidPlayBillingService()
|
||||||
{
|
{
|
||||||
@@ -31,7 +32,12 @@ public sealed class AndroidPlayBillingService : Java.Lang.Object, IPlayBillingSe
|
|||||||
{
|
{
|
||||||
var details = await GetProductDetailsAsync(cancellationToken);
|
var details = await GetProductDetailsAsync(cancellationToken);
|
||||||
return details?.GetSubscriptionOfferDetails()?
|
return details?.GetSubscriptionOfferDetails()?
|
||||||
.Where(item => string.IsNullOrEmpty(item.OfferId))
|
.Where(item => !string.IsNullOrWhiteSpace(item.BasePlanId)
|
||||||
|
&& !string.IsNullOrWhiteSpace(item.OfferToken))
|
||||||
|
.GroupBy(item => item.BasePlanId, StringComparer.Ordinal)
|
||||||
|
.Select(group => group
|
||||||
|
.OrderBy(item => string.IsNullOrEmpty(item.OfferId) ? 0 : 1)
|
||||||
|
.First())
|
||||||
.Select(item =>
|
.Select(item =>
|
||||||
{
|
{
|
||||||
var phase = item.PricingPhases.PricingPhaseList.LastOrDefault();
|
var phase = item.PricingPhases.PricingPhaseList.LastOrDefault();
|
||||||
@@ -99,13 +105,25 @@ public sealed class AndroidPlayBillingService : Java.Lang.Object, IPlayBillingSe
|
|||||||
|
|
||||||
var details = await GetProductDetailsAsync(cancellationToken);
|
var details = await GetProductDetailsAsync(cancellationToken);
|
||||||
var offer = details?.GetSubscriptionOfferDetails()?
|
var offer = details?.GetSubscriptionOfferDetails()?
|
||||||
.FirstOrDefault(item => item.BasePlanId == basePlanId
|
.Where(item => item.BasePlanId == basePlanId
|
||||||
&& string.IsNullOrEmpty(item.OfferId)
|
&& !string.IsNullOrWhiteSpace(item.OfferToken))
|
||||||
&& !string.IsNullOrWhiteSpace(item.OfferToken));
|
.OrderBy(item => string.IsNullOrEmpty(item.OfferId) ? 0 : 1)
|
||||||
if (details is null || offer is null)
|
.FirstOrDefault();
|
||||||
|
if (details is null)
|
||||||
{
|
{
|
||||||
return PlayBillingResult.Failed(
|
return PlayBillingResult.Failed(_lastCatalogFailure
|
||||||
$"Piano {basePlanId} non disponibile su Google Play. Verifica che l'abbonamento lme_pro e il piano siano attivi per il tuo Paese.");
|
?? "Google Play non ha restituito l'abbonamento lme_pro.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offer is null)
|
||||||
|
{
|
||||||
|
var availablePlans = string.Join(", ", details.GetSubscriptionOfferDetails()?
|
||||||
|
.Select(item => item.BasePlanId)
|
||||||
|
.Where(item => !string.IsNullOrWhiteSpace(item))
|
||||||
|
.Distinct(StringComparer.Ordinal) ?? []);
|
||||||
|
return PlayBillingResult.Failed(string.IsNullOrWhiteSpace(availablePlans)
|
||||||
|
? $"Google Play ha restituito lme_pro senza offerte acquistabili per il piano {basePlanId}."
|
||||||
|
: $"Piano {basePlanId} non restituito da Google Play. Piani ricevuti: {availablePlans}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var productParams = BillingFlowParams.ProductDetailsParams.NewBuilder()
|
var productParams = BillingFlowParams.ProductDetailsParams.NewBuilder()
|
||||||
@@ -191,7 +209,17 @@ public sealed class AndroidPlayBillingService : Java.Lang.Object, IPlayBillingSe
|
|||||||
throw new InvalidOperationException(ToMessage(result.Result));
|
throw new InvalidOperationException(ToMessage(result.Result));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.ProductDetailsList?.FirstOrDefault();
|
// The .NET async wrapper copies the Java result into the managed
|
||||||
|
// ProductDetails property; ProductDetailsList has no Java handle here.
|
||||||
|
var details = result.ProductDetails?.FirstOrDefault();
|
||||||
|
if (details is not null)
|
||||||
|
{
|
||||||
|
_lastCatalogFailure = null;
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastCatalogFailure = "Google Play ha restituito un catalogo vuoto per lme_pro. Verifica account Play Store, adesione al test e origine dell'installazione.";
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnsureConnectedAsync(CancellationToken cancellationToken)
|
private async Task EnsureConnectedAsync(CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -102,7 +102,11 @@ public class MainActivity : MauiAppCompatActivity
|
|||||||
{
|
{
|
||||||
if (view is null || insets is null) return insets;
|
if (view is null || insets is null) return insets;
|
||||||
var systemBars = insets.GetInsets(WindowInsetsCompat.Type.SystemBars());
|
var systemBars = insets.GetInsets(WindowInsetsCompat.Type.SystemBars());
|
||||||
view.SetPadding(_left, _top, _right, systemBars?.Bottom ?? 0);
|
view.SetPadding(
|
||||||
|
_left,
|
||||||
|
systemBars?.Top ?? _top,
|
||||||
|
_right,
|
||||||
|
systemBars?.Bottom ?? 0);
|
||||||
return insets;
|
return insets;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,32 @@ App .NET MAUI Blazor Hybrid per Android, iOS, Mac Catalyst e Windows, pensata pe
|
|||||||
|
|
||||||
Informativa privacy: [PRIVACY_POLICY.md](PRIVACY_POLICY.md).
|
Informativa privacy: [PRIVACY_POLICY.md](PRIVACY_POLICY.md).
|
||||||
|
|
||||||
|
## Versione Pro 1.0.11 test
|
||||||
|
|
||||||
|
- Il flag Fogli catastali mostra il WMS di tutti i fogli agli zoom panoramici e, in dettaglio, il confine ufficiale WFS del foglio corrente anche senza dati caricati.
|
||||||
|
- Se il foglio corrente e gia presente in un progetto o in un'importazione, l'overlay di navigazione non ne crea un duplicato.
|
||||||
|
- La toolbar superiore rispetta l'area occupata dalla barra di stato e dalle notifiche Android.
|
||||||
|
|
||||||
|
## Versione Pro 1.0.10 test
|
||||||
|
|
||||||
|
- Migliora la compatibilita del servizio catastale AdE sui dispositivi Android meno recenti con HTTP/1.1, tentativi automatici e una ricerca GPS piu tollerante.
|
||||||
|
- I messaggi sulla mappa si chiudono automaticamente dopo un intervallo configurabile, predefinito a 3 secondi, e includono un pulsante di chiusura.
|
||||||
|
- La barra GPS mantiene sempre disponibili i comandi Avvia/Ferma e Centra, impilati sugli schermi stretti, e puo essere ridotta.
|
||||||
|
|
||||||
|
## Versione Pro 1.0.9 test
|
||||||
|
|
||||||
|
- Corregge la lettura del catalogo dal wrapper asincrono .NET di Google Play Billing, usando la raccolta managed `ProductDetails` popolata dal binding.
|
||||||
|
|
||||||
|
## Versione Pro 1.0.8 test
|
||||||
|
|
||||||
|
- Google Play Billing accetta tutte le offerte idonee restituite per i piani `monthly` e `yearly`, preferendo il piano base quando disponibile.
|
||||||
|
- In caso di catalogo vuoto o prodotto non caricato, il messaggio mostra una diagnostica specifica restituita da Google Play.
|
||||||
|
|
||||||
|
## Versione Pro 1.0.7 test
|
||||||
|
|
||||||
|
- I messaggi informativi su telefono vengono mostrati sopra i controlli zoom e possono occupare fino a tre righe senza sovrapporsi.
|
||||||
|
- Durante il rilevamento GPS la mappa si ricentra automaticamente quando la nuova posizione esce dall'area visibile, mantenendo lo zoom corrente.
|
||||||
|
|
||||||
## Versione Pro 1.0.6 test
|
## Versione Pro 1.0.6 test
|
||||||
|
|
||||||
- Il pannello Layer usa tutta la larghezza disponibile sui telefoni e le righe si adattano ai testi su piu linee senza sovrapporsi.
|
- Il pannello Layer usa tutta la larghezza disponibile sui telefoni e le righe si adattano ai testi su piu linee senza sovrapporsi.
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ public sealed class CadastralCatalogService
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MunicipalityOption?> FindMunicipalityAsync(string municipalityCode)
|
||||||
|
{
|
||||||
|
var code = (municipalityCode ?? string.Empty).Trim();
|
||||||
|
var municipalities = await GetAllMunicipalitiesAsync();
|
||||||
|
return municipalities.FirstOrDefault(item =>
|
||||||
|
string.Equals(item.Code, code, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<string>> GetSheetsAsync(
|
public async Task<IReadOnlyList<string>> GetSheetsAsync(
|
||||||
MunicipalityOption municipality,
|
MunicipalityOption municipality,
|
||||||
IProgress<CatalogProgress>? progress = null,
|
IProgress<CatalogProgress>? progress = null,
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ public sealed class EntitlementService(
|
|||||||
AppFeatures.CloudSync,
|
AppFeatures.CloudSync,
|
||||||
AppFeatures.OfflineMaps,
|
AppFeatures.OfflineMaps,
|
||||||
AppFeatures.AdvancedGeometry,
|
AppFeatures.AdvancedGeometry,
|
||||||
AppFeatures.GpsTrail
|
AppFeatures.GpsTrail,
|
||||||
|
AppFeatures.FiducialPoints,
|
||||||
|
AppFeatures.MultipleNotes
|
||||||
};
|
};
|
||||||
|
|
||||||
public bool IsGoogleProActive { get; private set; }
|
public bool IsGoogleProActive { get; private set; }
|
||||||
@@ -185,4 +187,6 @@ public static class AppFeatures
|
|||||||
public const string OfflineMaps = "offline-maps";
|
public const string OfflineMaps = "offline-maps";
|
||||||
public const string AdvancedGeometry = "advanced-geometry";
|
public const string AdvancedGeometry = "advanced-geometry";
|
||||||
public const string GpsTrail = "gps-trail";
|
public const string GpsTrail = "gps-trail";
|
||||||
|
public const string FiducialPoints = "fiducial-points";
|
||||||
|
public const string MultipleNotes = "multiple-notes";
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace LMEMappeCatastaliApp.Services;
|
||||||
|
|
||||||
|
public sealed partial class FiducialPointService
|
||||||
|
{
|
||||||
|
private const string ServiceBaseUrl = "https://www1.agenziaentrate.gov.it/servizi/Monografie/";
|
||||||
|
private const string MappedPointsUrl = "https://cartografia.globogis.it/arcgis/rest/services/fiduciali/Punti_fiduciali/MapServer/2/query";
|
||||||
|
private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(30) };
|
||||||
|
|
||||||
|
public FiducialPointService()
|
||||||
|
{
|
||||||
|
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/1.0 (Android)");
|
||||||
|
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<FiducialAreaOption>> GetProvincesAsync(
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var html = await GetHtmlAsync("ricerca.php", cancellationToken);
|
||||||
|
return ParseOptions(html, "provincia");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<FiducialAreaOption>> GetMunicipalitiesAsync(
|
||||||
|
string provinceCode,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var code = NormalizeCode(provinceCode);
|
||||||
|
var html = await GetHtmlAsync($"ricerca.php?level=1&prov={Uri.EscapeDataString(code)}", cancellationToken);
|
||||||
|
return ParseOptions(html, "co");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<FiducialAreaOption>> GetSheetsAsync(
|
||||||
|
string provinceCode,
|
||||||
|
string municipalityCode,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var province = NormalizeCode(provinceCode);
|
||||||
|
var municipality = NormalizeCode(municipalityCode);
|
||||||
|
var path = $"ricerca.php?level=2&prov={Uri.EscapeDataString(province)}";
|
||||||
|
var html = await PostFormAsync(path, new Dictionary<string, string> { ["co"] = municipality }, cancellationToken);
|
||||||
|
return ParseOptions(html, "foglio");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<FiducialPointEntry>> GetFiducialsAsync(
|
||||||
|
string provinceName,
|
||||||
|
string municipalityName,
|
||||||
|
string municipalityCode,
|
||||||
|
string sheet,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var code = NormalizeCode(municipalityCode);
|
||||||
|
var normalizedSheet = sheet.Trim();
|
||||||
|
var query = string.Join("&",
|
||||||
|
[
|
||||||
|
$"provincia={Uri.EscapeDataString(provinceName.Trim())}",
|
||||||
|
$"comune={Uri.EscapeDataString(municipalityName.Trim())}",
|
||||||
|
$"co={Uri.EscapeDataString(code)}"
|
||||||
|
]);
|
||||||
|
var html = await PostFormAsync(
|
||||||
|
$"risultato.php?{query}",
|
||||||
|
new Dictionary<string, string> { ["foglio"] = normalizedSheet },
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
return ParseFiducialLinks(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<FiducialPointEntry>> GetFiducialsAsync(
|
||||||
|
string municipalityCode,
|
||||||
|
string sheet,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var code = NormalizeCode(municipalityCode);
|
||||||
|
var normalizedSheet = NormalizeSheet(sheet);
|
||||||
|
var html = await GetHtmlAsync(
|
||||||
|
$"risultato.php?co={Uri.EscapeDataString(code)}&foglio={Uri.EscapeDataString(normalizedSheet)}",
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
return ParseFiducialLinks(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<FiducialPointEntry> ParseFiducialLinks(string html) =>
|
||||||
|
DownloadLinkRegex().Matches(html)
|
||||||
|
.Select(match =>
|
||||||
|
{
|
||||||
|
var relativeUrl = WebUtility.HtmlDecode(match.Groups["url"].Value);
|
||||||
|
var fileName = WebUtility.HtmlDecode(match.Groups["name"].Value);
|
||||||
|
return new FiducialPointEntry(
|
||||||
|
FormatIdentifier(fileName),
|
||||||
|
new Uri(new Uri(ServiceBaseUrl), relativeUrl).AbsoluteUri);
|
||||||
|
})
|
||||||
|
.DistinctBy(item => item.Identifier, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(item => item.Identifier, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<MappedFiducialPoint>> GetMappedFiducialsAsync(
|
||||||
|
string municipalityCode,
|
||||||
|
string sheet,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var municipality = NormalizeCode(municipalityCode);
|
||||||
|
if (!int.TryParse(NormalizeSheet(sheet), out var sheetNumber))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Numero di foglio non valido.", nameof(sheet));
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["f"] = "json",
|
||||||
|
["where"] = $"COMUNENAZI='{municipality}' AND FOGLIO={sheetNumber}",
|
||||||
|
["outFields"] = "ETICHETTA,PARTICELLA,NORD,EST,ATTENDIBIL,QUOTA,DATAAGGIOR,IDORIGINE,URL_AGENZI,FOGLIO,ALLEGATO,FIDUCIALE",
|
||||||
|
["returnGeometry"] = "true",
|
||||||
|
["outSR"] = "3857",
|
||||||
|
["resultRecordCount"] = "500"
|
||||||
|
};
|
||||||
|
var url = $"{MappedPointsUrl}?{string.Join('&', query.Select(item => $"{item.Key}={Uri.EscapeDataString(item.Value)}"))}";
|
||||||
|
using var response = await _httpClient.GetAsync(url, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||||
|
using var document = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken);
|
||||||
|
if (document.RootElement.TryGetProperty("error", out var error))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(error.TryGetProperty("message", out var message)
|
||||||
|
? message.GetString()
|
||||||
|
: "Servizio cartografico PF non disponibile.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!document.RootElement.TryGetProperty("features", out var features))
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return features.EnumerateArray()
|
||||||
|
.Select(feature => ParseMappedPoint(feature, municipality))
|
||||||
|
.Where(point => point is not null)
|
||||||
|
.Cast<MappedFiducialPoint>()
|
||||||
|
.DistinctBy(point => point.Identifier, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(point => point.Identifier, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MappedFiducialPoint? ParseMappedPoint(JsonElement feature, string municipalityCode)
|
||||||
|
{
|
||||||
|
if (!feature.TryGetProperty("geometry", out var geometry)
|
||||||
|
|| !TryGetDouble(geometry, "x", out var x)
|
||||||
|
|| !TryGetDouble(geometry, "y", out var y))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributes = feature.GetProperty("attributes");
|
||||||
|
var label = GetString(attributes, "ETICHETTA");
|
||||||
|
var identifier = FormatIdentifier(label);
|
||||||
|
if (identifier == label)
|
||||||
|
{
|
||||||
|
var sheet = GetString(attributes, "FOGLIO").PadLeft(4, '0');
|
||||||
|
var number = GetString(attributes, "FIDUCIALE").PadLeft(2, '0');
|
||||||
|
identifier = $"PF{number}/{sheet}/{municipalityCode}";
|
||||||
|
}
|
||||||
|
|
||||||
|
const double earthRadius = 6378137d;
|
||||||
|
var longitude = x / earthRadius * 180d / Math.PI;
|
||||||
|
var latitude = (2d * Math.Atan(Math.Exp(y / earthRadius)) - Math.PI / 2d) * 180d / Math.PI;
|
||||||
|
return new MappedFiducialPoint(
|
||||||
|
identifier,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
GetNullableDouble(attributes, "NORD"),
|
||||||
|
GetNullableDouble(attributes, "EST"),
|
||||||
|
GetString(attributes, "ATTENDIBIL"),
|
||||||
|
GetNullableDouble(attributes, "QUOTA"),
|
||||||
|
GetString(attributes, "PARTICELLA"),
|
||||||
|
GetString(attributes, "DATAAGGIOR"),
|
||||||
|
GetString(attributes, "IDORIGINE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetDouble(JsonElement element, string name, out double value)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
return element.TryGetProperty(name, out var property)
|
||||||
|
&& (property.ValueKind == JsonValueKind.Number
|
||||||
|
? property.TryGetDouble(out value)
|
||||||
|
: double.TryParse(property.ToString(), System.Globalization.NumberStyles.Float,
|
||||||
|
System.Globalization.CultureInfo.InvariantCulture, out value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double? GetNullableDouble(JsonElement element, string name) =>
|
||||||
|
TryGetDouble(element, name, out var value) ? value : null;
|
||||||
|
|
||||||
|
private static string GetString(JsonElement element, string name) =>
|
||||||
|
element.TryGetProperty(name, out var property) && property.ValueKind != JsonValueKind.Null
|
||||||
|
? property.ToString().Trim()
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
private static string NormalizeSheet(string sheet)
|
||||||
|
{
|
||||||
|
var digits = new string((sheet ?? string.Empty).Where(char.IsDigit).ToArray());
|
||||||
|
if (string.IsNullOrWhiteSpace(digits))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Numero di foglio non valido.", nameof(sheet));
|
||||||
|
}
|
||||||
|
|
||||||
|
return int.TryParse(digits, out var number)
|
||||||
|
? number.ToString(System.Globalization.CultureInfo.InvariantCulture)
|
||||||
|
: digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetHtmlAsync(string relativeUrl, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var response = await _httpClient.GetAsync(ServiceBaseUrl + relativeUrl, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> PostFormAsync(
|
||||||
|
string relativeUrl,
|
||||||
|
IReadOnlyDictionary<string, string> values,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var content = new FormUrlEncodedContent(values);
|
||||||
|
using var response = await _httpClient.PostAsync(ServiceBaseUrl + relativeUrl, content, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<FiducialAreaOption> ParseOptions(string html, string selectId)
|
||||||
|
{
|
||||||
|
var select = SelectRegex().Matches(html)
|
||||||
|
.LastOrDefault(match => string.Equals(match.Groups["id"].Value, selectId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (select is null)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return OptionRegex().Matches(select.Groups["body"].Value)
|
||||||
|
.Select(match => new FiducialAreaOption(
|
||||||
|
WebUtility.HtmlDecode(match.Groups["value"].Value).Trim(),
|
||||||
|
WebUtility.HtmlDecode(StripTagsRegex().Replace(match.Groups["label"].Value, string.Empty)).Trim()))
|
||||||
|
.Where(item => !string.IsNullOrWhiteSpace(item.Code) && !string.IsNullOrWhiteSpace(item.Name))
|
||||||
|
.DistinctBy(item => item.Code, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeCode(string value)
|
||||||
|
{
|
||||||
|
var normalized = value.Trim().ToUpperInvariant();
|
||||||
|
if (normalized.Length is < 2 or > 4 || normalized.Any(character => !char.IsLetterOrDigit(character)))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Codice territoriale non valido.", nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatIdentifier(string fileName)
|
||||||
|
{
|
||||||
|
var parts = fileName.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
return parts.Length == 3 ? $"PF{parts[2]}/{parts[1]}/{parts[0]}" : fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("<select\\b[^>]*(?:id|name)=[\\\"'](?<id>[^\\\"']+)[\\\"'][^>]*>(?<body>[\\s\\S]*?)</select>", RegexOptions.IgnoreCase, matchTimeoutMilliseconds: 2000)]
|
||||||
|
private static partial Regex SelectRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex("<option\\b[^>]*value=[\\\"'](?<value>[^\\\"']*)[\\\"'][^>]*>(?<label>[\\s\\S]*?)</option>", RegexOptions.IgnoreCase, matchTimeoutMilliseconds: 2000)]
|
||||||
|
private static partial Regex OptionRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex("href=[\\\"'](?<url>download\\.php\\?[^\\\"']*namefile=(?<name>[A-Z0-9-]+))[^\\\"']*[\\\"']", RegexOptions.IgnoreCase, matchTimeoutMilliseconds: 2000)]
|
||||||
|
private static partial Regex DownloadLinkRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex("<[^>]+>", RegexOptions.None, 2000)]
|
||||||
|
private static partial Regex StripTagsRegex();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record FiducialAreaOption(string Code, string Name);
|
||||||
|
|
||||||
|
public sealed record FiducialPointEntry(string Identifier, string MonographUrl);
|
||||||
|
|
||||||
|
public sealed record MappedFiducialPoint(
|
||||||
|
string Identifier,
|
||||||
|
double Latitude,
|
||||||
|
double Longitude,
|
||||||
|
double? CadastralNorth,
|
||||||
|
double? CadastralEast,
|
||||||
|
string Reliability,
|
||||||
|
double? Elevation,
|
||||||
|
string Parcel,
|
||||||
|
string UpdatedAt,
|
||||||
|
string OriginId,
|
||||||
|
string MonographUrl = "");
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace LMEMappeCatastaliApp.Services;
|
namespace LMEMappeCatastaliApp.Services;
|
||||||
@@ -13,8 +15,9 @@ public sealed class OfficialCadastreService
|
|||||||
|
|
||||||
public OfficialCadastreService()
|
public OfficialCadastreService()
|
||||||
{
|
{
|
||||||
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/0.2");
|
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/1.0 (Android)");
|
||||||
_httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/gml+xml");
|
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/gml+xml"));
|
||||||
|
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml", 0.9));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetParcelsNearAsync(
|
public async Task<string> GetParcelsNearAsync(
|
||||||
@@ -22,14 +25,30 @@ public sealed class OfficialCadastreService
|
|||||||
double longitude,
|
double longitude,
|
||||||
double radiusMeters,
|
double radiusMeters,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
=> await GetFeaturesNearAsync(
|
{
|
||||||
|
var firstRadius = Math.Clamp(radiusMeters, 12, 30);
|
||||||
|
var xml = await GetFeaturesNearAsync(
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
radiusMeters,
|
firstRadius,
|
||||||
"CP:CadastralParcel",
|
"CP:CadastralParcel",
|
||||||
100,
|
100,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
if (firstRadius >= 30 || ContainsFeatures(xml))
|
||||||
|
{
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await GetFeaturesNearAsync(
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
30,
|
||||||
|
"CP:CadastralParcel",
|
||||||
|
100,
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string> GetSheetsNearAsync(
|
public async Task<string> GetSheetsNearAsync(
|
||||||
double latitude,
|
double latitude,
|
||||||
double longitude,
|
double longitude,
|
||||||
@@ -78,15 +97,65 @@ public sealed class OfficialCadastreService
|
|||||||
var url = WfsEndpoint + "?" + string.Join("&", query.Select(item =>
|
var url = WfsEndpoint + "?" + string.Join("&", query.Select(item =>
|
||||||
$"{Uri.EscapeDataString(item.Key)}={Uri.EscapeDataString(item.Value)}"));
|
$"{Uri.EscapeDataString(item.Key)}={Uri.EscapeDataString(item.Value)}"));
|
||||||
|
|
||||||
using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseContentRead, cancellationToken);
|
for (var attempt = 0; attempt < 3; attempt++)
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
{
|
||||||
throw new HttpRequestException($"Il servizio WFS AdE ha risposto {((int)response.StatusCode)}.");
|
try
|
||||||
|
{
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Get, url)
|
||||||
|
{
|
||||||
|
Version = HttpVersion.Version11,
|
||||||
|
VersionPolicy = HttpVersionPolicy.RequestVersionOrLower
|
||||||
|
};
|
||||||
|
request.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
|
||||||
|
|
||||||
|
using var response = await _httpClient.SendAsync(
|
||||||
|
request,
|
||||||
|
HttpCompletionOption.ResponseContentRead,
|
||||||
|
cancellationToken);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var xml = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
ThrowIfExceptionReport(xml);
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt < 2 && IsTransient(response.StatusCode))
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(500 * (attempt + 1)), cancellationToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HttpRequestException($"Il servizio WFS AdE ha risposto {((int)response.StatusCode)}.");
|
||||||
|
}
|
||||||
|
catch (HttpRequestException) when (attempt < 2)
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(500 * (attempt + 1)), cancellationToken);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException) when (!cancellationToken.IsCancellationRequested && attempt < 2)
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(500 * (attempt + 1)), cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var xml = await response.Content.ReadAsStringAsync(cancellationToken);
|
throw new HttpRequestException("Il servizio WFS AdE non ha risposto dopo tre tentativi.");
|
||||||
ThrowIfExceptionReport(xml);
|
}
|
||||||
return xml;
|
|
||||||
|
private static bool IsTransient(HttpStatusCode statusCode) =>
|
||||||
|
statusCode is HttpStatusCode.RequestTimeout or HttpStatusCode.TooManyRequests
|
||||||
|
|| (int)statusCode >= 500;
|
||||||
|
|
||||||
|
private static bool ContainsFeatures(string xml)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var document = XDocument.Parse(xml);
|
||||||
|
return document.Descendants().Any(element =>
|
||||||
|
element.Name.LocalName is "member" or "featureMember");
|
||||||
|
}
|
||||||
|
catch (System.Xml.XmlException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Format(double value) => value.ToString("R", CultureInfo.InvariantCulture);
|
private static string Format(double value) => value.ToString("R", CultureInfo.InvariantCulture);
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
param(
|
||||||
|
[string]$Configuration = "Release"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||||
|
$dotnet = Join-Path $projectRoot "artifacts\tooling\dotnet10\dotnet.exe"
|
||||||
|
$androidSdk = Join-Path $env:LOCALAPPDATA "Android\Sdk"
|
||||||
|
$documentsDirectory = Split-Path -Parent (Split-Path -Parent $projectRoot)
|
||||||
|
$privateDirectory = Join-Path $documentsDirectory "LMEMappeCatastali-Private\GooglePlay"
|
||||||
|
$keyStore = Join-Path $privateDirectory "lme-mappe-catastali-pro-upload.jks"
|
||||||
|
$recoveryFile = Join-Path $privateDirectory "UPLOAD-KEY-RECOVERY.txt"
|
||||||
|
$outputDirectory = Join-Path $projectRoot "artifacts\play-store"
|
||||||
|
$projectPath = Join-Path $projectRoot "LMEMappeCatastaliApp.csproj"
|
||||||
|
|
||||||
|
foreach ($requiredPath in @($dotnet, $androidSdk, $keyStore, $recoveryFile, $projectPath)) {
|
||||||
|
if (-not (Test-Path -LiteralPath $requiredPath)) {
|
||||||
|
throw "Componente richiesto non trovato: $requiredPath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[xml]$projectXml = Get-Content -Raw -LiteralPath $projectPath
|
||||||
|
$propertyGroup = $projectXml.Project.PropertyGroup |
|
||||||
|
Where-Object { $_.ApplicationDisplayVersion -and $_.ApplicationVersion } |
|
||||||
|
Select-Object -First 1
|
||||||
|
$displayVersion = [string]$propertyGroup.ApplicationDisplayVersion
|
||||||
|
$versionCode = [string]$propertyGroup.ApplicationVersion
|
||||||
|
if ([string]::IsNullOrWhiteSpace($displayVersion) -or [string]::IsNullOrWhiteSpace($versionCode)) {
|
||||||
|
throw "Versione applicazione non trovata nel progetto."
|
||||||
|
}
|
||||||
|
|
||||||
|
$recovery = Get-Content -LiteralPath $recoveryFile
|
||||||
|
function Read-RecoveryValue([string]$label) {
|
||||||
|
$line = $recovery | Where-Object { $_ -like "${label}:*" } | Select-Object -First 1
|
||||||
|
if ([string]::IsNullOrWhiteSpace($line)) {
|
||||||
|
throw "Valore $label non trovato nel file di recupero."
|
||||||
|
}
|
||||||
|
return ($line -split ":", 2)[1].Trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
$storePassword = Read-RecoveryValue "Store password"
|
||||||
|
$keyPassword = Read-RecoveryValue "Key password"
|
||||||
|
$env:LME_ANDROID_UPLOAD_STORE_PASS = $storePassword
|
||||||
|
$env:LME_ANDROID_UPLOAD_KEY_PASS = $keyPassword
|
||||||
|
$packages = [System.Collections.Generic.List[System.IO.FileInfo]]::new()
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
|
||||||
|
|
||||||
|
& $dotnet restore $projectPath `
|
||||||
|
-p:Configuration=$Configuration `
|
||||||
|
-p:TargetFrameworks=net10.0-android `
|
||||||
|
-p:AndroidSdkDirectory=$androidSdk `
|
||||||
|
-p:UseDefaultPublishRuntimeIdentifier=false
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Ripristino delle dipendenze Android non riuscito."
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($format in @("aab", "apk")) {
|
||||||
|
& $dotnet publish $projectPath `
|
||||||
|
-f net10.0-android `
|
||||||
|
-c $Configuration `
|
||||||
|
--no-restore `
|
||||||
|
-p:AndroidSdkDirectory=$androidSdk `
|
||||||
|
-p:UseDefaultPublishRuntimeIdentifier=false `
|
||||||
|
-p:AndroidPackageFormat=$format `
|
||||||
|
-p:AndroidKeyStore=true `
|
||||||
|
-p:AndroidSigningKeyStore=$keyStore `
|
||||||
|
-p:AndroidSigningKeyAlias=lme-upload `
|
||||||
|
-p:AndroidSigningStorePass=env:LME_ANDROID_UPLOAD_STORE_PASS `
|
||||||
|
-p:AndroidSigningKeyPass=env:LME_ANDROID_UPLOAD_KEY_PASS
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Pubblicazione Android $format non riuscita."
|
||||||
|
}
|
||||||
|
|
||||||
|
$publishDirectory = Join-Path $projectRoot "bin\$Configuration\net10.0-android\publish"
|
||||||
|
$package = Get-ChildItem -LiteralPath $publishDirectory -Filter "*-Signed.$format" -File |
|
||||||
|
Sort-Object LastWriteTime -Descending |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($null -eq $package) {
|
||||||
|
throw "Nessun pacchetto $format firmato prodotto in $publishDirectory"
|
||||||
|
}
|
||||||
|
|
||||||
|
$destination = Join-Path $outputDirectory "LME-Mappe-Catastali-Pro-$displayVersion-$versionCode.$format"
|
||||||
|
Copy-Item -LiteralPath $package.FullName -Destination $destination -Force
|
||||||
|
$packages.Add((Get-Item -LiteralPath $destination))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Remove-Item Env:LME_ANDROID_UPLOAD_STORE_PASS -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item Env:LME_ANDROID_UPLOAD_KEY_PASS -ErrorAction SilentlyContinue
|
||||||
|
$storePassword = $null
|
||||||
|
$keyPassword = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$hashLines = $packages | ForEach-Object {
|
||||||
|
$hash = Get-FileHash -Algorithm SHA256 -LiteralPath $_.FullName
|
||||||
|
"$($hash.Hash.ToLowerInvariant()) $($_.Name)"
|
||||||
|
}
|
||||||
|
$hashFile = Join-Path $outputDirectory "LME-Mappe-Catastali-Pro-$displayVersion-$versionCode-SHA256SUMS.txt"
|
||||||
|
$hashLines | Set-Content -LiteralPath $hashFile
|
||||||
|
|
||||||
|
$packages | Select-Object Name, Length, LastWriteTime
|
||||||
|
Get-Item -LiteralPath $hashFile | Select-Object Name, Length, LastWriteTime
|
||||||
+320
-9
@@ -906,6 +906,81 @@ input[type="checkbox"] {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.object-table-wrap .object-notes-button {
|
||||||
|
width: auto;
|
||||||
|
min-width: 70px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-notes-command {
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-notes-command small,
|
||||||
|
.managed-note-pro-message {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-notes-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-note {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #f8fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-note > header {
|
||||||
|
padding: 0 0 7px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-note time {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-note p,
|
||||||
|
.lme-popup-notes p {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-note-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-note-actions button {
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-note-editor {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-note-editor label {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.managed-note-editor textarea {
|
||||||
|
min-height: 90px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
.object-table-search,
|
.object-table-search,
|
||||||
.coordinate-result {
|
.coordinate-result {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -1185,6 +1260,17 @@ input[type="checkbox"] {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lme-geometry-toolbar a.lme-fiducial-download {
|
||||||
|
font-size: 12px !important;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lme-geometry-toolbar a.is-disabled {
|
||||||
|
color: #8b949e;
|
||||||
|
background: #eef1f3;
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
|
||||||
.leaflet-help {
|
.leaflet-help {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
@@ -1245,6 +1331,41 @@ input[type="checkbox"] {
|
|||||||
transition: top 140ms ease;
|
transition: top 140ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gps-overlay.is-collapsed {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
right: auto;
|
||||||
|
width: 48px;
|
||||||
|
max-width: 48px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gps-overlay.is-collapsed .gps-state,
|
||||||
|
.gps-overlay.is-collapsed .gps-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiducial-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiducial-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 1px solid #d9e0e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiducial-item strong {
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiducial-item .secondary-command {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.gps-overlay.search-open {
|
.gps-overlay.search-open {
|
||||||
top: 140px;
|
top: 140px;
|
||||||
}
|
}
|
||||||
@@ -1267,8 +1388,14 @@ input[type="checkbox"] {
|
|||||||
color: #a33227;
|
color: #a33227;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gps-actions {
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.gps-main,
|
.gps-main,
|
||||||
.gps-center {
|
.gps-center,
|
||||||
|
.gps-collapse {
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -1279,6 +1406,15 @@ input[type="checkbox"] {
|
|||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gps-collapse {
|
||||||
|
width: 38px;
|
||||||
|
padding: 0;
|
||||||
|
background: #e7ecef;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.leaflet-top.leaflet-right {
|
.leaflet-top.leaflet-right {
|
||||||
top: 68px;
|
top: 68px;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
@@ -1293,6 +1429,21 @@ input[type="checkbox"] {
|
|||||||
height: 38px;
|
height: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cadastral-sheet-label {
|
||||||
|
width: auto !important;
|
||||||
|
height: auto !important;
|
||||||
|
border: 1px solid #9a5b00;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
background: rgba(255, 251, 235, 0.94);
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
|
||||||
|
color: #713f12;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.2;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.lme-drawing-scope-actions a[aria-disabled="true"] {
|
.lme-drawing-scope-actions a[aria-disabled="true"] {
|
||||||
opacity: 0.45;
|
opacity: 0.45;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -1566,11 +1717,135 @@ label {
|
|||||||
z-index: 820;
|
z-index: 820;
|
||||||
max-width: min(620px, calc(100% - 460px));
|
max-width: min(620px, calc(100% - 460px));
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 9px 11px;
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 7px 7px 7px 11px;
|
||||||
background: rgba(22, 31, 38, 0.88);
|
background: rgba(22, 31, 38, 0.88);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
pointer-events: none;
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info-text {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 2px 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info-close {
|
||||||
|
flex: 0 0 28px;
|
||||||
|
width: 28px;
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-compass {
|
||||||
|
--compass-rotation: 0deg;
|
||||||
|
--compass-radius: -31px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 12px;
|
||||||
|
z-index: 810;
|
||||||
|
width: 82px;
|
||||||
|
height: 82px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.92);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(22, 31, 38, 0.86);
|
||||||
|
box-shadow: 0 2px 9px rgba(0, 0, 0, 0.3);
|
||||||
|
color: #fff;
|
||||||
|
cursor: grab;
|
||||||
|
pointer-events: auto;
|
||||||
|
touch-action: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-compass.is-dragging {
|
||||||
|
cursor: grabbing;
|
||||||
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-compass-rose {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-compass-direction {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 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;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-compass-needle {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 4px;
|
||||||
|
height: 23px;
|
||||||
|
transform: translateX(-50%) rotate(var(--compass-rotation));
|
||||||
|
transform-origin: 50% 100%;
|
||||||
|
border-radius: 4px 4px 1px 1px;
|
||||||
|
background: #ef4444;
|
||||||
|
transition: transform 120ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-compass-needle::after {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
left: 50%;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
border-bottom: 7px solid #ef4444;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-compass-center {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #17212b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wms-preview[hidden] {
|
.wms-preview[hidden] {
|
||||||
@@ -1816,13 +2091,19 @@ footer {
|
|||||||
top: 136px;
|
top: 136px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gps-overlay.is-collapsed {
|
||||||
|
right: auto;
|
||||||
|
width: 48px;
|
||||||
|
max-width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
.leaflet-top.leaflet-right {
|
.leaflet-top.leaflet-right {
|
||||||
top: 122px;
|
top: 174px;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-stage.search-active .leaflet-top.leaflet-right {
|
.map-stage.search-active .leaflet-top.leaflet-right {
|
||||||
top: 190px;
|
top: 242px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gps-main,
|
.gps-main,
|
||||||
@@ -1833,12 +2114,26 @@ footer {
|
|||||||
|
|
||||||
.map-info {
|
.map-info {
|
||||||
right: 8px;
|
right: 8px;
|
||||||
bottom: 72px;
|
bottom: calc(var(--nav-height) + 116px);
|
||||||
left: 8px;
|
left: 8px;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
line-height: 1.35;
|
||||||
white-space: nowrap;
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-compass {
|
||||||
|
--compass-radius: -27px;
|
||||||
|
left: 8px;
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info-text {
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-tabs button {
|
.bottom-tabs button {
|
||||||
@@ -1860,8 +2155,24 @@ footer {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gps-overlay {
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gps-overlay.is-collapsed {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
right: auto;
|
||||||
|
width: 48px;
|
||||||
|
max-width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiducial-item {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gps-main,
|
||||||
.gps-center {
|
.gps-center {
|
||||||
display: none;
|
min-width: 86px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-tabs button {
|
.bottom-tabs button {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
<script src="_framework/blazor.webview.js" autostart="false"></script>
|
<script src="_framework/blazor.webview.js" autostart="false"></script>
|
||||||
<script src="lib/leaflet/leaflet.js"></script>
|
<script src="lib/leaflet/leaflet.js"></script>
|
||||||
|
<script src="lib/leaflet-rotate/leaflet-rotate.umd.min.js"></script>
|
||||||
<script src="lib/leaflet-draw/leaflet.draw.js"></script>
|
<script src="lib/leaflet-draw/leaflet.draw.js"></script>
|
||||||
<script src="lib/googlemutant/Leaflet.GoogleMutant.js"></script>
|
<script src="lib/googlemutant/Leaflet.GoogleMutant.js"></script>
|
||||||
<script src="lib/jszip/jszip.min.js"></script>
|
<script src="lib/jszip/jszip.min.js"></script>
|
||||||
|
|||||||
+878
-53
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Grzegorz Tomicki
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/*! @tomickigrzegorz/leaflet-rotate v0.3.0 | MIT */
|
||||||
|
!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(require("leaflet")):"function"==typeof define&&define.amd?define(["leaflet"],i):i((t="undefined"!=typeof globalThis?globalThis:t||self).L)}(this,function(t){"use strict";t.Point.prototype.rotate=function(i){var e=Math.cos(i),o=Math.sin(i);return new t.Point(this.x*e-this.y*o,this.x*o+this.y*e)},t.Point.prototype.rotateFrom=function(t,i){return i?this.subtract(i).rotate(t).add(i):this.rotate(t)};const i=Math.PI/180,e=180/Math.PI;function o(t){return(t%360+360)%360}function a(t){return o(t+180)-180}function n(t){var i=2*Math.PI;return((t+Math.PI)%i+i)%i-Math.PI}const s=1e3/60;function r(t,i){return 1-Math.pow(1-t,i/s)}function h(){return"undefined"!=typeof performance&&performance.now?performance.now():Date.now()}var _=t.Map.prototype;t.Map.mergeOptions({rotate:!1,bearing:0,touchRotate:!1,shiftKeyRotate:!1,dragRotate:!0,rotateControl:!1,rotateClockwise:!0});var p=_.initialize;_.initialize=function(t,i){i&&i.rotate&&(this._rotate=!0,this._bearing=0,this._bearingRad=0),p.call(this,t,i),this._rotate&&this.setBearing(i.bearing||0)};var l=_._initPanes;_._initPanes=function(){if(l.call(this),this._rotate){var i=this._mapPane;this._rotatePane=t.DomUtil.create("div","leaflet-rotate-pane",i),this._norotatePane=t.DomUtil.create("div","leaflet-norotate-pane",i),this._rotatePane.appendChild(this._panes.tilePane),this._rotatePane.appendChild(this._panes.overlayPane),this._norotatePane.appendChild(this._panes.shadowPane),this._norotatePane.appendChild(this._panes.markerPane),this._norotatePane.appendChild(this._panes.tooltipPane),this._norotatePane.appendChild(this._panes.popupPane),t.DomUtil.addClass(this._rotatePane,"leaflet-proxy leaflet-zoom-animated")}},_.setBearing=function(e){if(!this._rotate||!isFinite(e))return this;var a=this._bearing||0,n=o(e);if(n===a)return this;if(this._commitRotatePan(),this._bearing=n,this._bearingRad=n*i,this._updateRotatePaneTransform(),0===a!=(0===n))for(var s in this._layers){var r=this._layers[s];r instanceof t.Renderer&&r._update()}return this.fire("rotate")},_.getBearing=function(){return this._bearing||0},_._updateRotatePaneTransform=function(){if(this._rotatePane){if(!this._bearing)return this._rotatePane.style[t.DomUtil.TRANSFORM]="",void(this._rotatePane.style[t.DomUtil.TRANSFORM+"Origin"]="");var i=this.getSize().divideBy(2);this._rotatePane.style[t.DomUtil.TRANSFORM+"Origin"]=i.x+"px "+i.y+"px",this._rotatePane.style[t.DomUtil.TRANSFORM]="rotate("+this._bearingRad+"rad)"}},_._commitRotatePan=function(){if(this._rotate&&!this._committingRotatePan){var t=this._getMapPanePos();!t||0===t.x&&0===t.y||(this._committingRotatePan=!0,this._resetView(this.getCenter(),this.getZoom(),!0),this._committingRotatePan=!1)}};var m=_.containerPointToLayerPoint;_.containerPointToLayerPoint=function(i){if(!this._rotate||!this._bearing)return m.call(this,i);var e=t.point(i),o=this._getMapPanePos(),a=this.getSize().divideBy(2);return e.subtract(o).subtract(a).rotate(-this._bearingRad).add(a)};var d=_.layerPointToContainerPoint;_.layerPointToContainerPoint=function(i){if(!this._rotate||!this._bearing)return d.call(this,i);var e=t.point(i),o=this._getMapPanePos(),a=this.getSize().divideBy(2);return e.subtract(a).rotate(this._bearingRad).add(a).add(o)},_.rotatedPointToMapPanePoint=function(i){if(!this._bearing)return t.point(i);var e=this.getSize().divideBy(2);return t.point(i).rotateFrom(this._bearingRad,e)},_.mapPanePointToRotatedPoint=function(i){if(!this._bearing)return t.point(i);var e=this.getSize().divideBy(2);return t.point(i).rotateFrom(-this._bearingRad,e)};var c=_._getCenterOffset;_._getCenterOffset=function(t){return this._rotate&&this._bearing?this.project(t).subtract(this.project(this.getCenter())).rotate(this._bearingRad):c.call(this,t)};var u=_.getBounds;_.getBounds=function(){if(!this._rotate||!this._bearing)return u.call(this);var i=this.getSize(),e=t.latLngBounds();return e.extend(this.containerPointToLatLng(t.point(0,0))),e.extend(this.containerPointToLatLng(t.point(i.x,0))),e.extend(this.containerPointToLatLng(t.point(i.x,i.y))),e.extend(this.containerPointToLatLng(t.point(0,i.y))),e},_.mapBoundsToContainerBounds=function(i){return t.bounds([this.latLngToContainerPoint(i.getNorthWest()),this.latLngToContainerPoint(i.getNorthEast()),this.latLngToContainerPoint(i.getSouthEast()),this.latLngToContainerPoint(i.getSouthWest())])};var g=_.getBoundsZoom;_.getBoundsZoom=function(i,e,o){if(!this._rotate||!this._bearing)return g.call(this,i,e,o);i=t.latLngBounds(i),o=t.point(o||[0,0]);var a=this.getZoom()||0,n=this.getMinZoom(),s=this.getMaxZoom(),r=this.getSize().subtract(o);if(r.x<=0||r.y<=0)return a;var h=this.mapBoundsToContainerBounds(i).getSize(),_=this.options.zoomSnap,p=r.x/h.x,l=r.y/h.y,m=e?Math.max(p,l):Math.min(p,l);return a=this.getScaleZoom(m,a),_&&(a=Math.round(a/_)*_),Math.max(n,Math.min(s,a))},_._animateZoomNoDelay=function(t,i,e){this._mapPane&&(e&&(this._animatingZoom=!0,this._animateToCenter=t,this._animLine truncated
|
||||||
Reference in new issue
Block a user