ST_Touches for more than one geometry again

postgispostgresql

I do have a similar problem as described in Stack Exchange article … the whole story is a bit complex: what I really want is to create polygon-skeleton with ST_ApproximateMedialAxis but this one returns "straight skeleton of Polygon with point touching rings is not implemented" – which indicates that I have invalid polygons in my data.

Looking for a way to identify these polygons I carried on with the following PostGIS statements:

-------  create outer and inner rings  ----------
    --- outer rings 
    CREATE TABLE skeleton_tests.dump_outer_rings AS 
    SELECT 
          b.id,
         b.path AS gid,
          b.geom ::geometry(Polygon) as geom 
        FROM  
          (SELECT 
                    id,
                  (ST_DumpRings(geom)).*
                    FROM skeleton_tests.dump_gehst_ta_klein) b
    WHERE b.path[1] =0;  -- OUTER rings ARE = 0
    
    --- inner rings 
    CREATE TABLE skeleton_tests.dump_inner_rings AS 
    SELECT 
          b.id,
         b.path AS gid,
          b.geom ::geometry(Polygon) as geom 
        From 
          (SELECT 
                   id,
                    (ST_DumpRings(geom)).*
                    FROM skeleton_tests.dump_gehst_ta_klein) b
    WHERE b.path[1] > 0; -- >0 means inner ring

which identifies the ring-types and is just a preparatory step – to give you an idea of my input data for the next steps.
The following two versions of finding touching parts of inner rings raise my confusion …

This one does identify a couple of touching inner rings:

--- Inner Ring touches outer Ring   / with cross join lateral "iteration"
SELECT
 a.id,
 c.id,
 a.geom,
 c.geom
FROM skeleton_tests.dump_outer_rings as a
CROSS JOIN LATERAL 
  (
  SELECT 
     b.id,
     b.geom
  FROM skeleton_tests.dump_inner_rings AS b
  where ST_TOUCHES(st_boundary(a.geom), b.geom)) AS c
  ORDER BY a.id;

But this one – which would be much faster – doesn't find any touching parts when using ST_TOUCHES but it does find some when using ST_INTERSECTS:

SELECT a.id, b.id 
    FROM
   skeleton_tests.dump_outer_rings AS a INNER JOIN skeleton_tests.dump_inner_rings AS b 
   ON ST_intersects(a.geom, b.geom) 
   WHERE a.id != b.id;

Depending on the data it could make a difference in finding intersecting vs. touching parts!
Any hint about that phenomenon?

Best Answer

In the first query you are testing:

ST_TOUCHES(st_boundary(a.geom), b.geom))

whereas in the second query you are testing

ST_intersects(a.geom, b.geom)

In fact, those are both wrong!

First of all, since the ST_DumpRings return shells and holes as polygons (for some unknown reason), ST_Boundary needs to be used to extract the boundary ring linework (otherwise, the test is compromised due to presence of polygon interior areas). These needs to be done on both inputs to the test.

Second, you need to use ST_Intersects instead of ST_Touches. The reason is that the definition of ST_Touches is:

Returns TRUE if A and B intersect, but their interiors do not intersect.

This means that ST_Touches is not useful for checking whether rings touch, since any touch point is in the interior of the ring linework. For instance, in this example of touching rings the result is false:

SELECT ST_Touches('LINEARRING (100 100, 100 300, 300 100, 100 100)', 
  'LINEARRING (200 200, 150 200, 150 150, 200 150, 200 200)');

 st_touches 
------------
 f

enter image description here

Instead, using ST_Intersects is the correct way to test whether rings touch:

 SELECT ST_Intersects('LINESTRING (100 100, 100 300, 300 100, 100 100)', 
   'LINESTRING (200 200, 150 200, 150 150, 200 150, 200 200)');

 st_intersects 
---------------
 t

So the test condition should be:

ST_Intersects( ST_Boundary(a.geom), ST_Boundary(b.geom))
Related Question