diff --git a/src/main/java/org/neo4j/gis/spatial/Constants.java b/src/main/java/org/neo4j/gis/spatial/Constants.java index 4ef1fb76..f56ae85f 100644 --- a/src/main/java/org/neo4j/gis/spatial/Constants.java +++ b/src/main/java/org/neo4j/gis/spatial/Constants.java @@ -69,4 +69,6 @@ public interface Constants { int GTYPE_MULTILINESTRING = 5; int GTYPE_MULTIPOLYGON = 6; + int SRID_COORDINATES_2D = 4326; + int SRID_COORDINATES_3D = 4979; } diff --git a/src/main/java/org/neo4j/gis/spatial/encoders/neo4j/Neo4jCRS.java b/src/main/java/org/neo4j/gis/spatial/encoders/neo4j/Neo4jCRS.java index d066589b..97ce16db 100644 --- a/src/main/java/org/neo4j/gis/spatial/encoders/neo4j/Neo4jCRS.java +++ b/src/main/java/org/neo4j/gis/spatial/encoders/neo4j/Neo4jCRS.java @@ -19,6 +19,7 @@ */ package org.neo4j.gis.spatial.encoders.neo4j; +import org.neo4j.gis.spatial.Constants; import org.neo4j.values.storable.CoordinateReferenceSystem; public class Neo4jCRS implements org.neo4j.graphdb.spatial.CRS { @@ -51,7 +52,7 @@ public int dimensions() { public static Neo4jCRS findCRS(String crs) { return switch (crs) { // name in Neo4j CRS table case "WGS-84", "WGS84(DD)" -> // name in geotools crs library - makeCRS(4326); + makeCRS(Constants.SRID_COORDINATES_2D); case "Cartesian" -> makeCRS(7203); default -> throw new IllegalArgumentException("Cypher type system does not support CRS: " + crs); }; diff --git a/src/main/java/org/neo4j/gis/spatial/functions/SpatialFunctions.java b/src/main/java/org/neo4j/gis/spatial/functions/SpatialFunctions.java index 142d379f..c0ca52c5 100644 --- a/src/main/java/org/neo4j/gis/spatial/functions/SpatialFunctions.java +++ b/src/main/java/org/neo4j/gis/spatial/functions/SpatialFunctions.java @@ -20,14 +20,22 @@ package org.neo4j.gis.spatial.functions; +import static org.neo4j.gis.spatial.Constants.SRID_COORDINATES_2D; +import static org.neo4j.gis.spatial.Constants.SRID_COORDINATES_3D; + +import java.util.Arrays; +import java.util.Collection; +import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.ParseException; import org.locationtech.jts.io.WKTReader; +import org.locationtech.jts.io.WKTWriter; import org.neo4j.gis.spatial.Layer; import org.neo4j.gis.spatial.procedures.SpatialProcedures.GeometryResult; import org.neo4j.gis.spatial.utilities.GeoJsonUtils; import org.neo4j.gis.spatial.utilities.SpatialApiBase; import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.spatial.Point; import org.neo4j.procedure.Description; import org.neo4j.procedure.Name; import org.neo4j.procedure.UserFunction; @@ -58,7 +66,6 @@ public Object asGeometry(@Name("geometry") Object geometry) { return toNeo4jGeometry(null, geometry); } - @UserFunction("spatial.wktToGeoJson") @Description("Converts a WKT to GeoJson structure") public Object wktToGeoJson(@Name("wkt") String wkt) throws ParseException { @@ -69,4 +76,38 @@ public Object wktToGeoJson(@Name("wkt") String wkt) throws ParseException { Geometry geometry = wktReader.read(wkt); return GeoJsonUtils.toGeoJsonStructure(geometry); } + + @UserFunction("spatial.neo4jGeometryToWkt") + @Description("Converts a point or point array to WKT") + public String nativeToWkt(@Name("data") Object object) { + if (object instanceof Point point) { + var coordinate = convertToCoordinate(point); + return WKTWriter.toPoint(coordinate); + } + if (object instanceof Point[] points) { + var coordinates = Arrays.stream(points).map(SpatialFunctions::convertToCoordinate) + .toArray(Coordinate[]::new); + return WKTWriter.toLineString(coordinates); + } + if (object instanceof Collection points) { + var coordinates = points.stream() + .filter(Point.class::isInstance) + .map(Point.class::cast) + .map(SpatialFunctions::convertToCoordinate) + .toArray(Coordinate[]::new); + return WKTWriter.toLineString(coordinates); + } + throw new IllegalArgumentException("Unsupported type: " + object.getClass()); + } + + private static Coordinate convertToCoordinate(Point point) { + double[] coordinate = point.getCoordinate().getCoordinate(); + if (point.getCRS().getCode() == SRID_COORDINATES_3D) { + return new Coordinate(coordinate[0], coordinate[1], coordinate[2]); + } else if (point.getCRS().getCode() == SRID_COORDINATES_2D) { + return new Coordinate(coordinate[0], coordinate[1]); + } else { + throw new IllegalArgumentException("Unsupported CRS: " + point.getCRS().getCode()); + } + } } diff --git a/src/main/java/org/neo4j/gis/spatial/utilities/GeotoolsAdapter.java b/src/main/java/org/neo4j/gis/spatial/utilities/GeotoolsAdapter.java index aada0f28..d9b73fcf 100644 --- a/src/main/java/org/neo4j/gis/spatial/utilities/GeotoolsAdapter.java +++ b/src/main/java/org/neo4j/gis/spatial/utilities/GeotoolsAdapter.java @@ -27,6 +27,7 @@ import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.geotools.referencing.CRS; import org.geotools.referencing.ReferencingFactoryFinder; +import org.neo4j.gis.spatial.Constants; import org.neo4j.gis.spatial.SpatialDatabaseException; /** @@ -54,8 +55,8 @@ public static CoordinateReferenceSystem getCRS(String crsText) { public static Integer getEPSGCode(CoordinateReferenceSystem crs) { try { - // TODO: upgrade geotools to avoid Java11 failures on CRS.lookupEpsgCode - return (crs == WGS84) ? Integer.valueOf(4326) : (crs == GENERIC_2D) ? null : CRS.lookupEpsgCode(crs, true); + return (crs == WGS84) ? Integer.valueOf(Constants.SRID_COORDINATES_2D) + : (crs == GENERIC_2D) ? null : CRS.lookupEpsgCode(crs, true); } catch (FactoryException e) { System.err.println("Failed to lookup CRS: " + e.getMessage()); return null; diff --git a/src/test/java/org/neo4j/gis/spatial/functions/SpatialFunctionsTest.java b/src/test/java/org/neo4j/gis/spatial/functions/SpatialFunctionsTest.java index 213de950..0b04f427 100644 --- a/src/test/java/org/neo4j/gis/spatial/functions/SpatialFunctionsTest.java +++ b/src/test/java/org/neo4j/gis/spatial/functions/SpatialFunctionsTest.java @@ -311,4 +311,19 @@ public void testGeometryCollection() { ))); } } + + @Test + public void testPointToWkt() { + Object wkt = executeObject("return spatial.neo4jGeometryToWkt(point({longitude: 1, latitude: 2})) as wkt", + "wkt"); + assertThat(wkt, equalTo("POINT ( 1 2 )")); + } + + @Test + public void testPointArrayToWkt() { + Object wkt = executeObject( + "return spatial.neo4jGeometryToWkt([point({longitude: 1, latitude: 2}), point({longitude: 3, latitude: 4}) ]) as wkt", + "wkt"); + assertThat(wkt, equalTo("LINESTRING (1 2, 3 4)")); + } }