Клуб API Карт

Выборка меток/кластера_меток по вхождению в полигон или окружность

Пост в архиве.

Требуется сделать выборку из ~5к+ меток на основе  попаданию в зону. Зона может быть обозначена как окружностью, так и пользовательским полигоном созданных через API карт и быть от 1 до 500 км. 

 

Хотел бы узнать, возможно ли (и целесообразно ли) произвести такую выборку на клиентской стороне через API? Если да, то в какую сторону курить?

Или подсчет следует вести исключительно на серверно стороне через селекты по координатам, например в базе?

15 комментариев

по-идее, 5к точек - не много.

что бы сделал я:

1. загружал точки с сервера уже с пиксельными координатами (географические вообще не нужны)

2. в цикле фильтрации сначала проверяем mbr (ограничивающий прямоугольник). если таки да, то затем считаем попадание в окружности или в полигон)

А разве пиксельные координаты не зависят от зума?

зависят, но прямопропорционально )

скажем, считаем все точки в пиксельные на нулевом зуме. получаем 

для каждой точки [ [0..255], [0..255]] . теперь нам надо узнать, попадают ли они в квадрат square = [[ lat, lng], [lat, lng]]. переводим гео в пиксельные на нулевой зум:

squareGlobal = [map.options.get('projection').toGlobalPixels(square[0], 0) ,

map.options.get('projection').toGlobalPixels(square[1], 0) ];

- вауля! можем сравнивать!  

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

А еще есть прикол с http://clubs.ya.ru/mapsapi/replies.xml?item_no=30509 (там картинка куда-то пропала).

если использовать пиксельные координаты - такого прикола не будет.

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

1. на сервере "метки" хранятся в геокординатах, те потом каким-то образом конвертить в пиксельные?

2.Пробую вариант через проверку myCircle.geometry.contains(json.markers[i].lat, json.markers[i].lon).  Странно, что при использовании заведомо минимального радиуса круга, к примеру 0.1, фильтрация все-таки срабатывает, но так, как будто это 1км, а не 10 сантиметров :) есть идеи почему так происходит?

 

на всякий случай круг у меня такой:

 

 

myCircle = new ymaps.Circle([                    // Координаты центра круга                    //[55.742523, 37.605311],                    cicenter,                    // Радиус круга в метрах                    radius                ], {                    // Контент балуна                    //balloonContent: "Радиус круга - 15 км",                    // Контент хинта                    //hintContent: "Зона оповещения о ЧС"                    geodesic: true                }, {                    // Круг можно перемещать                    draggable: false,                    interactivityModel: 'default#transparent',                    // Цвет заливки. Последние цифры "77" - прозрачность.                    // Прозрачность заливки также можно указывать в "fillOpacity".                    fillColor: "#DB709377",                    // Цвет и прозрачность линии окружности.                    strokeColor: "#990066",                    strokeOpacity: 0.3,                    // Ширина линии окружности                    strokeWidth: 5                });            // Добавляем круг на карту            myMap.geoObjects.add(myCircle);

1. можно на сервере конвертить на лету. а лучше сразу сконвертить и сохранить.

function geoToGlobal($geo)

        {

            if(abs($geo->lat) >=88) $geo->lat = ($geo->lat > 0) ? 88 : -88;

            $x = 1+ $geo->lng / 360;

            $k1 = $k2 = sin($geo->lat * M_PI / 180);

            if($k2 == 1) $k2 += 0.1;

            $y = 1 - 0.5 * log((1 + $k1) / (1 - $k2)) / (2 * M_PI);

            return (object) array('x'=>$x, 'y' =>$y);

        };

2. на клиенте он каждый раз будет конвертить в пикселы, а это затратная операция (sin и log - медленные)

Запутался.

2. правильно ли я понял из Вашего ответа, что contains не умеет сравнивать с геокоординатами напрямую и перед использованием myCircle.geometry.contains(json.markers[i].lat, json.markers[i].lon) координаты нужно сконвертировать в пиксельные?

нет, конечно. contains внутри себя производит хитрые преобразования.

в я дал исходник на php для серверной реализации.

вот исходник:

contains: function (position) {

        return imports.util.math.pointInPolygon(

            this.options.get("projection").toGlobalPixels(position, this._map.getZoom()),

            this.getPixelGeometry({

                simplification: false,

                pixelRendering: 'static'

            }).getCoordinates(),

            this._fillRuleComponent.getFillRule()

        );

    },

поэтому, сравнивать 5000 точек на клиенте - довольно долго.

 

Нельзя ли, открытьтему по подробнее.

У меня задача Определить входят ли в окружность с радиусом 400 м координаты из базы (координаты хранятся как 61.451745 55.169514), если входят показать их на карте.

1. определяете квадрат расстояния в пикселях. для этого надо загуглить функцию, переводящую метры в пиксели на заданных координатах на 0  зуме. 

$d = distToPixels($dist,  $lng, $zoom = 0);

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

полученный резалт возвести в квадрат.

$d2 = $d * $d

получить центр окружности в пикселях $centerGlobal = geoToGlol($geo, 0);

получить $mbr = array('top' => $centerGlobal->y - $d / 2, 

'left' => $centerGlobal->x - $d / 2],  'right' => $centerGlobal->x + $d / 2, 

'bottom' => $centerGlobal->y + $d / 2);

2. для каждой точки в цикле:

- пересчитать координаты в пиксельные $pixelGlobal на 0 зуме  и закэшить. 

- проверить mbr:

if ($pixelGlobal->x >= $mbr->left && $pixelGlobal->x right &&

$pixelGlobal->y >= $mbr->top && $pixelGlobal->y bottom)

тогда проверяем вхождеине в круг

pow(($centerPixel->x - $pixelGlobal-x), 2) + pow(($centerPixel->y - $pixelGlobal-y), 2) < d2 

Трудновато.

А если так.

1 Строим квадрат, получаем координаты вершин. (левая верхняя(65,  50) и правая нижняя (60,  55)) - для примера, отправляем их на сервер.

2 Каждая точка входящая в квадрат будет между (60 - 65, 50 - 55)

вытаскиваю по этому условию из  sql записи.

 

Или я вообще дерево, не вижу всей картины в целом.