From 72f5f158282333ad833381154e436e548ee6e2dc Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Mon, 27 Feb 2023 22:05:24 -0500 Subject: [PATCH 01/14] Add LabelCenterline function --- sql/LabelCenterline.sql | 69 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 sql/LabelCenterline.sql diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql new file mode 100644 index 00000000..b6625a5f --- /dev/null +++ b/sql/LabelCenterline.sql @@ -0,0 +1,69 @@ +/****************************************************************************** +### LabelCenterline ### + +Given a polygon or multipolygon, calculates a linestring suitable for a label. + +__Parameters:__ + +- `geometry` input - A polygon or multipolygon. + +__Returns:__ `geometry(linestring)` +******************************************************************************/ +CREATE OR REPLACE FUNCTION CountDisconnectedEndpoints(polyline geometry, line geometry) RETURNS integer + AS 'SELECT ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(polyline))) + - ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(ST_Difference(polyline, line)))) + - ST_NPoints(line) + 2;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; +CREATE OR REPLACE FUNCTION TrimmedCenterline(polyline geometry) RETURNS geometry AS ' + WITH tbla AS ( + SELECT polyline, (ST_Dump(polyline)).geom as line + ), + tblb AS ( + SELECT polyline, line as shortestBranchLine + FROM tbla + WHERE CountDisconnectedEndpoints(polyline, line) > 0 + ORDER BY ST_Length(line) ASC + LIMIT 1 + ), + tblc AS ( + SELECT ST_LineMerge(ST_Difference(polyline, shortestBranchLine)) as polyline + FROM tblb + ) + SELECT TrimmedCenterline(polyline) as polyline + FROM tblc + WHERE ST_NumGeometries(polyline) > 1 + UNION ALL + SELECT polyline FROM tblc +;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; +CREATE OR REPLACE FUNCTION LabelCenterline(input geometry) RETURNS geometry AS ' + WITH tbla AS ( + SELECT input as polygon WHERE ST_GeometryType(input) = ''ST_Polygon'' + UNION ALL + SELECT ST_ConcaveHull(input, 0.1) as polygon WHERE ST_GeometryType(input)=''ST_MultiPolygon'' + ), + tblb AS ( + SELECT ST_MakePolygon(ST_ExteriorRing(polygon)) as polygon + FROM tbla + ), + tblc AS ( + SELECT polygon, (ST_Dump(ST_VoronoiLines(ST_LineInterpolatePoints(ST_Boundary(polygon), 0.0075)))).geom as lines + FROM tblb + WHERE ST_GeometryType(polygon) = ''ST_Polygon'' + ), + tbld AS ( + SELECT ST_LineMerge(ST_Collect(lines)) as polyline + FROM tblc + WHERE ST_Contains(polygon, lines) + ) + SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(TrimmedCenterline(polyline), 80), 3, false) FROM tbld +;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; + + From c17a3d750843037ca102dd6d155b0e01a04b778c Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Mon, 27 Feb 2023 22:39:56 -0500 Subject: [PATCH 02/14] Polygon is implied at this step --- sql/LabelCenterline.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index b6625a5f..1c9eea41 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -53,7 +53,6 @@ CREATE OR REPLACE FUNCTION LabelCenterline(input geometry) RETURNS geometry AS ' tblc AS ( SELECT polygon, (ST_Dump(ST_VoronoiLines(ST_LineInterpolatePoints(ST_Boundary(polygon), 0.0075)))).geom as lines FROM tblb - WHERE ST_GeometryType(polygon) = ''ST_Polygon'' ), tbld AS ( SELECT ST_LineMerge(ST_Collect(lines)) as polyline From 3d5b0c8fea3e9ca3d775945d36197062e2876e54 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Tue, 28 Feb 2023 10:44:16 -0500 Subject: [PATCH 03/14] Convert function from string definitions to plpgsql begin/end Speed up multipolygon hull calculations somewhat --- sql/LabelCenterline.sql | 118 +++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 56 deletions(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index 1c9eea41..7edb7151 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -5,64 +5,70 @@ Given a polygon or multipolygon, calculates a linestring suitable for a label. __Parameters:__ -- `geometry` input - A polygon or multipolygon. +- `geometry` inGeometry - A polygon or multipolygon. -__Returns:__ `geometry(linestring)` +__Returns:__ `geometry(multiline)` ******************************************************************************/ -CREATE OR REPLACE FUNCTION CountDisconnectedEndpoints(polyline geometry, line geometry) RETURNS integer - AS 'SELECT ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(polyline))) - - ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(ST_Difference(polyline, line)))) - - ST_NPoints(line) + 2;' - LANGUAGE SQL - IMMUTABLE - RETURNS NULL ON NULL INPUT; -CREATE OR REPLACE FUNCTION TrimmedCenterline(polyline geometry) RETURNS geometry AS ' - WITH tbla AS ( - SELECT polyline, (ST_Dump(polyline)).geom as line - ), - tblb AS ( - SELECT polyline, line as shortestBranchLine - FROM tbla - WHERE CountDisconnectedEndpoints(polyline, line) > 0 - ORDER BY ST_Length(line) ASC - LIMIT 1 - ), - tblc AS ( - SELECT ST_LineMerge(ST_Difference(polyline, shortestBranchLine)) as polyline - FROM tblb - ) - SELECT TrimmedCenterline(polyline) as polyline - FROM tblc - WHERE ST_NumGeometries(polyline) > 1 - UNION ALL - SELECT polyline FROM tblc -;' - LANGUAGE SQL - IMMUTABLE - RETURNS NULL ON NULL INPUT; -CREATE OR REPLACE FUNCTION LabelCenterline(input geometry) RETURNS geometry AS ' - WITH tbla AS ( - SELECT input as polygon WHERE ST_GeometryType(input) = ''ST_Polygon'' - UNION ALL - SELECT ST_ConcaveHull(input, 0.1) as polygon WHERE ST_GeometryType(input)=''ST_MultiPolygon'' - ), - tblb AS ( - SELECT ST_MakePolygon(ST_ExteriorRing(polygon)) as polygon - FROM tbla - ), - tblc AS ( - SELECT polygon, (ST_Dump(ST_VoronoiLines(ST_LineInterpolatePoints(ST_Boundary(polygon), 0.0075)))).geom as lines - FROM tblb - ), - tbld AS ( - SELECT ST_LineMerge(ST_Collect(lines)) as polyline +CREATE OR REPLACE FUNCTION CountDisconnectedEndpoints(polyline geometry, line geometry) + RETURNS integer AS $$ + BEGIN + RETURN ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(polyline))) + - ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(ST_Difference(polyline, line)))) + - ST_NPoints(line) + 2; + END + $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) + RETURNS geometry AS $$ + DECLARE outPolyline geometry; + BEGIN + WITH tbla AS ( + SELECT inPolyline as polyline, (ST_Dump(inPolyline)).geom as line + ), + tblb AS ( + SELECT polyline, line as shortestBranchLine + FROM tbla + WHERE CountDisconnectedEndpoints(polyline, line) > 0 + ORDER BY ST_Length(line) ASC + LIMIT 1 + ), + tblc AS ( + SELECT ST_LineMerge(ST_Difference(polyline, shortestBranchLine)) as polyline + FROM tblb + ) + SELECT TrimmedCenterline(polyline) as polyline FROM tblc - WHERE ST_Contains(polygon, lines) - ) - SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(TrimmedCenterline(polyline), 80), 3, false) FROM tbld -;' - LANGUAGE SQL - IMMUTABLE - RETURNS NULL ON NULL INPUT; + WHERE ST_NumGeometries(polyline) > 1 + UNION ALL + SELECT polyline INTO outPolyline FROM tblc; + RETURN outPolyline; + END + $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION LabelCenterline(inGeometry geometry) + RETURNS geometry AS $$ + DECLARE outPolyline geometry; + BEGIN + WITH tbla AS ( + SELECT inGeometry as polygon WHERE ST_GeometryType(inGeometry) = 'ST_Polygon' + UNION ALL + SELECT ST_ConcaveHull(ST_Points(ST_Simplify(inGeometry, 25)), 0.2) as polygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' + ), + tblb AS ( + SELECT ST_MakePolygon(ST_ExteriorRing(polygon)) as polygon + FROM tbla + ), + tblc AS ( + SELECT polygon, (ST_Dump(ST_VoronoiLines(ST_LineInterpolatePoints(ST_Boundary(polygon), 0.0075)))).geom as lines + FROM tblb + ), + tbld AS ( + SELECT ST_LineMerge(ST_Collect(lines)) as polyline + FROM tblc + WHERE ST_Contains(polygon, lines) + ) + SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(TrimmedCenterline(polyline), 80), 3, false) INTO outPolyline FROM tbld; + RETURN outPolyline; + END + $$ LANGUAGE plpgsql; + From 966c0a5dd34196659dcd490ac836e7f82e975a78 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Tue, 28 Feb 2023 10:51:31 -0500 Subject: [PATCH 04/14] Use more descriptive variable names --- sql/LabelCenterline.sql | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index 7edb7151..acac30ea 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -9,12 +9,12 @@ __Parameters:__ __Returns:__ `geometry(multiline)` ******************************************************************************/ -CREATE OR REPLACE FUNCTION CountDisconnectedEndpoints(polyline geometry, line geometry) +CREATE OR REPLACE FUNCTION CountDisconnectedEndpoints(polyline geometry, testline geometry) RETURNS integer AS $$ BEGIN RETURN ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(polyline))) - - ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(ST_Difference(polyline, line)))) - - ST_NPoints(line) + 2; + - ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(ST_Difference(polyline, testline)))) + - ST_NPoints(testline) + 2; END $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) @@ -22,13 +22,13 @@ CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) DECLARE outPolyline geometry; BEGIN WITH tbla AS ( - SELECT inPolyline as polyline, (ST_Dump(inPolyline)).geom as line + SELECT inPolyline as polyline, (ST_Dump(inPolyline)).geom as edge ), tblb AS ( - SELECT polyline, line as shortestBranchLine + SELECT polyline, edge as shortestBranchLine FROM tbla - WHERE CountDisconnectedEndpoints(polyline, line) > 0 - ORDER BY ST_Length(line) ASC + WHERE CountDisconnectedEndpoints(polyline, edge) > 0 + ORDER BY ST_Length(edge) ASC LIMIT 1 ), tblc AS ( @@ -48,27 +48,24 @@ CREATE OR REPLACE FUNCTION LabelCenterline(inGeometry geometry) DECLARE outPolyline geometry; BEGIN WITH tbla AS ( - SELECT inGeometry as polygon WHERE ST_GeometryType(inGeometry) = 'ST_Polygon' + SELECT inGeometry as inPolygon WHERE ST_GeometryType(inGeometry) = 'ST_Polygon' UNION ALL - SELECT ST_ConcaveHull(ST_Points(ST_Simplify(inGeometry, 25)), 0.2) as polygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' + SELECT ST_ConcaveHull(ST_Points(ST_Simplify(inGeometry, 25)), 0.2) as inPolygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' ), tblb AS ( - SELECT ST_MakePolygon(ST_ExteriorRing(polygon)) as polygon + SELECT ST_MakePolygon(ST_ExteriorRing(inPolygon)) as shellPolygon FROM tbla ), tblc AS ( - SELECT polygon, (ST_Dump(ST_VoronoiLines(ST_LineInterpolatePoints(ST_Boundary(polygon), 0.0075)))).geom as lines + SELECT shellPolygon, (ST_Dump(ST_VoronoiLines(ST_LineInterpolatePoints(ST_Boundary(shellPolygon), 0.0075)))).geom as voroniLines FROM tblb ), tbld AS ( - SELECT ST_LineMerge(ST_Collect(lines)) as polyline + SELECT ST_LineMerge(ST_Collect(voroniLines)) as voroniPolyline FROM tblc - WHERE ST_Contains(polygon, lines) + WHERE ST_Contains(shellPolygon, voroniLines) ) - SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(TrimmedCenterline(polyline), 80), 3, false) INTO outPolyline FROM tbld; + SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(TrimmedCenterline(voroniPolyline), 80), 3, false) INTO outPolyline FROM tbld; RETURN outPolyline; END $$ LANGUAGE plpgsql; - - - From 3d77ebc2ec9c19deff4a934605b99f1271f4b1f5 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Tue, 28 Feb 2023 16:22:37 -0500 Subject: [PATCH 05/14] Multipoint-based hull causes some issues --- sql/LabelCenterline.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index acac30ea..86b07de0 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -50,7 +50,7 @@ CREATE OR REPLACE FUNCTION LabelCenterline(inGeometry geometry) WITH tbla AS ( SELECT inGeometry as inPolygon WHERE ST_GeometryType(inGeometry) = 'ST_Polygon' UNION ALL - SELECT ST_ConcaveHull(ST_Points(ST_Simplify(inGeometry, 25)), 0.2) as inPolygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' + SELECT ST_ConcaveHull(ST_Simplify(inGeometry, 25), 0.2) as inPolygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' ), tblb AS ( SELECT ST_MakePolygon(ST_ExteriorRing(inPolygon)) as shellPolygon From 5fe3a050b3ac59b6841b98407b0c830e774613e5 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Sun, 5 Mar 2023 13:19:02 -0500 Subject: [PATCH 06/14] 2x faster execution by avoiding ST_Difference entirely --- sql/LabelCenterline.sql | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index 86b07de0..adea8f7e 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -11,10 +11,17 @@ __Returns:__ `geometry(multiline)` ******************************************************************************/ CREATE OR REPLACE FUNCTION CountDisconnectedEndpoints(polyline geometry, testline geometry) RETURNS integer AS $$ + DECLARE count integer; BEGIN - RETURN ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(polyline))) - - ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(ST_Difference(polyline, testline)))) - - ST_NPoints(testline) + 2; + WITH linesExceptTestline AS ( + SELECT (ST_Dump(polyline)).geom as linestring + EXCEPT + SELECT testline as linestring + ) + SELECT ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(polyline))) + - ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(ST_Collect(linestring)))) + - ST_NPoints(testline) + 2 INTO count FROM linesExceptTestline; + RETURN count; END $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) @@ -22,24 +29,29 @@ CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) DECLARE outPolyline geometry; BEGIN WITH tbla AS ( - SELECT inPolyline as polyline, (ST_Dump(inPolyline)).geom as edge + SELECT (ST_Dump(inPolyline)).geom as edge ), tblb AS ( - SELECT polyline, edge as shortestBranchLine + SELECT edge FROM tbla - WHERE CountDisconnectedEndpoints(polyline, edge) > 0 + WHERE CountDisconnectedEndpoints(inPolyline, edge) > 0 ORDER BY ST_Length(edge) ASC LIMIT 1 ), tblc AS ( - SELECT ST_LineMerge(ST_Difference(polyline, shortestBranchLine)) as polyline - FROM tblb + SELECT * FROM tbla + EXCEPT + SELECT * FROM tblb + ), + tbld AS ( + SELECT ST_LineMerge(ST_Collect(edge)) as polyline + FROM tblc ) SELECT TrimmedCenterline(polyline) as polyline - FROM tblc + FROM tbld WHERE ST_NumGeometries(polyline) > 1 UNION ALL - SELECT polyline INTO outPolyline FROM tblc; + SELECT polyline INTO outPolyline FROM tbld; RETURN outPolyline; END $$ LANGUAGE plpgsql; From 8eba5d57a3de6835eaf6060e495063c9f35d0d9d Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Sun, 5 Mar 2023 14:15:43 -0500 Subject: [PATCH 07/14] Create separate centerline for each part of a multipolygon instead of using a concave hull --- sql/LabelCenterline.sql | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index adea8f7e..fd0b4826 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -59,25 +59,28 @@ CREATE OR REPLACE FUNCTION LabelCenterline(inGeometry geometry) RETURNS geometry AS $$ DECLARE outPolyline geometry; BEGIN - WITH tbla AS ( + WITH polygons AS ( SELECT inGeometry as inPolygon WHERE ST_GeometryType(inGeometry) = 'ST_Polygon' UNION ALL - SELECT ST_ConcaveHull(ST_Simplify(inGeometry, 25), 0.2) as inPolygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' + SELECT (ST_Dump(inGeometry)).geom as inPolygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' ), - tblb AS ( + shellPolygons AS ( SELECT ST_MakePolygon(ST_ExteriorRing(inPolygon)) as shellPolygon - FROM tbla + FROM polygons ), - tblc AS ( + allVoroniLines AS ( SELECT shellPolygon, (ST_Dump(ST_VoronoiLines(ST_LineInterpolatePoints(ST_Boundary(shellPolygon), 0.0075)))).geom as voroniLines - FROM tblb + FROM shellPolygons ), - tbld AS ( + containedVoroniPolylines AS ( SELECT ST_LineMerge(ST_Collect(voroniLines)) as voroniPolyline - FROM tblc + FROM allVoroniLines WHERE ST_Contains(shellPolygon, voroniLines) + GROUP BY shellPolygon ) - SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(TrimmedCenterline(voroniPolyline), 80), 3, false) INTO outPolyline FROM tbld; + SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(ST_Collect(TrimmedCenterline(voroniPolyline)), 80), 3, false) as trimmedPolyline + INTO outPolyline + FROM containedVoroniPolylines; RETURN outPolyline; END $$ LANGUAGE plpgsql; From 9bf39d7fd3be5e4ff0b25a806b8612fe26daf039 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Sun, 5 Mar 2023 14:18:03 -0500 Subject: [PATCH 08/14] Uppercase `as` --- sql/LabelCenterline.sql | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index fd0b4826..79622b70 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -14,9 +14,9 @@ CREATE OR REPLACE FUNCTION CountDisconnectedEndpoints(polyline geometry, testlin DECLARE count integer; BEGIN WITH linesExceptTestline AS ( - SELECT (ST_Dump(polyline)).geom as linestring + SELECT (ST_Dump(polyline)).geom AS linestring EXCEPT - SELECT testline as linestring + SELECT testline AS linestring ) SELECT ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(polyline))) - ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(ST_Collect(linestring)))) @@ -29,7 +29,7 @@ CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) DECLARE outPolyline geometry; BEGIN WITH tbla AS ( - SELECT (ST_Dump(inPolyline)).geom as edge + SELECT (ST_Dump(inPolyline)).geom AS edge ), tblb AS ( SELECT edge @@ -44,10 +44,10 @@ CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) SELECT * FROM tblb ), tbld AS ( - SELECT ST_LineMerge(ST_Collect(edge)) as polyline + SELECT ST_LineMerge(ST_Collect(edge)) AS polyline FROM tblc ) - SELECT TrimmedCenterline(polyline) as polyline + SELECT TrimmedCenterline(polyline) AS polyline FROM tbld WHERE ST_NumGeometries(polyline) > 1 UNION ALL @@ -60,25 +60,25 @@ CREATE OR REPLACE FUNCTION LabelCenterline(inGeometry geometry) DECLARE outPolyline geometry; BEGIN WITH polygons AS ( - SELECT inGeometry as inPolygon WHERE ST_GeometryType(inGeometry) = 'ST_Polygon' + SELECT inGeometry AS inPolygon WHERE ST_GeometryType(inGeometry) = 'ST_Polygon' UNION ALL - SELECT (ST_Dump(inGeometry)).geom as inPolygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' + SELECT (ST_Dump(inGeometry)).geom AS inPolygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' ), shellPolygons AS ( - SELECT ST_MakePolygon(ST_ExteriorRing(inPolygon)) as shellPolygon + SELECT ST_MakePolygon(ST_ExteriorRing(inPolygon)) AS shellPolygon FROM polygons ), allVoroniLines AS ( - SELECT shellPolygon, (ST_Dump(ST_VoronoiLines(ST_LineInterpolatePoints(ST_Boundary(shellPolygon), 0.0075)))).geom as voroniLines + SELECT shellPolygon, (ST_Dump(ST_VoronoiLines(ST_LineInterpolatePoints(ST_Boundary(shellPolygon), 0.0075)))).geom AS voroniLines FROM shellPolygons ), containedVoroniPolylines AS ( - SELECT ST_LineMerge(ST_Collect(voroniLines)) as voroniPolyline + SELECT ST_LineMerge(ST_Collect(voroniLines)) AS voroniPolyline FROM allVoroniLines WHERE ST_Contains(shellPolygon, voroniLines) GROUP BY shellPolygon ) - SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(ST_Collect(TrimmedCenterline(voroniPolyline)), 80), 3, false) as trimmedPolyline + SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(ST_Collect(TrimmedCenterline(voroniPolyline)), 80), 3, false) AS trimmedPolyline INTO outPolyline FROM containedVoroniPolylines; RETURN outPolyline; From 4d3474c8b27cd4976afc08a73174cc2f0520c9a6 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Sun, 5 Mar 2023 14:30:16 -0500 Subject: [PATCH 09/14] Avoid unnecessary plpgsql. Make functions immutable, strict, parallel safe --- sql/LabelCenterline.sql | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index 79622b70..69a2b70e 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -10,9 +10,9 @@ __Parameters:__ __Returns:__ `geometry(multiline)` ******************************************************************************/ CREATE OR REPLACE FUNCTION CountDisconnectedEndpoints(polyline geometry, testline geometry) - RETURNS integer AS $$ - DECLARE count integer; - BEGIN + RETURNS integer + LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE + BEGIN ATOMIC WITH linesExceptTestline AS ( SELECT (ST_Dump(polyline)).geom AS linestring EXCEPT @@ -20,14 +20,12 @@ CREATE OR REPLACE FUNCTION CountDisconnectedEndpoints(polyline geometry, testlin ) SELECT ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(polyline))) - ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(ST_Collect(linestring)))) - - ST_NPoints(testline) + 2 INTO count FROM linesExceptTestline; - RETURN count; - END - $$ LANGUAGE plpgsql; + - ST_NPoints(testline) + 2 FROM linesExceptTestline; + END; CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) - RETURNS geometry AS $$ - DECLARE outPolyline geometry; - BEGIN + RETURNS geometry + LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE + BEGIN ATOMIC WITH tbla AS ( SELECT (ST_Dump(inPolyline)).geom AS edge ), @@ -51,14 +49,12 @@ CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) FROM tbld WHERE ST_NumGeometries(polyline) > 1 UNION ALL - SELECT polyline INTO outPolyline FROM tbld; - RETURN outPolyline; - END - $$ LANGUAGE plpgsql; + SELECT polyline FROM tbld; + END; CREATE OR REPLACE FUNCTION LabelCenterline(inGeometry geometry) - RETURNS geometry AS $$ - DECLARE outPolyline geometry; - BEGIN + RETURNS geometry + LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE + BEGIN ATOMIC WITH polygons AS ( SELECT inGeometry AS inPolygon WHERE ST_GeometryType(inGeometry) = 'ST_Polygon' UNION ALL @@ -79,8 +75,5 @@ CREATE OR REPLACE FUNCTION LabelCenterline(inGeometry geometry) GROUP BY shellPolygon ) SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(ST_Collect(TrimmedCenterline(voroniPolyline)), 80), 3, false) AS trimmedPolyline - INTO outPolyline FROM containedVoroniPolylines; - RETURN outPolyline; - END - $$ LANGUAGE plpgsql; + END; From ada765382830c93baa2be8e27635df5397d7a687 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Sun, 5 Mar 2023 14:34:57 -0500 Subject: [PATCH 10/14] Name remaining CTE tables --- sql/LabelCenterline.sql | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index 69a2b70e..dc9ebb39 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -26,30 +26,30 @@ CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) RETURNS geometry LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE BEGIN ATOMIC - WITH tbla AS ( + WITH edges AS ( SELECT (ST_Dump(inPolyline)).geom AS edge ), - tblb AS ( - SELECT edge - FROM tbla + shortestBranchEdge AS ( + SELECT * + FROM edges WHERE CountDisconnectedEndpoints(inPolyline, edge) > 0 ORDER BY ST_Length(edge) ASC LIMIT 1 ), - tblc AS ( - SELECT * FROM tbla + edgesWithoutShortestBranch AS ( + SELECT * FROM edges EXCEPT - SELECT * FROM tblb + SELECT * FROM shortestBranchEdge ), - tbld AS ( + trimmedPolyline AS ( SELECT ST_LineMerge(ST_Collect(edge)) AS polyline - FROM tblc + FROM edgesWithoutShortestBranch ) SELECT TrimmedCenterline(polyline) AS polyline - FROM tbld + FROM trimmedPolyline WHERE ST_NumGeometries(polyline) > 1 UNION ALL - SELECT polyline FROM tbld; + SELECT polyline FROM trimmedPolyline; END; CREATE OR REPLACE FUNCTION LabelCenterline(inGeometry geometry) RETURNS geometry From 5c47d32e907d3f654ce90bc029dc7d2b26c41ce8 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Sun, 5 Mar 2023 16:00:32 -0500 Subject: [PATCH 11/14] Add parameters to control centerline creation --- sql/LabelCenterline.sql | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index dc9ebb39..44df0b00 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -6,6 +6,10 @@ Given a polygon or multipolygon, calculates a linestring suitable for a label. __Parameters:__ - `geometry` inGeometry - A polygon or multipolygon. +- `integer` maxVoronoiVertices - The maximum number of points per polygon of the `geometry` that will be used to calculate the centerline. Lower values will significantly increase execution speed but can result in much less accurate results. Set to `0` to use all points of the input geometry (maximum accuracy). +- `float` sourceSimplification - The `tolerance` parameter of `ST_SimplifyPreserveTopology`, applied to the polygon before the centerline is calculated. Higher values will increase execution speed but can result in less accurate results. Set to `0` to disable simplification (maximum accuracy). +- `float` simplification - The `tolerance` parameter of `ST_SimplifyPreserveTopology`, applied to the output centerline. Does not appreciably affect performance. Set to `0` to disable simplification. +- `integer` smoothingReps - The number of smoothing iterations to apply to the output centerline. Corresponds to the `nIterations` parameter of `ST_ChaikinSmoothing`. Does not appreciably affect performance. Set to `0` to disable smoothing. __Returns:__ `geometry(multiline)` ******************************************************************************/ @@ -51,7 +55,14 @@ CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) UNION ALL SELECT polyline FROM trimmedPolyline; END; -CREATE OR REPLACE FUNCTION LabelCenterline(inGeometry geometry) +DROP FUNCTION LabelCenterline(geometry, integer, float, float, integer); +CREATE OR REPLACE FUNCTION LabelCenterline( + inGeometry geometry, + maxVoronoiVertices integer default 100, + sourceSimplification float default 0, + simplification float default 100, + smoothingReps integer default 3 +) RETURNS geometry LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE BEGIN ATOMIC @@ -61,19 +72,37 @@ CREATE OR REPLACE FUNCTION LabelCenterline(inGeometry geometry) SELECT (ST_Dump(inGeometry)).geom AS inPolygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' ), shellPolygons AS ( - SELECT ST_MakePolygon(ST_ExteriorRing(inPolygon)) AS shellPolygon + SELECT ST_SimplifyPreserveTopology(ST_MakePolygon(ST_ExteriorRing(inPolygon)), sourceSimplification) AS shellPolygon FROM polygons ), - allVoroniLines AS ( - SELECT shellPolygon, (ST_Dump(ST_VoronoiLines(ST_LineInterpolatePoints(ST_Boundary(shellPolygon), 0.0075)))).geom AS voroniLines + shellVertices AS ( + SELECT shellPolygon, ST_LineInterpolatePoints(ST_Boundary(shellPolygon), 1.0/maxVoronoiVertices) as vertices + FROM shellPolygons + WHERE maxVoronoiVertices > 0 AND ST_NPoints(shellPolygon) > maxVoronoiVertices + UNION ALL + SELECT shellPolygon, ST_Points(shellPolygon) as vertices FROM shellPolygons + WHERE maxVoronoiVertices = 0 OR ST_NPoints(shellPolygon) <= maxVoronoiVertices + ), + allVoroniLines AS ( + SELECT shellPolygon, (ST_Dump(ST_VoronoiLines(vertices))).geom AS voroniLines + FROM shellVertices ), containedVoroniPolylines AS ( SELECT ST_LineMerge(ST_Collect(voroniLines)) AS voroniPolyline FROM allVoroniLines WHERE ST_Contains(shellPolygon, voroniLines) GROUP BY shellPolygon + ), + simplifedCenterline AS ( + SELECT ST_SimplifyPreserveTopology(ST_Collect(TrimmedCenterline(voroniPolyline)), simplification) AS polyline + FROM containedVoroniPolylines ) - SELECT ST_ChaikinSmoothing(ST_SimplifyPreserveTopology(ST_Collect(TrimmedCenterline(voroniPolyline)), 80), 3, false) AS trimmedPolyline - FROM containedVoroniPolylines; + SELECT ST_ChaikinSmoothing(polyline, smoothingReps, false) AS polyline + FROM simplifedCenterline + WHERE smoothingReps != 0 + UNION ALL + SELECT * + FROM simplifedCenterline + WHERE smoothingReps = 0; END; From a83ebde55de03cf522b87d97548d5375e393868b Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Sun, 5 Mar 2023 16:31:34 -0500 Subject: [PATCH 12/14] Don't need drop --- sql/LabelCenterline.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index 44df0b00..00d08326 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -55,7 +55,6 @@ CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) UNION ALL SELECT polyline FROM trimmedPolyline; END; -DROP FUNCTION LabelCenterline(geometry, integer, float, float, integer); CREATE OR REPLACE FUNCTION LabelCenterline( inGeometry geometry, maxVoronoiVertices integer default 100, From d1586bd28c410942a60162babf0b1d505c8317ea Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Sun, 5 Mar 2023 20:23:38 -0500 Subject: [PATCH 13/14] Add parameter to enable multiple branches in the output (useful for polygons with lots bulges/arms) --- sql/LabelCenterline.sql | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index 00d08326..d5bcd529 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -1,13 +1,14 @@ /****************************************************************************** ### LabelCenterline ### -Given a polygon or multipolygon, calculates a linestring suitable for a label. +Given a polygon or multipolygon, calculates one or more linestrings suitable for labeling. __Parameters:__ - `geometry` inGeometry - A polygon or multipolygon. - `integer` maxVoronoiVertices - The maximum number of points per polygon of the `geometry` that will be used to calculate the centerline. Lower values will significantly increase execution speed but can result in much less accurate results. Set to `0` to use all points of the input geometry (maximum accuracy). - `float` sourceSimplification - The `tolerance` parameter of `ST_SimplifyPreserveTopology`, applied to the polygon before the centerline is calculated. Higher values will increase execution speed but can result in less accurate results. Set to `0` to disable simplification (maximum accuracy). +- `float` minSegmentLengthRatio - The minimum length allowed for a branch or independent segment relative to the total length of the output. Set to `1` to return only a single centerline. Set to `0` to include all branches (not recommended). Values between `0.15 and 0.25` tend to have nice results for polygons with multiple "arms". - `float` simplification - The `tolerance` parameter of `ST_SimplifyPreserveTopology`, applied to the output centerline. Does not appreciably affect performance. Set to `0` to disable simplification. - `integer` smoothingReps - The number of smoothing iterations to apply to the output centerline. Corresponds to the `nIterations` parameter of `ST_ChaikinSmoothing`. Does not appreciably affect performance. Set to `0` to disable smoothing. @@ -26,18 +27,26 @@ CREATE OR REPLACE FUNCTION CountDisconnectedEndpoints(polyline geometry, testlin - ST_NPoints(ST_RemoveRepeatedPoints(ST_Points(ST_Collect(linestring)))) - ST_NPoints(testline) + 2 FROM linesExceptTestline; END; -CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) +CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry, minSegmentLengthRatio float) RETURNS geometry LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE BEGIN ATOMIC WITH edges AS ( SELECT (ST_Dump(inPolyline)).geom AS edge ), - shortestBranchEdge AS ( - SELECT * + edgesWithLength AS ( + SELECT edge, ST_Length(edge) AS edgeLength FROM edges + ), + edgesSummary AS ( + SELECT sum(edgeLength) AS totalEdgeLength FROM edgesWithLength + ), + shortestBranchEdge AS ( + SELECT edge + FROM edgesWithLength WHERE CountDisconnectedEndpoints(inPolyline, edge) > 0 - ORDER BY ST_Length(edge) ASC + AND edgeLength < (SELECT totalEdgeLength FROM edgesSummary) * minSegmentLengthRatio + ORDER BY edgeLength ASC LIMIT 1 ), edgesWithoutShortestBranch AS ( @@ -49,9 +58,11 @@ CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry) SELECT ST_LineMerge(ST_Collect(edge)) AS polyline FROM edgesWithoutShortestBranch ) - SELECT TrimmedCenterline(polyline) AS polyline + SELECT TrimmedCenterline(polyline, minSegmentLengthRatio) AS polyline FROM trimmedPolyline WHERE ST_NumGeometries(polyline) > 1 + -- if we didn't trim anything then we're done + AND ST_NumGeometries(polyline) != ST_NumGeometries(inPolyline) UNION ALL SELECT polyline FROM trimmedPolyline; END; @@ -59,6 +70,7 @@ CREATE OR REPLACE FUNCTION LabelCenterline( inGeometry geometry, maxVoronoiVertices integer default 100, sourceSimplification float default 0, + minSegmentLengthRatio float default 1, --0.175 is a good value if you want to allow a couple big branches simplification float default 100, smoothingReps integer default 3 ) @@ -75,11 +87,11 @@ CREATE OR REPLACE FUNCTION LabelCenterline( FROM polygons ), shellVertices AS ( - SELECT shellPolygon, ST_LineInterpolatePoints(ST_Boundary(shellPolygon), 1.0/maxVoronoiVertices) as vertices + SELECT shellPolygon, ST_LineInterpolatePoints(ST_Boundary(shellPolygon), 1.0/maxVoronoiVertices) AS vertices FROM shellPolygons WHERE maxVoronoiVertices > 0 AND ST_NPoints(shellPolygon) > maxVoronoiVertices UNION ALL - SELECT shellPolygon, ST_Points(shellPolygon) as vertices + SELECT shellPolygon, ST_Points(shellPolygon) AS vertices FROM shellPolygons WHERE maxVoronoiVertices = 0 OR ST_NPoints(shellPolygon) <= maxVoronoiVertices ), @@ -94,10 +106,10 @@ CREATE OR REPLACE FUNCTION LabelCenterline( GROUP BY shellPolygon ), simplifedCenterline AS ( - SELECT ST_SimplifyPreserveTopology(ST_Collect(TrimmedCenterline(voroniPolyline)), simplification) AS polyline + SELECT ST_SimplifyPreserveTopology(ST_Union(TrimmedCenterline(voroniPolyline, minSegmentLengthRatio)), simplification) AS polyline FROM containedVoroniPolylines ) - SELECT ST_ChaikinSmoothing(polyline, smoothingReps, false) AS polyline + SELECT ST_ChaikinSmoothing(polyline, smoothingReps, true /* preserveEndPoints */) AS polyline FROM simplifedCenterline WHERE smoothingReps != 0 UNION ALL From 9f75d705812a9b613878ef18ee82e96f71c99395 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Fri, 10 Mar 2023 13:42:36 -0500 Subject: [PATCH 14/14] Support holes Make simplification parameters ratios to scale to geometries of vastly different scales --- sql/LabelCenterline.sql | 96 ++++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/sql/LabelCenterline.sql b/sql/LabelCenterline.sql index d5bcd529..58abaab7 100644 --- a/sql/LabelCenterline.sql +++ b/sql/LabelCenterline.sql @@ -7,9 +7,10 @@ __Parameters:__ - `geometry` inGeometry - A polygon or multipolygon. - `integer` maxVoronoiVertices - The maximum number of points per polygon of the `geometry` that will be used to calculate the centerline. Lower values will significantly increase execution speed but can result in much less accurate results. Set to `0` to use all points of the input geometry (maximum accuracy). -- `float` sourceSimplification - The `tolerance` parameter of `ST_SimplifyPreserveTopology`, applied to the polygon before the centerline is calculated. Higher values will increase execution speed but can result in less accurate results. Set to `0` to disable simplification (maximum accuracy). +- `float` sourceSimplificationRatio - The `tolerance` parameter of `ST_SimplifyPreserveTopology` as a fraction of the geometry's bounding box, applied to the polygon before the centerline is calculated. Higher values will increase execution speed but can result in less accurate results. Set to `0` to disable simplification (maximum accuracy). - `float` minSegmentLengthRatio - The minimum length allowed for a branch or independent segment relative to the total length of the output. Set to `1` to return only a single centerline. Set to `0` to include all branches (not recommended). Values between `0.15 and 0.25` tend to have nice results for polygons with multiple "arms". -- `float` simplification - The `tolerance` parameter of `ST_SimplifyPreserveTopology`, applied to the output centerline. Does not appreciably affect performance. Set to `0` to disable simplification. +- `float` minHoleAreaRatio - The minimum area of a hole relative to the total area of the geometry for it to be included. Set to `1` to ignore all holes. Set to `0` to account for all holes. +- `float` simplificationRatio - The `tolerance` parameter of `ST_SimplifyPreserveTopology` as a fraction of the geometry's bounding box, applied to the output centerline. Does not appreciably affect performance. Set to `0` to disable simplification. - `integer` smoothingReps - The number of smoothing iterations to apply to the output centerline. Corresponds to the `nIterations` parameter of `ST_ChaikinSmoothing`. Does not appreciably affect performance. Set to `0` to disable smoothing. __Returns:__ `geometry(multiline)` @@ -69,45 +70,92 @@ CREATE OR REPLACE FUNCTION TrimmedCenterline(inPolyline geometry, minSegmentLeng CREATE OR REPLACE FUNCTION LabelCenterline( inGeometry geometry, maxVoronoiVertices integer default 100, - sourceSimplification float default 0, - minSegmentLengthRatio float default 1, --0.175 is a good value if you want to allow a couple big branches - simplification float default 100, + sourceSimplificationRatio float default 0.0001, + minSegmentLengthRatio float default 1, -- 0.175 is a good value if you want to allow a couple big branches + minHoleAreaRatio float default 1, -- 0.01 has decent results for ignoring all but the largest holes + simplificationRatio float default 0.02, smoothingReps integer default 3 ) RETURNS geometry LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE BEGIN ATOMIC - WITH polygons AS ( - SELECT inGeometry AS inPolygon WHERE ST_GeometryType(inGeometry) = 'ST_Polygon' + WITH inGeometryInfo AS ( + SELECT LEAST(ST_XMax(inGeometry)-ST_XMin(inGeometry), ST_YMax(inGeometry)-ST_YMin(inGeometry)) AS leastBBoxDimension + ), simplifiedGeometry AS ( + SELECT ST_MakeValid(ST_Simplify(inGeometry, (SELECT leastBBoxDimension * sourceSimplificationRatio FROM inGeometryInfo), false)) AS inGeometry + ), polygons AS ( + SELECT inGeometry AS inPolygon + FROM simplifiedGeometry + WHERE ST_GeometryType(inGeometry) = 'ST_Polygon' UNION ALL - SELECT (ST_Dump(inGeometry)).geom AS inPolygon WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' + SELECT (ST_Dump(inGeometry)).geom AS inPolygon + FROM simplifiedGeometry + WHERE ST_GeometryType(inGeometry)='ST_MultiPolygon' ), - shellPolygons AS ( - SELECT ST_SimplifyPreserveTopology(ST_MakePolygon(ST_ExteriorRing(inPolygon)), sourceSimplification) AS shellPolygon + polygonsSummary AS ( + SELECT inPolygon, ST_Area(Box2D(inPolygon)) AS exteriorRingArea, ST_DumpRings(inPolygon) AS ringInfo FROM polygons ), - shellVertices AS ( - SELECT shellPolygon, ST_LineInterpolatePoints(ST_Boundary(shellPolygon), 1.0/maxVoronoiVertices) AS vertices - FROM shellPolygons - WHERE maxVoronoiVertices > 0 AND ST_NPoints(shellPolygon) > maxVoronoiVertices + rings AS ( + SELECT inPolygon, exteriorRingArea, ST_ExteriorRing((ringInfo).geom) AS ring, (ringInfo).path[1] AS ringNum + FROM polygonsSummary + ), + largeEnoughRings AS ( + SELECT inPolygon, ring + FROM rings + --always keep the exterior ring + WHERE ringNum = 0 + OR ST_Area(Box2D(ST_LineInterpolatePoints(ring, 0.05))) >= exteriorRingArea * minHoleAreaRatio + ), + filteredRings AS ( + SELECT * + FROM largeEnoughRings + WHERE minHoleAreaRatio < 1 + UNION ALL + SELECT inPolygon, ST_ExteriorRing(inPolygon) AS ring + FROM polygons + WHERE minHoleAreaRatio >= 1 + ), + preppedRings AS ( + SELECT inPolygon, ring, ST_LineInterpolatePoints(ring, 1.0/maxVoronoiVertices) AS voronoiVertices + FROM filteredRings + WHERE maxVoronoiVertices > 0 AND ST_NPoints(ring) > maxVoronoiVertices UNION ALL - SELECT shellPolygon, ST_Points(shellPolygon) AS vertices - FROM shellPolygons - WHERE maxVoronoiVertices = 0 OR ST_NPoints(shellPolygon) <= maxVoronoiVertices + SELECT inPolygon, ring, ST_Points(ring) AS voronoiVertices + FROM filteredRings + WHERE maxVoronoiVertices = 0 OR ST_NPoints(ring) <= maxVoronoiVertices + ), + ringArrays AS ( + SELECT (array_agg(ring)) AS ringArray, ST_Collect(voronoiVertices) AS voronoiVertices + FROM preppedRings + GROUP BY inPolygon + ), + preppedPolygons AS ( + SELECT ST_MakePolygon(ringArray[1], ringArray[2:]) AS preppedPolygon, voronoiVertices + FROM ringArrays ), allVoroniLines AS ( - SELECT shellPolygon, (ST_Dump(ST_VoronoiLines(vertices))).geom AS voroniLines - FROM shellVertices + SELECT preppedPolygon, (ST_Dump(ST_VoronoiLines(voronoiVertices))).geom AS voroniLine + FROM preppedPolygons ), containedVoroniPolylines AS ( - SELECT ST_LineMerge(ST_Collect(voroniLines)) AS voroniPolyline + SELECT ST_LineMerge(ST_Collect(voroniLine)) AS voroniPolyline FROM allVoroniLines - WHERE ST_Contains(shellPolygon, voroniLines) - GROUP BY shellPolygon + WHERE ST_Contains(preppedPolygon, voroniLine) + GROUP BY preppedPolygon ), - simplifedCenterline AS ( - SELECT ST_SimplifyPreserveTopology(ST_Union(TrimmedCenterline(voroniPolyline, minSegmentLengthRatio)), simplification) AS polyline + centerline AS ( + SELECT ST_Union(TrimmedCenterline(voroniPolyline, minSegmentLengthRatio)) AS polyline FROM containedVoroniPolylines + ), + simplifedCenterline AS ( + SELECT ST_SimplifyPreserveTopology(polyline, (SELECT leastBBoxDimension*simplificationRatio FROM inGeometryInfo)) AS polyline + FROM centerline + WHERE simplificationRatio != 0 + UNION ALL + SELECT * + FROM centerline + WHERE simplificationRatio = 0 ) SELECT ST_ChaikinSmoothing(polyline, smoothingReps, true /* preserveEndPoints */) AS polyline FROM simplifedCenterline