Créer une grille de polygones régulière dans PostGIS?

61

Comment créer, sur la forme d'un polygone, une grille régulière de polygones / carrés d'une taille donnée, dans postgis?

J'ai pensé à une fonction du type Comment créer une grille de points régulière dans un polygone dans Postgis? uniquement pour les carrés, les carrés pouvant mesurer 5 mx 5 m ou même 10 mx 10 m. Mais vous n'avez aucune idée de changer cela correctement.

mk.archaeo
la source
2
La généralisation que vous recherchez n'est pas claire. Voulez-vous dire que vous commencez avec un seul polygone (arbitraire) et souhaitez paver le plan avec des copies congruentes de celui-ci? En général, cela n’est pas possible, mais ce polygone a peut-être des propriétés particulières (par exemple, il est connu pour être un parallélogramme, un triangle ou un hexagone).
whuber

Réponses:

60

Voici une fonction de retour d'ensemble ST_CreateFishnetqui crée une grille 2D de géométries de polygones:

CREATE OR REPLACE FUNCTION ST_CreateFishnet(
        nrow integer, ncol integer,
        xsize float8, ysize float8,
        x0 float8 DEFAULT 0, y0 float8 DEFAULT 0,
        OUT "row" integer, OUT col integer,
        OUT geom geometry)
    RETURNS SETOF record AS
$$
SELECT i + 1 AS row, j + 1 AS col, ST_Translate(cell, j * $3 + $5, i * $4 + $6) AS geom
FROM generate_series(0, $1 - 1) AS i,
     generate_series(0, $2 - 1) AS j,
(
SELECT ('POLYGON((0 0, 0 '||$4||', '||$3||' '||$4||', '||$3||' 0,0 0))')::geometry AS cell
) AS foo;
$$ LANGUAGE sql IMMUTABLE STRICT;

nrowet ncolsont le nombre de lignes et de colonnes, xsizeet ysizesont les longueurs de la taille de la cellule, et sont facultatives x0et y0sont les coordonnées du coin inférieur gauche.

Le résultat est rowet des colchiffres, à partir de 1 dans le coin inférieur gauche, et des geompolygones rectangulaires pour chaque cellule. Donc par exemple:

SELECT *
FROM ST_CreateFishnet(4, 6, 10, 10) AS cells;
 row | col |         geom
-----+-----+--------------------------------
   1 |   1 | 0103000000010000000500000000...
   2 |   1 | 0103000000010000000500000000...
   3 |   1 | 0103000000010000000500000000...
   4 |   1 | 0103000000010000000500000000...
   1 |   2 | 0103000000010000000500000000...
   2 |   2 | 0103000000010000000500000000...
   ...
   3 |   6 | 0103000000010000000500000000...
   4 |   6 | 0103000000010000000500000000...
(24 rows)

Ou pour créer une seule collection de géométrie pour la grille complète:

SELECT ST_Collect(cells.geom)
FROM ST_CreateFishnet(4, 6, 10, 10) AS cells;

Grille 4x6

Vous pouvez ajouter les décalages x0/ y0origine (leur valeur par défaut est zéro).

Mike T
la source
1
Merci! Il ne me reste plus qu'à relier le filet à la BBox du polygone.
mk.archaeo
Ceci est très utile .. J'ai une requête. Comment créer des grilles à l'intérieur d'un polygone / bbox?
Mohammed shafeek
Beau travail Mike, c'est très utile.
Mounaim
56

Voici une variante spécifique de la génération, dans les cas où vous devez créer une grille pour une carte géographique avec une étape de mesure constante (des cellules peuvent être utilisées pour regrouper des valeurs, par exemple la densité de foudre dans une région).

La fonction n'est pas très élégante, mais je n'ai pas trouvé de meilleure solution pour cette tâche (y compris la fonction de Mike Toews ci-dessus). Donc, vous avez un polygone lié (par exemple, arrivé d'une interface Google Maps), vous avez une valeur de pas en mètres:

CREATE OR REPLACE FUNCTION public.makegrid_2d (
  bound_polygon public.geometry,
  grid_step integer,
  metric_srid integer = 28408 --metric SRID (this particular is optimal for the Western Russia)
)
RETURNS public.geometry AS
$body$
DECLARE
  BoundM public.geometry; --Bound polygon transformed to the metric projection (with metric_srid SRID)
  Xmin DOUBLE PRECISION;
  Xmax DOUBLE PRECISION;
  Ymax DOUBLE PRECISION;
  X DOUBLE PRECISION;
  Y DOUBLE PRECISION;
  sectors public.geometry[];
  i INTEGER;
BEGIN
  BoundM := ST_Transform($1, $3); --From WGS84 (SRID 4326) to the metric projection, to operate with step in meters
  Xmin := ST_XMin(BoundM);
  Xmax := ST_XMax(BoundM);
  Ymax := ST_YMax(BoundM);

  Y := ST_YMin(BoundM); --current sector's corner coordinate
  i := -1;
  <<yloop>>
  LOOP
    IF (Y > Ymax) THEN  --Better if generating polygons exceeds the bound for one step. You always can crop the result. But if not you may get not quite correct data for outbound polygons (e.g. if you calculate frequency per sector)
        EXIT;
    END IF;

    X := Xmin;
    <<xloop>>
    LOOP
      IF (X > Xmax) THEN
          EXIT;
      END IF;

      i := i + 1;
      sectors[i] := ST_GeomFromText('POLYGON(('||X||' '||Y||', '||(X+$2)||' '||Y||', '||(X+$2)||' '||(Y+$2)||', '||X||' '||(Y+$2)||', '||X||' '||Y||'))', $3);

      X := X + $2;
    END LOOP xloop;
    Y := Y + $2;
  END LOOP yloop;

  RETURN ST_Transform(ST_Collect(sectors), ST_SRID($1));
END;
$body$
LANGUAGE 'plpgsql';

Comment l'utiliser:

SELECT cell FROM 
(SELECT (
ST_Dump(makegrid_2d(ST_GeomFromText('Polygon((35.099577 45.183417,47.283415 45.183417,47.283415 49.640445,35.099577 49.640445,35.099577 45.183417))',
 4326), -- WGS84 SRID
 10000) -- cell step in meters
)).geom AS cell) AS q_grid

Vous pouvez donc voir que les lignes formatées par les polygones générés sont situées le long des parallèles géographiques et des méridiens, ce qui est très pratique.

Un exemple de grille avec pas de 50 km

Conseil: Si vous calculez quelque chose comme la densité (par exemple, la carte des coups de foudre par cellules), et que la grille est générée dynamiquement Pour augmenter les performances, je suggérerais d'utiliser des tables temporaires pour stocker les cellules en tant que polygones géométriques, avec un index spatial sur la colonne représentée la cellule.

Alexander Palamarchuk
la source
J'aimerais pouvoir voter encore une fois ... c'était une solution parfaite! et la possibilité de personnaliser le système de coordonnées est fantastique ~!
DPSSpatial
Juste une suggestion mineure, au lieu d'utiliser ST_GeomFromTextlors de la création d'une boîte à ajouter sectors, vous pouvez utiliser ST_MakeEnvelopeet spécifier simplement les coordonnées en bas à gauche et en haut à droite de la boîte.
Matt
Cela apporte des potentiels
nickves
11

Vous pouvez créer une grille régulière simplement en vectorisant un raster vide:

SELECT (ST_PixelAsPolygons(ST_AddBand(ST_MakeEmptyRaster(100, 100, 1.1, 1.1, 1.0), '8BSI'::text, 1, 0), 1, false)).geom
Pierre Racine
la source
1
C'est une solution si simple, après l'avoir fait tant de fois par le vecteur.
John Powell
6

J'ai créé une variante de la fonction de @ Alexander qui n'exige pas de transformation en un autre SRID. Cela évite d'avoir à trouver une projection utilisant des mètres comme unités pour une région donnée. Il utilise ST_Projectpour bien marcher en utilisant la projection donnée. J'ai également ajouté un width_stepet height_steppour permettre l'utilisation de carreaux rectangulaires au lieu de les exiger.

CREATE OR REPLACE FUNCTION public.makegrid_2d (
  bound_polygon public.geometry,
  width_step integer,
  height_step integer
)
RETURNS public.geometry AS
$body$
DECLARE
  Xmin DOUBLE PRECISION;
  Xmax DOUBLE PRECISION;
  Ymax DOUBLE PRECISION;
  X DOUBLE PRECISION;
  Y DOUBLE PRECISION;
  NextX DOUBLE PRECISION;
  NextY DOUBLE PRECISION;
  CPoint public.geometry;
  sectors public.geometry[];
  i INTEGER;
  SRID INTEGER;
BEGIN
  Xmin := ST_XMin(bound_polygon);
  Xmax := ST_XMax(bound_polygon);
  Ymax := ST_YMax(bound_polygon);
  SRID := ST_SRID(bound_polygon);

  Y := ST_YMin(bound_polygon); --current sector's corner coordinate
  i := -1;
  <<yloop>>
  LOOP
    IF (Y > Ymax) THEN  
        EXIT;
    END IF;

    X := Xmin;
    <<xloop>>
    LOOP
      IF (X > Xmax) THEN
          EXIT;
      END IF;

      CPoint := ST_SetSRID(ST_MakePoint(X, Y), SRID);
      NextX := ST_X(ST_Project(CPoint, $2, radians(90))::geometry);
      NextY := ST_Y(ST_Project(CPoint, $3, radians(0))::geometry);

      i := i + 1;
      sectors[i] := ST_MakeEnvelope(X, Y, NextX, NextY, SRID);

      X := NextX;
    END LOOP xloop;
    CPoint := ST_SetSRID(ST_MakePoint(X, Y), SRID);
    NextY := ST_Y(ST_Project(CPoint, $3, radians(0))::geometry);
    Y := NextY;
  END LOOP yloop;

  RETURN ST_Collect(sectors);
END;
$body$
LANGUAGE 'plpgsql';

Vous pouvez l'utiliser comme ceci:

SELECT ST_AsGeoJSON(cell) FROM (
  SELECT (
    ST_Dump(
      makegrid_2d(
        ST_GeomFromText(
          'Polygon((35.099577 45.183417,47.283415 45.183417,47.283415 49.640445,35.099577 49.640445,35.099577 45.183417))',
          4326
        ),
         10000, -- width step in meters
         10000  -- height step in meters
       ) 
    )
  ) .geom AS cell
)q;
Mat
la source
5

Voici un algorithme optimisé et efficace pour créer un réseau de résille, une grille régulière, une grille polygonale, une grille rectangulaire dans une enveloppe, un polygone ou des multipolygones. presque gérer n'importe quel SRID;

GitHub Repo Link

entrez la description de l'image ici

DROP FUNCTION IF EXISTS PUBLIC.I_Grid_Regular(geometry, float8, float8);
CREATE OR REPLACE FUNCTION PUBLIC.I_Grid_Regular
( geom geometry, x_side float8, y_side float8, OUT geometry )
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;
geom_cell geometry := ST_GeomFromText(FORMAT('POLYGON((0 0, 0 %s, %s %s, %s 0,0 0))',
                                        $3, $2, $3, $2), srid);
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;
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 With foo AS (
    SELECT
    ST_Translate( geom_cell, j * $2 + x_min, i * $3 + y_min ) AS cell
    FROM
        generate_series ( 0, x_series ) AS j,
        generate_series ( 0, y_series ) AS i
    ) SELECT ST_CollectionExtract(ST_Collect(ST_Transform ( ST_Intersection(cell, geom), input_srid)), 3)
    FROM foo where ST_intersects (cell, geom);
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE STRICT;

Utilisez-le avec une requête simple. l'entrée doit être un polygone valide, un multipolygone ou une enveloppe.

select I_Grid_Regular(st_setsrid(g.geom, 4326), .0001, .0001 ), geom from polygons limit 1
Muhammad Imran Siddique
la source