Расстояние между двумя координатами, как я могу упростить это и / или использовать другую технику? - PullRequest
3 голосов
/ 20 января 2011

Мне нужно написать запрос, который позволит мне найти все местоположения в пределах диапазона (миль) из указанного местоположения.

Таблица выглядит следующим образом:

id  |  name  |  lat  |  lng 

Итак, я провел исследование и обнаружил: это мое представление sql

Я проверил егона столе около 100 строк и будет гораздо больше!- Должно быть масштабируемым.

Сначала я попробовал что-то более простое:

//just some test data this would be required by user input    
set @orig_lat=55.857807; set @orig_lng=-4.242511; set @dist=10;

SELECT *, 3956 * 2 * ASIN(
          SQRT( POWER(SIN((orig.lat - abs(dest.lat)) * pi()/180 / 2), 2) 
              + COS(orig.lat * pi()/180 ) * COS(abs(dest.lat) * pi()/180)  
              * POWER(SIN((orig.lng - dest.lng) * pi()/180 / 2), 2) )) 
          AS distance
  FROM locations dest, locations orig
 WHERE orig.id = '1'
HAVING distance < 1
 ORDER BY distance;

Это вернуло строки примерно за 50ms , что довольно хорошо!Однако это приведет к значительному замедлению при увеличении количества строк.

EXPLAIN показывает, что он использует только ПЕРВИЧНЫЙ ключ, что очевидно.


Затем, после прочтения статьи , указанной выше.Я пытался что-то вроде этого:

// defining variables - this when made into a stored procedure will call
// the values with a SELECT query.
set @mylon = -4.242511;
set @mylat = 55.857807;
set @dist = 0.5;

-- calculate lon and lat for the rectangle:
set @lon1 = @mylon-@dist/abs(cos(radians(@mylat))*69);
set @lon2 = @mylon+@dist/abs(cos(radians(@mylat))*69);
set @lat1 = @mylat-(@dist/69); 
set @lat2 = @mylat+(@dist/69);

-- run the query:

SELECT *, 3956 * 2 * ASIN(
          SQRT( POWER(SIN((@mylat - abs(dest.lat)) * pi()/180 / 2) ,2)
              + COS(@mylat * pi()/180 ) * COS(abs(dest.lat) * pi()/180)
              * POWER(SIN((@mylon - dest.lng) * pi()/180 / 2), 2) ))
          AS distance
  FROM locations dest
 WHERE dest.lng BETWEEN @lon1 AND @lon2
   AND dest.lat BETWEEN @lat1 AND @lat2
HAVING distance < @dist
 ORDER BY distance;

Время этого запроса составляет около 240 мс , это не так уж плохо, но медленнее, чем в прошлом.Но я могу представить, что при большем количестве строк это будет работать быстрее.Однако EXPLAIN показывает возможные ключи как lat, lng или PRIMARY и используется PRIMARY.

Как я могу сделать это лучше ???

Я знаю, что мог бы сохранить лат в качестве ТОЧКИ ();но я также не нашел слишком много документации по этому вопросу, которая показывает, является ли это быстрее или точнее?

Любые другие идеи будут с радостью приняты!

Большое спасибо!

-Stefan


ОБНОВЛЕНИЕ:

Как отметил Джонатан Леффлер, я допустил несколько ошибок, которые не заметил:

Iтолько поместил abs () в одно из значений lat.Я использовал поиск по идентификатору в предложении WHERE и во втором, когда в этом не было необходимости.Первый запрос был чисто экспериментальным, а второй с большей вероятностью попадет в производство.

После этих изменений EXPLAIN показывает, что ключ теперь использует столбец lng и среднее время ответа около 180 мс сейчас, что является улучшением.

Ответы [ 5 ]

2 голосов
/ 20 января 2011

Первый запрос игнорирует заданные вами параметры - используя 1 вместо @dist для расстояния и используя псевдоним таблицы orig вместо параметров @orig_lat и @orig_lon.

Затем у вас есть запрос, делающий декартово произведение между таблицей и самим собой, что редко является хорошей идеей, если вы можете избежать этого. Вам это сходит с рук из-за условия фильтра orig.id = 1, что означает, что есть только одна строка из orig, соединенная с каждой из строк в dest (включая точку с dest.id = 1; у вас, вероятно, должно быть условие AND orig.id != dest.id). У вас также есть предложение HAVING, но нет предложения GROUP BY, что свидетельствует о проблемах. Предложение HAVING не относится к каким-либо агрегатам, но предложение HAVING (главным образом) предназначено для сравнения агрегатных значений.

Если моя память не подводит меня, COS (ABS (x)) === COS (x), так что вы можете упростить вещи, отбросив ABS (). В противном случае неясно, почему одна широта нуждается в ABS, а другая нет - симметрия имеет решающее значение в сферической тригонометрии.

У вас есть доза магических чисел - значение 69 - это, вероятно, количество миль в градусах (долготы, на экваторе), а 3956 - радиус Земли.

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

Условие dest.id = 1 во втором запросе нечетно; Я считаю, что это должно быть опущено, но его присутствие должно ускорить процесс, потому что только одна строка соответствует этому условию. Поэтому дополнительное время вызывает недоумение. Но использование индекса первичного ключа уместно, как написано.

Вы должны переместить условие в предложении HAVING в предложение WHERE.

Но я не уверен, что это действительно помогает ...

2 голосов
/ 20 января 2011

Любые другие идеи будут с радостью приняты!

Если вам нужна скорость (и простота), вам понадобится приличная геопространственная поддержка из вашей базы данных. Это вводит геопространственные типы данных, геопространственные индексы и (многие) функции для обработки / построения / анализа геопространственных данных.

MySQL реализует часть спецификаций OpenGIS , хотя это / было (в прошлый раз, когда я проверял, было), очень очень грубо по краям / преждевременно (бесполезно для реальной работы).

PostGis on PostgreSql сделает это легко и легко читаемым:

(это находит все точки из таблицыb, которые находятся ближе 1000 метров от точки А в таблице с идентификатором 123)

select 
    myvalue
from 
    tablea, tableb
where 
    st_dwithin(tablea.the_geom, tableb.the_geom, 1000)
and
    tablea.id = 123
1 голос
/ 12 мая 2011

Инверсионный геодезический калькулятор NGS является традиционным эталонным средством для расчета расстояния между любыми двумя точками земного эллипсоида:

http://www.ngs.noaa.gov/cgi-bin/Inv_Fwd/inverse2.prl

Но выше калькулятор все еще проблематичен.Особенно между двумя почти антиподальными точками вычисленное расстояние может показать ошибку в несколько десятков километров !!!Таддеус Винсенти (Taddeus Vincenty) давно определил причину числовой проблемы:

http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf

В любом случае предпочтительнее использовать надежный и очень точный онлайн-калькуляторЧарльз Карни:

http://geographiclib.sourceforge.net/cgi-bin/Geod

0 голосов
/ 10 августа 2012

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

SELECT *, SQRT(POWER(RADIANS(@mylat - dest.lat), 2) +
               POWER(RADIANS(@mylon - dst.lng)*COS(RADIANS(@mylat)), 2)
              )*@radiusOfEarth AS approximateDistance
…

Вы можете сделать это еще более эффективным, сохраняя радианы вместо (или в дополнение к) градусам в вашей базе данных. Если ваши запросы могут пересекать 180-градусный меридиан, то в этом случае потребуется некоторая дополнительная осторожность, но многим приложениям не приходится иметь дело с этими местоположениями. Вы также можете попробовать изменить POWER( x ) на x * x , что может быть вычислено быстрее.

0 голосов
/ 20 января 2011

Некоторые мысли по улучшению производительности.Это не упростит вещи с точки зрения удобства обслуживания (усложнит), но может помочь с масштабируемостью.

  1. Поскольку вы знаете радиус, вы можете добавить условия для ограничительной рамки, что может позволить БД оптимизировать запрос, чтобы исключить некоторые строки без необходимости выполнять триггерные вычисления.

  2. Вы можете предварительно рассчитать некоторые из значений триггера широты / долготысохраненные местоположения и сохраните их в таблице.Это приведет к некоторому снижению производительности при вставке записи, но если количество запросов превышает количество вставок, это будет хорошо.Посмотрите этот ответ для идеи этого подхода:

    Запрос для получения записей, основанных на радиусе в SQLite?

  3. Вы можете посмотреть на что-то вроде геохеширование .

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

Вы можете найти SO для некоторых идей о том, какагрегат: https://stackoverflow.com/search?q=geohash

...