Получить все долготы и широты в радиусе без пространственного типа Entity Framework - PullRequest
0 голосов
/ 06 марта 2019

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

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

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

Поэтому мне нужно позвонить toList, а затем использовать только 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 && postal.LONGITUDE != null &&  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()

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

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

Ответы [ 2 ]

0 голосов
/ 07 марта 2019

После того, как я опубликовал свой первый ответ с помощью 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;
    }
} 
0 голосов
/ 07 марта 2019

Географическая функция SQL Server «STBuffer» добавляет буферное расстояние к входной географии.Вы можете ввести одну точку в STBuffer, вы получите обратно многоугольник, приблизительно равный окружности с центром в точке.Затем вы можете найти минимальную и максимальную LAT / LONG этого полигона.

(Это не является точным на больших расстояниях, потому что Земля - ​​это сфера, но для радиуса 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 && postal.LONGITUDE != null && distance < 3000
                && postal.LATITUDE > minLat1 && postal.LATITUDE < maxLat1
                && postal.LONGITUDE > minLong1 && postal.LONGITUDE < maxLong1
                orderby distance
                select postal.POSTAL_CODE;

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