Как стать автором
Обновить

Точное определение региона по GPS координатам

Время на прочтение4 мин
Количество просмотров20K
При разработке одного приложения возникла проблема разграничения доступа для регионов.

Встала проблема определения принадлежности объекта к какому-либо региону России по его GPS координатам

Первое, что мы начали использовать — это API Google,
после того как прописали алиасы к возвращаемым строкам и оплаты доступа (чтобы убрать лимит на запросы) все заработало.
И все было нормально пока гугл не сменил выдачу, например было раньше: Moskovskaya oblast', стало Moscow oblast'
Тут то и было решено не надеяться на гугл, а определять регион своими силами.




Первым, что пришло на ум было: распарсить возвращаемые данные Яндекса (в яндекс-картах версии 1.x был модуль для работы с регионами)
Так и поступили, получилось 2 таблицы.
В одной хранились названия регионов, во второй координаты многоугольника, описывающие регион. (83 — регионы и 8500 — точки )

Был найден алгоритм, для проверки лежит ли точка в многоугольнике. Код был переписан с Си на PHP, разбирать как он работает нет смысла, он просто работает.

$sx,$sy — координаты точки, которую проверяем
$coords — координаты точек выпуклого многоугольника (отсортированы в порядке обхода):
$coords = array(
array('x'=> 66.6634, 'y' => '66.4433'),
и тп.
)
$x, $y — названия ключей в массиве
public static function into_poly($sx, $sy, &$coords, $x='x', $y='y')
{
    Profiler::start('Detect collision', __FUNCTION__);
    $pj=0;
    $pk=0;
    $wrkx=0;
    $yu = 0;
    $yl = 0;
    $n = count($coords);
    for ($pj=0; $pj<$n; $pj++)
    {
        $yu = $coords[$pj][$y]>$coords[($pj+1)%$n][$y]?$coords[$pj][$y]:$coords[($pj+1)%$n][$y];
        $yl = $coords[$pj][$y]<$coords[($pj+1)%$n][$y]?$coords[$pj][$y]:$coords[($pj+1)%$n][$y];
        if ($coords[($pj+1)%$n][$y] - $coords[$pj][$y])
            $wrkx = $coords[$pj][$x] + ($coords[($pj+1)%$n][$x] - $coords[$pj][$x])*($sy - $coords[$pj][$y])/($coords[($pj+1)%$n][$y] - $coords[$pj][$y]);
        else
            $wrkx = $coords[$pj][$x];
        if ($yu >= $sy)
            if ($yl < $sy)
            {
                if ($sx > $wrkx)
                    $pk++;
                if (abs($sx - $wrkx) < 0.00001) return 1;
            }
        if ((abs($sy - $yl) < 0.00001) && (abs($yu - $yl) < 0.00001) && (abs(abs($wrkx - $coords[$pj][$x]) + abs($wrkx - $coords[($pj+1)%$n][$x]) - abs($coords[$pj][$x] - $coords[($pj+1)%$n][$x])) < 0.0001))
            return 1;
    }
    if ($pk%2)
        return 1;
    else
        return 0;
}


Пример вызова:
$coords = array(
array('lng'=> 66.6634, 'lat' => '66.4433'),
array('lng'=> 66.6534, 'lat' => '66.4433'),
array('lng'=> 66.6434, 'lat' => '66.4433'),
array('lng'=> 66.6334, 'lat' => '66.4433'),
);
$in = into_poly(66.4455, 66.2255, &$coords, $x='lng', $y='lat');


Вернет true, если точка лежит внутри многоугольника.

Теперь просто перебором областей для точки мы получаем ответ.

Всё бы было хорошо, но 8500 точек границ регионов для России — это очень мало. Разброс получится +- 50 км

Чтобы получить точность больше – нам нужно больше точек границ регионов.
Поискав на просторах был найден ресурс, который нам свободно отдает эти точки: gis-lab.info/qa/rusbounds-rosreestr.html (спасибо энтузиастам за вашу работу)
Я почему-то выбрал формат KML (наверное потому, знал чем его открывать — это формат, который поддерживает Google Earth)
Распарсил данные и теперь таблицы получились пожирнее. Таблица с координатами точек границ регионов стала 620 000 точек (34 мб)

Теперь если проверять по старому алгоритму принадлежность точки к многоугольнику региона — на стареньком Core 2 Duo — около 70 секунд.
Не вариант. Алгоритм нужно оптимизировать:

Например, Москва:
Имеем 2,064 точек многоугольника (на самом деле несколько многоугольников):


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

вот его реализация на PHP:
public static function sort_points($mass, $x = 'x', $y = 'y'){
    $current=0;
    $next=0;
    $p1 = $mass[0];
    $mass2 = array();
    //определяем первую точку
    for ($i=1; $i<count($mass); $i++){
        if ( ($p1[$y] > $mass[$i][$y]) || ($p1[$y] == $mass[$i][$y] && $p1[$x] < $mass[$i][$x]) ) {
            $p1 = $mass[$i];
            $current = $i;
        }
    }
    $n = count($mass);

    $p0 = $p1;
    $p0[$x]--;

    $mass2[] = $mass[$current];
    $first = $current;

    //начинаем перебор всех элементов

    do{
        $cmax_not_set=1;
        for ( $i=0; $i<$n; $i++ ) {
            $key = $i;
            //продолжаем если такой элемент уже выбран
            if ( $mass[$current][$x] == $mass[$key][$x] && $mass[$current][$y] == $mass[$key][$y] ) continue;
            //берем 1ю точку
            if ($cmax_not_set) {
                $next = $key;
                $cmax_not_set=0;
                continue;
            }

            $v1_x = $mass[$key][$x] - $mass[$current][$x];
            $v1_y = $mass[$key][$y] - $mass[$current][$y];

            $v2_x = $mass[$next][$x] - $mass[$current][$x];
            $v2_y = $mass[$next][$y] - $mass[$current][$y];

            //магия
            $c = $v1_x * $v2_y - $v1_y * $v2_x;

            if ( $c > 0 ) $next = $key;
           // if ( ($c==0) && ( self::_dist($mass[$current], $mass[$key], $x, $y) > self::_dist($mass[$current], $mass[$next], $x, $y) ) ) $next = $key;

        }
        //записываем найденный элемент в массив
        $mass2[] = $mass[$next];
        $current = $next;
    }while($first != $next);
    return $mass2;
}


На выходе мы получим массив точек выпуклого многоугольника.
Для Москвы; 19 точек:


На рисунке первым слоем отмечены все 2500 точек Москвы + слой, который мы получили после расчета выпуклого многоугольника.
Сгенерируем для каждого региона такую область.

Теперь доработаем наш алгоритм и будем сначала проверять возможность, что точка может лежать в области – таких областей может быть несколько.
Теперь после прогона всех областей, мы прогоним области, в которых возможно, что лежит точка, алгоритмом описанным выше.
Итого: 0.177сек на слабеньком Core 2 Duo

На самом деле есть еще одна сложность – это анклавы (Москва лежит внутри Московской области, как и Питер, было решено использовать приоритеты, сначала проверяется лежит ли точка в Москве, а потом уже в Московской области)
Теги:
Хабы:
Всего голосов 15: ↑10 и ↓5+5
Комментарии25

Публикации

Истории

Работа

PHP программист
106 вакансий

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань