Gere pontos que estão dentro do polígono


30

Eu tenho o recurso de polígono e quero poder gerar pontos dentro dele. Eu preciso disso para uma tarefa de classificação.

Gerar pontos aleatórios até que um esteja dentro do polígono não funcionaria porque é realmente imprevisível o tempo que leva.


3
Pelo contrário, o tempo é previsível. É proporcional à proporção da área de extensão do polígono dividida pela área do polígono, vezes o tempo necessário para gerar e testar um único ponto. O tempo varia um pouco, mas a variação é proporcional à raiz quadrada do número de pontos. Para números grandes, isso se torna inconseqüente. Divida os polígonos tortuosos em pedaços mais compactos, se necessário, para reduzir esse índice de área a um valor baixo. Apenas polígonos fractais lhe causam problemas, mas duvido que você os tenha!
whuber



@ Pablo: boas descobertas. No entanto, ambas as perguntas são específicas software e ambos preocupação colocando matrizes regulares de pontos dentro de polígonos, os pontos não aleatórios
whuber

Concordo com a diferença entre whuber e pontos aleatórios versus geração regular de pontos em um polígono.
Mapperz

Respostas:


20

Comece decompondo o polígono em triângulos e gere pontos dentro deles . (Para uma distribuição uniforme, pese cada triângulo pela sua área.)


2
+1 Simples e eficaz. Vale ressaltar que pontos aleatoriamente uniformes podem ser gerados dentro de um triângulo sem nenhuma rejeição, porque existem mapeamentos (facilmente computáveis) de preservação de área entre qualquer triângulo e um triângulo retângulo isósceles, que é meio quadrado, digamos o meio onde a coordenada y excede a coordenada x. Gere duas coordenadas aleatórias e classifique-as para obter um ponto aleatório no triângulo isósceles e depois mapeie-o de volta ao triângulo original.
whuber

+1 Gosto muito da discussão de coordenadas trilineares mencionadas no artigo que você cita. Suponho que isso seria passível de esfera cuja superfície é representada como um mosaico de triângulos. Em um plano projetado, não seria uma distribuição verdadeiramente aleatória, seria?
22611 Kirk Kuykendall

@whuber - +1 de volta para você. Outra maneira (no link, mas eles acenaram com a mão) é refletir os pontos rejeitados do quadrilátero uniformemente amostrado através da borda compartilhada e de volta ao triângulo.
Dan S.

@Kirk - o link de citação é um pouco anti-útil, pois lista vários métodos de amostragem errados (não uniformes), incluindo coordenadas trilineares, antes do caminho "certo". Não parece haver uma maneira direta de obter uma amostragem uniforme com coordenadas trilineares. Eu abordaria a amostragem uniforme em toda a esfera convertendo vetores de unidades aleatórias em 3d para seus equivalentes lat / lon, mas sou apenas eu. (. Incerto sobre amostragem restrita a esférica triângulos / polígonos) (também inseguro sobre amostragem verdadeiramente uniforme sobre, por exemplo WGS84: ângulos apenas escolher vai viés um pouco em direção aos pólos, eu acho.)
Dan S.

1
@ Dan Para amostrar uniformemente a esfera, use uma projeção cilíndrica de área igual (as coordenadas são longitude e cosseno de latitude). Se você quiser amostrar sem usar uma projeção, há um belo truque: gere três variáveis ​​normais padrão independentes (x, y, z) e projete-as no ponto (R x / n, Ry / n, R * z / n ) onde n ^ 2 = x ^ 2 + y ^ 2 + z ^ 2 e R é o raio da terra. Converta para (lat, lon), se necessário (usando latitudes autálicas ao trabalhar em um esferóide). Funciona porque essa distribuição normal trivariada é esfericamente simétrica. Para amostrar triângulos, mantenha uma projeção.
whuber

14

Ao colocar uma tag QGIS nesta pergunta: A ferramenta Random Points pode ser usada com uma camada de limite.

insira a descrição da imagem aqui

Se você estiver procurando por código, o código-fonte do plug-in subjacente deve ser útil.


1
Mesmo 5 anos depois, ainda é realmente útil!
Stranded Kid

10

Você pode determinar a extensão do polígono e restringir a geração de números aleatórios para os valores X e Y dentro dessas extensões.

Processo básico: 1) Determine maxx, maxy, minx, miny de vértices poligonais, 2) Gere pontos aleatórios usando esses valores como limites 3) Teste cada ponto para interseção com seu polígono, 4) Interrompa a geração quando houver pontos suficientes que satisfaçam a interseção teste

Aqui está um algoritmo (C #) para o teste de interseção:

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

Existem algumas boas bibliotecas por aí que fazem a maior parte do trabalho pesado para você.

Exemplo usando [shapely] [1] em 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)

Ou use .representative_point()para obter um ponto dentro do objeto (como mencionado por dain):

Retorna um ponto computado barato que é garantido estar dentro do objeto geométrico.

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

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

2
Não deveria ser da importação de shapely.geometry ...?
PyMapr 17/05/19

1
Você também pode usar o representative_pointmétodo: shapely.readthedocs.io/en/latest/…
dain

6

Se R é uma opção, veja ?spsampleno sppacote. Os polígonos podem ser lidos em qualquer formato suportado pelo GDAL embutido no pacote rgdal e, em seguida, spsampletrabalha diretamente no objeto importado com uma variedade de opções de amostragem.


+1 - Como R é de código aberto, se alguém deseja replicar, você sempre pode acessar o código-fonte para ver como eles são feitos. Para padrões de pontos, também pode estar interessado nas ferramentas de simulação no pacote spatstat.
Andy W

5

Eu gostaria de oferecer uma solução que requer muito pouco em termos de análise GIS. Em particular, ele não requer triangulação de polígonos.

O algoritmo a seguir, fornecido no pseudocódigo, refere-se a algumas operações simples, além dos recursos básicos de manipulação de lista (criar, encontrar comprimento, anexar, classificar, extrair sublistas e concatenar) e geração de flutuações aleatórias no intervalo [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.

Eles estão disponíveis em quase todos os ambientes de GIS ou de programação gráfica (e fáceis de codificar, se não). Clipnão deve retornar polígonos degenerados (ou seja, aqueles com área zero).

O procedimento SimpleRandomSampleobtém com eficiência uma lista de pontos distribuídos aleatoriamente em um polígono. É um invólucro para SRS, que quebra o polígono em pedaços menores até que cada peça seja suficientemente compacta para ser amostrada com eficiência. Para fazer isso, ele usa uma lista pré-computada de números aleatórios para decidir quantos pontos alocar para cada peça.

O SRS pode ser "ajustado" alterando o parâmetro t. Essa é a proporção máxima da caixa delimitadora: área do polígono que pode ser tolerada. Se o tamanho for pequeno (mas maior que 1), a maioria dos polígonos será dividida em várias partes; torná-lo grande pode fazer com que muitos pontos de teste sejam rejeitados em alguns polígonos (sinuosos, com lascas ou cheios de orifícios). Isso garante que o tempo máximo para amostrar o polígono original é previsível.

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

O próximo procedimento chama a si próprio recursivamente, se necessário. A expressão misteriosa t*N + 5*Sqrt(t*N)estima conservadoramente um limite superior de quantos pontos serão necessários, respondendo pela variabilidade do acaso. A probabilidade de que isso falhe é de apenas 0,3 por milhão de chamadas de procedimento. Aumente 5 para 6 ou até 7 para reduzir essa probabilidade, se quiser.

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 seu polígono é convexo e você conhece todos os vértices, considere fazer uma ponderação convexa "aleatória" dos vértices para provar um novo ponto que é garantido que fica dentro do casco convexo (neste caso, polígono).

Por exemplo, digamos que você tenha um polígono convexo em N lados com vértices

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

Gere aleatoriamente pesos N convexos

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

O ponto amostrado aleatoriamente é então dado por

Y=  w_i*V_i

Pode haver uma maneira diferente de amostrar pesos convexos N

  • Escolha números N-1 uniformemente aleatoriamente dentro de um intervalo (sem substituição), ordene-os e normalize os intervalos N entre eles para obter os pesos.
  • Você também pode obter amostras da distribuição Dirichlet, que é frequentemente usada como um conjugado anterior para a distribuição multinomial, que é semelhante aos pesos convexos no seu caso.

Quando o polígono não é muito severamente não convexo, considere primeiro convertê-lo em um casco convexo. Isso deve pelo menos limitar o número de pontos fora do polígono em grande parte.


2

A tarefa é muito fácil de resolver no GRASS GIS (um comando) usando o v.random .

Abaixo um exemplo de como adicionar 3 pontos aleatórios em polígonos selecionados (aqui áreas de CEP da cidade de Raleigh, NC) na página de manual. Modificando a instrução "where" do SQL, os polígonos podem ser selecionados.

Gerando pontos aleatórios em polígonos selecionados


1
Lembrete obrigatório de que códigos postais são linhas, não polígonos.
Richard

Você pode elaborar? Para mim, também aqui, refere-se a áreas: en.wikipedia.org/wiki/ZIP_Code#Primary_state_prefixes
markusN 12-18

Claro: os códigos postais fazem referência a agências postais específicas e suas rotas de entrega de correio. Como resultado, os códigos postais são linhas, não polígonos. Eles podem se sobrepor, conter buracos e não necessariamente cobrir todo o país ou qualquer estado. Usá-los para dividir a área é perigoso por esse motivo. As unidades de censo (como grupos de blocos) são uma escolha melhor. Veja também: isto e isto .
Richard

1
Obrigado! Provavelmente também depende do país; veja, por exemplo, en.wikipedia.org/wiki/Postal_codes_in_Alemanha - no entanto, os CEPs não são o meu tópico principal, apenas querem ilustrar e responder à pergunta original "Gere pontos que estão dentro do polígono" em vez de discutir ZIP definições de código que é OT aqui :-)
markusN

1
Observado em ambas as contagens. Eu provavelmente deveria fazer um pouco de entrada de blog para que eu possa dizer isto na próxima vez tudo de forma mais sucinta :-)
Richard

1

Link de resposta

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

Três algoritmos usando abordagens diferentes.

Git Repo Link

  1. Aqui está uma abordagem simples e melhor, usando a distância real das coordenadas da direção xey. O algoritmo interno usa o WGS 1984 (4326) e a transformação de resultado no SRID inserido.

Função =================================================== ==================

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;

Use a função com uma consulta simples, a geometria deve ser válida e polígono, multi-polígonos ou envelope

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

Resultado =================================================== ===================== insira a descrição da imagem aqui

  1. Segunda função baseada no algoritmo Nicklas Avén . Já tentei lidar com qualquer SRID.

    Eu apliquei após as alterações no algoritmo.

    1. Variável separada para direção x e y para tamanho de pixel,
    2. Nova variável para calcula a distância em esferóide ou elipsóide.
    3. Insira qualquer SRID, função transforme Geom no ambiente de trabalho do Spheroid ou Ellipsoid Datum, aplique a distância em cada lado, obtenha o resultado e transforme na entrada SRID.

Função =================================================== ==================

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;

Use-o com uma consulta simples.

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

Resultado =================================================== ==================insira a descrição da imagem aqui

  1. Função baseada em gerador de séries.

Função =================================================== =================

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;

Use-o com uma consulta simples.

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

insira a descrição da imagem aqui

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.