Утворіть точки, що лежать всередині багатокутника


30

У мене є функція багатокутника і хочу, щоб я міг генерувати очки всередині неї. Мені це потрібно для одного завдання класифікації.

Генерування випадкових точок, поки одна не знаходиться всередині полігону, не буде працювати, оскільки це дійсно непередбачувано час, який потрібно.


3
Навпаки, час передбачуваний. Вона пропорційна відношенню площі міри багатокутника, поділеної на площу полігону, в рази більше часу, необхідного для формування та випробування однієї точки. Час трохи варіюється, але варіація пропорційна квадратному кореню кількості точок. Для великого числа це стає несуттєвим. Розбийте звивисті багатокутники на більш компактні шматочки, якщо необхідно, щоб зменшити це співвідношення площі до низького значення. Тільки фрактальні багатокутники будуть доставляти вам клопоти, але я сумніваюся, у вас їх є!
whuber



@Pablo: хороші знахідки. Однак обидва ці питання стосуються програмного забезпечення, і обидва стосуються розміщення регулярних масивів точок у полігонах, а не випадкових точок
whuber

Погодьтеся з великою різницею - випадкові точки проти регулярного генерації точок у полігоні.
Mapperz

Відповіді:


20

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


2
+1 Простий та ефективний. Варто зазначити, що рівномірно випадкові точки можна генерувати всередині трикутника без відхилення взагалі, тому що між будь-яким трикутником та правильним рівнобедреним трикутником є ​​(легко обчислені) відображення, що становить половину квадрата, скажімо половина, де координата y перевищує координату x. Створіть дві випадкові координати та відсортуйте їх, щоб отримати випадкову точку в рівнобедреному трикутнику, а потім перенесіть на початковий трикутник.
whuber

+1 Мені дуже подобається обговорення трилінійних координат, на які посилається стаття, яку ви цитуєте. Я припускаю, що це було б придатним до сфери, поверхня якої представлена ​​як тесселяція трикутників. На проектованій площині це не було б справді випадковим розподілом, чи не так?
Кірк Куйкендалл

@whuber - +1 до тебе. Інший спосіб (за посиланням, але вони махнули рукою над ним) - відобразити відхилені точки з рівномірно відібраного чотирикутника через спільний край і назад у трикутник.
Дан С.

@Kirk - посилання на посилання є дотиком, що допомагає в тому, що в ньому перелічено купу неправильних (нерівномірних) методів вибірки, включаючи трилінійні координати, перед "правильним" способом. Не схоже, що існує прямий спосіб отримати рівномірну вибірку з трилінійними координатами. Я б підійшов до рівномірного відбору проб по всій сфері, перетворивши випадкові одиничні вектори в 3d в їх еквівалент lat / lon, але це тільки я. (Не впевнені в вибірці, обмеженій сферичними трикутниками / багатокутниками.) (Також не впевнені в дійсно рівномірному відборі проб, наприклад, wgs84: я думаю, що просто кути нахилу трохи змістяться до полюсів.)
Dan S.

1
@Dan Для рівномірного відбору проб сфери використовуйте циліндричну проекцію на рівну площу (координати - довгота та косинус широти). Якщо ви хочете зробити вибірку без використання проекції, є прекрасна хитрість: генеруйте три незалежні стандартні звичайні змінні (x, y, z) та запроектуйте їх у точку (R x / n, R y / n, R * z / n ) де n ^ 2 = x ^ 2 + y ^ 2 + z ^ 2 і R - радіус землі. Перетворити на (лат., Лон), якщо потрібно (використовуючи авталійські широти під час роботи над сфероїдом). Це працює тому, що цей триваріантний нормальний розподіл сферично симетричний. Для вибірки трикутників дотримуйтесь проекції.
whuber

14

Коли ви ставите тег QGIS в цьому питанні: інструмент «Довільні точки» може використовуватися з межовим шаром.

введіть тут опис зображення

Якщо ви шукаєте код, базовий вихідний код плагіна повинен допомогти.


1
Навіть через 5 років, все ще дуже корисно!
Набитий малюк

10

Ви можете визначити ступінь багатокутника, а потім обмежити генерацію випадкових чисел для значень X та Y в межах цих відрізків.

Основний процес: 1) Визначте maxx, maxy, minx, miny багатокутних вершин, 2) Створіть випадкові точки, використовуючи ці значення як межі 3) Перевірте кожну точку перетину з вашим багатокутником, 4) Перестаньте генерувати, коли у вас достатньо точок, що задовольняють перетину тест

Ось алгоритм (C #) для тесту перетину:

bool PointIsInGeometry(PointCollection points, MapPoint point)
{
int i;
int j = points.Count - 1;
bool output = false;

for (i = 0; i < points.Count; i++)
{
    if (points[i].X < point.X && points[j].X >= point.X || points[j].X < point.X && points[i].X >= point.X)
    {
        if (points[i].Y + (point.X - points[i].X) / (points[j].X - points[i].X) * (points[j].Y - points[i].Y) < point.Y)
        {
            output = !output;
        }
    }
    j = i;
}
return output;
}

10

Є кілька хороших бібліотек, які роблять більшу частину важкого підйому для вас.

Приклад з використанням [shapely] [1] в python.

import random
from shapely.geometry import Polygon, Point

def get_random_point_in_polygon(poly):
     minx, miny, maxx, maxy = poly.bounds
     while True:
         p = Point(random.uniform(minx, maxx), random.uniform(miny, maxy))
         if poly.contains(p):
             return p

p = Polygon([(0, 0), (0, 2), (1, 1), (2, 2), (2, 0), (1, 1), (0, 0)])
point_in_poly = get_random_point_in_polygon(mypoly)

Або скористайтеся, .representative_point()щоб отримати точку в об'єкті (як згадує dain):

Повертає дешево обчислену точку, яка гарантовано знаходиться в межах геометричного об'єкта.

poly.representative_point().wkt
'POINT (-1.5000000000000000 0.0000000000000000)'

  [1]: https://shapely.readthedocs.io

2
Чи не повинно бути з імпорту shapely.geometry ...?
PyMapr

1
Можна також використовувати representative_pointметод: shapely.readthedocs.io/en/latest / ...
Дейн

6

Якщо R - опція, дивіться ?spsampleв spупаковці. Полігони можна читати в будь-якому підтримуваному GDAL форматі, вбудованому в пакет rgdal, а потім spsampleпрацює безпосередньо на імпортованому об'єкті з різними параметрами вибірки.


+1 - Оскільки R є відкритим кодом, якщо хочеться повторити, ви завжди можете зайти в джерело, щоб побачити, як вони робляться. Для точкових моделей також можуть бути зацікавлені інструменти моделювання в пакеті spatstat.
Енді Ш

5

Я хотів би запропонувати рішення, яке потребує дуже мало з точки зору ГІС-аналізу. Зокрема, для цього не потрібно тріангулювання будь-яких багатокутників.

Наступний алгоритм, наведений у псевдокоді, стосується деяких простих операцій на додаток до основних можливостей обробки списку (створювати, знаходити довжину, додавати, сортувати, витягувати підсписи та з'єднувати) та генерацію випадкових плавців в інтервалі [0, 1):

Area:        Return the area of a polygon (0 for an empty polygon).
BoundingBox: Return the bounding box (extent) of a polygon.
Width:       Return the width of a rectangle.
Height:      Return the height of a rectangle.
Left:        Split a rectangle into two halves and return the left half.
Right:       ... returning the right half.
Top:         ... returning the top half.
Bottom:      ... returning the bottom half.
Clip:        Clip a polygon to a rectangle.
RandomPoint: Return a random point in a rectangle.
Search:      Search a sorted list for a target value.  Return the index  
             of the last element less than the target.
In:          Test whether a point is inside a polygon.

Вони доступні майже в будь-якому середовищі програмування ГІС чи графіки (і легко їх кодувати, якщо ні). Clipне повинні повертати вироджені багатокутники (тобто ті, що мають нульову площу).

Процедура SimpleRandomSampleефективно отримує список точок, випадковим чином розподілених у межах багатокутника. Це обгортка SRS, яка розбиває багатокутник на більш дрібні шматочки до тих пір, поки кожен шматок не стане достатньо компактним для його ефективного відбору. Для цього він використовує попередньо обчислений список випадкових чисел, щоб вирішити, скільки балів виділити кожному твору.

SRS можна "настроїти", змінивши параметр t. Це максимальне обмеження коробки: співвідношення площі полігона, яке можна допустити. Якщо зробити його невеликим (але більшим, ніж 1), це призведе до розбиття більшості полігонів на багато частин; зробивши його великим, це може призвести до відхилення багатьох пробних балів для деяких багатокутників (синусоподібних, із повзунками або повних дірок). Це гарантує, що максимальний час для вибірки оригінального багатокутника передбачуваний.

Procedure SimpleRandomSample(P:Polygon, N:Integer) {
    U = Sorted list of N independent uniform values between 0 and 1
    Return SRS(P, BoundingBox(P), U)
}

Наступна процедура вимагає себе рекурсивно, якщо необхідно. Таємничий вираз t*N + 5*Sqrt(t*N)консервативно оцінює верхню межу, скільки балів знадобиться, враховуючи мінливість випадкових випадків. Ймовірність, що це не вдасться, становить лише 0,3 на мільйон процедурних дзвінків. Збільшити 5 до 6 або навіть 7, щоб зменшити цю ймовірність, якщо хочете.

Procedure SRS(P:Polygon, B:Rectangle, U:List) {
    N = Length(U)
    If (N == 0) {Return empty list}
    aP = Area(P)
    If (aP <= 0) {
        Error("Cannot sample degenerate polygons.")
        Return empty list
    }
    t = 2
    If (aP*t < Area(B)) {
        # Cut P into pieces
        If (Width(B) > Height(B)) {
            B1 = Left(B); B2 = Right(B)
        } Else {
            B1 = Bottom(B); B2 = Top(B)
        }
        P1 = Clip(P, B1); P2 = Clip(P, B2)
        K = Search(U, Area(P1) / aP)
        V = Concatenate( SRS(P1, B1, U[1::K]), SRS(P2, B2, U[K+1::N]) )
    } Else {
        # Sample P
        V = empty list
        maxIter = t*N + 5*Sqrt(t*N)
        While(Length(V) < N and maxIter > 0) {
            Decrement maxIter
            Q = RandomPoint(B)
            If (Q In P) {Append Q to V}
        }
       If (Length(V) < N) {
            Error("Too many iterations.")
       }
    }
    Return V
}

2

Якщо ваш багатокутник випуклий і ви знаєте всі вершини, ви можете розглянути можливість «випадкового» опуклого зважування вершин для вибірки нової точки, яка гарантовано лежить всередині опуклого корпусу (полігон у цьому випадку).

Наприклад, скажімо, у вас N-сторонній опуклий багатокутник з вершинами

V_i, i={1,..,N}

Потім генерують випадковим чином N опуклих ваг

 w_1,w_2,..,w_N such that  w_i = 1; w_i>=0

Точку вибіркової вибірки дається через

Y=  w_i*V_i

Існує різний спосіб вибірки N опуклих ваг

  • Виберіть числа N-1 рівномірно випадковим чином в межах діапазону (без заміни), відсортуйте їх і нормалізуйте N інтервалів між ними, щоб отримати ваги.
  • Ви також можете взяти вибірку з розподілу Діріхле, який часто використовується як кон'югат перед мультиноміальним розподілом, який подібний до опуклих ваг у вашому випадку.

Коли ваш багатокутник не дуже сильно невипуклий, ви можете спершу перетворити його на опуклий корпус. Це має принаймні обмежити кількість точок, що лежать поза вашим полігоном, значною мірою.


2

Завдання дуже легко вирішити в GRASS GIS (одна команда) за допомогою v.random .

Нижче наведено приклад того, як додати 3 випадкові точки до вибраних полігонів (тут області поштового індексу міста Релі, штат Північна Кароліна) зі сторінки керівництва. Змінивши оператор SQL "where", можна вибрати багатокутники.

Генерація випадкових точок у виділених многокутниках


1
Обов’язкове нагадування, що поштові індекси - це лінії, а не багатокутники.
Річард

Чи можете ви докладно? На мене також тут йдеться про райони: en.wikipedia.org/wiki/ZIP_Code#Primary_state_prefixes
markusN

Звичайно: поштові індекси посилаються на конкретні поштові відділення та шляхи доставки пошти. Як результат, поштові індекси - це рядки, а не багатокутники. Вони можуть перекриватися один з одним, містити отвори і не обов'язково охоплювати весь США або будь-який даний стан. Використання їх для поділу площі небезпечно з цієї причини. Переписні одиниці (як блок-групи) - кращий вибір. Дивіться також: це і це .
Річард 3

1
Спасибі! Це, мабуть, також залежить від країни, див., Наприклад, en.wikipedia.org/wiki/Postal_codes_in_Germany - однак, поштові індекси не є моєю основною темою, я просто хотів проілюструвати та відповісти на оригінальне запитання "Створити точки, що лежать всередині полігону", а не обговоріть визначення поштового індексу, який є ОТ тут :-)
markusN

1
Помічено в обох положеннях. Я, мабуть, мушу зробити невеликий запис у блозі, щоб наступного разу сказати це все кращіше :-)
Річард

1

Посилання для відповідей

/gis//a/307204/103524

Три алгоритми, що використовують різні підходи.

Git Repo Link

  1. Ось простий і найкращий підхід, використовуючи фактичну відстань координат від х і у напрямку. Внутрішній алгоритм використовує WGS 1984 (4326) і перетворює результат у вставлений SRID.

Функція ===================================================== ===================

CREATE OR REPLACE FUNCTION public.I_Grid_Point_Distance(geom public.geometry, x_side decimal, y_side decimal)
RETURNS public.geometry AS $BODY$
DECLARE
x_min decimal;
x_max decimal;
y_max decimal;
x decimal;
y decimal;
returnGeom public.geometry[];
i integer := -1;
srid integer := 4326;
input_srid integer;
BEGIN
CASE st_srid(geom) WHEN 0 THEN
    geom := ST_SetSRID(geom, srid);
        ----RAISE NOTICE 'No SRID Found.';
    ELSE
        ----RAISE NOTICE 'SRID Found.';
END CASE;
    input_srid:=st_srid(geom);
    geom := st_transform(geom, srid);
    x_min := ST_XMin(geom);
    x_max := ST_XMax(geom);
    y_max := ST_YMax(geom);
    y := ST_YMin(geom);
    x := x_min;
    i := i + 1;
    returnGeom[i] := st_setsrid(ST_MakePoint(x, y), srid);
<<yloop>>
LOOP
IF (y > y_max) THEN
    EXIT;
END IF;

CASE i WHEN 0 THEN 
    y := ST_Y(returnGeom[0]);
ELSE 
    y := ST_Y(ST_Project(st_setsrid(ST_MakePoint(x, y), srid), y_side, radians(0))::geometry);
END CASE;

x := x_min;
<<xloop>>
LOOP
  IF (x > x_max) THEN
      EXIT;
  END IF;
    i := i + 1;
    returnGeom[i] := st_setsrid(ST_MakePoint(x, y), srid);
    x := ST_X(ST_Project(st_setsrid(ST_MakePoint(x, y), srid), x_side, radians(90))::geometry);
END LOOP xloop;
END LOOP yloop;
RETURN
ST_CollectionExtract(st_transform(ST_Intersection(st_collect(returnGeom), geom), input_srid), 1);
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE;

Використовуйте функцію за допомогою простого запиту, геометрія повинна бути дійсною і багатокутник, багатокутник або конверт

SELECT I_Grid_Point_Distance(geom, 50, 61) from polygons limit 1;

Результат ===================================================== ====================== введіть тут опис зображення

  1. Друга функція, заснована на алгоритмі Нікласа Авена . Спробували обробити будь-який SRID.

    Я застосував наступні зміни в алгоритмі.

    1. Роздільна змінна для x і y напрямку для розміру пікселів,
    2. Нова змінна для обчислення відстані в сфероїді або еліпсоїді.
    3. Введіть будь-яку SRID, функцію перетворення Geom в робоче середовище сфероїда або еліпсоїдальної дати, потім застосуйте відстань до кожної сторони, отримайте результат і перетворіть на вхід SRID.

Функція ===================================================== ===================

CREATE OR REPLACE FUNCTION I_Grid_Point(geom geometry, x_side decimal, y_side decimal, spheroid boolean default false)
RETURNS SETOF geometry AS $BODY$ 
DECLARE
x_max decimal; 
y_max decimal;
x_min decimal;
y_min decimal;
srid integer := 4326;
input_srid integer; 
BEGIN
CASE st_srid(geom) WHEN 0 THEN
  geom := ST_SetSRID(geom, srid);
  RAISE NOTICE 'SRID Not Found.';
    ELSE
        RAISE NOTICE 'SRID Found.';
    END CASE;

    CASE spheroid WHEN false THEN
        RAISE NOTICE 'Spheroid False';
        srid := 4326;
        x_side := x_side / 100000;
        y_side := y_side / 100000;
    else
        srid := 900913;
        RAISE NOTICE 'Spheroid True';
    END CASE;
    input_srid:=st_srid(geom);
    geom := st_transform(geom, srid);
    x_max := ST_XMax(geom);
    y_max := ST_YMax(geom);
    x_min := ST_XMin(geom);
    y_min := ST_YMin(geom);
RETURN QUERY
WITH res as (SELECT ST_SetSRID(ST_MakePoint(x, y), srid) point FROM
generate_series(x_min, x_max, x_side) as x,
generate_series(y_min, y_max, y_side) as y
WHERE st_intersects(geom, ST_SetSRID(ST_MakePoint(x, y), srid))
) select ST_TRANSFORM(ST_COLLECT(point), input_srid) from res;
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE STRICT;

Використовуйте його за допомогою простого запиту.

SELECT I_Grid_Point(geom, 22, 15, false) from polygons;

Результат ===================================================== ===================введіть тут опис зображення

  1. Функція заснована на генераторі серій.

Функція ===================================================== =================

CREATE OR REPLACE FUNCTION I_Grid_Point_Series(geom geometry, x_side decimal, y_side decimal, spheroid boolean default false)
RETURNS SETOF geometry AS $BODY$
DECLARE
x_max decimal;
y_max decimal;
x_min decimal;
y_min decimal;
srid integer := 4326;
input_srid integer;
x_series DECIMAL;
y_series DECIMAL;
BEGIN
CASE st_srid(geom) WHEN 0 THEN
  geom := ST_SetSRID(geom, srid);
  RAISE NOTICE 'SRID Not Found.';
    ELSE
        RAISE NOTICE 'SRID Found.';
    END CASE;

    CASE spheroid WHEN false THEN
        RAISE NOTICE 'Spheroid False';
    else
        srid := 900913;
        RAISE NOTICE 'Spheroid True';
    END CASE;
    input_srid:=st_srid(geom);
    geom := st_transform(geom, srid);
    x_max := ST_XMax(geom);
    y_max := ST_YMax(geom);
    x_min := ST_XMin(geom);
    y_min := ST_YMin(geom);

    x_series := CEIL ( @( x_max - x_min ) / x_side);
    y_series := CEIL ( @( y_max - y_min ) / y_side );
RETURN QUERY
SELECT st_collect(st_setsrid(ST_MakePoint(x * x_side + x_min, y*y_side + y_min), srid)) FROM
generate_series(0, x_series) as x,
generate_series(0, y_series) as y
WHERE st_intersects(st_setsrid(ST_MakePoint(x*x_side + x_min, y*y_side + y_min), srid), geom);
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE STRICT;

Використовуйте його за допомогою простого запиту.

SELECT I_Grid_Point_Series(geom, 22, 15, false) from polygons; Результат ===================================================== ==========================

введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.