Spatial Relationships

  1. Non-Intersection
    1. Find geometries which do NOT intersect/equal geometries in another set
    2. Find geometries which do not intersect other geometries in same set
  2. Spatial Relationships with a Tolerance
    1. Geometry Equality with Tolerance
    2. Emulating Touches with tolerance
    3. Discrepancy between GEOS predicates and PostGIS Intersects
    4. Test if two 3D geometries are equal
    5. ST_ClosestPoint does not intersect Line
  3. Polygon / Polygon
    1. Find Polygons not contained by other Polygons
    2. Find Polygons not contained by other Polygons
    3. Find non-covered polygons
    4. Find Polygons NOT covered by union of other Polygons
    5. Find Polygons covered by a set of other polygons
    6. Improve performance to find Polygons covered by other Polygons
    7. Find Polygons which touch in a line
    8. Find Polygons in a Coverage NOT fully enclosed by other Polygons
    9. Find Polygons with maximum overlap with another Polygon table
    10. Find Polygons intersecting another Polygon table using ST_Subdivide
    11. Compute hierarchy of a nested Polygonal Coverage
  4. Polygon / Line
    1. Find Start Points of Rivers and Headwater polygons
    2. Find routes which terminate in Polygons but do not cross them
    3. Find Lines that touch Polygon at both ends
    4. Find Lines that touch but do not cross Polygons
  5. Line / Line
    1. Find LineStrings with Common Segments
  6. Line / Point
    1. Test if Point is on a Line

Non-Intersection

Find geometries which do NOT intersect/equal geometries in another set

https://gis.stackexchange.com/questions/162651/looking-for-boolean-intersection-of-small-table-with-huge-table

Solution

Do NOT use a JOIN with ST_Disjoint or NOT ST_Equals, since that does not use the spatial index.

Use an anti-join with NOT EXISTS or LEFT JOIN / null

  • Anti-join with NOT EXISTS:
SELECT * FROM polygons
WHERE NOT EXISTS (SELECT 1 FROM streets WHERE ST_Intersects(polygons.geom, streets.geom))
  • Anti-join with LEFT JOIN ON ST_Intersects/ST_Equals with WHERE right-side = NULL

Find geometries which do not intersect other geometries in same set

https://gis.stackexchange.com/questions/448440/finding-all-isolated-buffer-points-using-postgis

Solution - Anti-Join

Use NOT EXISTS against a sub-query testing for intersection with another geometry. Avoid reporting self-intersection by checking if ids are different

Solution - ST_ClusterDBSCAN

Use DBSCAN clustering with tolerance = 0 and a minimum cluster size of 2:

SELECT id, geom 
FROM (SELECT id, geom,
             ST_ClusterDBSCAN(geom, eps := 0, minpoints := 2) OVER () AS cluster_id
      FROM data) t 
WHERE cluster_id IS NULL

Spatial Relationships with a Tolerance

Geometry Equality with Tolerance

Example: https://gis.stackexchange.com/questions/176359/tolerance-in-postgis

This post is about needing ST_Equals to have a tolerance to accommodate small differences caused by reprojection https://gis.stackexchange.com/questions/141790/postgis-st-equals-false-when-st-intersection-100-of-geometry

This is about small coordinate differences defeating ST_Equals, and using ST_SnapToGrid to resolve the problem: https://gis.stackexchange.com/questions/56617/st-equals-postgis-problems

This says that copying geometries to another database causes them to fail ST_Equals (not sure why copy would change the geom - perhaps done using WKT?). Says that using buffer is too slow https://gis.stackexchange.com/questions/213240/st-equals-not-matching-with-exact-geometry

https://gis.stackexchange.com/questions/176359/tolerance-in-postgis

Emulating Touches with tolerance

https://gis.stackexchange.com/a/488563/14766

The problem arises when using overlay functions with a distance tolerance (snap-rounding) with polygons as input. This is done to avoid result artifacts. Theoretically, if C = difference(union(A, B), B) then touches(A, C) = true (i.e. A and C should only intersect in a line.) This is not necessarily true in practice, due to numerical imprecision. Using overlay with snap-rounding makes this obvious, but it can happen in any overlay mode. A tolerance-based approach must be used to determine if A and result C effectively touch (i.e. intersect “almost” in a line).

Solutions

  • test whether ST_Intersection(A, B, tol) is linear
  • test whether ST_Intersection(A, B) has a small area (based on a fraction of the areas of the input geometries)
  • test whether the boundary of A which lies within B lies within the tolerance distance of the boundary of B. This is the exact equivalent of “touches with tolerance”. It can be done in two ways:
    • ST_Covers( ST_Buffer(ST_Boundary(B), tol), ST_Intersection(ST_Boundary(A), B) )
    • ST_OrientedHausdorffDistance(ST_Intersection(ST_Boundary(A), B), ST_Boundary(B)) <= tol (NOTE: ST_OrientedHausdorffDistance (or equivalent) is not yet available in PostGIS)

Discrepancy between GEOS predicates and PostGIS Intersects

https://gis.stackexchange.com/questions/259210/how-can-a-point-not-be-within-or-touch-but-still-intersect-a-polygon

Actually it doesn’t look like there is a discrepancy now. But still a case where a distance tolerance might clarify things.

Test if two 3D geometries are equal

https://gis.stackexchange.com/questions/373978/how-to-check-two-3d-geometry-are-equal-in-postgis

ST_ClosestPoint does not intersect Line

https://gis.stackexchange.com/questions/11510/st-closestpointline-point-does-not-intersect-line

Solution

Use ST_DWithin

Polygon / Polygon

Find Polygons not contained by other Polygons

https://gis.stackexchange.com/questions/453009/how-to-check-if-two-polygons-have-internal-points-in-common-with-geodjango-and

Solution

This relationship can be called interiorIntersects.
It can be used to determine if polygons form a valid polygonal coverage. It can be evaluated as:

  ST_Relate(a.geom, b.geom, 'T********')

See this post for possible robustness issues with this relationship test: https://gis.stackexchange.com/questions/407520/postgis-thresholded-intersect-or-signfiicant-intersect-query

Find Polygons not contained by other Polygons

https://gis.stackexchange.com/questions/185308/find-polygons-that-does-not-contain-any-polygons-with-postgis

Solution

Use the LEFT JOIN on ST_Contains with NULL result pattern

Find non-covered polygons

https://gis.stackexchange.com/questions/333302/selecting-non-overlapping-polygons-from-a-one-layer-in-postgis

WITH
data AS (
    SELECT * FROM (VALUES
        ( 'A', 'POLYGON ((100 200, 200 200, 200 100, 100 100, 100 200))'::geometry ),
        ( 'B', 'POLYGON ((300 200, 400 200, 400 100, 300 100, 300 200))'::geometry ),
        ( 'C', 'POLYGON ((100 400, 200 400, 200 300, 100 300, 100 400))'::geometry ),
        ( 'AA', 'POLYGON ((120 380, 180 380, 180 320, 120 320, 120 380))'::geometry ),
        ( 'BA', 'POLYGON ((110 180, 160 180, 160 130, 110 130, 110 180))'::geometry ),
        ( 'BB', 'POLYGON ((170 130, 190 130, 190 110, 170 110, 170 130))'::geometry ),
        ( 'CA', 'POLYGON ((330 170, 380 170, 380 120, 330 120, 330 170))'::geometry ),
        ( 'AAA', 'POLYGON ((330 170, 380 170, 380 120, 330 120, 330 170))'::geometry ),
        ( 'BAA', 'POLYGON ((121 171, 151 171, 151 141, 121 141, 121 171))'::geometry ),
        ( 'CAA', 'POLYGON ((341 161, 351 161, 351 141, 341 141, 341 161))'::geometry ),
        ( 'CAB', 'POLYGON ((361 151, 371 151, 371 131, 361 131, 361 151))'::geometry )
    ) AS t(id, geom)
)
SELECT a.id
FROM data AS A
LEFT JOIN data AS b ON a.id <> b.id AND ST_CoveredBy(a.geom, b.geom)
WHERE b.geom IS NULL;

Find Polygons NOT covered by union of other Polygons

https://gis.stackexchange.com/questions/313039/find-what-polygons-are-not-fully-covered-by-union-of-polygons-from-another-layer

Find Polygons covered by a set of other polygons

https://gis.stackexchange.com/questions/212543/compare-row-to-all-others-in-postgis

Solution

For each polygon, compute union of polygons which intersect it, then test if the union covers the polygon

Improve performance to find Polygons covered by other Polygons

https://gis.stackexchange.com/questions/404461/best-practice-for-counting-polygons-in-polygons

pop represents populated areas and it contains about 30k of polygons. polys is smaller polygons with about 120M recors. Want to count how many smaller polygons are within populated areas

Depending on the average shape of those Polygons you may see a boost in performance when pre-filtering by the ST_Centroid of the public.polys.wkb_geometry: The idea is to limit the containment check to only those Polygons where at least the ST_Centroid intersects the larger Polygon. Could use ST_PointOnSurface too, but this approach only makes sense where the checked polygon is small enough to pass the bbox containment, but outside the actual bounds.

SELECT COUNT(ply.*)
FROM   public.polys AS small
JOIN   public.pop_area AS big
  ON   big.geom ~ small.geom 
 AND   ST_Intersects(big.geom, ST_Centroid( small.geom ))
WHERE  ST_Contains(big.geom, small.geom);

Find Polygons which touch in a line

WITH 
data(id, geom) AS (VALUES
    ( 1, 'POLYGON ((100 200, 200 200, 200 100, 100 100, 100 200))'::geometry ),
    ( 2, 'POLYGON ((250 150, 200 150, 200 250, 250 250, 250 150))'::geometry ),
    ( 3, 'POLYGON ((300 100, 250 100, 250 150, 300 150, 300 100))'::geometry )
)
SELECT a.id, b.id, 
    ST_Relate(a.geom, b.geom),
    ST_Relate(a.geom, b.geom, '****1****') AS is_line_touch
    FROM data a CROSS JOIN data b WHERE a.id < b.id;

Find Polygons in a Coverage NOT fully enclosed by other Polygons

https://gis.stackexchange.com/questions/291824/determine-if-a-polygon-is-not-enclosed-by-other-polygons

Solution

Find each polygon where the total length of the intersection with adjacent polygons is less than length of its boundary.

SELECT a.id
  FROM data a 
  INNER JOIN data b ON (ST_Intersects(a.geom, b.geom) AND a.id != b.id) 
GROUP BY a.id
HAVING 1e-6 > 
  abs( ST_Length(ST_ExteriorRing(a.geom)) - 
  SUM( ST_Length(ST_Intersection(ST_ExteriorRing(a.geom), ST_ExteriorRing(b.geom)))));

Find Polygons with maximum overlap with another Polygon table

Solution with JOIN LATERAL

https://gis.stackexchange.com/questions/287412/in-postgresql-how-to-get-the-polygon-id-that-intersects-the-most-in-case-it-i

Example: For each mountain area, find country which has largest overlap.

SELECT a.id, a.mountain, b.country
FROM mountains a
LEFT JOIN LATERAL
  (SELECT country FROM countrytable
    WHERE ST_Intersects(countrytable.geom, a.geom)
    ORDER BY ST_Area(ST_Intersection(countrytable.geom, a.geom)) DESC NULLS LAST
    LIMIT 1
  ) b ON true;

Solution using DISTINCT ON

https://gis.stackexchange.com/questions/41501/join-based-on-maximum-overlap-in-postgis-postgresql

1) Calculate area of intersection for every pair of rows which intersect. 2) Order them by a.id and by intersection area when a.id are equal. 3) For each a.id keep the first row (which has the largest intersection area because of ordering in step 2).

SELECT DISTINCT ON (a.id)
  a.id AS a_id,
  b.id AS b_id,
  ST_Area(ST_Intersection( a.geom, b.geom )) AS intersect_area
FROM a, b
WHERE st_intersects( a.geom, b.geom)
ORDER BY a.id, ST_Area(ST_Intersection( a.geom, b.geom )) DESC

Find Polygons intersecting another Polygon table using ST_Subdivide

https://gis.stackexchange.com/questions/224138/postgis-st-intersects-vs-arcgis-select-by-location https://blog.cleverelephant.ca/2019/11/subdivide.html

Subdividing Polygons into smaller pieces improves the selectivity of indexes, and improves the performance of ST_Intersects.

CREATE TABLE lte_subdivided AS 
SELECT ST_Subdivide(geom) as GEOM, gid
FROM lde_coverage;

CREATE TABLE cities_subdivided AS 
SELECT ST_Subdivide(geom) as GEOM, id
FROM cities;

CREATE INDEX cgx ON cities_subdivided USING GIST (geom);
CREATE INDEX lgx on lte_subdivided USING GIST (geom);

SELECT distinct c.id 
FROM cities_subdivided c
JOIN lte_subdivided l 
ON ST_Intersects(c.geom, l.geom)

Compute hierarchy of a nested Polygonal Coverage

https://gis.stackexchange.com/questions/343100/intersecting-polygons-to-build-boundary-hierearchy-using-postgis

Given a table of polygons which form a set of nested/hierarchical coverages, compute an explicit hierachy path for each polygon

If the source table is:

geom of A
geom of AA
geom of AB
geom of AC
geom of AB1
geom of AB2
geom of AC1
geom of AC2
geom of AC11
geom of AC12
geom of AC21
geom of AC22

the output will be:

AA, A, geom of AA
AB1, AB, A, geom of AB1
AB2, AB, A, geom of AB2
AC11, AC1, AC, A, geom of AC11
AC12, AC1, AC, A, geom of AC12
AC21, AC2, AC, A, geom of AC21
AC22, AC2, AC, A, geom of AC21

Solution

Determine contains relationships based on interior points and areas. Can use a recursive query on that to extract paths if needed.

WITH RECURSIVE data(id, geom) AS (VALUES
('AC11', 'POLYGON ((100 200, 150 200, 150 150, 100 150, 100 200))'),
('AC12', 'POLYGON ((200 200, 200 150, 150 150, 150 200, 200 200))'),
('AC21', 'POLYGON ((200 100, 150 100, 150 150, 200 150, 200 100))'),
('AC22', 'POLYGON ((100 100, 100 150, 150 150, 150 100, 100 100))'),
('AC1', 'POLYGON ((200 200, 200 150, 100 150, 100 200, 200 200))'),
('AC2', 'POLYGON ((200 100, 100 100, 100 150, 200 150, 200 100))'),
('AC', 'POLYGON ((100 200, 200 200, 200 100, 100 100, 100 200))'),
('AB1', 'POLYGON ((100 300, 150 300, 150 200, 100 200, 100 300))'),
('AB2', 'POLYGON ((200 300, 200 200, 150 200, 150 300, 200 300))'),
('AB', 'POLYGON ((100 300, 200 300, 200 200, 100 200, 100 300))'),
('AA', 'POLYGON ((0 300, 100 300, 100 100, 0 100, 0 300))'),
('A', 'POLYGON ((200 100, 0 100, 0 300, 200 300, 200 100))')
),
-- compute all containment links
contains AS ( SELECT p.id idpar, c.id idch, ST_Area(p.geom) par_area
    FROM data p 
    JOIN data c ON ST_Contains(p.geom, ST_PointOnSurface(c.geom))
    WHERE ST_Area(p.geom) > ST_Area(c.geom)
),
-- extract direct containment links, by choosing parent with min area
pcrel AS ( SELECT DISTINCT ON (idch) idpar, idch 
    FROM contains ORDER BY idch, par_area ASC
),
-- compute paths as strings
pcpath(id, path) AS (
    SELECT 'A' AS id, 'A' AS path
    UNION ALL
    SELECT idch AS id, path || ',' || idch
        FROM pcpath JOIN pcrel ON pcpath.id = pcrel.idpar
)
SELECT * FROM pcpath;

Polygon / Line

Find Start Points of Rivers and Headwater polygons

https://gis.stackexchange.com/questions/131806/find-start-of-river https://gis.stackexchange.com/questions/132266/find-headwater-polygons

Find routes which terminate in Polygons but do not cross them

https://gis.stackexchange.com/questions/254051/selecting-lines-with-start-and-end-points-inside-polygons-but-do-not-cross-them

Find Lines that touch Polygon at both ends

https://gis.stackexchange.com/questions/299319/select-only-lines-that-touch-both-sides-of-polygon-postgis

Find Lines that touch but do not cross Polygons

https://gis.stackexchange.com/questions/160142/intersection-between-line-polygon-in-postgis

SELECT lines.geom
 FROM lines, polygons
 WHERE ST_Touches(lines.geom, polygons.geom) AND
                 NOT EXISTS (SELECT 1 FROM polygons p2 WHERE ST_Crosses(lines.geom, p2.geom));

Line / Line

Find LineStrings with Common Segments

https://gis.stackexchange.com/questions/268147/find-linestrings-with-common-segments-in-postgis-2-3

SELECT ST_Relate('LINESTRING(0 0, 2 0)'::geometry,
                 'LINESTRING(1 0, 2 0)'::geometry,
                 '1********');

Line / Point

Test if Point is on a Line

https://gis.stackexchange.com/questions/11510/st-closestpointline-point-does-not-intersect-line https://gis.stackexchange.com/questions/350461/find-path-containing-point