Le applicazioni che fanno uso di mappe hanno sempre un grande potere comunicativo perché riescono a contestualizzare luoghi, eventi e fenomeni nelle zone geografiche familiari all'utente. Quest'oggi vedremo come utilizzare gli Spatial Types di SQL Server in un'applicazione ASP.NET per coniugare meglio i nostri dati con le funzionalità offerte da una mappa.
Iniziamo installando il pacchetto NuGet Microsoft.SqlServer.Types, che ci abilita sia all'uso del tipo DbGeometry, utile se abbiamo a che fare con dati riguardanti la geometria euclidea, e il tipo DbGeography che invece, come vedremo in questo script, ci permette di lavorare con dati ellissoidali come le coordinate GPS.
Digitiamo quanto segue nella Package Manager Console di Visual Studio.
Install-Package Microsoft.SqlServer.Types
Questo pacchetto fornisce anche di un assembly nativo da caricare all'avvio dell'applicazione. Dunque aggiungiamo questa riga di codice al metodo Application_Start del Global.asax:
protected void Application_Start() { SqlServerTypes.Utilities.LoadNativeAssemblies(Server.MapPath("~/bin")); }
A questo punto possiamo iniziare a definire delle proprietà di tipo DbGeography nelle nostre classi di entità, come quella illustrata qui di seguito che rappresenta un Comune italiano.
public class Comune { public string CodiceIstat { get; set; } public string Nome { get; set; } public string Provincia { get; set; } public DbGeography Posizione { get; set; } }
Una proprietà di tipo DbGeography, che qui valorizzeremo con le coordinate GPS, è in realtà in grado di conservare molte più informazioni di un semplice punto descritto da latitudine e longitudine. Può infatti essere usato per lavorare con una buona varietà di forme:
- Punti 2D e 3D;
- Linee spezzate, anche chiamate polilinee;
- Poligoni e "ciambelle";
- Una collezione omogenea od eterogenea delle precedenti.
Questo lo rende un tipo idoneo a rappresentare anche superfici urbane, parchi, confini amministrativi, e così via, in vari sistemi geodetici di riferimento.
Ora siamo pronti a creare un'istanza della classe Comune, che potremo poi persistere nel database usando Entity Framework 5 (o superiore) che supporta pienamente gli Spatial Types di SQL Server.
using (var context = new MyDbContext()){ var roma = new Comune { Nome = "Roma", Provincia = "RM", CodiceIstat = "058091", Posizione = DbGeography.FromText("POINT(12.4833 41.9)", 4326) }; context.Comuni.Add(roma); context.SaveChanges(); }
Come si vede nell'esempio, abbiamo costruito un oggetto di tipo DbGeography invocando DbGeography.FromText, uno dei suoi factory methods. Al metodo abbiamo fornito una stringa Well-Known Text (https://it.wikipedia.org/wiki/Well-Known_Text) che esprime una forma geografica in maniera testuale, ed un codice SRID che rappresenta invece il sistema geodetico di riferimento. In questo caso abbiamo usato il codice 4326 che identifica il comunissimo WGS84 (https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84), ovvero quello adottato anche dal sistema GPS.
Dopo aver popolato il nostro database di entità, avremo sufficienti dati per iniziare ad inviare delle ricerche geografiche, come:
- Ricerche di prossimità. Da impiegare quando vogliamo estrarre risultati nelle vicinanze di un punto specifico o lungo un percorso;
- Ricerche per intersezione. Utili quando vogliano ottenere tutti i risultati che si trovano inclusi in un'area, anche parzialmente.
Vediamo come effettuare tali ricerche con una query LINQ to Entites.
using (var context = new MyDbContext()){ //Per prossimità: estraiamo tutti i Comuni entro 50Km da Roma var posizioneRoma = DbGeography.FromText("POINT(12.4833 41.9)", 4326); //Nel sistema geodetico WGS84 la distanza va espressa in metri var distanzaInMetri = 50 * 1000; var query1 = context.Comuni .Where(comune => comune.Posizione.Distance(posizioneRoma) <= distanzaInMetri); //Per intersezione: estraggo tutti i Comuni situati in un rettangolo //che circoscrive la Sardegna (approssimativo) var rettangoloSardegna = DbGeography.FromText( "POLYGON((8.0 41.2,8.0 38.8,10.0 38.8,10.0 41.2,8.0 41.2))"); var query2 = context.Comuni .Where(comune => comune.Posizione.Intersects(rettangoloSardegna)); }
Il tipo DbGeography possiede vari metodi, come i Distance ed Intersects che abbiamo appena visto, che ci permettono di comparare due oggetti di tipo DbGeography in maniera estremamente semplice. Lo stesso compito sarebbe stato molto più complesso o addirittura impraticabile se avessimo scelto di memorizzare le posizioni GPS dei Comuni in semplici campi float di SQL Server.
La vera potenza degli Spatial Types la possiamo apprezzare quando generiamo dinamicamente le stringhe Well-Known Text. Ad esempio, potremmo consentire ai nostri utenti di generarle lato client interagendo con una mappa.
In questo stralcio di codice javascript, gestiamo l'evento moveend di una mappa OpenStreetMap per ottenerne gli estremi ogni volta che l'utente sposta o zooma la visualizzazione.
map.events.register("moveend", map, function () { //getExtent restituisce la latitudine e la longitudine degli estremi //della mappa visualizzata var extent = map.getExtent(); //E' importante disegnare il poligono in senso antiorario, per //"orientarlo positivamente" var template = "POLYGON((left bottom, right bottom, right top, left top, left bottom))"; var wellKnownText = template .replace(/top/g, extent.top) .replace(/left/g, extent.left) .replace(/bottom/g, extent.bottom) .replace(/right/g, extent.right); //TODO: effettua una richiesta ajax al server inviando //la stringa Well-Known Text che abbiamo creato qui dinamicamente });
Un'applicazione ASP.NET MVC 5 dimostrativa, che consente all'utente di disegnare punti, linee e poligoni, è disponibile in questo repository GitHub.
https://github.com/BrightSoul/MapAndSpatialTypesDemo
Con pochissimo sforzo siamo riusciti ad arricchire la nostra applicazione di una funzionalità di ricerca geografica avanzata che darà ai nostri utenti un'opportunità in più di trovare i loro risultati.
Per approfondimenti, la documentazione MSDN offre molte altre informazioni sugli Spatial Types, al seguente indirizzo.
https://msdn.microsoft.com/en-us/library/bb933790.aspx
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Creare una libreria CSS universale: Clip-path
Migliorare i tempi di risposta di GPT tramite lo streaming endpoint in ASP.NET Core
Creare alias per tipi generici e tuple in C#
Creare una libreria CSS universale: Immagini
Visualizzare le change sul plan di Terraform tramite le GitHub Actions
Eseguire script pre e post esecuzione di un workflow di GitHub
Creare gruppi di client per Event Grid MQTT
Migliorare la scalabilità delle Azure Function con il Flex Consumption
Popolare una classe a partire dal testo, con Semantic Kernel e ASP.NET Core Web API
Ottimizzare il mapping di liste di tipi semplici con Entity Framework Core
Generare token per autenicarsi sulle API di GitHub
Proteggere le risorse Azure con private link e private endpoints