Используйте пространственные расширения MySQL для выбора точек внутри круга

У меня есть таблица под названием flags, которая содержит столбец с именем coordinates, который заполнен точками MySQL. Мне нужно выполнить запрос, где я получаю все флаги в круге на основе положения широты и долготы с радиусом 100 м.

С точки зрения использования это основано на позиции пользователя. Например, мобильный телефон предоставит пользователю широту и долготу, а затем передаст его в эту часть API. Затем он до API создаст невидимый круг вокруг пользователя с радиусом 100 метров, а затем вернет флаги, которые находятся в этом круге.

В этой части API я не уверен, как создать, поскольку я не уверен, как использовать SQL для создания этого невидимого круга и выбирать точки только в пределах этого радиуса.

Возможно ли это? Есть ли пространственная функция MySQL, которая поможет мне сделать это?

Я считаю, что функция Buffer() может это сделать, но я не могу найти документацию о том, как ее использовать (например, пример SQL). В идеале мне нужен ответ, который показывает мне, как использовать эту функцию или ближайшую к ней. Где я храню эти координаты в качестве геопространственных точек, я должен использовать геопространственную функцию, чтобы делать то, что я прошу максимизировать эффективность.

Таблица флагов:

  • ID
  • координаты
  • имя

Пример строки:

1 | [GEOMETRY - 25B] | Tenacy AB

Для таблицы флагов у меня есть широта, долгота и восток и север (UTM)

Расположение пользователя - это стандартная широта/долгота, но у меня есть библиотека, которая может конвертировать эту позицию в UTM

Ответ 1

В MySQL нет функций геопространственного расширения, поддерживающих вычисления расстояний широты/долготы.

Вы просите о приближенных кругах на поверхности земли. Вы указываете в своем вопросе, что у вас есть значения lat/long для каждой строки в таблице flags, а также универсальные поперечные Mercator (UTM) прогнозируемые значения в одном из нескольких различные зоны UTM. Если я правильно помню, что мои карты обрядов в Великобритании правильно, UTM полезна для поиска предметов на этих картах.

Просто вычислить расстояние между двумя точками в одной и той же зоне в UTM: декартовое расстояние делает трюк. Но, когда точки находятся в разных зонах, это вычисление не работает.

Соответственно, для приложения, описанного в вашем вопросе, необходимо использовать Great Circle Distance, которое вычисляется с использованием haversine или другой подходящей формулы.

MySQL, дополненный геопространственными расширениями, поддерживает способ представления различных плоских фигур (точек, полилиний, многоугольников и т.д.) в качестве геометрических примитивов. MySQL 5.6 реализует недокументированную функцию расстояния st_distance(p1, p2). Однако эта функция возвращает декартовы расстояния. Поэтому он совершенно непригоден для вычислений на основе широты и долготы. В умеренных широтах градус широты почти в два раза превышает поверхностное расстояние (с севера на юг) в виде долготы (восток-запад), поскольку широтные линии ближе расположены ближе к полюсам.

Итак, круговая формула близости должна использовать настоящую широту и долготу.

В вашем приложении вы можете найти все теги flags в пределах десяти статутных миль заданного latpoint,longpoint с таким запросом:

 SELECT id, coordinates, name, r,
        units * DEGREES( ACOS(
                   COS(RADIANS(latpoint)) 
                 * COS(RADIANS(X(coordinates))) 
                 * COS(RADIANS(longpoint) - RADIANS(Y(coordinates))) 
                 + SIN(RADIANS(latpoint)) 
                 * SIN(RADIANS(X(coordinates))))) AS distance
   FROM flags
   JOIN (
        SELECT 42.81  AS latpoint,  -70.81 AS longpoint, 
               10.0 AS r, 69.0 AS units
        ) AS p ON (1=1)
  WHERE MbrContains(GeomFromText (
        CONCAT('LINESTRING(',
              latpoint-(r/units),' ',
              longpoint-(r /(units* COS(RADIANS(latpoint)))),
              ',', 
              latpoint+(r/units) ,' ',
              longpoint+(r /(units * COS(RADIANS(latpoint)))),
              ')')),  coordinates)

Если вы хотите найти точки в пределах 20 км, измените эту строку запроса

               20.0 AS r, 69.0 AS units

к этому, например

               20.0 AS r, 111.045 AS units

r - это радиус, в котором вы хотите выполнить поиск. units - это единицы расстояния (мили, км, фарлоны, все, что вы хотите) на градус широты на поверхности земли.

Этот запрос использует ограничивающий lat/long вместе с MbrContains, чтобы исключить точки, которые определенно слишком далеки от вашей начальной точки, а затем использует формулу расстояния большого круга для создания расстояний для остальных точек. Описание можно найти здесь. Если ваша таблица использует метод доступа MyISAM и имеет пространственный индекс, MbrContains будет использовать этот индекс для быстрого поиска.

Наконец, запрос выше выбирает все точки внутри прямоугольника. Чтобы сузить это до только точек в круге и упорядочить их по близости, оберните запрос следующим образом:

 SELECT id, coordinates, name
   FROM (
         /* the query above, paste it in here */
        ) AS d
  WHERE d.distance <= d.r
  ORDER BY d.distance ASC 

Ответ 2

Это предполагает, что координаты в таблице сохраняются как тип данных POINT() в столбце с надписью "точка". Функция X (точка) и Y (точка) извлекают значения широты и долготы из значения точки соответственно.

SET @lat = the latitude of the point
SET @lon = the longitude of the point
SET @rad = radius in Kilometers to search from the point
SET @table = name of your table

SELECT
    X(point),Y(point),*, (
      6373 * acos (
      cos ( radians( @lat ) )
      * cos( radians( X(point) ) )
      * cos( radians( Y(point) ) - radians( @lon ) )
      + sin ( radians( @lat ) )
      * sin( radians( X(point) ) )
    )
) AS distance
FROM @table
HAVING distance < @rad

Если вы хотите сделать это в милях, замените константу 6373 на 3959

Для тех, кто хочет уменьшить синтаксис запроса, здесь представлена ​​общая реализация пользовательской функции MySQL для реализации функции расстояния, основанной на формулах Хаверсина.

CREATE FUNCTION HAVERSINE ( coord1 POINT, coord2 POINT )
RETURNS DOUBLE
DETERMINISTIC
BEGIN
    DECLARE dist DOUBLE;
    SET rlat1 = radians( X( coord1 ) );
    SET rlat2 = radians( X( coord2 ) );
    SET rlon1 = radians( Y( coord1 ) );
    SET rlon2 = radians( Y( coord2 ) );
    SET dist  = ACOS( COS( rlat1 ) * COS( rlon1 ) * COS( rlat2 ) * COS( rlon2 ) + COS( rlat1 ) * SIN( rlon1 ) * COS( rlat2 ) * SIN( rlon2 ) + SIN( rlat1 ) * SIN( rlat2 ) ) * 6372.8;
    RETURN dist;
END

Ответ 4

Буферы не помогут вам в MySQL < 5.6, поскольку буфер является многоугольником, а операции многоугольника в MySQL < 5.6 реализованы как "Минимальные ограничивающие прямоугольники" (MBR), которые довольно бесполезны.

Начиная с MySQL 5.6, были реализованы полные операции без MBR st_*. Но лучшим решением для вас в случае круга является использование недокументированная функция st_distance:

select *
from waypoints
where st_distance(point(@center_lon, @center_lat), coordinates) <= radius;

Трудно было найти, так как он недокументирован:-) Но он упомянул в этом блоге, автор которого также заполнил упомянутый bugreport. Есть предостережения (ссылка на блог):

Плохая новость:

1) Все функции по-прежнему используют только координаты планарной системы. Различные SRID не поддерживаются.

2) Пространственные индексы (RTREE) поддерживаются только для таблиц MyISAM. Один может использовать функции для таблиц InnoDB, но не будет использовать пространственные ключи.

Точка 1) означает, что единица расстояния будет такой же, как единица координат (в случае WGS84). Если вам нужно расстояние в метрах, вы должны использовать проекционную систему координации (например, UTM или аналогичную), которая имеет единицы измерения, соответствующие метрам.

Итак, если вы не хотите идти с этими оговорками или в случае MySQL < 5.6, вам нужно будет написать свою собственную функцию расстояния.

Ответ 6

от: https://gis.stackexchange.com/questions/31628/find-points-within-a-distance-using-mysql

SELECT
    id, (
      6371 * acos (
      cos ( radians(78.3232) )
      * cos( radians( lat ) )
      * cos( radians( lng ) - radians(65.3234) )
      + sin ( radians(78.3232) )
      * sin( radians( lat ) )
    )
) AS distance
FROM markers
HAVING distance < 30
ORDER BY distance
LIMIT 0 , 20;

(не забудьте заменить все константы, этот пример для километров)

Ответ 7

Вы можете использовать:

SELECT name, lat, lng   
FROM vw_mytable 
WHERE ST_Contains(ST_Buffer(
          ST_GeomFromText('POINT(12.3456 34.5678)'), (0.00001*1000)) , mypoint) = 1  

Выражение: 0,00001 * 1000 внутри статута выше дает вам круг с диаметром 1000 метров, его применение на представлении здесь, столбец имен - это всего лишь метка для точки, mypoint - это имя моего столбца точки, lat был рассчитан внутри вида с помощью ST_X (mytable.mypoint) и lng с ST_Y (mytable.mypoint), и они просто показывают мне буквальные значения lat и lng. Он предоставит вам все координаты, принадлежащие кругу.

Ответ 8

для полноты, начиная с MySQL 5.7.6. вы можете использовать функцию ST_Distance_Sphere, которая обеспечивает тот же результат:

SET @pt1 = ST_GeomFromText('POINT(12.3456 34.5678)');

SELECT * from 
(SELECT * ,(ST_Distance_Sphere(@pt1, location, 6373)) AS distance FROM mydb.Event ORDER BY distance) x WHERE x.distance <= 30;

В этом случае мы обеспечиваем приблизительный радиус Земли в километрах (6373) и точку (@pt1). Этот код рассчитает расстояние (в километрах) между этой точкой (длинное 12.3456, lat 34.5678) и все точки, содержащиеся в базе данных, где расстояние составляет 30 км или менее.