Come posso usare le spatial per cercare un raggio di codici postali?

c# entity-framework entity-framework-6 sql-server-2012

Domanda

sfondo

Sto scrivendo un'applicazione che trova eventi all'interno di un certo raggio di un codice postale. Puoi pensare a questo come ticketmaster, dove digiti il ​​tuo codice di avviamento postale e tutti i concerti nel raggio di x si presentano.

Ho una tabella di database che ha il codice di avviamento postale e ogni codice postale 'Latitudine e Longitudine. Ho anche una tabella 'EventListings' dove ogni 'Event' ha un campo ZipCode.

Problema

Attualmente sto utilizzando la formula di Haversine in una query Linq-to-Entities nel mio livello di servizio per trovare gli eventi nel raggio. In questo momento, lo sto usando come filtro nella clausola where. Mi piacerebbe anche inserirlo nella clausola select così sul sito posso mostrare "questo è 4,6 miglia di distanza", ecc.

Non riesco a spostare questo codice in un metodo C # separato perché Linq-to-Entities si lamenterà che non può convertirlo in sql, quindi questo mi lascia duplicare l'intera formula anche nell'istruzione select. Questo è molto brutto. Ho provato a sistemarlo.

Quello che ho provato

Ho modificato l'entità e aggiunto una proprietà scalare speciale di "DistanceFromOrigin". Ho quindi creato una stored procedure che riportava tutti i dati dell'entità, oltre a un valore hard coded (a scopo di test) per il nuovo campo "DistanceFromOrigin".

Poi ho capito che non posso dire all'entità framework di usare il mio sproc per la sua istruzione select sull'entità EventListings ... Phil ha suggerito gli spazi, quindi è quello che sono andato con.

Domanda

Come posso utilizzare Spatials per cercare eventi all'interno di un raggio di codici postali?

Risposta accettata

Quindi, usando il suggerimento di Phil, ho riscritto molte delle cose usando le spaziali. Questo ha funzionato alla grande, hai solo bisogno di .NET4.5, EF5 + e SQL Server (2008 e sopra credo). Sto usando EF6 + Sql Server 2012.

Impostare

Il primo passo è stato aggiungere una colonna Geography alla tabella EventListings mio database (fai clic con il tasto destro del mouse su -> Design). Ho chiamato la mia posizione:

inserisci la descrizione dell'immagine qui

Successivamente, dal momento che sto usando l'EDM con database-first, ho dovuto aggiornare il mio modello per utilizzare il nuovo campo che ho creato. Poi ho ricevuto un errore sul non essere in grado di convertire Geography in raddoppio, quindi quello che ho fatto per risolverlo è stato selezionare la proprietà Location nell'entità, andare alle sue proprietà e cambiare il suo tipo da double a Geography :

inserisci la descrizione dell'immagine qui

Interrogare all'interno di un raggio e aggiungere eventi con posizioni

Quindi tutto ciò che devi fare è interrogare la tua collezione di entità in questo modo:

 var events = EventRepository.EventListings
                             .Where(x => x.Location.Distance(originCoordinates) * 0.00062 <= radiusParam); 

Il metodo di estensione Distanza ottiene la distanza dall'oggetto corrente, dall'oggetto "altro" passato. Questo altro oggetto è di tipo DbGeography . Basta chiamare un metodo statico e crea uno di questi cuccioli, quindi lanciare la tua longitudine e latitudine in esso come un punto:

DBGeography originCoordinates = DBGeography.fromText("Point(" + originLongitude + " " + originLatitude + ")");

Non è così che ho creato la mia origine Coordinate. Ho scaricato un database separato con un elenco di tutti i codici postali e le loro latitudini e lunghezze. Ho aggiunto anche una colonna di tipo Geography . Mostrerò come alla fine di questa risposta. Ho quindi interrogato il contesto zipcode per ottenere un oggetto DbGeography dal campo Posizione nella tabella ZipCode.

Se l'utente desidera un'origine più specifica rispetto a un semplice codice postale, faccio una chiamata all'API di Google Maps (GeoCode per essere più specifico) e ottengo la latitudine e la longitudine per l'indirizzo specifico dell'utente tramite un servizio web e creo un oggetto DBGeography da i valori di latitudine e longitudine nella risposta.

Uso le API di google anche quando creo un evento. Ho appena impostato la variabile di posizione in questo modo prima di aggiungere la mia entità a EF:

someEventEntity.Location = DBGeography.fromText("Point(" + longitudeFromGoogle+ " " + latitudeFromGoogle + ")");

Altri suggerimenti e trucchi e risoluzione dei problemi

Come ho ottenuto il metodo di estensione della distanza?

Per ottenere il metodo di estensione Distance è necessario aggiungere un riferimento al progetto: System.Data.Entity Dopo averlo fatto è necessario aggiungere l'utilizzo: using System.Data.Entity.Spatial; alla tua classe.

Il metodo di estensione Distance restituisce la distanza con un'unità di misura di metri (è possibile cambiarlo credo, ma questo è l'impostazione predefinita). Qui, il mio parametro di raggio era in miglia, quindi ho fatto alcuni calcoli da convertire.

Nota : esiste una classe DBGeography in System.Data.Spatial . Questo è quello sbagliato e non funzionerebbe per me. Un sacco di esempi che ho trovato su internet hanno usato questo.

Come convertire Lat / Long in Geography Column

Quindi, se tu fossi come me e hai scaricato un database zipcode con tutte le colonne Latitude & Longitude , e poi hai realizzato che non aveva una colonna Geography ... questo potrebbe aiutare:

1) Aggiungi una colonna Location alla tua tabella. Imposta il tipo su Geografia.

2) Esegui il seguente sql

UPDATE [ZipCodes]
SET Location = geography::STPointFromText('POINT(' + CAST([Longitude] AS VARCHAR(20)) + ' ' + CAST([Latitude] AS VARCHAR(20)) + ')', 4326)

Come visualizzare i dettagli di un elemento di geografia

Pertanto, quando esegui una query nella tua tabella EventListings in Sql Server Management Studio dopo aver inserito alcuni elementi di DbGeography, vedrai la colonna Location contenente un valore esadecimale come: 0x1234513462346. Questo non è molto utile quando vuoi essere sicuro di aver inserito i valori corretti.

Per visualizzare effettivamente la latitudine e la longitudine di questo campo è necessario interrogarlo in questo modo:

SELECT Location.Lat Latitude, Location.Long Longitude
FROM [EventListings]


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché