Genera punti che si trovano all'interno del poligono


30

Ho la funzione poligono e voglio essere in grado di generare punti al suo interno. Ne ho bisogno per un compito di classificazione.

Generare punti casuali finché uno non è all'interno del poligono non funzionerebbe perché è davvero imprevedibile il tempo impiegato.


3
Al contrario, il tempo è prevedibile. È proporzionale al rapporto dell'area di estensione del poligono diviso per l'area del poligono, moltiplicato per il tempo necessario per generare e testare un singolo punto. Il tempo varia leggermente, ma la variazione è proporzionale alla radice quadrata del numero di punti. Per i numeri più grandi, questo diventa irrilevante. Rompi i poligoni tortuosi in pezzi più compatti se necessario per ridurre questo rapporto di area a un valore basso. Solo i poligoni frattali ti daranno problemi, ma dubito che tu li abbia!
whuber



@Pablo: buone scoperte. Tuttavia, entrambe queste domande sono specifiche del software ed entrambe riguardano il posizionamento di matrici regolari di punti all'interno di poligoni, non punti casuali
whuber

concordare con la differenza whuber è punti casuali rispetto alla generazione di punti regolari all'interno di un poligono.
Mapperz

Risposte:


20

Inizia decomponendo il poligono in triangoli, quindi genera punti all'interno di quelli . (Per una distribuzione uniforme, ponderare ciascun triangolo in base alla sua area.)


2
+1 Semplice ed efficace. Vale la pena sottolineare che all'interno di un triangolo possono essere generati punti uniformemente casuali senza alcun rifiuto, perché ci sono mappature (facilmente calcolabili) che preservano l'area tra qualsiasi triangolo e un triangolo rettangolo isoscele, che è mezzo quadrato, diciamo la metà in cui la coordinata y supera la coordinata x. Genera due coordinate casuali e ordinale per ottenere un punto casuale nel triangolo isoscele, quindi mappalo al triangolo originale.
whuber

+1 Mi piace molto la discussione sulle coordinate trilineari a cui fa riferimento l'articolo che citi. Suppongo che ciò sarebbe suscettibile alla sfera la cui superficie è rappresentata come una tassellatura di triangoli. Su un piano proiettato, non sarebbe una distribuzione davvero casuale, vero?
Kirk Kuykendall,

@whuber - +1 su di te. Un altro modo (nel collegamento, ma ci hanno agitato la mano sopra) è quello di riflettere i punti rifiutati dal quadrilatero uniformemente campionato attraverso il bordo condiviso e di nuovo nel triangolo.
Dan S.

@Kirk - il link di citazione è un tocco anti-utile in quanto elenca un sacco di metodi di campionamento errati (non uniformi), comprese le coordinate trilineari, prima del modo "giusto". Non sembra che ci sia un modo diretto per ottenere un campionamento uniforme con coordinate trilineari. Mi avvicinerei al campionamento uniforme su tutta la sfera convertendo i vettori di unità casuali in 3d nel loro equivalente lat / lon, ma sono solo io. (. Dubbi campionamento costretto a sferica triangoli / poligoni) (anche sicuri di campionamento veramente uniforme su esempio WGS84: angoli appena raccolta sarà pregiudizi un po 'verso i poli, credo.)
Dan S.

1
@Dan Per campionare uniformemente la sfera, utilizzare una proiezione cilindrica di uguale area (le coordinate sono longitudine e coseno della latitudine). Se vuoi campionare senza usare una proiezione, c'è un bel trucco: generare tre variate normali standard indipendenti (x, y, z) e proiettarle sul punto (R x / n, R y / n, R * z / n ) dove n ^ 2 = x ^ 2 + y ^ 2 + z ^ 2 e R è il raggio terrestre. Converti in (lat, lon) se necessario (usando latitudini autiche quando si lavora su uno sferoide). Funziona perché questa banale distribuzione normale è sfericamente simmetrica. Per i triangoli di campionamento, attenersi a una proiezione.
whuber

14

Quando si inserisce un tag QGIS su questa domanda: lo strumento Punti casuali può essere utilizzato con un livello limite.

inserisci qui la descrizione dell'immagine

Se stai cercando il codice, il codice sorgente del plugin sottostante dovrebbe essere di aiuto.


1
Anche 5 anni dopo, ancora molto utile!
Stranded Kid,

10

È possibile determinare l'estensione del poligono, quindi vincolare la generazione di numeri casuali per i valori X e Y all'interno di tali estensioni.

Processo di base: 1) Determina maxx, maxy, minx, miny di poligoni vertici, 2) Genera punti casuali usando questi valori come limiti 3) Prova ogni punto per l'intersezione con il poligono, 4) Smetti di generare quando hai abbastanza punti che soddisfano l'intersezione test

Ecco un algoritmo (C #) per il test di intersezione:

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

Ci sono alcune buone librerie là fuori che fanno la maggior parte del lavoro pesante per te.

Esempio usando [ben fatto] [1] in 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)

Oppure usa .representative_point()per ottenere un punto all'interno dell'oggetto (come menzionato da dain):

Restituisce un punto calcolato a buon mercato che è garantito per essere all'interno dell'oggetto geometrico.

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

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

2
Non dovrebbe provenire dall'importazione shapely.geometry ...?
PyMapr,

1
Puoi anche usare il representative_pointmetodo: shapely.readthedocs.io/en/latest/…
dain

6

Se R è un'opzione, vedere ?spsamplenel sppacchetto. I poligoni possono essere letti da qualsiasi formato supportato da GDAL integrato nel pacchetto rgdal e quindi spsamplefunzionano direttamente sull'oggetto importato con una varietà di opzioni di campionamento.


+1 - Poiché R è open source se si desidera replicare, è sempre possibile accedere al codice sorgente per vedere come vengono eseguiti. Per i modelli di punti si potrebbe anche essere interessati agli strumenti di simulazione nel pacchetto spatstat.
Andy W,

5

Vorrei offrire una soluzione che richiede pochissimo in termini di analisi GIS. In particolare, non richiede la triangolazione di alcun poligono.

Il seguente algoritmo, fornito in pseudocodice, fa riferimento ad alcune semplici operazioni oltre alle funzionalità di base per la gestione degli elenchi (creare, trovare lunghezza, aggiungere, ordinare, estrarre elenchi e concatenare) e la generazione di float casuali nell'intervallo [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.

Questi sono tutti disponibili in quasi tutti gli ambienti di programmazione grafica o GIS (e facile da codificare se non). Clipnon deve restituire poligoni degeneri (ovvero quelli con area zero).

La procedura SimpleRandomSampleottiene in modo efficiente un elenco di punti distribuiti casualmente all'interno di un poligono. È un involucro per SRS, che rompe il poligono in pezzi più piccoli fino a quando ogni pezzo è sufficientemente compatto per essere campionato in modo efficiente. Per fare ciò, utilizza un elenco precompilato di numeri casuali per decidere quanti punti assegnare a ciascun pezzo.

SRS può essere "sintonizzato" modificando il parametro t. Questa è la casella di delimitazione massima: rapporto area poligonale che può essere tollerato. Rendendolo piccolo (ma maggiore di 1), la maggior parte dei poligoni verrà suddivisa in molti pezzi; aumentandolo può causare il rifiuto di molti punti di prova per alcuni poligoni (sinuosi, con schegge o pieni di buchi). Ciò garantisce che il tempo massimo per campionare il poligono originale sia prevedibile.

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

La procedura successiva si chiama ricorsivamente se necessario. L'espressione misteriosa t*N + 5*Sqrt(t*N)stima prudentemente un limite superiore su quanti punti saranno necessari, tenendo conto della variabilità casuale. La probabilità che ciò fallisca è solo dello 0,3 per milione di chiamate di procedura. Aumenta da 5 a 6 o anche 7 per ridurre questa probabilità, se lo desideri.

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

Se il tuo poligono è convesso e conosci tutti i vertici, potresti prendere in considerazione la possibilità di fare una ponderazione convessa "casuale" dei vertici per campionare un nuovo punto che è garantito trovarsi all'interno dello scafo convesso (poligono in questo caso).

Ad esempio, supponiamo che tu abbia un poligono convesso a N con vertici

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

Quindi generare casualmente N pesi convessi

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

Il punto campionato casualmente viene quindi indicato da

Y=  w_i*V_i

Può esserci un modo diverso di campionare pesi N convessi

  • Scegli i numeri N-1 in modo uniforme in un intervallo (senza sostituzione), ordinali e normalizza gli intervalli N tra di loro per ottenere i pesi.
  • Puoi anche campionare dalla distribuzione di Dirichlet che viene spesso usata come coniugato prima della distribuzione multinomiale, che è simile ai pesi convessi nel tuo caso.

Quando il tuo poligono non è molto gravemente non convesso, potresti considerare prima di convertirlo in uno scafo convesso. Questo dovrebbe almeno limitare il numero di punti che si trovano all'esterno del poligono in larga misura.


2

Il compito è molto semplice da risolvere in GRASS GIS (un comando) usando v.random .

Di seguito un esempio su come aggiungere 3 punti casuali nei poligoni selezionati (qui aree di codice postale della città di Raleigh, NC) dalla pagina del manuale. Modificando l'istruzione SQL "where" è possibile selezionare i poligoni.

Generazione di punti casuali in poligoni selezionati


1
Promemoria obbligatorio che i codici postali sono linee, non poligoni.
Richard,

Puoi elaborare? Anche a me qui si riferisce alle aree: en.wikipedia.org/wiki/ZIP_Code#Primary_state_prefixes
markusN

Certo: i codici postali fanno riferimento a particolari uffici postali e ai loro percorsi di consegna della posta. Di conseguenza, i codici postali sono linee, non poligoni. Possono sovrapporsi tra loro, contenere buchi e non necessariamente coprire tutti gli Stati Uniti o un dato stato. Usarli per dividere l'area è pericoloso per questo motivo. Le unità di censimento (come i gruppi di blocchi) sono una scelta migliore. Vedi anche: questo e questo .
Richard,

1
Grazie! Probabilmente dipende anche dal paese, vedi ad esempio en.wikipedia.org/wiki/Postal_codes_in_Germania - tuttavia, i codici postali non sono il mio argomento principale, volevo solo illustrare e rispondere alla domanda originale "Genera punti che giacciono all'interno del poligono" piuttosto che discutere le definizioni del codice postale che è OT qui :-)
markusN

1
Notato su entrambi i punti. Probabilmente dovrei fare un piccolo post sul blog in modo da poterlo dire più succintamente la prossima volta :-)
Richard

1

Link di risposta

https://gis.stackexchange.com/a/307204/103524

Tre algoritmi che utilizzano approcci diversi.

Git Repo Link

  1. Ecco un approccio semplice e migliore, utilizzando la distanza effettiva delle coordinate dalla direzione xey. L'algoritmo interno utilizza WGS 1984 (4326) e il risultato si trasforma in SRID inserito.

Funzione ================================================= ==================

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;

Utilizzare la funzione con una query semplice, la geometria deve essere valida e poligono, poligoni multipli o inviluppo

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

Risultato ================================================= ===================== inserisci qui la descrizione dell'immagine

  1. Seconda funzione basata sull'algoritmo Nicklas Avén . Ho provato a gestire qualsiasi SRID.

    Ho applicato le seguenti modifiche all'algoritmo.

    1. Variabile separata per la direzione xey per la dimensione dei pixel,
    2. Nuova variabile per il calcolo della distanza in sferoide o ellissoide.
    3. Immettere qualsiasi SRID, la funzione trasforma Geom nell'ambiente di lavoro di Spheroid o Ellipsoid Datum, quindi applica la distanza su ciascun lato, ottiene il risultato e trasforma in input SRID.

Funzione ================================================= ==================

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;

Usalo con una semplice query.

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

Risultato ================================================= ==================inserisci qui la descrizione dell'immagine

  1. Funzione basata sul generatore di serie.

funzione ================================================= =================

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;

Usalo con una semplice query.

SELECT I_Grid_Point_Series(geom, 22, 15, false) from polygons; Risultato ================================================= =========================

inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.