Получить всю долготу и широту в радиусе без структуры объектов пространственного типа

#sql-server #entity-framework #sqlgeography

#sql-сервер #entity-framework #sqlgeography

Вопрос:

У нас есть таблица почтовых индексов с долготой и широтой в качестве столбца, не может быть изменен на пространственный тип данных из-за производственных требований.

Использовал STDistance() , но я получаю сообщение об ошибке:

LINQ to Entities не распознает метод

Поэтому мне приходится вызывать, toList затем только use STDistance() , который загружает каждый почтовый индекс в систему и приводит к снижению производительности.

 private static List<string> GetPostalCodesWithinRange( DbContext db, double latitude, double longitude)
{
    var yourLocation = SqlGeography.Point(latitude, longitude, 4326);

    var query = from postal in db.POSTALS
                                 .Where(x => x.LATITUDE != null || 
                                             x.LONGITUDE != null)
                                 .ToList()
    let distance = SqlGeography.Point((double)postal.LATITUDE.Value, (double)postal.LONGITUDE.Value, 4326).STDistance(yourLocation)
                    .Value
                where postal.LATITUDE != null amp;amp; postal.LONGITUDE != null amp;amp;  distance < 3000
                orderby distance
                select postal.POSTAL_CODE;

    return query.Distinct().ToList();
}
  

В настоящее время Postals toList() метод отфильтровывает только нулевые широту и долготу

 db.POSTALS.Where(x => x.LATITUDE != null || x.LONGITUDE != null).ToList()
  

Поскольку мы проверяем только длину в пределах 3 км, теперь мы хотели бы оптимизировать запрос, извлекая только долготу и широту, которые находятся в пределах квадратной границы 3 км, а затем фильтровать только по радиусу, используя описанный выше метод.

Могу ли я спросить, каким способом получить угловые точки longlat, расположенные в 1,5 км от центра?

Комментарии:

1. Из любопытства, есть ли у вас желание добавить вычисляемый столбец, который является пространственной точкой, на основе существующих столбцов lat и long?

2. @BenThul это могло бы быть отличным решением, но пока я планировал сделать это без изменения рабочей базы данных.

3. Вы могли бы создать хранимую процедуру T-SQL, которая использует функцию STDistance, и вызвать хранимую процедуру, используя LINQ to Entities. Если ваша производственная система не позволит вам создать хранимую процедуру, альтернативно вы могли бы написать SQL-запрос с помощью функции STDistance и выполнить SQL непосредственно из LINQ к сущностям с помощью SqlQuery(). Любой из этих способов избавил бы от необходимости вычислять угловые точки!

Ответ №1:

Географическая функция SQL Server «STBuffer» добавляет расстояние буфера к вводимой географии. Вы можете ввести одну точку в STBuffer, и вы получите многоугольник, аппроксимирующий окружность с центром в точке. Затем вы можете найти минимальную и максимальную широту / ДЛИНУ этого полигона.

(Это неточно на больших расстояниях, потому что Земля является сферой, но для радиуса 1,5 км это достаточно близко; он также разбивается на долготу около 180 градусов, поскольку вблизи этого значения вы переходите к -180 градусам.)

Игнорируя эти предостережения, вот функция для этого:

 public static void GetBorderBounds(SqlGeography point, double distanceMeters, out double minLat, out double minLong, out double maxLat, out double maxLong)
{
    var buffer = point.STBuffer(distanceMeters);

    minLat = (double)point.Lat;
    maxLat = (double)point.Lat;
    minLong = (double)point.Long;
    maxLong = (double)point.Long;
    for (int i = 1; i <= buffer.STNumPoints(); i  )
    {
        var p = buffer.STPointN(i);
        if (p.Lat < minLat) { minLat = (double)p.Lat; }
        if (p.Long < minLong) { minLong = (double)p.Long; }
        if (p.Lat > minLat) { maxLat = (double)p.Lat; }
        if (p.Long > maxLong) { maxLong = (double)p.Long; }
    }
}
  

Вот как вы могли бы добавить эту функцию в свой исходный запрос:

 private static List<string> GetPostalCodesWithinRange(DbContext db, double latitude, double longitude)
{
    var yourLocation = SqlGeography.Point(latitude, longitude, 4326);

    double minLat1, minLong1, maxLat1, maxLong1;
    GetBorderBounds(yourLocation, 1500, out minLat1, out minLong1, out maxLat1, out maxLong1);

    var query = from postal in db.POSTALS
                                 .Where(x => x.LATITUDE != null ||
                                             x.LONGITUDE != null)
                                 .ToList()
                let distance = SqlGeography.Point((double)postal.LATITUDE.Value, (double)postal.LONGITUDE.Value, 4326).STDistance(yourLocation)
                                .Value
                where postal.LATITUDE != null amp;amp; postal.LONGITUDE != null amp;amp; distance < 3000
                amp;amp; postal.LATITUDE > minLat1 amp;amp; postal.LATITUDE < maxLat1
                amp;amp; postal.LONGITUDE > minLong1 amp;amp; postal.LONGITUDE < maxLong1
                orderby distance
                select postal.POSTAL_CODE;

    return query.Distinct().ToList();
}
  

Ответ №2:

После того, как я опубликовал свой первый ответ, используя STBuffer, я понял, что есть более простой способ получить угловые точки на определенном расстоянии от точки.

Я сохраняю исходный ответ STBuffer, поскольку он не является неправильным, и он может быть полезен в качестве примера использования STBuffer и повторения точек в объекте Geography.

Вот более простая реализация GetBorderBounds:

 public static void GetBorderBounds2(SqlGeography point, double distanceMeters, out double minLat, out double minLong, out double maxLat, out double maxLong)
{
    var metresPerDegreeLat = point.STDistance(SqlGeography.Point((double)point.Lat   1.0, (double)point.Long, 4326));
    var metresPerDegreeLong = point.STDistance(SqlGeography.Point((double)point.Lat, (double)point.Long   1.0, 4326));
    minLat = (double)(point.Lat - distanceMeters / metresPerDegreeLat);
    maxLat = (double)(point.Lat   distanceMeters / metresPerDegreeLat);

    minLong = (double)(point.Long - distanceMeters / metresPerDegreeLong);
    maxLong = (double)(point.Long   distanceMeters / metresPerDegreeLong);
}
  

Эта реализация выходит из строя вблизи Северного и Южного полюсов. Также, как и в первом решении, оно дает неправильный ответ, если граница пересекает 180-й меридиан.

Если все ваши точки удалены от полюсов или 180-го меридиана, это не имеет значения.

Если ваши точки могут находиться вблизи полюсов или 180-го меридиана, вот пуленепробиваемая реализация:

 public static void GetBorderBounds3(SqlGeography point, double distanceMeters, out double minLat, out double minLong, out double maxLat, out double maxLong)
{
    // Near the North pole: 
    // Select whole circle of longitude from North pole to latitude south of point by distance
    if (point.Lat >= 89)
    {
        minLat = (double)(point.Lat - distanceMeters / point.STDistance(SqlGeography.Point((double)point.Lat - 1.0, (double)point.Long, 4326)));
        maxLat = 90;
        minLong = -180;
        maxLong = 180;
        return;
    }

    // Near the South pole: 
    // Select whole circle of longitude from South pole to latitude north of point by distance metres
    if (point.Lat <= -89)
    {
        minLat = -90;
        maxLat = (double)(point.Lat   distanceMeters / point.STDistance(SqlGeography.Point((double)point.Lat   1.0, (double)point.Long, 4326)));
        minLong = -180;
        maxLong = 180;
        return;
    }

    var metresPerDegreeLat = point.STDistance(SqlGeography.Point((double)point.Lat   1.0, (double)point.Long, 4326));
    var metresPerDegreeLong = point.STDistance(SqlGeography.Point((double)point.Lat, (double)point.Long   1.0, 4326));
    minLat = (double)(point.Lat - distanceMeters / metresPerDegreeLat);
    maxLat = (double)(point.Lat   distanceMeters / metresPerDegreeLat);
    minLong = (double)(point.Long - distanceMeters / metresPerDegreeLong);
    maxLong = (double)(point.Long   distanceMeters / metresPerDegreeLong);

    // If we cross the 180th meridian, select the whole circle of longitude:
    if (minLong < -180 || maxLong > 180.0)
    {
        minLong = -180;
        maxLong = 180;
    }
}