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
Ottimizzare le performance di serializzazione e deserializzazione JSON con System.Text.Json e i source generators
Recuperare la data di creazione e ultima modifica di un record con Entity Framework Core e le temporal table di SQL Server
Sfruttare i tag nell'output cache di ASP.NET Core 7
Impostare il forward degli header in un sito ASP.NET Core dietro a un reverse proxy
Innestare una query nel metodo Contains di Entity Framework Core
Eseguire uno scroll all'interno di una pagina Blazor
Gestire tipi complessi in query string grazie a IParsable in ASP.NET Core 7.0
Installazione di una PWA Blazor
Creare automaticamente una pipeline YAML da una sua definizione in Azure DevOps
Definire le impostazioni di cache a livello di controller in ASP.NET Core 7
I più letti di oggi
- 3 metodi JavaScript che ogni applicazione web dovrebbe contenere - Parte 2
- Monitorare i server on-premises con Azure Arc
- Gestire server e pc on premise con Azure Arc
- Taggare la output cache in base al routing in ASP.NET Core
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!