Точка в географическом радиусе - SQL Server 2008

Учитывая следующие данные, возможно ли это, и если да, то какой будет наиболее эффективный метод определения того, содержится ли местоположение «Шердингтон» в первой таблице в пределах заданного радиуса любого из местоположений во второй таблице.

Столбец GeoData относится к типу «география», поэтому можно использовать пространственные функции SQL Server, а также широту и долготу.

Location      GeoData       Latitude    Longitude
===========================================================
Shurdington   XXXXXXXXXX    51.8677979  -2.113189

ID  Location            GeoData     Latitude    Longitude   Radius
==============================================================================
1000    Gloucester      XXXXXXXXXX  51.8907127  -2.274598   10
1001    Leafield        XXXXXXXXXX  51.8360519  -1.537438   10
1002    Wotherton       XXXXXXXXXX  52.5975151  -3.061798   5
1004    Nether Langwith XXXXXXXXXX  53.2275276  -1.212108   20
1005    Bromley         XXXXXXXXXX  51.4152069  0.0292294   10

Мы очень ценим любую помощь.


person Nathan    schedule 04.02.2013    source источник


Ответы (3)


Создать данные

CREATE TABLE #Data (
    Id int,
    Location nvarchar(50),
    Latitude decimal(10,5),
    Longitude decimal(10,5),
    Radius int
)

INSERT #Data (Id,Location,Latitude,Longitude,Radius) VALUES 
(1000,'Gloucester', 51.8907127 ,-2.274598  , 20), -- Increased to 20
(1001,'Leafield', 51.8360519 , -1.537438  , 10),
(1002,'Wotherton', 52.5975151,  -3.061798  , 5),
(1004,'Nether Langwith', 53.2275276 , -1.212108  , 20),
(1005,'Bromley', 51.4152069 , 0.0292294  , 10)

Тест

Объявите свою достопримечательность как POINT

DECLARE @p GEOGRAPHY = GEOGRAPHY::STGeomFromText('POINT(-2.113189 51.8677979)', 4326);

Чтобы узнать, находится ли он в радиусе другой точки:

-- First create a Point.
DECLARE @point GEOGRAPHY = GEOGRAPHY::STGeomFromText('POINT(-2.27460 51.89071)', 4326);
-- Buffer the point (meters) and check if the 1st point intersects
SELECT @point.STBuffer(50000).STIntersects(@p)

Объединив все это в один запрос:

select  *,
        GEOGRAPHY::STGeomFromText('POINT('+ 
            convert(nvarchar(20), Longitude)+' '+
            convert( nvarchar(20), Latitude)+')', 4326)
        .STBuffer(Radius * 1000).STIntersects(@p) as [Intersects]
from    #Data  

Дает:

Id      Location        Latitude    Longitude   Radius  Intersects
1000    Gloucester      51.89071    -2.27460    20      1
1001    Leafield        51.83605    -1.53744    10      0
1002    Wotherton       52.59752    -3.06180    5       0
1004    Nether Langwith 53.22753    -1.21211    20      0
1005    Bromley         51.41521    0.02923     10      0

Re: Эффективность. При правильном индексировании кажется, что пространственные индексы SQL могут быть очень быстрыми.

person Paddy    schedule 04.02.2013
comment
Мне нравится этот пример, хотя, как ни странно, расстояния кажутся незначительными - возможно, это связано с моим незнанием пространственных функций SQL Server, но даже в этом случае я бы предположил, что, добавив столбец '. STDistance (@p) / 1609.34 'он вернет расстояние в милях, хотя кажется, что это далеко - или это я? - person Nathan; 04.02.2013
comment
Выяснилось, что при создании POINT из STGeomFromText требуется, чтобы lat / long вводились наоборот, например долгий / шир. - person Nathan; 04.02.2013

Если вы хотите провести математику самостоятельно, вы можете использовать равнопрямоугольное приближение на основе Пифагора. Формула:

var x = (lon2-lon1) * Math.cos ((lat1 + lat2) / 2); var y = (lat2-lat1); var d = Math.sqrt (x * x + y * y) * R;

С точки зрения SQL, это должно дать те места во второй таблице, которые содержат вашу запись, в первой в пределах своего радиуса:

SELECT *
FROM Table2 t2
WHERE EXISTS (
 SELECT 1 FROM Table1 t1
 WHERE 
  ABS (
  SQRT (
    (SQUARE((RADIANS(t2.longitude) - RADIANS(t1.longitude)) * COS((RADIANS(t2.Latitude) + RADIANS(t1.Latitude))/2))) +
    (SQUARE(RADIANS(t1.Latitude) - RADIANS(t2.Latitude)))
    ) * 6371 --Earth radius in km, use 3959 for miles
    )
    <= t2.Radius
)

Обратите внимание, что это не самый точный доступный метод, но, скорее всего, он достаточно хорош. Если вы смотрите на расстояния, которые простираются через земной шар, вы можете захотеть погуглить формулу «гаверсинуса».

Возможно, стоит сравнить это решение с решением Падди, чтобы увидеть, насколько они согласны и какое из них работает лучше всего.

person NigelK    schedule 04.02.2013

Вы вычисляете расстояние между двумя точками и сравниваете это расстояние с заданным радиусом.

Для расчета малых расстояний вы можете использовать формулу в Википедия - Географическое расстояние - Сферическая Земля в проекции на плоскость, который утверждает, что он «очень быстрый и дает довольно точный результат для небольших расстояний».

По формуле нужна разница широты и долготы и средняя широта.

with geo as (select g1.id, g1.latitude as lat1, g1.longitude as long1, g1.radius,
                    g2.latitude as lat2, g2.longitude as long2
             from geography g1
             join geography g2 on g2.location = 'shurdington'
                               and g1.location <> 'shurdington')
     base as (select id,
                     (radians(lat1) - radians(lat2)) as dlat,
                     (radians(long1) - radians(long2)) as dlong,
                     (radians(lat1) + radians(lat2)) / 2 as mlat, radius
              from geo)
     dist as (select id,
                     6371.009 * sqrt(square(dlat) + square(cos(mlat) * dlong)) as distance,
                     radius
              from base)
select id, distance
from dist
where distance <= radius

Я использовал with selects в качестве промежуточных шагов, чтобы расчеты оставались «читаемыми».

person Olaf Dietsche    schedule 04.02.2013
comment
Зачем делать это самому, учитывая, что использование пространственных функций SQL Server - вариант? - person AakashM; 04.02.2013
comment
@AakashM Потому что это было интересное упражнение. - person Olaf Dietsche; 04.02.2013