Как использовать пространственные данные для поиска радиуса почтовых индексов?

Фон

Я пишу приложение, которое находит события в определенном радиусе почтового индекса. Вы можете думать об этом, как ticketmaster, где вы вводите свой почтовый индекс, и все концерты в радиусе x отображаются.

У меня есть таблица базы данных, в которой есть почтовый индекс, и каждый почтовый индекс "Широта и долгота". У меня также есть таблица EventListings, где каждое "событие" имеет поле ZipCode.

Проблема

В настоящее время я использую формулу Haversine в запросе Linq-to-Entities на моем уровне сервиса, чтобы определить, какие события находятся в радиусе. Прямо сейчас, я использую его как фильтр в предложении where. Я также хотел бы разместить его в предложении select, поэтому на веб-сайте я могу показать "это в 4,6 км" и т.д.

Я не могу переместить этот код в отдельный метод С#, потому что Linq-to-Entities будет жаловаться, что он не сможет преобразовать его в sql, так что это оставляет меня с дублированием всей формулы в инструкции select. Это очень уродливо. Я попытался это исправить.

Что я пробовал

Я редактировал Entity и добавил специальное скалярное свойство "DistanceFromOrigin". Затем я создал хранимую процедуру, которая вернула все данные сущности, а также твердое кодированное значение (для целей тестирования) для нового поля "DistanceFromOrigin".

Затем я понял, что я не могу сказать сущностной структуре использовать мой sproc для своего оператора select в объекте EventListings... Фил предложил пространственные данные, поэтому я пошел с ним.

Вопрос

Как я могу использовать Spatials для поиска событий в радиусе почтовых индексов?

Ответ 1

Итак, используя предложение Фила, я переписал много всего этого с помощью пространств. Это получилось отлично, вам просто нужны .NET4.5, EF5 + и sql-сервер (2008 и выше, я считаю). Я использую EF6 + Sql Server 2012.

Set-Up

Первым шагом было добавить столбец Geography в мою базу данных EventListings (щелкните правой кнопкой мыши по ней → Дизайн). Я назвал мое Место:

enter image description here

Далее, поскольку я использую EDM с базой данных - сначала мне пришлось обновить мою модель, чтобы использовать новое поле, которое я создал. Затем я получил ошибку в том, что я не смог преобразовать географию в двойную, поэтому я исправил ее, выбрав свойство Location в объекте, перейдите к его свойствам и измените его тип с двойного на Geography:

enter image description here

Запрос в радиусе и добавление событий с местоположением

Затем все, что вам нужно сделать, - это запросить коллекцию объектов следующим образом:

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

Метод расширения расстояния получает расстояние от текущего объекта до объекта "другого", который вы передаете. Этот другой объект имеет тип DbGeography. Вы просто вызываете статический метод, и он создает одного из этих щенков, а затем просто бросайте в него свою долготу и широту как точку:

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

Это не то, как я создал свой исходный код. Я загрузил отдельную базу данных, в которой был список всех почтовых индексов и их широты и долготы. Я добавил к этому столбцу тип Geography. Я покажу, как в конце этого ответа. Затем я запросил контекст zipcode, чтобы получить объект DbGeography из поля Location в таблице ZipCode.

Если пользователю требуется более конкретное происхождение, а не только почтовый индекс, я обращаюсь к API Карт Google (более конкретно к GeoCode) и получаю широту и долготу для конкретного адреса пользователя через веб-службу и создаю Объект DBGeography из значений широты и долготы в ответе.

Я использую API Google при создании события. Я просто установил переменную местоположения, как это, прежде чем добавить мою сущность в EF:

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

Другие советы и рекомендации и устранение неполадок

Как я получил метод расширения расстояния?

Чтобы получить метод расширения Distance, вы должны добавить ссылку на свой проект: System.Data.Entity После этого вы должны добавить в свой класс: using System.Data.Entity.Spatial;.

Метод расширения Distance возвращает расстояние с помощью единицы измерения метров (вы можете изменить его, я думаю, но это значение по умолчанию). Здесь мой параметр радиуса находился в милях, поэтому я сделал некоторую математику для преобразования.

Примечание. В System.Data.Spatial есть класс DbGeography. Это неправильный, и это не сработает для меня. Множество примеров, которые я нашел в Интернете, использовали это.

Как преобразовать столбцы Lat/Long в столбцы географии

Итак, если вы были похожи на меня и загрузили базу данных zipcode со всеми столбцами Latitude и Longitude, а затем поняли, что у нее нет столбца География... это может помочь:

1) Добавьте столбец местоположения в таблицу. Установите тип в Географию.

2) Выполните следующий sql

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

Как просмотреть детали объекта географии

Итак, когда вы запрашиваете таблицу EventListings в Sql Server Management Studio после того, как вы вставили некоторые элементы DbGeography, вы увидите, что столбец Location содержит шестнадцатеричное значение, подобное: 0x1234513462346. Это не очень полезно, если вы хотите убедиться, что правильные значения вставлены.

Чтобы просмотреть широту и долготу этого поля, вы должны запросить его так:

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