Constructions

  1. Geometric Shapes
    1. Construct regular N-gons
    2. Construct an envelope for a Geometry at an Angle
  2. Geodetic Shapes
    1. Construct ellipses in WGS84
  3. 3D Shapes
    1. 3D Centroid
  4. Lines
    1. Construct a line with given Length and Angle
    2. Construct Transects (Hatching) along a Line
  5. Extending / Filling Polygons
    1. Construct polygons filling gaps in a coverage
    2. Remove Gaps between Polygons
    3. Construct polygon joining two polygons
    4. Construct expanded polygons to touches a bounding polygon
    5. Extend Polygon to meet Line
  6. Grids
    1. Construct regular point grid inside polygon
    2. Construct Land-Constrained Point Grid
    3. Construct Square Grid
    4. Construct Quadrilateral Grid
  7. Medial Axis / Skeleton
    1. Construct Average / Centrelines between Lines
    2. Construct Straight Skeleton
  8. Polygon Diagonal / Radius
    1. Construct lines joining every vertex in a polygon
    2. Construct all diagonals in a concave polygon
    3. Construct longest horizontal line within polygon
    4. Construct set of parallel lines across Polygon
    5. Construct closest boundary point to a point in a polygon

Geometric Shapes

Construct regular N-gons

https://gis.stackexchange.com/questions/426933/buffer-with-postgis-in-the-form-of-a-pentagon-or-hexagon

Solution 1: SQL

Construct a pentagon (5 sides) of radius 2 centred at (10,10). Those values can be changed to whatever is needed.

SELECT ST_MakePolygon( ST_MakeLine( ARRAY_AGG( 
    ST_Point( 10 + 2 * COSD(90 + i * 360 / 5 ), 
              10 + 2 * SIND(90 + i * 360 / 5 )  ))))
  FROM generate_series(0, 5) AS s(i);

Solution 2 - Function ST_MakeNGon

https://gist.github.com/geozelot/ddf88a9ae0438d7a46f176e9555ce7a1

/*
 * @in_params
 * center - center POINT geometry
 * radius - circumradius [in CRS units] (cirlce that inscribes the *gon)
 * sides  - desired side count (e.g. 6 for Hexagon)
 * rot    - rotation offset, clockwise [in degree]; DEFAULT 0.0 (corresponds to planar NORTH)
 *
 * @out_params
 * ngon   - resulting N-gon POLYGON geometry
 *
 *
 * The function will create a @sides sided regular, equilateral & equiangular Polygon
 * from a given @center and @radius and optional @rot rotation from NORTH
 */
 
CREATE OR REPLACE FUNCTION ST_MakeNGon(
  IN  center   GEOMETRY(POINT),
  IN  radius   FLOAT8,
  IN  sides    INT,
  IN  rot      FLOAT8 DEFAULT 0.0,
  OUT ngon     GEOMETRY(POLYGON)
) LANGUAGE 'plpgsql' IMMUTABLE STRICT PARALLEL SAFE AS
  $$
  DECLARE
    _x FLOAT8  := ST_X($1);
    _y FLOAT8  := ST_Y($1);
	
    _cr FLOAT8 := 360.0/$3;
		
    __v GEOMETRY(POINT)[];
	
  BEGIN
    FOR i IN 0..$3 LOOP
      __v[i] := ST_MakePoint(_x + $2*SIND(i*_cr + $4), _y + $2*COSD(i*_cr + $4));
    END LOOP;
		
    ngon := ST_MakePolygon(ST_MakeLine(__v));
  END;
  $$
;

Construct an envelope for a Geometry at an Angle

https://gis.stackexchange.com/questions/443727/how-to-control-the-angle-of-st-orientedenvelope-postgis

Solution

  • Rotate the geometry CW to the desired angle
  • Compute the envelope
  • Rotate the envelope CCW by the desired angle
WITH data(angle, geom) AS (VALUES
   (0.2, 'POLYGON ((10 60, 10 20, 50 30, 70 10, 90 60, 60 80, 40 60, 30 90, 10 60))'::geometry)
)
SELECT ST_Rotate( ST_Envelope(
            ST_Rotate(geom, -angle, ST_Centroid(geom))),
        angle, ST_Centroid(geom)) AS envRot
    FROM data;

Geodetic Shapes

Construct ellipses in WGS84

https://gis.stackexchange.com/questions/218159/postgis-ellipse-issue

https://gis.stackexchange.com/questions/409129/making-ellipses-on-a-global-data-set

3D Shapes

3D Centroid

https://gis.stackexchange.com/questions/449451/3d-centroid-in-postgis

WITH pts AS
(SELECT 
  geom(
    ST_DumpPoints(
      ST_RemovePoint(
        ST_ExteriorRing(
          ST_GeomFromText('POLYGON Z ((0 0 0, 1 0 0, 1 0 1, 1 1 1, 0 1 1, 0 0 1, 0 0 0 ))')
        ), 0 -- remove first point because, for a polygon, first point = last point by definition
      )
    )
  )
)
SELECT 
  ST_AsText(
    ST_MakePoint(
      AVG(ST_X(geom)),
      AVG(ST_Y(geom)),
      AVG(ST_Z(geom))
    )
  ) geom
FROM pts;

Lines

Construct a line with given Length and Angle

https://gis.stackexchange.com/questions/75407/how-to-create-linestrings-with-a-definite-angle-and-length-that-are-fixed-to-a

Solution 1

WITH params AS (SELECT ST_Point(10, 10) AS origin, 10.0 AS length, 60 AS angle)
SELECT ST_Translate( ST_Rotate(ST_MakeLine(ST_Point( -length/2, 0), 
				          ST_Point(  length/2, 0)),
                                      	radians(angle)),
		ST_X(origin), ST_Y(origin), ST_SRID(origin)) AS line
FROM params;

Solution 2

WITH params AS (SELECT ST_Point(10, 10) AS origin, 10.0 AS length, 60 AS angle)
SELECT ST_SetSRID(ST_MakeLine(  ST_Point( ST_X(origin) - length/2 * cos(radians(angle)),
				          ST_Y(origin) - length/2 * sin(radians(angle)) ), 
				ST_Point( ST_X(origin) + length/2 * cos(radians(angle)),
					  ST_Y(origin) + length/2 * sin(radians(angle)) )),
	ST_SRID(origin)) AS line
FROM params;

Construct Transects (Hatching) along a Line

https://gis.stackexchange.com/questions/300243/creating-perpendicular-line-transects-in-postgis

CREATE OR REPLACE FUNCTION ST_LineTransects(
    lineGeom geometry,
    secLen float,
    transectLen float)
    RETURNS geometry 
    LANGUAGE sql AS 
$BODY$
WITH
sections AS (SELECT ST_LineSubstring(lineGeom, secStart, CASE WHEN secEnd > 1 THEN 1 ELSE secEnd END) geom
    FROM (SELECT lineGeom, ST_Length(lineGeom) AS lineLen) AS t
    CROSS JOIN LATERAL 
        (SELECT i * secLen/lineLen AS secStart, (i+1) * secLen/lineLen AS secEnd
            FROM generate_series(0, floor(lineLen / secLen)::integer) AS t(i) 
            WHERE (secLen * i)/lineLen <> 1.0) AS t2 ),
sectAnglePt AS (SELECT pi() - ST_Azimuth(ST_StartPoint(geom), ST_EndPoint(geom)) AS ang,
                  ST_LineInterpolatePoint(geom, 0.5) AS centre
                  FROM sections)
SELECT ST_Collect(ST_MakeLine(
                    ST_Point( ST_X(centre) - transectLen/2 * cos(ang), 
                              ST_Y(centre) - transectLen/2 * sin(ang)),
                    ST_Point( ST_X(centre) + transectLen/2 * cos(ang), 
                              ST_Y(centre) + transectLen/2 * sin(ang)) )) AS geom 
  FROM sectAnglePt;
$BODY$;

Example:

SELECT ST_LineTransects('LINESTRING (1 1, 3 5, 5 3, 7 4, 7 1)', 0.5, 1.0);

Extending / Filling Polygons

Construct polygons filling gaps in a coverage

https://gis.stackexchange.com/questions/368406/postgis-create-new-polygons-in-between-existing

SELECT ST_DIFFERENCE(foo.geom, bar.geom)
FROM (SELECT ST_CONVEXHULL(ST_COLLECT(shape::geometry)) as geom FROM schema.polytable) as foo, 
(SELECT ST_BUFFER(ST_UNION(shape),0.5) as geom FROM schema.polytable) as bar

To scale this up/out, could process batches of polygons using a rectangular grid defined over the data space. The constructed gap polygons can be clipped to grid cells. and optional unioned afterwards

Remove Gaps between Polygons

https://gis.stackexchange.com/questions/356480/enclose-polygons-that-are-not-overlapped-and-remove-gaps-between-them

Also: https://gis.stackexchange.com/questions/316000/using-st-union-to-combine-several-polygons-to-one-multipolygon-using-postgis

Suggestions

  • Buffer positively and negatively (see ST_BufferedUnionin PostGIS Addons )
  • Answer using ST_Buffer(geom, dist, 'join=mitre mitre_limit=5.0') with pos/neg distance

Construct polygon joining two polygons

https://gis.stackexchange.com/questions/352884/how-can-i-get-a-polygon-of-everything-between-two-polygons-in-postgis

See Also https://gis.stackexchange.com/questions/49726/postgis-algorithm-to-unite-points-of-two-geometries-that-are-within-specified-ra

Solution

  • construct convex hull of both polygons together
  • subtract convex hull of each polygon
  • union with original polygons
  • keep only the polygon shell (remove holes)
WITH data(geom) AS (VALUES
( 'POLYGON ((100 300, 200 300, 200 200, 100 200, 100 300))'::geometry )
,( 'POLYGON ((50 150, 100 150, 100 100, 50 100, 50 150))'::geometry )
)
SELECT ST_MakePolygon(ST_ExteriorRing(ST_Union(
  ST_Difference( 
      ST_ConvexHull( ST_Union(geom)), 
      ST_Union( ST_ConvexHull(geom))), 
  ST_Collect(geom))))
FROM data;

Construct expanded polygons to touches a bounding polygon

https://gis.stackexchange.com/questions/294163/sql-postgis-expanding-polygons-contained-inside-another-polygon-until-one-ver

Expanding polygons to touch

WITH 
    to_buffer(distance, b.id) AS (
SELECT
   ST_Distance(ST_Exteriorring(a.geom), b.geom),
   b.id
 FROM 
    polygons_outer a, polygons_inner b 
 WHERE ST_Contains(a.geom, b.geom)
 UPDATE polygons_inner b
   SET geom = ST_Buffer(geom, distance)
  FROM to_buffer tb
  WHERE b.id = tb.id;

See also note about using a scaling rather than buffer, to preserve shape of polygon

Extend Polygon to meet Line

https://gis.stackexchange.com/questions/408202/postgis-st-convexhull-but-perpendicular-to-a-line

Solution Outline

  • For each vertex in the polygon shell, use ST_LineLocatePoint to find its fractional index along the line.
  • Construct the line segments from each vertex to the line, and pick the shortest ones with max and min index
  • Construct the subline along the baseline between the max and min indices
  • Extract all the line segments from the polygon
  • Polygonize the extracted and constructed line segments. This should produce two polygons
  • Union the polygonization results

Grids

Construct regular point grid inside polygon

https://gis.stackexchange.com/questions/4663/creating-regular-point-grid-inside-polygon-in-postgis

Construct Land-Constrained Point Grid

https://korban.net/posts/postgres/2019-10-17-generating-land-constrained-point-grids/

with geoms as (
    select st_setsrid(encode(wkb_geometry, 'hex'), 4326) as g from coastlines
) 
, row as (
    select st_project(st_setSrid(st_point(174.8431, -41.2581), 4326)::geography, s.a * 50000, pi() / 2) as pt
    from generate_series(-10, 10, 1) as s(a)
)
select st_project(pt, s.a * 50000, 0) as projected_pt
from row, generate_series(-10, 10, 1) as s(a)
where st_distance(st_setSrid(st_point(174.8431, -41.2581), 4326)::geography, st_project(pt, s.a * 50000, 0)) < 500000
    and exists(select 1 from geoms where st_nPoints(g) > 1000 and st_contains(g, st_project(pt, s.a * 50000, 0)::geometry))

Construct Square Grid

https://gis.stackexchange.com/questions/16374/creating-regular-polygon-grid-in-postgis

https://gis.stackexchange.com/questions/4663/how-to-create-regular-point-grid-inside-a-polygon-in-postgis

https://gis.stackexchange.com/questions/271234/creating-a-grid-on-a-polygon-and-find-number-of-points-in-each-grid

Construct Quadrilateral Grid

Construct a regularly spaced grid in an arbitrary quadrilateral.

https://gis.stackexchange.com/questions/437745/creating-an-irregular-point-grid-with-provided-row-and-column-numbers-in-postgis

Solution Use a pseudo-projective transformation of a grid on a unit square to the quadrilateral. Transformation is a simple non-linear combination of three basis vectors. Formula and explanation are here.

WITH quad AS (SELECT 
  5 AS LLx, 5 AS LLy,
  10 AS ULx, 30 AS ULy,
  25 AS URx, 25 AS URy,
  30 AS LRx, 0 AS LRy
),
vec AS (
  SELECT  LLx AS ox, LLy AS oy,
          ULx - LLx AS ux, ULy - LLy AS uy, 
          LRx - LLx AS vx, LRy - LLy As vy, 
          URx - LLx - ((ULX - LLx) + (LRx - LLx)) AS wx, 
          URy - LLy - ((ULy - LLy) + (LRy - LLy)) AS wy 
  FROM quad
),
grid AS (SELECT x / 10.0 AS x, y / 10.0 AS y 
  FROM        generate_series(0, 10) AS sx(x)
  CROSS JOIN  generate_series(0, 10) AS sy(y)
)
SELECT ST_Point(ox + ux * x + vx * y + wx * x * y, 
                oy + uy * x + vy * y + wy * x * y) AS geom
  FROM vec CROSS JOIN grid;

Medial Axis / Skeleton

Construct Average / Centrelines between Lines

https://gis.stackexchange.com/questions/322392/average-of-two-lines

https://gis.stackexchange.com/questions/50668/how-can-i-merge-collapse-nearby-and-parallel-road-lines-eg-a-dual-carriageway

https://github.com/migurski/Skeletron

Idea: triangulate polygon, then connect midpoints of interior lines

Idea 2: find line segments for nearest points of each line vertex. Order by distance along line (percentage?). Discard any that have a retrograde direction. Join centrepoints of segments.

Construct Straight Skeleton

https://github.com/twak/campskeleton

Polygon Diagonal / Radius

Construct lines joining every vertex in a polygon

https://gis.stackexchange.com/questions/58534/get-the-lines-between-all-points-of-a-polygon-in-postgis-avoid-nested-loop

Better answer:

WITH poly(id, geom)AS (VALUES
    ( 1, 'POLYGON ((2 7, 5 9, 9 7, 8 3, 5 2, 2 3, 3 5, 2 7))'::geometry )
  )
,ring AS (SELECT ST_ExteriorRing(geom) geom FROM poly)
,pts AS (SELECT i, ST_PointN(geom, i) AS geom
  FROM ring
  JOIN LATERAL generate_series(2, ST_NumPoints(ring.geom)) AS s(i) ON true)
SELECT ST_MakeLine(p1.geom, p2.geom) AS geom
FROM pts p1, pts p2
WHERE p1.i > p2.i;

Construct all diagonals in a concave polygon

https://gis.stackexchange.com/questions/407946/performant-possible-pairs-in-concave-polygon

Solution

Using only polygon shell:

WITH poly AS 
(SELECT ST_MakePolygon(ST_ExteriorRing((ST_Dump(geom)).geom)) geom FROM
    (SELECT ST_GeomFromText('POLYGON((25.390624999999982 23.62298461759423,18.183593749999982 19.371888927008566,7.812499999999982 17.87273879517762,5.878906249999982 24.90497143578641,9.570312499999982 25.223427998254586,12.734374999999982 25.064303191014304,15.195312499999982 30.048744443788348,22.578124999999982 30.352588399664125,24.687499999999982 25.857838723065772,25.390624999999982 23.62298461759423))') AS geom
) boo),
poly_ext AS (SELECT ST_ExteriorRing((ST_Dump(geom)).geom) geom FROM poly),
points AS (SELECT (g.gdump).path AS id, (g.gdump).geom AS geom
                  FROM (SELECT ST_DumpPoints(geom) AS gdump FROM poly_ext) AS g)
SELECT ST_MakeLine(a.geom, b.geom) AS geom FROM points a CROSS JOIN points b
       JOIN poly ON ST_Contains(poly.geom, ST_MakeLine(a.geom, b.geom)) WHERE a.id < b.id;

Construct longest horizontal line within polygon

https://gis.stackexchange.com/questions/32552/calculating-maximum-distance-within-polygon-in-x-direction-east-west-direction

Algorithm

  • For every Y value:
  • Construct horizontal line across bounding box at Y value
  • Intersect with polygon
  • Keep longest result line

Construct set of parallel lines across Polygon

https://gis.stackexchange.com/questions/24064/filling-a-polygon-with-lines-using-postgis

SELECT ST_Intersection(line, polygon) AS geom FROM
    (SELECT polygon, ST_SetSRID( ST_MakeLine (ST_Point(x_min, y_value),ST_Point(x_max, y_value) ), ST_SRID(polygon)) AS line FROM 
        (SELECT polygon, GENERATE_SERIES(
				FLOOR( ST_YMin(polygon))::int,
				CEILING(ST_YMax(polygon))::int,200) y_value,
			ST_XMin(polygon) x_min,
			ST_XMax(polygon) x_max
		FROM (SELECT geom AS polygon FROM data) l
        ) AS c
    ) AS lines;

Construct closest boundary point to a point in a polygon

https://gis.stackexchange.com/questions/159318/finding-closest-outside-point-to-point-inside-polygon-in-postgis

(Code below shows use of ST_Buffer to force the constructed point to lie outside the polygon.)

 WITH polygons AS(
    SELECT ST_Buffer(ST_GeomFromText('POINT(0 0)', 4326),2) as geom. -- example polygon
 )
 SELECT 
 ST_AsTEXT(ST_ClosestPoint(ST_ExteriorRing(ST_Buffer(polygons.geom,0.0000001))
        ,ST_GeomFromText('POINT(1 0)', 4326)))
FROM polygons;