Создание правильной многоугольной сетки в PostGIS?

61

Как создать в форме многоугольника правильную сетку многоугольников / квадратов заданного размера в postgis?

Я думал о функции, как Как создать регулярную точечную сетку внутри многоугольника в Postgis? только для квадратов, так что квадраты могут быть 5 х 5 м или даже 10 х 10 м. Но понятия не имею, чтобы изменить это правильно.

mk.archaeo
источник
2
Обобщение, которое вы ищете, не ясно. Вы говорите, что начинаете с (произвольного) одиночного многоугольника и хотите разбить плоскость на совпадающие копии? В общем, это невозможно, но, возможно, этот многоугольник обладает особыми свойствами (например, известно, что это параллелограмм, треугольник или шестиугольник).
whuber

Ответы:

60

Вот возвращаемая функция набора, ST_CreateFishnetкоторая создает двумерную сетку геометрии многоугольника:

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;

где nrowи ncol- количество строк и столбцов, xsizeа ysizeтакже длина ячейки, необязательный параметр x0и y0координаты левого нижнего угла.

Результатом являются rowи colчисла, начиная с 1 в левом нижнем углу, и geomпрямоугольные многоугольники для каждой ячейки. Так, например:

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)

Или создать единую коллекцию геометрии для полной сетки:

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

Сетка 4х6

Вы можете добавить смещения x0/ y0origin (по умолчанию это ноль).

Майк Т
источник
1
Спасибо! Теперь мне просто нужно привязать сетку к BBox многоугольника.
mk.archaeo
Это очень полезно .. У меня есть один запрос. Как я могу создать сетки внутри многоугольника / Bbox?
Мохаммед Шафиек
Хорошая работа, Майк, это очень полезно.
Mounaim
56

Вот конкретный вариант генерации для ситуации, когда вам нужно создать сетку для географической карты с постоянным шагом метрики (ячейки могут использоваться для группировки значений, например, плотности молнии в регионе).

Функция не очень элегантная, но я не нашел лучшего решения для этой задачи (включая функцию Майка Тьюса выше). Таким образом, у вас есть связанный многоугольник (например, полученный из интерфейса Google Maps), значение шага в метрах:

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';

Как это использовать:

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

Итак, вы можете видеть, что линии, отформатированные сгенерированными полигонами, лежат вдоль географических параллелей и меридианов - это очень удобно.

Пример сетки с шагом 50 км

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

Александр Паламарчук
источник
Хотел бы я снова проголосовать за это ... это было идеальное решение! и возможность настроить систему координат фантастическая ~!
DPSSpatial
Просто незначительное предложение, вместо использования ST_GeomFromTextпри создании поля для добавления sectors, вы можете использовать ST_MakeEnvelopeи просто указать нижнюю левую и верхнюю правую координаты поля.
Мэтт
Это приносит потенциалы
nickves
11

Вы можете создать регулярную сетку, просто векторизовав пустой растр:

SELECT (ST_PixelAsPolygons(ST_AddBand(ST_MakeEmptyRaster(100, 100, 1.1, 1.1, 1.0), '8BSI'::text, 1, 0), 1, false)).geom
Пьер Расин
источник
1
Это такое простое решение, сделав его векторным способом так много раз.
Джон Пауэлл
6

Я создал вариант функции @ Alexander, который не требует преобразования в другой SRID. Это позволяет избежать необходимости поиска проекции, в которой в качестве единиц измерения используются метры. Он использует, ST_Projectчтобы правильно шагать, используя данную проекцию. Я также добавил width_stepи height_stepдля прямоугольных плиток вместо того, чтобы они были квадратными.

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';

Вы можете использовать это так:

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;
Matt
источник
5

Вот оптимизированный и эффективный алгоритм для создания рыболовной сети, регулярной сетки, многоугольной сетки, прямоугольной сетки внутри любого конверта, многоугольника или мультиполигонов. почти справиться с любым SRID;

GitHub Repo Ссылка

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

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;

Используйте это с простым запросом; ввод должен быть действительным полигоном, мультиполигоном или конвертом.

select I_Grid_Regular(st_setsrid(g.geom, 4326), .0001, .0001 ), geom from polygons limit 1
Мухаммед Имран Сиддик
источник