Алгоритм нахождения неправильного многоугольника центроида (точка метки)


13

Мне нужно найти центроид (или точку метки) для полигонов неправильной формы в Картах Google. Я показываю InfoWindows для посылок, и мне нужно место для привязки InfoWindow, которое гарантированно будет на поверхности. Смотрите изображения ниже.

альтернативный текст альтернативный текст

На самом деле мне не нужно ничего специфичного для Google Maps, я просто ищу идею, как автоматически найти эту точку.

Моя первая идея состояла в том, чтобы найти «ложный» центроид, взяв средние значения широты и долготы и случайно расположив точки оттуда, пока я не найду тот, который пересекает многоугольник. У меня уже есть код точки-полигона. Это просто кажется мне ужасно "хакерским".

Я должен отметить, что у меня нет доступа ни к одному из серверных кодов, выводящих геометрию, поэтому я не могу ничего сделать, например ST_PointOnSurface (the_geom).

Ответы:


6

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


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

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

@Jason Если вы используете настоящий центроид, у вас может быть меньше шансов столкнуться с этой проблемой. Не должно быть слишком сложно быстро что-то перевести на JavaScript: github.com/cartesiananalytics/Pipra/blob/master/src/…
Денди

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

3

Вы можете посмотреть на это: http://github.com/tparkin/Google-Maps-Point-in-Polygon

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

Об этом есть запись в блоге. http://appdelegateinc.com/blog/2010/05/16/point-in-polygon-checking/


Если вы хотите реализовать это на стороне сервера, JTS (Java) и Geos (C) реализуют эту функциональность.
DavidF

Да, я, вероятно, должен был добавить, что у меня уже есть код, чтобы определить, находится ли мой "вычисленный" центроид в пределах многоугольника или нет. Что я на самом деле хочу, так это какой-нибудь способ создать центроид, который находится внутри многоугольника.
Джейсон

3

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


1

Я решил свою проблему, расширив популярный эпологический код с http://econym.org.uk/gmap . В основном то, что я закончил, было:

  • Создайте серию лучей, которые начинаются с «ложного центроида» и распространяются на каждый угол и сторону (всего 8)
  • Постепенно создайте точку 10,20,30 ... процентов вниз по каждому лучу и посмотрите, находится ли эта точка в нашем исходном многоугольнике

Расширенный код эполии ниже:

google.maps.Polygon.prototype.Centroid = function() {
var p = this;
var b = this.Bounds();
var c = new google.maps.LatLng((b.getSouthWest().lat()+b.getNorthEast().lat())/2,(b.getSouthWest().lng()+b.getNorthEast().lng())/2);
if (!p.Contains(c)){
    var fc = c; //False Centroid
    var percentages = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]; //We'll check every 10% down each ray and see if we're inside our polygon
    var rays = [
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getNorthEast().lat(),fc.lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(fc.lat(),b.getNorthEast().lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getSouthWest().lat(),fc.lng())]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(fc.lat(),b.getSouthWest().lng())]}),
        new google.maps.Polyline({path:[fc,b.getNorthEast()]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getSouthWest().lat(),b.getNorthEast().lng())]}),
        new google.maps.Polyline({path:[fc,b.getSouthWest()]}),
        new google.maps.Polyline({path:[fc,new google.maps.LatLng(b.getNorthEast().lat(),b.getSouthWest().lng())]})
    ];
    var lp;
    for (var i=0;i<percentages.length;i++){
        var percent = percentages[i];
        for (var j=0;j<rays.length;j++){
            var ray = rays[j];
            var tp = ray.GetPointAtDistance(percent*ray.Distance()); //Test Point i% down the ray
            if (p.Contains(tp)){
                lp = tp; //It worked, store it
                break;
            }
        }
        if (lp){
            c = lp;
            break;
        }
    }
}
return c;}

Все еще немного хакерский, но это, кажется, работает.


Этот метод не удастся для некоторых извилистых полигонов. Например, буферизуйте ломаную линию {{0, 9}, {10, 20}, {9, 9}, {20, 10}, {9, 0}, {20, -10}, {9, -9} , {10, -20}, {0, -9}, {-10, -20}, {-9, -9}, {-20, -10}, {-9, 0}, {-20, 10}, {-9, 9}, {-10, 20}, {0,9}} менее чем на 1/2. Это также неэффективно по сравнению с методом Дэнди QAD, например.
whuber

1

Еще один «грязный» алгоритм для этого:

  • Возьмите ограничивающую рамку геометрии (Xmax, Ymax, Xmin, Ymin)

  • Цикл, пока ( Xmin+rand*(Xmax-Xmin), Ymin+rand*(Ymax-Ymin) ) в геометрии не будет найдена случайная точка (используя Google-Maps-Point-in-Polygon )


+1, потому что это может иметь хороший шанс попадания во второй раз. Пока ваш «случайный» воспроизводится каждый раз, чтобы не раздражать пользователя, это также является правильным решением. Вероятность того, что он не достиг верной точки, невелика, особенно если вы начнете с хорошей точки угадывания.
Денди

1
@ Денди: На самом деле, в некоторых случаях это может быть очень плохой алгоритм. Рассмотрим, например, узкую диагональную полоску. Они существуют на практике (например, длинные участки дороги) и могут легко занимать менее 0,1% ограничительной рамки (иногда гораздо меньше). Чтобы быть достаточно уверенным (на 95% уверенным) в ударе такого многоугольника с помощью этой техники, потребуется около 3000 итераций.
whuber

@ Whuber: Если вы выберете плохое начальное местоположение, да, это может занять некоторое время, чтобы дойти до завершения. Если вы также считаете, что гипотетически 95% кликов будет иметь более желательную геометрию, это может быть проблемой только в 5% случаев. Также как и в случае с другим вопросом GIS.se, если целью является производительность, единого решения никогда не бывает, лучше всего менять тактику на основе эвристики. Нет причин запускать это в течение 3000 итераций. Вы всегда можете выручить мой QAD после 10. Я думаю, что стоило бы попробовать этот в течение нескольких итераций, поскольку местоположение может быть более желательным.
Денди

@ Денди: Но что случилось с вашим решением QAD? Вы даже можете немного изменить его, перейдя от исходной пробной метки к ближайшей вершине в некотором внутреннем буфере многоугольника: все еще QAD, но теперь гарантированно приземлится на внутреннее местоположение исходного объекта. Кстати, ваша стратегия спасения скоро хороша. Всякий раз, когда я кодирую случайный зонд, подобный этому, я предварительно вычисляю отношение площади объекта к его ограничивающей рамке, использую его, чтобы найти ожидаемое время для успеха, и немедленно предупреждаю пользователя, если он может быть длинным.
whuber

@ Whuber эвристическое соотношение площадей - отличная идея, потому что вы вычисляете центр тяжести, когда вычисляете площадь. Что касается проблемы с моим решением QAD: это на грани. Если я выберу эту точку и буферизую ее, как вы говорите, этот «маленький» радиус может быть больше, чем длина в этом узком сечении. Всегда есть угловой шкаф. Так много, чтобы рассмотреть, просто сделать воздушный шар, который будет загромождать пользовательский интерфейс и затемнять геометрию в любом случае. Вероятно, лучше выбрать самую высокую или самую низкую вершину.
Денди

1

В свете вашего недавнего разъяснения, что вы бы предпочли строго внутреннее расположение, вы можете выбрать любую точку в преобразовании медиальной оси, которая также не находится на границе многоугольника. (Если у вас нет кода для MAT, вы можете аппроксимировать его отрицательной буферизацией многоугольника. Бинарный или секущий поиск быстро создаст небольшой внутренний многоугольник, который приблизительно соответствует части MAT; используйте любую точку на его границе.)


Я понимаю, что вы говорили об использовании ребра геометрии так, чтобы ребро находилось внутри интересующего вас многоугольника. Я не понимаю, как бы вы пошли на создание этого ребра / вершины. Единственное, о чем я могу думать, это создать виртуальный треугольник, пересекая перпендикулярный луч от точки интереса до отрезка, противоположного отрезку выбранной точки. Средняя точка между этими двумя точками может быть вершиной этого виртуального треугольника.
Денди

@ Денди: Это доходит до сути. Есть много способов сделать это в зависимости от того, что делает ваша ГИС изначально. Например, как только вы нашли луч, который пересекает исходный объект в наборе положительной длины, это пересечение будет несвязанным объединением отрезков. Используйте центр любого из этих сегментов. Другой способ - начать с любой точки объекта (предпочтительно около его середины, что и было выполнено вашим методом QED), создать небольшой простой многоугольник (например, квадрат) с центром в нем, пересечь его с исходным объектом, выбрать уникальный подключенный объект. Компонент ...
Whuber

(продолжение) ... содержащий начальную точку, и рекурсивно выберите центр для этого компонента. Когда ваша ГИС позволит вам зацикливаться на последовательностях вершин, описывающих границу объекта, будет доступно множество методов. Если отрицательные буферы поддерживаются, вы можете итеративно найти набор внутренних точек максимального расстояния («скелет», который является подмножеством MAT). Это немного дорого, но довольно легко программировать и дает отличные метки.
whuber

0

Почему бы не использовать центроид только для вертикального (широтного) положения? Затем вы можете расположить метку по горизонтали, выбрав среднюю долготу на этой широте . (Для этого вам нужно найти значение долготы для ребра многоугольника на определенной широте, что не должно вызывать проблем).

Кроме того, будьте осторожны с U-образными формами и более сложными. :) Возможно, для них выберите среднее значение самой правой пары долгот (каждая пара будет соответствовать срезу многоугольника), так как информационное окно ориентировано таким образом?

Это также дает вам немного больше контроля над позиционированием; например, было бы неплохо расположить информационное окно на 66 или 75% по вертикали, чтобы оставить больше видимого многоугольника. (А может и нет! Но у вас есть ручка для настройки.)


0

Как насчет того, чтобы просто использовать точку, которую щелкнул пользователь, чтобы выбрать ее, если она выбрана пользователем, который есть.


Его можно выбрать щелчком мыши или непространственным запросом, поэтому это не всегда работает.
Джейсон

0

Я тоже пытаюсь это решить. Я наложил условие на мои полигоны, чтобы они не могли пересекать линии, что входит в то, что я опишу.

Итак, мой подход использует триангуляцию. Возьмите случайную вершину (возможно, взять вершину в крайних N, E, W или S может упростить вещи).

Из этой вершины нарисуйте линии к вершине на одну вершину, т.е. если ваша вершина - вершина 3, посмотрите на вершину 3 + 2.

Постройте линию от вашей исходной вершины до этой вершины. Если построена линия:

  1. не пересекает никакой другой линии и
  2. его середина не за пределами многоугольника

Затем вы построили треугольник внутри многоугольника. Если успешной вершиной было n + 2, то ваш треугольник - {n, n + 1, n + 2}, который мы будем называть {v, v1, v2}. Если нет, попробуйте следующую вершину и продолжайте, пока все вершины не будут опробованы.

Когда вы найдете треугольник, найдите его центр, проведя линию от вершины v к средней точке v1 и v2. Середина этой линии гарантированно будет внутри треугольника и внутри многоугольника.

Я еще не закодировал это, но я могу видеть, что полигон с пересекающимися линиями фактически вызовет некоторые экзотические условия, когда это не работает. Если это тип полигонов, который у вас есть, вам нужно проверить каждый отрезок на полигоне и убедиться, что он не пересекается. Пропустите отрезанные отрезки, и я думаю, что это сработает.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.