From ca80dbeda0c715cb6d80f233bb479eba9ae08300 Mon Sep 17 00:00:00 2001 From: ebocher Date: Tue, 11 Jul 2023 10:33:07 +0200 Subject: [PATCH] Add two new functions ST_LineInterpolatePoint and ST_LineSubstring --- docs/CHANGELOG.md | 1 + .../functions/factory/H2GISFunctions.java | 6 +- .../ST_LineInterpolatePoint.java | 83 +++++++++++++ .../linear_referencing/ST_LineSubstring.java | 92 ++++++++++++++ .../LinearReferencingTest.java | 117 ++++++++++++++++++ 5 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 h2gis-functions/src/main/java/org/h2gis/functions/spatial/linear_referencing/ST_LineInterpolatePoint.java create mode 100644 h2gis-functions/src/main/java/org/h2gis/functions/spatial/linear_referencing/ST_LineSubstring.java create mode 100644 h2gis-functions/src/test/java/org/h2gis/functions/spatial/linear_referencing/LinearReferencingTest.java diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 18e08f5acd..834e0e2877 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,3 +6,4 @@ + Fix empty polygon with ST_TESSELLATE + Add ST_ConcaveHull function + Improve ST_Graph function to keep a list of columns for the edges ++ Add ST_LineSubstring and ST_LineInterpolatePoint functions diff --git a/h2gis-functions/src/main/java/org/h2gis/functions/factory/H2GISFunctions.java b/h2gis-functions/src/main/java/org/h2gis/functions/factory/H2GISFunctions.java index b5dfcd44ce..0402587900 100644 --- a/h2gis-functions/src/main/java/org/h2gis/functions/factory/H2GISFunctions.java +++ b/h2gis-functions/src/main/java/org/h2gis/functions/factory/H2GISFunctions.java @@ -62,6 +62,8 @@ import org.h2gis.functions.spatial.generalize.ST_PrecisionReducer; import org.h2gis.functions.spatial.generalize.ST_Simplify; import org.h2gis.functions.spatial.generalize.ST_SimplifyPreserveTopology; +import org.h2gis.functions.spatial.linear_referencing.ST_LineInterpolatePoint; +import org.h2gis.functions.spatial.linear_referencing.ST_LineSubstring; import org.h2gis.functions.spatial.mesh.ST_ConstrainedDelaunay; import org.h2gis.functions.spatial.mesh.ST_Delaunay; import org.h2gis.functions.spatial.mesh.ST_Tessellate; @@ -320,7 +322,9 @@ public static Function[] getBuiltInsFunctions() { new ST_MemSize(), new ST_Multi(), new ST_AsEWKB(), - new ST_ConcaveHull() + new ST_ConcaveHull(), + new ST_LineSubstring(), + new ST_LineInterpolatePoint() }; } diff --git a/h2gis-functions/src/main/java/org/h2gis/functions/spatial/linear_referencing/ST_LineInterpolatePoint.java b/h2gis-functions/src/main/java/org/h2gis/functions/spatial/linear_referencing/ST_LineInterpolatePoint.java new file mode 100644 index 0000000000..afb94fc29c --- /dev/null +++ b/h2gis-functions/src/main/java/org/h2gis/functions/spatial/linear_referencing/ST_LineInterpolatePoint.java @@ -0,0 +1,83 @@ +/** + * H2GIS is a library that brings spatial support to the H2 Database Engine + * http://www.h2database.com. H2GIS is developed by CNRS + * http://www.cnrs.fr/. + * + * This code is part of the H2GIS project. H2GIS is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation; + * version 3.0 of the License. + * + * H2GIS is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details . + * + * + * For more information, please consult: http://www.h2gis.org/ + * or contact directly: info_at_h2gis.org + */ +package org.h2gis.functions.spatial.linear_referencing; + +import org.h2gis.api.DeterministicScalarFunction; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.MultiLineString; +import org.locationtech.jts.linearref.LengthIndexedLine; + +import java.sql.SQLException; +import java.util.ArrayList; + +/** + * Returns a point interpolate along the input LineString or MultiLineString starting at the given fraction. + * @author Erwan Bocher, CNRS (2023) + */ +public class ST_LineInterpolatePoint extends DeterministicScalarFunction { + + public ST_LineInterpolatePoint(){ + addProperty(PROP_REMARKS, "Returns a point interpolate along the input LineString or MultiLineString starting at the given fraction."); + } + + @Override + public String getJavaStaticMethod() { + return "execute"; + } + + /** + * Returns a point interpolate along the input LineString or MultiLineString starting at the given fractions. + * @param geometry the input lines + * @param start the start fraction between 0 and 1 + * @return single or multiparts lines + * @throws SQLException + */ + public static Geometry execute(Geometry geometry, double start) throws SQLException { + if(geometry==null){ + return null; + } + if ( start < 0 || start > 1 ){ + throw new SQLException("Allowed between 0 and 1"); + } + + if(geometry.isEmpty()){ + return geometry; + } + + if(geometry instanceof LineString){ + double length = geometry.getLength(); + LengthIndexedLine ll = new LengthIndexedLine(geometry); + return geometry.getFactory().createPoint(ll.extractPoint(start*length)); + } else if (geometry instanceof MultiLineString) { + int nb = geometry.getNumGeometries(); + ArrayList points = new ArrayList<>(); + for (int i = 0; i < nb; i++) { + Geometry line = geometry.getGeometryN(i); + double length = line.getLength(); + LengthIndexedLine ll = new LengthIndexedLine(geometry.getGeometryN(i)); + points.add(ll.extractPoint(start*length)); + } + return geometry.getFactory().createMultiPointFromCoords(points.toArray(new Coordinate[0])); + } + throw new SQLException("Only LineString or MultiLineString are supported"); + } +} diff --git a/h2gis-functions/src/main/java/org/h2gis/functions/spatial/linear_referencing/ST_LineSubstring.java b/h2gis-functions/src/main/java/org/h2gis/functions/spatial/linear_referencing/ST_LineSubstring.java new file mode 100644 index 0000000000..bc47bf8e6f --- /dev/null +++ b/h2gis-functions/src/main/java/org/h2gis/functions/spatial/linear_referencing/ST_LineSubstring.java @@ -0,0 +1,92 @@ +/** + * H2GIS is a library that brings spatial support to the H2 Database Engine + * http://www.h2database.com. H2GIS is developed by CNRS + * http://www.cnrs.fr/. + * + * This code is part of the H2GIS project. H2GIS is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation; + * version 3.0 of the License. + * + * H2GIS is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details . + * + * + * For more information, please consult: http://www.h2gis.org/ + * or contact directly: info_at_h2gis.org + */ + +package org.h2gis.functions.spatial.linear_referencing; + +import org.h2gis.api.DeterministicScalarFunction; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.MultiLineString; +import org.locationtech.jts.linearref.LengthIndexedLine; + +import java.sql.SQLException; +import java.util.ArrayList; + +/** + * @author Erwan Bocher, CNRS (2023) + * Extract a section of the input line starting and ending at the given fractions. + */ +public class ST_LineSubstring extends DeterministicScalarFunction { + + + public ST_LineSubstring(){ + addProperty(PROP_REMARKS, "Extract a section of the input LineString or MultiLineString starting and ending at the given fractions."); + } + + @Override + public String getJavaStaticMethod() { + return "execute"; + } + + /** + * Extract a section of the input LineString or MultiLineString starting and ending at the given fractions. + * @param geometry the input lines + * @param start the start fraction between 0 and 1 + * @param end the end fraction between 0 and 1 + * @return single or multiparts lines + * @throws SQLException + */ + public static Geometry execute(Geometry geometry, double start, double end) throws SQLException { + if(geometry==null){ + return null; + } + if ( start < 0 || start > 1 ){ + throw new SQLException("Allowed between 0 and 1"); + } + + if ( end < 0 || end > 1 ){ + throw new SQLException("Allowed between 0 and 1"); + } + + if(start> end){ + throw new SQLException("Start fraction must be smaller than end fraction"); + } + if(geometry.isEmpty()){ + return geometry; + } + + if(geometry instanceof LineString){ + double length = geometry.getLength(); + LengthIndexedLine ll = new LengthIndexedLine(geometry); + return ll.extractLine(start*length, end*length); + } else if (geometry instanceof MultiLineString) { + int nb = geometry.getNumGeometries(); + ArrayList lines = new ArrayList<>(); + for (int i = 0; i < nb; i++) { + Geometry line = geometry.getGeometryN(i); + double length = line.getLength(); + LengthIndexedLine ll = new LengthIndexedLine(geometry.getGeometryN(i)); + lines.add((LineString) ll.extractLine(start*length, end*length)); + } + return geometry.getFactory().createMultiLineString(lines.toArray(new LineString[0])); + } + throw new SQLException("Only LineString or MultiLineString are supported"); + } +} diff --git a/h2gis-functions/src/test/java/org/h2gis/functions/spatial/linear_referencing/LinearReferencingTest.java b/h2gis-functions/src/test/java/org/h2gis/functions/spatial/linear_referencing/LinearReferencingTest.java new file mode 100644 index 0000000000..0f6c90cda3 --- /dev/null +++ b/h2gis-functions/src/test/java/org/h2gis/functions/spatial/linear_referencing/LinearReferencingTest.java @@ -0,0 +1,117 @@ +/** + * H2GIS is a library that brings spatial support to the H2 Database Engine + * http://www.h2database.com. H2GIS is developed by CNRS + * http://www.cnrs.fr/. + * + * This code is part of the H2GIS project. H2GIS is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License as published by the Free Software Foundation; + * version 3.0 of the License. + * + * H2GIS is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details . + * + * + * For more information, please consult: http://www.h2gis.org/ + * or contact directly: info_at_h2gis.org + */ +package org.h2gis.functions.spatial.linear_referencing; + +import org.h2gis.functions.factory.H2GISDBFactory; +import org.junit.jupiter.api.*; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import static org.h2gis.unitTest.GeometryAsserts.assertGeometryEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Erwan Bocher, CNRS (2023) + */ +public class LinearReferencingTest { + + private static Connection connection; + private Statement st; + + @BeforeAll + public static void tearUp() throws Exception { + connection = H2GISDBFactory.createSpatialDataBase(LinearReferencingTest.class.getSimpleName()); + } + + @AfterAll + public static void tearDown() throws Exception { + connection.close(); + } + + @BeforeEach + public void setUpStatement() throws Exception { + st = connection.createStatement(); + } + + @AfterEach + public void tearDownStatement() throws Exception { + st.close(); + } + + @Test + public void test_ST_LineSubstring1() throws Exception { + ResultSet rs = st.executeQuery("SELECT ST_LineSubstring('LINESTRING(0 0, 10 0)'::GEOMETRY, 0, 0.5)"); + assertTrue(rs.next()); + assertGeometryEquals("LINESTRING(0 0, 5 0)", rs.getObject(1)); + rs.close(); + } + + @Test + public void test_ST_LineSubstring2() throws Exception { + assertThrows(SQLException.class, () -> { + st.executeQuery("SELECT ST_LineSubstring('LINESTRING(0 0, 10 0)'::GEOMETRY, 1, 10)"); + }); + } + + @Test + public void test_ST_LineSubstring3() throws Exception { + ResultSet rs = st.executeQuery("SELECT ST_LineSubstring('MULTILINESTRING((0 0, 10 0), (10 10, 10 20))'::GEOMETRY, 0, 0.5)"); + assertTrue(rs.next()); + assertGeometryEquals("MULTILINESTRING ((0 0, 5 0), (10 10, 10 15))", rs.getObject(1)); + rs.close(); + } + + @Test + public void test_ST_LineSubstring4() throws Exception { + ResultSet rs = st.executeQuery("SELECT ST_LineSubstring('LINESTRINGZ(0 0 0, 10 0 10)'::GEOMETRY, 0, 0.5)"); + assertTrue(rs.next()); + assertGeometryEquals("LINESTRING Z (0 0 0, 5 0 5)", rs.getObject(1)); + rs.close(); + } + + @Test + public void test_ST_LineInterpolatePoint1() throws Exception { + ResultSet rs = st.executeQuery("SELECT ST_LineInterpolatePoint('LINESTRING(0 0, 10 0)'::GEOMETRY, 0.5)"); + assertTrue(rs.next()); + assertGeometryEquals("POINT(5 0)", rs.getObject(1)); + rs.close(); + } + + @Test + public void test_ST_LineInterpolatePoint2() throws Exception { + ResultSet rs = st.executeQuery("SELECT ST_LineInterpolatePoint('MULTILINESTRING((0 0, 10 0), (10 10, 10 20))'::GEOMETRY,0.5)"); + assertTrue(rs.next()); + assertGeometryEquals("MULTIPOINT ((5 0), (10 15))", rs.getObject(1)); + rs.close(); + } + + @Test + public void test_ST_LineInterpolatePoint3() throws Exception { + ResultSet rs = st.executeQuery("SELECT ST_LineInterpolatePoint('LINESTRINGZ(0 0 0, 10 0 10)'::GEOMETRY, 0.5)"); + assertTrue(rs.next()); + assertGeometryEquals("POINT Z (5 0 5)", rs.getObject(1)); + rs.close(); + } + +}