Let's see if we can simulate @dr_jts Concave Polygon Hull algorithm within PostGIS - I haven't actually read the JTS algorithm, and have not tested this for 100% robustness, but it seems to work quite well...
The core idea is to check the (vector) direction of a given Point relative to the vector between its pre- and successor and the enclosing Polygon area, to determine it's concavity predicate. We do this by finding the determinant sign of the cross product between the preceding and succeeding Points, and the test Point; the sign then tells us if the so formed triangle is concave or convex with respect to the enclosed Polygon area.
First, let's create a utility function to calculate the cross product:
CREATE FUNCTION _ST_CrossProduct (
pt GEOMETRY(POINT),
v1 GEOMETRY(POINT),
v2 GEOMETRY(POINT)
) RETURNS FLOAT LANGUAGE 'plpgsql' AS
$BODY$
DECLARE
x1 FLOAT := ST_X($1); x2 FLOAT := ST_X($2); x3 FLOAT := ST_X($3);
y1 FLOAT := ST_Y($1); y2 FLOAT := ST_Y($2); y3 FLOAT := ST_Y($3);
BEGIN
RETURN ( x3-x2 ) * ( y1-y2 ) - ( y3-y2 ) * ( x1-x2 );
END;
$BODY$
IMMUTABLE STRICT PARALLEL SAFE
;
Then, we want to feed it the Point sequence of the Polygon boundary; this is an example of how to realize that most performantly:
SELECT _ST_CrossProduct(
COALESCE(
ST_PointN(bdy, i-1),
ST_PointN(bdy, -2)
),
ST_PointN(bdy, i),
ST_PointN(bdy, i+1)
) AS cross_product,
i,
ST_PointN(bdy, i) AS geom
FROM ply,
LATERAL ST_Boundary(ply.geom) AS bdy,
LATERAL Generate_Series(1, ST_NPoints(bdy)) AS i
;
We use COALESCE
to have the first Point in the sequence get compared to its respective pre- and successors.
Now, if the cross_product
is
- negative, this Point is to the left of the vector between its pre- and successor
- positive, this Point is to the right of the vector between its pre- and successor
- 0, this Point is co-linear with its pre- and successor
It is important to put this in perspective of the vertex order of your Polygon - for this example I assume a clockwise order!
With the above in mind, run this to get an estimated Polygon Concave Hull for your Polygon:
SELECT ST_MakePolygon(ST_AddPoint(geom, ST_PointN(geom, 1))) AS geom
FROM (
SELECT ST_MakeLine(ST_PointN(bdy, i) ORDER BY i) AS geom
FROM ply,
LATERAL ST_Boundary(ply.geom) AS bdy,
LATERAL Generate_Series(1, ST_NPoints(bdy)) AS i
WHERE _ST_CrossProduct(
COALESCE(
ST_PointN(bdy, i-1),
ST_PointN(bdy, -2)
),
ST_PointN(bdy, i),
ST_PointN(bdy, i+1)
) <= 0.0 -- <= 0 for an OUTER hull; >= 0 for an INNER hull
) q
;
You can ever so slightly go beyond 0.0
(e.g. <= 0.0001
or >= -0.0005
) to include almost co-linear Points of the opposite concavity! The below image is with <= 0.0
:
[Grey area]: reference Polygon | [Green line]: outer concave hull boundary | [Blue line]: inner concave hull boundary | [Black dots]: Polygon vertices
As it stands, this will only work with simple Polygon geometries, having no interior rings! For anything more sophisticated, this should be packed into a more efficient PL/pgSQL function.
If I find the time, I'll write up a function and add a link to the Github Gist.