diff --git a/README.md b/README.md index 0e50c44b..e1089d66 100755 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ The spatial queries implemented are: The simplest way to build Neo4j Spatial is by using maven. Just clone the git repository and run ~~~bash - mvn install +mvn install ~~~ This will download all dependencies, compiled the library, run the tests and install the artifact in your local @@ -207,22 +207,18 @@ Spatial data is divided in Layers and indexed by a RTree. ~~~java GraphDatabaseService database = new GraphDatabaseFactory().newEmbeddedDatabase(storeDir); try{ -ShapefileImporter importer = new ShapefileImporter(database); - importer. - -importFile("roads.shp","layer_roads"); -}finally{ - database. - -shutdown(); + ShapefileImporter importer = new ShapefileImporter(database); + importer.importFile("roads.shp","layer_roads"); +} finally { + database.shutdown(); } ~~~ If using the server, the same can be achieved with spatial procedures (3.x only): ~~~cypher -CALL spatial.addWKTLayer('layer_roads','geometry') -CALL spatial.importShapefileToLayer('layer_roads','roads.shp') +CALL spatial.addWKTLayer('layer_roads', 'geometry') +CALL spatial.importShapefileToLayer('layer_roads', 'roads.shp') ~~~ ### Importing an Open Street Map file ### @@ -267,24 +263,24 @@ try { If using the server, the same can be achieved with spatial procedures (3.x only): ~~~cypher -CALL spatial.bbox('layer_roads', {lon:15.0,lat:60.0}, {lon:15.3, lat:61.0}) +CALL spatial.bbox('layer_roads', {lon: 15.0, lat: 60.0}, {lon: 15.3, lat: 61.0}) ~~~ Or using a polygon: ~~~cypher -WITH "POLYGON((15.3 60.2, 15.3 60.4, 15.7 60.4, 15.7 60.2, 15.3 60.2))" as polygon -CALL spatial.intersects('layer_roads',polygon) YIELD node RETURN node.name as name + +WITH 'POLYGON((15.3 60.2, 15.3 60.4, 15.7 60.4, 15.7 60.2, 15.3 60.2))' AS polygon +CALL spatial.intersects('layer_roads', polygon) YIELD node +RETURN node.name AS name ~~~ -For further Java examples, refer to the test code in -the [LayersTest](https://github.com/neo4j-contrib/spatial/blob/master/src/test/java/org/neo4j/gis/spatial/LayersTest.java) -and -the [TestSpatial](https://github.com/neo4j-contrib/spatial/blob/master/src/test/java/org/neo4j/gis/spatial/TestSpatial.java) -classes. +For further Java examples, refer to the test code in the +[LayersTest](src/test/java/org/neo4j/gis/spatial/LayersTest.java) and +the [TestSpatial](src/test/java/org/neo4j/gis/spatial/TestSpatial.java) classes. For further Procedures examples, refer to the code in -the [SpatialProceduresTest](https://github.com/neo4j-contrib/spatial/blob/master/src/test/java/org/neo4j/gis/spatial/procedures/SpatialProceduresTest.java) +the [SpatialProceduresTest](src/test/java/org/neo4j/gis/spatial/procedures/SpatialProceduresTest.java) class. ## Neo4j Spatial Geoserver Plugin ## @@ -306,7 +302,7 @@ This has not been tested at all in any GeoTools enabled application, but could p ### Building ### ~~~bash - mvn clean install +mvn clean install ~~~ ### Deployment into Geoserver ### @@ -326,38 +322,38 @@ This has not been tested at all in any GeoTools enabled application, but could p * check out the geoserver source ~~~bash - svn co https://svn.codehaus.org/geoserver/trunk geoserver-trunk +svn co https://svn.codehaus.org/geoserver/trunk geoserver-trunk ~~~ * build the source ~~~bash - cd geoserver-trunk - mvn clean install +cd geoserver-trunk +mvn clean install ~~~ * check that you can run the web app as of [The GeoServer Maven build guide](http://docs.geoserver.org/latest/en/developer/maven-guide/index.html#running-the-web-module-with-jetty) ~~~bash - cd src/web/app - mvn jetty:run +cd src/web/app +mvn jetty:run ~~~ * in `$GEOSERVER_SOURCE/src/web/app/pom.xml` https://svn.codehaus.org/geoserver/trunk/src/web/app/pom.xml, add the following lines under the profiles section: ~~~xml - - neo4j - - - org.neo4j - neo4j-spatial - 0.19-neo4j-3.0.3 - - - + + neo4j + + + org.neo4j + neo4j-spatial + 0.19-neo4j-3.0.3 + + + ~~~ The version specified on the version line can be changed to match the version you wish to work with (based on the @@ -461,15 +457,15 @@ The Java API (the original API for Neo4j Spatial) still remains, however, the mo and therefor we recommend that if you need to access Neo4j server remotely, and want deeper access to Spatial functions, consider writing your own Procedures. The Neo4j 3.0 documentation provides some good information on how to do this, and you can also refer to -the [Neo4j Spatial procedures source code](https://github.com/neo4j-contrib/spatial/blob/master/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java) +the [Neo4j Spatial procedures source code](src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java) for examples. ## Building Neo4j spatial ## ~~~bash - git clone https://github.com/neo4j/spatial.git - cd spatial - mvn clean package +git clone https://github.com/neo4j-contrib/spatial/spatial.git +cd spatial +mvn clean package ~~~ ### Building Neo4j Spatial Documentation ### @@ -478,22 +474,22 @@ Add your GitHub credentials in your `~/.m2/settings.xml` ~~~xml - - - github - xxx@xxx.xx - secret - - + + + github + xxx@xxx.xx + secret + + ~~~ To build and deploy: ~~~bash - git clone https://github.com/neo4j/spatial.git - cd spatial - mvn clean install site -Pneo-docs-build +git clone https://github.com/neo4j/spatial.git +cd spatial +mvn clean install site -Pneo-docs-build ~~~ ## Using Neo4j spatial in your Java project with Maven ## @@ -501,34 +497,34 @@ To build and deploy: Add the following repositories and dependency to your project's pom.xml: ~~~xml - - - neo4j-contrib-releases - https://raw.github.com/neo4j-contrib/m2/master/releases - - true - - - false - - - - neo4j-contrib-snapshots - https://raw.github.com/neo4j-contrib/m2/master/snapshots - - false - - - true - - - - [...] - - org.neo4j - neo4j-spatial - 0.30.0-neo4j-5.13.0 - + + + neo4j-contrib-releases + https://raw.github.com/neo4j-contrib/m2/master/releases + + true + + + false + + + + neo4j-contrib-snapshots + https://raw.github.com/neo4j-contrib/m2/master/snapshots + + false + + + true + + + +[...] + + org.neo4j + neo4j-spatial + 0.30.0-neo4j-5.13.0 + ~~~ The version specified on the last version line can be changed to match the version you wish to work with (based on the @@ -547,16 +543,16 @@ other using the 'exec:java' target in maven. In both cases we use maven to set u ### Compile ### ~~~bash - git clone git://github.com/neo4j-contrib/spatial.git - cd spatial - mvn clean compile +git clone git://github.com/neo4j-contrib/spatial.git +cd spatial +mvn clean compile ~~~ ### Run using JAVA command ### ~~~bash - mvn dependency:copy-dependencies - java -cp target/classes:target/dependency/* org.neo4j.gis.spatial.osm.OSMImporter osm-db two-street.osm +mvn dependency:copy-dependencies +java -cp target/classes:target/dependency/* org.neo4j.gis.spatial.osm.OSMImporter osm-db two-street.osm ~~~ _Note: On windows remember to separate the classpath with ';' instead of ':'._ @@ -571,7 +567,7 @@ the above approach is most certainly the easiest way to do this. ### Run using 'mvn exec:java' ### ~~~bash - mvn exec:java -Dexec.mainClass=org.neo4j.gis.spatial.osm.OSMImporter -Dexec.args="osm-db two-street.osm" +mvn exec:java -Dexec.mainClass=org.neo4j.gis.spatial.osm.OSMImporter -Dexec.args="osm-db two-street.osm" ~~~ Note that the OSMImporter cannot re-import the same data multiple times, diff --git a/src/main/java/org/neo4j/gis/spatial/functions/SpatialFunctions.java b/src/main/java/org/neo4j/gis/spatial/functions/SpatialFunctions.java new file mode 100644 index 00000000..dd8ef3ab --- /dev/null +++ b/src/main/java/org/neo4j/gis/spatial/functions/SpatialFunctions.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j Spatial. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.neo4j.gis.spatial.functions; + +import org.neo4j.gis.spatial.Layer; +import org.neo4j.gis.spatial.procedures.SpatialProcedures.GeometryResult; +import org.neo4j.gis.spatial.utilities.SpatialApiBase; +import org.neo4j.graphdb.Node; +import org.neo4j.procedure.Description; +import org.neo4j.procedure.Name; +import org.neo4j.procedure.UserFunction; + +public class SpatialFunctions extends SpatialApiBase { + + @UserFunction("spatial.decodeGeometry") + @Description("Returns a geometry of a layer node as the Neo4j geometry type, to be passed to other procedures or returned to a client") + public Object decodeGeometry( + @Name("layerName") String name, + @Name("node") Node node) { + + Layer layer = getLayerOrThrow(tx, spatial(), name); + GeometryResult result = new GeometryResult( + toNeo4jGeometry(layer, layer.getGeometryEncoder().decodeGeometry(node))); + return result.geometry; + } + + @UserFunction("spatial.asMap") + @Description("Returns a Map object representing the Geometry, to be passed to other procedures or returned to a client") + public Object asMap(@Name("object") Object geometry) { + return toGeometryMap(geometry); + } + + @UserFunction("spatial.asGeometry") + @Description("Returns a geometry object as the Neo4j geometry type, to be passed to other functions or procedures or returned to a client") + public Object asGeometry(@Name("geometry") Object geometry) { + return toNeo4jGeometry(null, geometry); + } + + +} diff --git a/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java b/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java index 18506840..02f7fc2e 100644 --- a/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java +++ b/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java @@ -20,14 +20,11 @@ package org.neo4j.gis.spatial.procedures; import static org.neo4j.gis.spatial.SpatialDatabaseService.RTREE_INDEX_NAME; -import static org.neo4j.gis.spatial.encoders.neo4j.Neo4jCRS.findCRS; -import static org.neo4j.internal.helpers.collection.MapUtil.map; import static org.neo4j.procedure.Mode.WRITE; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -35,7 +32,6 @@ import java.util.function.BiFunction; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.geotools.api.referencing.ReferenceIdentifier; import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.locationtech.jts.geom.Coordinate; @@ -59,10 +55,6 @@ import org.neo4j.gis.spatial.encoders.SimpleGraphEncoder; import org.neo4j.gis.spatial.encoders.SimplePointEncoder; import org.neo4j.gis.spatial.encoders.SimplePropertyEncoder; -import org.neo4j.gis.spatial.encoders.neo4j.Neo4jCRS; -import org.neo4j.gis.spatial.encoders.neo4j.Neo4jGeometry; -import org.neo4j.gis.spatial.encoders.neo4j.Neo4jPoint; -import org.neo4j.gis.spatial.index.IndexManager; import org.neo4j.gis.spatial.index.LayerGeohashPointIndex; import org.neo4j.gis.spatial.index.LayerHilbertPointIndex; import org.neo4j.gis.spatial.index.LayerZOrderPointIndex; @@ -73,6 +65,7 @@ import org.neo4j.gis.spatial.pipes.GeoPipeline; import org.neo4j.gis.spatial.pipes.processing.OrthodromicDistance; import org.neo4j.gis.spatial.rtree.ProgressLoggingListener; +import org.neo4j.gis.spatial.utilities.SpatialApiBase; import org.neo4j.graphdb.Entity; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; @@ -88,7 +81,6 @@ import org.neo4j.procedure.Description; import org.neo4j.procedure.Name; import org.neo4j.procedure.Procedure; -import org.neo4j.procedure.UserFunction; /* TODO: @@ -96,26 +88,14 @@ * optional default simplePointLayer should use the long form of "latitude and longitude" like the spatial functions do */ -public class SpatialProcedures { +public class SpatialProcedures extends SpatialApiBase { @Context public GraphDatabaseService db; - @Context - public GraphDatabaseAPI api; - - @Context - public Transaction tx; - - @Context - public KernelTransaction ktx; - @Context public Log log; - private SpatialDatabaseService spatial() { - return new SpatialDatabaseService(new IndexManager(api, ktx.securityContext())); - } public record NodeResult(Node node) { @@ -219,7 +199,6 @@ public Stream getAllLayers() { @Procedure("spatial.layerTypes") @Description("Returns the different registered layer types") public Stream getAllLayerTypes() { - SpatialDatabaseService sdb = spatial(); Stream.Builder builder = Stream.builder(); for (Map.Entry entry : SpatialDatabaseService.getRegisteredLayerTypes().entrySet()) { builder.accept(new NameResult(entry.getKey(), entry.getValue())); @@ -811,32 +790,6 @@ public Stream findGeometriesWithinDistance( }); } - @UserFunction("spatial.decodeGeometry") - @Description("Returns a geometry of a layer node as the Neo4j geometry type, to be passed to other procedures or returned to a client") - public Object decodeGeometry( - @Name("layerName") String name, - @Name("node") Node node) { - - Layer layer = getLayerOrThrow(tx, spatial(), name); - GeometryResult result = new GeometryResult( - toNeo4jGeometry(layer, layer.getGeometryEncoder().decodeGeometry(node))); - return result.geometry; - } - - @UserFunction("spatial.asMap") - @Description("Returns a Map object representing the Geometry, to be passed to other procedures or returned to a client") - public Object asMap(@Name("object") Object geometry) { - return toGeometryMap(geometry); - } - - @UserFunction("spatial.asGeometry") - @Description("Returns a geometry object as the Neo4j geometry type, to be passed to other functions or procedures or returned to a client") - public Object asGeometry( - @Name("geometry") Object geometry) { - - return toNeo4jGeometry(null, geometry); - } - @Deprecated @Procedure("spatial.asGeometry") @Description("Returns a geometry object as the Neo4j geometry type, to be passed to other procedures or returned to a client") @@ -897,173 +850,11 @@ private static Geometry toJTSGeometry(Layer layer, Object value) { throw new RuntimeException("Can't convert " + value + " to a geometry"); } - private static org.neo4j.graphdb.spatial.Coordinate toNeo4jCoordinate(Coordinate coordinate) { - if (coordinate.z == Coordinate.NULL_ORDINATE) { - return new org.neo4j.graphdb.spatial.Coordinate(coordinate.x, coordinate.y); - } - return new org.neo4j.graphdb.spatial.Coordinate(coordinate.x, coordinate.y, coordinate.z); - } - - private static List toNeo4jCoordinates(Coordinate[] coordinates) { - ArrayList converted = new ArrayList<>(); - for (Coordinate coordinate : coordinates) { - converted.add(toNeo4jCoordinate(coordinate)); - } - return converted; - } - - private org.neo4j.graphdb.spatial.Geometry toNeo4jGeometry(Layer layer, Object value) { - if (value instanceof org.neo4j.graphdb.spatial.Geometry) { - return (org.neo4j.graphdb.spatial.Geometry) value; - } - Neo4jCRS crs = findCRS("Cartesian"); - if (layer != null) { - CoordinateReferenceSystem layerCRS = layer.getCoordinateReferenceSystem(tx); - if (layerCRS != null) { - ReferenceIdentifier crsRef = layer.getCoordinateReferenceSystem(tx).getName(); - crs = findCRS(crsRef.toString()); - } - } - if (value instanceof Point point) { - return new Neo4jPoint(point, crs); - } - if (value instanceof Geometry geometry) { - return new Neo4jGeometry(geometry.getGeometryType(), toNeo4jCoordinates(geometry.getCoordinates()), crs); - } - if (value instanceof String && layer != null) { - GeometryFactory factory = layer.getGeometryFactory(); - WKTReader reader = new WKTReader(factory); - try { - Geometry geometry = reader.read((String) value); - return new Neo4jGeometry(geometry.getGeometryType(), toNeo4jCoordinates(geometry.getCoordinates()), - crs); - } catch (ParseException e) { - throw new IllegalArgumentException("Invalid WKT: " + e.getMessage()); - } - } - Map latLon = null; - if (value instanceof Entity) { - latLon = ((Entity) value).getProperties("latitude", "longitude", "lat", "lon"); - } - if (value instanceof Map) { - //noinspection unchecked - latLon = (Map) value; - } - Coordinate coord = toCoordinate(latLon); - if (coord != null) { - return new Neo4jPoint(coord, crs); - } - throw new RuntimeException("Can't convert " + value + " to a geometry"); - } - - private Object toPublic(Object obj) { - if (obj instanceof Map map) { - return toPublic(map); - } - if (obj instanceof Entity entity) { - return toPublic(entity.getProperties()); - } - if (obj instanceof Geometry geometry) { - return toMap(geometry); - } - return obj; - } - - private Map toGeometryMap(Object geometry) { - return toMap(toNeo4jGeometry(null, geometry)); - } - - private Map toMap(Geometry geometry) { - return toMap(toNeo4jGeometry(null, geometry)); - } - - private static double[][] toCoordinateArrayFromCoordinates(List coords) { - List coordinates = new ArrayList<>(coords.size()); - for (org.neo4j.graphdb.spatial.Coordinate coord : coords) { - coordinates.add(coord.getCoordinate()); - } - return toCoordinateArray(coordinates); - } - - private static double[][] toCoordinateArray(List coords) { - double[][] coordinates = new double[coords.size()][]; - for (int i = 0; i < coordinates.length; i++) { - coordinates[i] = coords.get(i); - } - return coordinates; - } - - private static Map toMap(org.neo4j.graphdb.spatial.Geometry geometry) { - if (geometry instanceof org.neo4j.graphdb.spatial.Point point) { - return map("type", geometry.getGeometryType(), "coordinate", point.getCoordinate().getCoordinate()); - } - return map("type", geometry.getGeometryType(), "coordinates", - toCoordinateArrayFromCoordinates(geometry.getCoordinates())); - } - - private Map toPublic(Map incoming) { - Map map = new HashMap<>(incoming.size()); - for (Object key : incoming.keySet()) { - map.put(key.toString(), toPublic(incoming.get(key))); - } - return map; - } - - private static Coordinate toCoordinate(Object value) { - if (value instanceof Coordinate) { - return (Coordinate) value; - } - if (value instanceof org.neo4j.graphdb.spatial.Coordinate) { - return toCoordinate((org.neo4j.graphdb.spatial.Coordinate) value); - } - if (value instanceof org.neo4j.graphdb.spatial.Point) { - return toCoordinate(((org.neo4j.graphdb.spatial.Point) value).getCoordinate()); - } - if (value instanceof Entity) { - return toCoordinate(((Entity) value).getProperties("latitude", "longitude", "lat", "lon")); - } - if (value instanceof Map) { - return toCoordinate((Map) value); - } - throw new RuntimeException("Can't convert " + value + " to a coordinate"); - } - - private static Coordinate toCoordinate(org.neo4j.graphdb.spatial.Coordinate point) { - double[] coordinate = point.getCoordinate(); - return new Coordinate(coordinate[0], coordinate[1]); - } - - private static Coordinate toCoordinate(Map map) { - if (map == null) { - return null; - } - Coordinate coord = toCoordinate(map, "longitude", "latitude"); - if (coord == null) { - return toCoordinate(map, "lon", "lat"); - } - return coord; - } - - private static Coordinate toCoordinate(Map map, String xName, String yName) { - if (map.containsKey(xName) && map.containsKey(yName)) { - return new Coordinate(((Number) map.get(xName)).doubleValue(), ((Number) map.get(yName)).doubleValue()); - } - return null; - } - private static EditableLayerImpl getEditableLayerOrThrow(Transaction tx, SpatialDatabaseService spatial, String name) { return (EditableLayerImpl) getLayerOrThrow(tx, spatial, name); } - private static Layer getLayerOrThrow(Transaction tx, SpatialDatabaseService spatial, String name) { - EditableLayer layer = (EditableLayer) spatial.getLayer(tx, name); - if (layer != null) { - return layer; - } - throw new IllegalArgumentException("No such layer '" + name + "'"); - } - private static void assertLayerDoesNotExists(Transaction tx, SpatialDatabaseService spatial, String name) { if (spatial.getLayer(tx, name) != null) { throw new IllegalArgumentException("Layer already exists: '" + name + "'"); diff --git a/src/main/java/org/neo4j/gis/spatial/utilities/SpatialApiBase.java b/src/main/java/org/neo4j/gis/spatial/utilities/SpatialApiBase.java new file mode 100644 index 00000000..a0ad3f31 --- /dev/null +++ b/src/main/java/org/neo4j/gis/spatial/utilities/SpatialApiBase.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j Spatial. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.neo4j.gis.spatial.utilities; + +import static org.neo4j.gis.spatial.encoders.neo4j.Neo4jCRS.findCRS; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.geotools.api.referencing.ReferenceIdentifier; +import org.geotools.api.referencing.crs.CoordinateReferenceSystem; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKTReader; +import org.neo4j.gis.spatial.EditableLayer; +import org.neo4j.gis.spatial.Layer; +import org.neo4j.gis.spatial.SpatialDatabaseService; +import org.neo4j.gis.spatial.encoders.neo4j.Neo4jCRS; +import org.neo4j.gis.spatial.encoders.neo4j.Neo4jGeometry; +import org.neo4j.gis.spatial.encoders.neo4j.Neo4jPoint; +import org.neo4j.gis.spatial.index.IndexManager; +import org.neo4j.graphdb.Entity; +import org.neo4j.graphdb.Transaction; +import org.neo4j.kernel.api.KernelTransaction; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.procedure.Context; + +public class SpatialApiBase { + + @Context + public Transaction tx; + + @Context + public GraphDatabaseAPI api; + + @Context + public KernelTransaction ktx; + + protected SpatialDatabaseService spatial() { + return new SpatialDatabaseService(new IndexManager(api, ktx.securityContext())); + } + + protected org.neo4j.graphdb.spatial.Geometry toNeo4jGeometry(Layer layer, Object value) { + if (value instanceof org.neo4j.graphdb.spatial.Geometry) { + return (org.neo4j.graphdb.spatial.Geometry) value; + } + Neo4jCRS crs = findCRS("Cartesian"); + if (layer != null) { + CoordinateReferenceSystem layerCRS = layer.getCoordinateReferenceSystem(tx); + if (layerCRS != null) { + ReferenceIdentifier crsRef = layer.getCoordinateReferenceSystem(tx).getName(); + crs = findCRS(crsRef.toString()); + } + } + if (value instanceof Point point) { + return new Neo4jPoint(point, crs); + } + if (value instanceof Geometry geometry) { + return new Neo4jGeometry(geometry.getGeometryType(), toNeo4jCoordinates(geometry.getCoordinates()), crs); + } + if (value instanceof String && layer != null) { + GeometryFactory factory = layer.getGeometryFactory(); + WKTReader reader = new WKTReader(factory); + try { + Geometry geometry = reader.read((String) value); + return new Neo4jGeometry(geometry.getGeometryType(), toNeo4jCoordinates(geometry.getCoordinates()), + crs); + } catch (ParseException e) { + throw new IllegalArgumentException("Invalid WKT: " + e.getMessage()); + } + } + Map latLon = null; + if (value instanceof Entity) { + latLon = ((Entity) value).getProperties("latitude", "longitude", "lat", "lon"); + } + if (value instanceof Map) { + //noinspection unchecked + latLon = (Map) value; + } + Coordinate coord = toCoordinate(latLon); + if (coord != null) { + return new Neo4jPoint(coord, crs); + } + throw new RuntimeException("Can't convert " + value + " to a geometry"); + } + + private static List toNeo4jCoordinates(Coordinate[] coordinates) { + ArrayList converted = new ArrayList<>(); + for (Coordinate coordinate : coordinates) { + converted.add(toNeo4jCoordinate(coordinate)); + } + return converted; + } + + private static org.neo4j.graphdb.spatial.Coordinate toNeo4jCoordinate(Coordinate coordinate) { + if (coordinate.z == Coordinate.NULL_ORDINATE) { + return new org.neo4j.graphdb.spatial.Coordinate(coordinate.x, coordinate.y); + } + return new org.neo4j.graphdb.spatial.Coordinate(coordinate.x, coordinate.y, coordinate.z); + } + + private static Coordinate toCoordinate(org.neo4j.graphdb.spatial.Coordinate point) { + double[] coordinate = point.getCoordinate(); + return new Coordinate(coordinate[0], coordinate[1]); + } + + private static Coordinate toCoordinate(Map map) { + if (map == null) { + return null; + } + Coordinate coord = toCoordinate(map, "longitude", "latitude"); + if (coord == null) { + return toCoordinate(map, "lon", "lat"); + } + return coord; + } + + private static Coordinate toCoordinate(Map map, String xName, String yName) { + if (map.containsKey(xName) && map.containsKey(yName)) { + return new Coordinate(((Number) map.get(xName)).doubleValue(), ((Number) map.get(yName)).doubleValue()); + } + return null; + } + + protected static Coordinate toCoordinate(Object value) { + if (value instanceof Coordinate) { + return (Coordinate) value; + } + if (value instanceof org.neo4j.graphdb.spatial.Coordinate) { + return toCoordinate((org.neo4j.graphdb.spatial.Coordinate) value); + } + if (value instanceof org.neo4j.graphdb.spatial.Point) { + return toCoordinate(((org.neo4j.graphdb.spatial.Point) value).getCoordinate()); + } + if (value instanceof Entity) { + return toCoordinate(((Entity) value).getProperties("latitude", "longitude", "lat", "lon")); + } + if (value instanceof Map) { + return toCoordinate((Map) value); + } + throw new RuntimeException("Can't convert " + value + " to a coordinate"); + } + + private static double[][] toCoordinateArrayFromCoordinates(List coords) { + List coordinates = new ArrayList<>(coords.size()); + for (org.neo4j.graphdb.spatial.Coordinate coord : coords) { + coordinates.add(coord.getCoordinate()); + } + return toCoordinateArray(coordinates); + } + + private static double[][] toCoordinateArray(List coords) { + double[][] coordinates = new double[coords.size()][]; + for (int i = 0; i < coordinates.length; i++) { + coordinates[i] = coords.get(i); + } + return coordinates; + } + + protected static Map toMap(org.neo4j.graphdb.spatial.Geometry geometry) { + if (geometry instanceof org.neo4j.graphdb.spatial.Point point) { + return Map.of("type", geometry.getGeometryType(), "coordinate", point.getCoordinate().getCoordinate()); + } + return Map.of("type", geometry.getGeometryType(), "coordinates", + toCoordinateArrayFromCoordinates(geometry.getCoordinates())); + } + + protected Map toGeometryMap(Object geometry) { + return toMap(toNeo4jGeometry(null, geometry)); + } + + protected static Layer getLayerOrThrow(Transaction tx, SpatialDatabaseService spatial, String name) { + EditableLayer layer = (EditableLayer) spatial.getLayer(tx, name); + if (layer != null) { + return layer; + } + throw new IllegalArgumentException("No such layer '" + name + "'"); + } +} diff --git a/src/test/java/org/neo4j/doc/tools/AsciiDocGenerator.java b/src/test/java/org/neo4j/doc/tools/AsciiDocGenerator.java index 0a4a9491..f9838a67 100644 --- a/src/test/java/org/neo4j/doc/tools/AsciiDocGenerator.java +++ b/src/test/java/org/neo4j/doc/tools/AsciiDocGenerator.java @@ -179,7 +179,7 @@ public static String dumpToSeparateFileWithType(File dir, String type, } counter++; counters.put(key, counter); - String testId = type + "-" + String.valueOf(counter); + String testId = type + "-" + counter; return dumpToSeparateFile(dir, testId, content); } diff --git a/src/test/java/org/neo4j/doc/tools/DocumentationData.java b/src/test/java/org/neo4j/doc/tools/DocumentationData.java index 8c0af7da..fb11a92e 100644 --- a/src/test/java/org/neo4j/doc/tools/DocumentationData.java +++ b/src/test/java/org/neo4j/doc/tools/DocumentationData.java @@ -45,8 +45,8 @@ public String getPayload() { .isEmpty() && MediaType.APPLICATION_JSON_TYPE.equals(payloadType)) { return JSONPrettifier.parse(this.payload); - } - return this.payload; + } + return this.payload; } public String getPrettifiedEntity() { diff --git a/src/test/java/org/neo4j/doc/tools/SpatialGraphVizHelper.java b/src/test/java/org/neo4j/doc/tools/SpatialGraphVizHelper.java index 02cbf9bc..0773ceb1 100644 --- a/src/test/java/org/neo4j/doc/tools/SpatialGraphVizHelper.java +++ b/src/test/java/org/neo4j/doc/tools/SpatialGraphVizHelper.java @@ -21,7 +21,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Transaction; @@ -61,22 +60,18 @@ public static String createGraphViz(String title, GraphDatabaseService graph, St String colorSet = "neoviz"; String graphAttrs = ""; - try { - String result = "." + title + "\n[graphviz, " - + (safeTitle + "-" + identifier).replace(" ", "-") - + ", svg]\n" - + "----\n" + - new GraphVizConfig( - out.toString(StandardCharsets.UTF_8.name()), - fontsDir, - colorSet, graphAttrs - ).get() + "\n" + - "----\n"; - System.out.println(result); - return result; - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + String result = "." + title + "\n[graphviz, " + + (safeTitle + "-" + identifier).replace(" ", "-") + + ", svg]\n" + + "----\n" + + new GraphVizConfig( + out.toString(StandardCharsets.UTF_8), + fontsDir, + colorSet, graphAttrs + ).get() + "\n" + + "----\n"; + System.out.println(result); + return result; } } } diff --git a/src/test/java/org/neo4j/gis/spatial/AbstractApiTest.java b/src/test/java/org/neo4j/gis/spatial/AbstractApiTest.java new file mode 100644 index 00000000..eeada0da --- /dev/null +++ b/src/test/java/org/neo4j/gis/spatial/AbstractApiTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j Spatial. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.neo4j.gis.spatial; + +import static org.neo4j.configuration.GraphDatabaseSettings.DEFAULT_DATABASE_NAME; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.neo4j.configuration.GraphDatabaseSettings; +import org.neo4j.dbms.api.DatabaseManagementService; +import org.neo4j.exceptions.KernelException; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.ResourceIterator; +import org.neo4j.graphdb.Transaction; +import org.neo4j.internal.helpers.collection.Iterators; +import org.neo4j.io.fs.FileUtils; +import org.neo4j.kernel.api.procedure.GlobalProcedures; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.test.TestDatabaseManagementServiceBuilder; + +public abstract class AbstractApiTest { + + private DatabaseManagementService databases; + protected GraphDatabaseService db; + + @BeforeEach + public void setUp() throws KernelException, IOException { + Path dbRoot = new File("target/procedures").toPath(); + FileUtils.deleteDirectory(dbRoot); + databases = new TestDatabaseManagementServiceBuilder(dbRoot) + .setConfig(GraphDatabaseSettings.procedure_unrestricted, List.of("spatial.*")) + .impermanent() + .build(); + db = databases.database(DEFAULT_DATABASE_NAME); + registerApiProceduresAndFunctions(); + } + + protected abstract void registerApiProceduresAndFunctions() throws KernelException; + + protected void registerProceduresAndFunctions(Class api) throws KernelException { + GlobalProcedures procedures = ((GraphDatabaseAPI) db).getDependencyResolver() + .resolveDependency(GlobalProcedures.class); + procedures.registerProcedure(api); + procedures.registerFunction(api); + } + + @AfterEach + public void tearDown() { + databases.shutdown(); + } + + protected long execute(String statement) { + return execute(statement, null); + } + + protected long execute(String statement, Map params) { + try (Transaction tx = db.beginTx()) { + if (params == null) { + params = Collections.emptyMap(); + } + long count = Iterators.count(tx.execute(statement, params)); + tx.commit(); + return count; + } + } + + protected void executeWrite(String call) { + try (Transaction tx = db.beginTx()) { + tx.execute(call).accept(v -> true); + tx.commit(); + } + } + + protected Node createNode(String call, String column) { + return (Node) executeObject(call, null, column); + } + + protected Object executeObject(String call, String column) { + return executeObject(call, null, column); + } + + protected Object executeObject(String call, Map params, String column) { + Object obj; + try (Transaction tx = db.beginTx()) { + if (params == null) { + params = Collections.emptyMap(); + } + ResourceIterator values = tx.execute(call, params).columnAs(column); + obj = values.next(); + values.close(); + tx.commit(); + } + return obj; + } +} diff --git a/src/test/java/org/neo4j/gis/spatial/Neo4jTestCase.java b/src/test/java/org/neo4j/gis/spatial/Neo4jTestCase.java index 3a5c5756..0c55c926 100644 --- a/src/test/java/org/neo4j/gis/spatial/Neo4jTestCase.java +++ b/src/test/java/org/neo4j/gis/spatial/Neo4jTestCase.java @@ -56,7 +56,7 @@ public abstract class Neo4jTestCase { //NORMAL_CONFIG.put( GraphDatabaseSettings.nodestore_propertystore_mapped_memory_size.name(), "150M" ); //NORMAL_CONFIG.put( GraphDatabaseSettings.strings_mapped_memory_size.name(), "200M" ); //NORMAL_CONFIG.put( GraphDatabaseSettings.arrays_mapped_memory_size.name(), "0M" ); - NORMAL_CONFIG.put(GraphDatabaseSettings.pagecache_memory, 200000000l); + NORMAL_CONFIG.put(GraphDatabaseSettings.pagecache_memory, 200000000L); NORMAL_CONFIG.put(GraphDatabaseInternalSettings.trace_cursors, true); } @@ -68,7 +68,7 @@ public abstract class Neo4jTestCase { //LARGE_CONFIG.put( GraphDatabaseSettings.nodestore_propertystore_mapped_memory_size.name(), "400M" ); //LARGE_CONFIG.put( GraphDatabaseSettings.strings_mapped_memory_size.name(), "800M" ); //LARGE_CONFIG.put( GraphDatabaseSettings.arrays_mapped_memory_size.name(), "10M" ); - LARGE_CONFIG.put(GraphDatabaseSettings.pagecache_memory, 100000000l); + LARGE_CONFIG.put(GraphDatabaseSettings.pagecache_memory, 100000000L); } private static final File basePath = new File("target/var"); diff --git a/src/test/java/org/neo4j/gis/spatial/Neo4jTestUtils.java b/src/test/java/org/neo4j/gis/spatial/Neo4jTestUtils.java index ee933602..15ad4ee4 100644 --- a/src/test/java/org/neo4j/gis/spatial/Neo4jTestUtils.java +++ b/src/test/java/org/neo4j/gis/spatial/Neo4jTestUtils.java @@ -87,10 +87,10 @@ public static void printTree(Node root, int depth) { } if (root.hasProperty(Constants.PROP_BBOX)) { - System.out.println(tab.toString() + "INDEX: " + root + " BBOX[" + arrayString( + System.out.println(tab + "INDEX: " + root + " BBOX[" + arrayString( (double[]) root.getProperty(Constants.PROP_BBOX)) + "]"); } else { - System.out.println(tab.toString() + "INDEX: " + root); + System.out.println(tab + "INDEX: " + root); } StringBuffer data = new StringBuffer(); diff --git a/src/test/java/org/neo4j/gis/spatial/ProgressLoggingListenerTest.java b/src/test/java/org/neo4j/gis/spatial/ProgressLoggingListenerTest.java index 974435e7..4e8dd54b 100644 --- a/src/test/java/org/neo4j/gis/spatial/ProgressLoggingListenerTest.java +++ b/src/test/java/org/neo4j/gis/spatial/ProgressLoggingListenerTest.java @@ -63,6 +63,7 @@ private static void testProgressLoggingListenerWithSpecifiedWaits(int unitsOfWor } listener.done(); verify(out).println("Starting test"); + //noinspection RedundantStringFormatCall verify(out).println(String.format("%.2f (10/10) - Completed test", 100f)); verify(out, times(expectedLogCount)).println(Mockito.anyString()); } diff --git a/src/test/java/org/neo4j/gis/spatial/RTreeBulkInsertTest.java b/src/test/java/org/neo4j/gis/spatial/RTreeBulkInsertTest.java index bd2e8bf5..c0019de6 100644 --- a/src/test/java/org/neo4j/gis/spatial/RTreeBulkInsertTest.java +++ b/src/test/java/org/neo4j/gis/spatial/RTreeBulkInsertTest.java @@ -29,7 +29,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.neo4j.configuration.GraphDatabaseSettings.DEFAULT_DATABASE_NAME; import static org.neo4j.gis.spatial.rtree.RTreeIndex.DEFAULT_MAX_NODE_REFERENCES; -import static org.neo4j.internal.helpers.collection.MapUtil.map; import java.io.File; import java.io.IOException; @@ -106,6 +105,7 @@ public void after() throws IOException { } @Disabled + @Test public void shouldDeleteRecursiveTree() { int depth = 5; int width = 2; @@ -122,7 +122,7 @@ public void shouldDeleteRecursiveTree() { for (Node parent : nodes.get(i - 1)) { for (int j = 0; j < width; j++) { Node node = tx.createNode(); - node.setProperty("name", "" + i + "-" + j); + node.setProperty("name", i + "-" + j); parent.createRelationshipTo(node, RTreeRelationshipTypes.RTREE_CHILD); children.add(node); } @@ -187,6 +187,7 @@ private EditableLayer getOrCreateSimplePointLayer(String name, String index, Str } @Disabled + @Test public void shouldInsertSimpleRTree() { int width = 20; int blockSize = 10000; @@ -262,7 +263,7 @@ public IndexTestConfig(String name, int width, Coordinate searchMin, Coordinate this.searchMax = searchMax; this.expectedCount = expectedCount; this.expectedGeometries = expectedGeometries; - this.totalCount = width * width; + this.totalCount = (long) width * width; } } @@ -476,7 +477,7 @@ private RTreeIndexMaker(String name, String splitMode, String insertMode, int ma public EditableLayer setupLayer(Transaction tx) { this.nodes = setup(name, "rtree", config.width); this.layer = (EditableLayer) spatial.getLayer(tx, name); - layer.getIndex().configure(map( + layer.getIndex().configure(Map.of( RTreeIndex.KEY_SPLIT, splitMode, RTreeIndex.KEY_MAX_NODE_REFERENCES, maxNodeReferences, RTreeIndex.KEY_SHOULD_MERGE_TREES, shouldMergeTrees) @@ -519,34 +520,34 @@ public void verifyStructure() { */ @Test - public void shouldInsertManyNodesIndividuallyWithGeohash_very_small() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithGeohash_very_small() { insertManyNodesIndividually(new GeohashIndexMaker("Coordinates", "Single", testConfigs.get("very_small")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithGeohash_very_small() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithGeohash_very_small() { insertManyNodesInBulk(new GeohashIndexMaker("Coordinates", "Bulk", testConfigs.get("very_small")), 5000); } @Test - public void shouldInsertManyNodesIndividuallyWithZOrder_very_small() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithZOrder_very_small() { insertManyNodesIndividually(new ZOrderIndexMaker("Coordinates", "Single", testConfigs.get("very_small")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithZOrder_very_small() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithZOrder_very_small() { insertManyNodesInBulk(new ZOrderIndexMaker("Coordinates", "Bulk", testConfigs.get("very_small")), 5000); } @Test - public void shouldInsertManyNodesIndividuallyWithHilbert_very_small() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithHilbert_very_small() { insertManyNodesIndividually(new HilbertIndexMaker("Coordinates", "Single", testConfigs.get("very_small")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithHilbert_very_small() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithHilbert_very_small() { insertManyNodesInBulk(new HilbertIndexMaker("Coordinates", "Bulk", testConfigs.get("very_small")), 5000); } @@ -576,41 +577,43 @@ public void shouldInsertManyNodesInBulkWithGreenesSplit_very_small_10() throws F */ @Test - public void shouldInsertManyNodesIndividuallyWithGeohash_small() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithGeohash_small() { insertManyNodesIndividually(new GeohashIndexMaker("Coordinates", "Single", testConfigs.get("small")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithGeohash_small() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithGeohash_small() { insertManyNodesInBulk(new GeohashIndexMaker("Coordinates", "Bulk", testConfigs.get("small")), 5000); } @Test - public void shouldInsertManyNodesIndividuallyWithZOrder_small() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithZOrder_small() { insertManyNodesIndividually(new ZOrderIndexMaker("Coordinates", "Single", testConfigs.get("small")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithZOrder_small() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithZOrder_small() { insertManyNodesInBulk(new ZOrderIndexMaker("Coordinates", "Bulk", testConfigs.get("small")), 5000); } @Test - public void shouldInsertManyNodesIndividuallyWithHilbert_small() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithHilbert_small() { insertManyNodesIndividually(new HilbertIndexMaker("Coordinates", "Single", testConfigs.get("small")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithHilbert_small() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithHilbert_small() { insertManyNodesInBulk(new HilbertIndexMaker("Coordinates", "Bulk", testConfigs.get("small")), 5000); } @Disabled // takes too long, change to @Test when benchmarking + @Test public void shouldInsertManyNodesIndividuallyWithQuadraticSplit_small_10() throws FactoryException, IOException { insertManyNodesIndividually(RTreeIndex.QUADRATIC_SPLIT, 5000, 10, testConfigs.get("small")); } @Disabled // takes too long, change to @Test when benchmarking + @Test public void shouldInsertManyNodesIndividuallyGreenesSplit_small_10() throws FactoryException, IOException { insertManyNodesIndividually(RTreeIndex.GREENES_SPLIT, 5000, 10, testConfigs.get("small")); } @@ -630,11 +633,13 @@ public void shouldInsertManyNodesInBulkWithGreenesSplit_small_10() throws Factor */ @Disabled // takes too long, change to @Test when benchmarking + @Test public void shouldInsertManyNodesIndividuallyWithQuadraticSplit_small_100() throws FactoryException, IOException { insertManyNodesIndividually(RTreeIndex.QUADRATIC_SPLIT, 5000, 100, testConfigs.get("small")); } @Disabled // takes too long, change to @Test when benchmarking + @Test public void shouldInsertManyNodesIndividuallyGreenesSplit_small_100() throws FactoryException, IOException { insertManyNodesIndividually(RTreeIndex.GREENES_SPLIT, 5000, 100, testConfigs.get("small")); } @@ -654,41 +659,43 @@ public void shouldInsertManyNodesInBulkWithGreenesSplit_small_100() throws Facto */ @Test - public void shouldInsertManyNodesIndividuallyWithGeohash_medium() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithGeohash_medium() { insertManyNodesIndividually(new GeohashIndexMaker("Coordinates", "Single", testConfigs.get("medium")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithGeohash_medium() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithGeohash_medium() { insertManyNodesInBulk(new GeohashIndexMaker("Coordinates", "Bulk", testConfigs.get("medium")), 5000); } @Test - public void shouldInsertManyNodesIndividuallyWithZOrder_medium() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithZOrder_medium() { insertManyNodesIndividually(new ZOrderIndexMaker("Coordinates", "Single", testConfigs.get("medium")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithZOrder_medium() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithZOrder_medium() { insertManyNodesInBulk(new ZOrderIndexMaker("Coordinates", "Bulk", testConfigs.get("medium")), 5000); } @Test - public void shouldInsertManyNodesIndividuallyWithHilbert_medium() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithHilbert_medium() { insertManyNodesIndividually(new HilbertIndexMaker("Coordinates", "Single", testConfigs.get("medium")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithHilbert_medium() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithHilbert_medium() { insertManyNodesInBulk(new HilbertIndexMaker("Coordinates", "Bulk", testConfigs.get("medium")), 5000); } @Disabled + @Test public void shouldInsertManyNodesIndividuallyWithQuadraticSplit_medium_10() throws FactoryException, IOException { insertManyNodesIndividually(RTreeIndex.QUADRATIC_SPLIT, 5000, 10, testConfigs.get("medium")); } @Disabled + @Test public void shouldInsertManyNodesIndividuallyGreenesSplit_medium_10() throws FactoryException, IOException { insertManyNodesIndividually(RTreeIndex.GREENES_SPLIT, 5000, 10, testConfigs.get("medium")); } @@ -704,11 +711,13 @@ public void shouldInsertManyNodesInBulkWithGreenesSplit_medium_10() throws Facto } @Disabled + @Test public void shouldInsertManyNodesInBulkWithQuadraticSplit_medium_10_merge() throws FactoryException, IOException { insertManyNodesInBulk(RTreeIndex.QUADRATIC_SPLIT, 5000, 10, testConfigs.get("medium"), true); } @Disabled + @Test public void shouldInsertManyNodesInBulkWithGreenesSplit_medium_10_merge() throws FactoryException, IOException { insertManyNodesInBulk(RTreeIndex.GREENES_SPLIT, 5000, 10, testConfigs.get("medium"), true); } @@ -718,16 +727,19 @@ public void shouldInsertManyNodesInBulkWithGreenesSplit_medium_10_merge() throws */ @Disabled + @Test public void shouldInsertManyNodesIndividuallyWithQuadraticSplit_medium_100() throws FactoryException, IOException { insertManyNodesIndividually(RTreeIndex.QUADRATIC_SPLIT, 5000, 100, testConfigs.get("medium")); } @Disabled + @Test public void shouldInsertManyNodesIndividuallyGreenesSplit_medium_100() throws FactoryException, IOException { insertManyNodesIndividually(RTreeIndex.GREENES_SPLIT, 5000, 100, testConfigs.get("medium")); } @Disabled // takes too long, change to @Test when benchmarking + @Test public void shouldInsertManyNodesInBulkWithQuadraticSplit_medium_100() throws FactoryException, IOException { insertManyNodesInBulk(RTreeIndex.QUADRATIC_SPLIT, 5000, 100, testConfigs.get("medium")); } @@ -738,11 +750,13 @@ public void shouldInsertManyNodesInBulkWithGreenesSplit_medium_100() throws Fact } @Disabled + @Test public void shouldInsertManyNodesInBulkWithQuadraticSplit_medium_100_merge() throws FactoryException, IOException { insertManyNodesInBulk(RTreeIndex.QUADRATIC_SPLIT, 5000, 100, testConfigs.get("medium"), true); } @Disabled + @Test public void shouldInsertManyNodesInBulkWithGreenesSplit_medium_100_merge() throws FactoryException, IOException { insertManyNodesInBulk(RTreeIndex.GREENES_SPLIT, 5000, 100, testConfigs.get("medium"), true); } @@ -752,51 +766,55 @@ public void shouldInsertManyNodesInBulkWithGreenesSplit_medium_100_merge() throw */ @Test - public void shouldInsertManyNodesIndividuallyWithGeohash_large() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithGeohash_large() { insertManyNodesIndividually(new GeohashIndexMaker("Coordinates", "Single", testConfigs.get("large")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithGeohash_large() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithGeohash_large() { insertManyNodesInBulk(new GeohashIndexMaker("Coordinates", "Bulk", testConfigs.get("large")), 5000); } @Test - public void shouldInsertManyNodesIndividuallyWithZOrder_large() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithZOrder_large() { insertManyNodesIndividually(new ZOrderIndexMaker("Coordinates", "Single", testConfigs.get("large")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithZOrder_large() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithZOrder_large() { insertManyNodesInBulk(new ZOrderIndexMaker("Coordinates", "Bulk", testConfigs.get("large")), 5000); } @Test - public void shouldInsertManyNodesIndividuallyWithHilbert_large() throws FactoryException, IOException { + public void shouldInsertManyNodesIndividuallyWithHilbert_large() { insertManyNodesIndividually(new HilbertIndexMaker("Coordinates", "Single", testConfigs.get("large")), 5000); } @Test - public void shouldInsertManyNodesInBulkWithHilbert_large() throws FactoryException, IOException { + public void shouldInsertManyNodesInBulkWithHilbert_large() { insertManyNodesInBulk(new HilbertIndexMaker("Coordinates", "Bulk", testConfigs.get("large")), 5000); } @Disabled // takes too long, change to @Test when benchmarking + @Test public void shouldInsertManyNodesInBulkWithQuadraticSplit_large_10() throws FactoryException, IOException { insertManyNodesInBulk(RTreeIndex.QUADRATIC_SPLIT, 5000, 10, testConfigs.get("large")); } @Disabled // takes too long, change to @Test when benchmarking + @Test public void shouldInsertManyNodesInBulkWithGreenesSplit_large_10() throws FactoryException, IOException { insertManyNodesInBulk(RTreeIndex.GREENES_SPLIT, 5000, 10, testConfigs.get("large")); } @Disabled // takes too long, change to @Test when benchmarking + @Test public void shouldInsertManyNodesInBulkWithQuadraticSplit_large_100() throws FactoryException, IOException { insertManyNodesInBulk(RTreeIndex.QUADRATIC_SPLIT, 5000, 100, testConfigs.get("large")); } @Disabled // takes too long, change to @Test when benchmarking + @Test public void shouldInsertManyNodesInBulkWithGreenesSplit_large_100() throws FactoryException, IOException { insertManyNodesInBulk(RTreeIndex.GREENES_SPLIT, 5000, 100, testConfigs.get("large")); } @@ -810,7 +828,7 @@ class TreePrintingMonitor extends RTreeMonitor { private final RTreeImageExporter imageExporter; private final String splitMode; private final String insertMode; - private HashMap called = new HashMap<>(); + private final HashMap called = new HashMap<>(); TreePrintingMonitor(RTreeImageExporter imageExporter, String insertMode, String splitMode) { this.imageExporter = imageExporter; @@ -870,8 +888,7 @@ private void printRTreeImage(String context, Node rootNode, List envel } private void insertManyNodesIndividually(String splitMode, int blockSize, int maxNodeReferences, - IndexTestConfig config) - throws FactoryException, IOException { + IndexTestConfig config) { insertManyNodesIndividually(new RTreeIndexMaker("Coordinates", splitMode, "Single", maxNodeReferences, config), blockSize); } @@ -902,7 +919,7 @@ private void insertManyNodesIndividually(IndexMaker indexMaker, int blockSize) { } tx.commit(); } - log.log("Splits: " + monitor.getNbrSplit(), (i + 1) * blockSize); + log.log("Splits: " + monitor.getNbrSplit(), (long) (i + 1) * blockSize); } System.out.println("Took " + (System.currentTimeMillis() - start) + "ms to add " + config.totalCount + " nodes to RTree in bulk"); @@ -919,7 +936,8 @@ private void insertManyNodesIndividually(IndexMaker indexMaker, int blockSize) { * ffmpeg -f image2 -r 12 -i rtree-single/rtree-%d.png -r 12 -s 1280x960 rtree-single2_12fps.mp4 */ @Disabled - public void shouldInsertManyNodesIndividuallyAndGenerateImagesForAnimation() throws FactoryException, IOException { + @Test + public void shouldInsertManyNodesIndividuallyAndGenerateImagesForAnimation() throws IOException { IndexTestConfig config = testConfigs.get("medium"); int blockSize = 5; int maxBlockSize = 1000; @@ -981,14 +999,13 @@ public void shouldInsertManyNodesIndividuallyAndGenerateImagesForAnimation() thr } } - private void insertManyNodesInBulk(String splitMode, int blockSize, int maxNodeReferences, IndexTestConfig config) - throws FactoryException, IOException { + private void insertManyNodesInBulk(String splitMode, int blockSize, int maxNodeReferences, IndexTestConfig config) { insertManyNodesInBulk(new RTreeIndexMaker("Coordinates", splitMode, "Bulk", maxNodeReferences, config, false), blockSize); } private void insertManyNodesInBulk(String splitMode, int blockSize, int maxNodeReferences, IndexTestConfig config, - boolean shouldMergeTrees) throws FactoryException, IOException { + boolean shouldMergeTrees) { insertManyNodesInBulk( new RTreeIndexMaker("Coordinates", splitMode, "Bulk", maxNodeReferences, config, shouldMergeTrees), blockSize); @@ -1012,7 +1029,7 @@ private void insertManyNodesInBulk(IndexMaker indexMaker, int blockSize) { } log.log(startIndexing, "Rebuilt: " + monitor.getNbrRebuilt() + ", Splits: " + monitor.getNbrSplit() + ", Cases " - + monitor.getCaseCounts(), (i + 1) * blockSize); + + monitor.getCaseCounts(), (long) (i + 1) * blockSize); } System.out.println( "Took " + (System.currentTimeMillis() - start) + "ms to add " + indexMaker.getConfig().totalCount @@ -1032,7 +1049,8 @@ private void insertManyNodesInBulk(IndexMaker indexMaker, int blockSize) { * ffmpeg -f image2 -r 12 -i rtree-single/rtree-%d.png -r 12 -s 1280x960 rtree-single2_12fps.mp4 */ @Disabled - public void shouldInsertManyNodesInBulkAndGenerateImagesForAnimation() throws FactoryException, IOException { + @Test + public void shouldInsertManyNodesInBulkAndGenerateImagesForAnimation() throws IOException { IndexTestConfig config = testConfigs.get("medium"); int blockSize = 1000; int maxNodeReferences = 10; @@ -1065,7 +1083,7 @@ public void shouldInsertManyNodesInBulkAndGenerateImagesForAnimation() throws Fa } log.log(startIndexing, "Rebuilt: " + monitor.getNbrRebuilt() + ", Splits: " + monitor.getNbrSplit() + ", Cases " - + monitor.getCaseCounts(), (i + 1) * blockSize); + + monitor.getCaseCounts(), (long) (i + 1) * blockSize); try (Transaction tx = db.beginTx()) { imageExporter.saveRTreeLayers(tx, new File("rtree-bulk-" + splitMode + "/rtree-" + i + ".png"), 7); tx.commit(); @@ -1088,7 +1106,8 @@ public void shouldInsertManyNodesInBulkAndGenerateImagesForAnimation() throws Fa } @Disabled - public void shouldAccessIndexAfterBulkInsertion() throws Exception { + @Test + public void shouldAccessIndexAfterBulkInsertion() { // Use these two lines if you want to examine the output. // File dbPath = new File("target/var/BulkTest"); // GraphDatabaseService db = new GraphDatabaseFactory().newEmbeddedDatabase(dbPath.getCanonicalPath()); @@ -1162,6 +1181,7 @@ public void shouldAccessIndexAfterBulkInsertion() throws Exception { } @Disabled + @Test public void shouldBuildTreeFromScratch() throws Exception { //GraphDatabaseService db = this.databases.database("BultTest2"); GraphDatabaseService db = this.db; @@ -1187,7 +1207,7 @@ public void shouldBuildTreeFromScratch() throws Exception { range.addAll(IntStream.rangeClosed(4700, 5000).boxed().collect(Collectors.toList())); for (int i : range) { - System.out.println("Building a Tree with " + Integer.toString(i) + " nodes"); + System.out.println("Building a Tree with " + i + " nodes"); try (Transaction tx = db.beginTx()) { RTreeIndex rtree = new RTreeIndex(); @@ -1205,7 +1225,6 @@ public void shouldBuildTreeFromScratch() throws Exception { } buildRTreeFromScratch.invoke(rtree, rtree.getIndexRoot(tx), decodeEnvelopes.invoke(rtree, coords), 0.7); - RTreeTestUtils testUtils = new RTreeTestUtils(rtree); Map results = RTreeTestUtils.get_height_map(tx, rtree.getIndexRoot(tx)); assertEquals(1, results.size()); @@ -1218,7 +1237,8 @@ public void shouldBuildTreeFromScratch() throws Exception { } @Disabled - public void shouldPerformRTreeBulkInsertion() throws Exception { + @Test + public void shouldPerformRTreeBulkInsertion() { // Use this line if you want to examine the output. //GraphDatabaseService db = databases.database("BulkTest"); @@ -1252,7 +1272,6 @@ public void shouldPerformRTreeBulkInsertion() throws Exception { - time) + "ms"); RTreeIndex rtree = (RTreeIndex) layer.getIndex(); - RTreeTestUtils utils = new RTreeTestUtils(rtree); assertTrue(RTreeTestUtils.check_balance(tx, rtree.getIndexRoot(tx))); tx.commit(); @@ -1281,12 +1300,11 @@ public void shouldPerformRTreeBulkInsertion() throws Exception { for (Relationship r : root.getRelationships(Direction.OUTGOING, RTreeRelationshipTypes.RTREE_CHILD)) { children.add(r.getEndNode()); } - RTreeTestUtils utils = new RTreeTestUtils(rtree); - double root_overlap = utils.calculate_overlap(root); + double root_overlap = RTreeTestUtils.calculate_overlap(root); assertTrue(root_overlap < 0.01); //less than one percent System.out.println("********* Bulk Overlap Percentage" + root_overlap); - double average_child_overlap = children.stream().mapToDouble(utils::calculate_overlap).average() + double average_child_overlap = children.stream().mapToDouble(RTreeTestUtils::calculate_overlap).average() .getAsDouble(); assertTrue(average_child_overlap < 0.02); System.out.println("*********** Bulk Average Child Overlap Percentage" + average_child_overlap); @@ -1717,7 +1735,7 @@ private void restart() throws IOException { db = databases.database(DEFAULT_DATABASE_NAME); } - private void doCleanShutdown() throws IOException { + private void doCleanShutdown() { try { System.out.println("Shutting down database"); if (databases != null) { diff --git a/src/test/java/org/neo4j/gis/spatial/RTreeTestUtils.java b/src/test/java/org/neo4j/gis/spatial/RTreeTestUtils.java index 261f2049..c3d75401 100644 --- a/src/test/java/org/neo4j/gis/spatial/RTreeTestUtils.java +++ b/src/test/java/org/neo4j/gis/spatial/RTreeTestUtils.java @@ -37,20 +37,12 @@ import org.neo4j.graphdb.Transaction; /** + * This class contain functions which can be used to test the integrity of the Rtree. + *

* Created by Philip Stephens on 12/11/2016. */ public class RTreeTestUtils { - /** - * This class contain functions which can be used to test the integrity of the Rtree. - */ - - private final RTreeIndex rtree; - - public RTreeTestUtils(RTreeIndex rtree) { - this.rtree = rtree; - } - public static double one_d_overlap(double a1, double a2, double b1, double b2) { return Double.max( Double.min(a2, b2) - Double.max(a1, b1), @@ -63,7 +55,7 @@ public static double compute_overlap(Envelope a, Envelope b) { one_d_overlap(a.getMinY(), a.getMaxY(), b.getMinY(), b.getMaxY()); } - public double calculate_overlap(Node child) { + public static double calculate_overlap(Node child) { Envelope parent = RTreeIndex.getIndexNodeEnvelope(child); List children = new ArrayList(); diff --git a/src/test/java/org/neo4j/gis/spatial/TestDynamicLayers.java b/src/test/java/org/neo4j/gis/spatial/TestDynamicLayers.java index 8b04b196..bf71633c 100644 --- a/src/test/java/org/neo4j/gis/spatial/TestDynamicLayers.java +++ b/src/test/java/org/neo4j/gis/spatial/TestDynamicLayers.java @@ -246,7 +246,7 @@ private static String toCoordinateText(Coordinate[] coordinates) { } private static String toCoordinateText(Envelope bbox) { - return "" + bbox.getMinX() + ", " + bbox.getMinY() + ", " + bbox.getMaxX() + ", " + bbox.getMaxY(); + return bbox.getMinX() + ", " + bbox.getMinY() + ", " + bbox.getMaxX() + ", " + bbox.getMaxY(); } private void checkIndexAndFeatureCount(Layer layer) throws IOException { diff --git a/src/test/java/org/neo4j/gis/spatial/TestIntersectsPathQueries.java b/src/test/java/org/neo4j/gis/spatial/TestIntersectsPathQueries.java index e50bc3b6..0f04e045 100644 --- a/src/test/java/org/neo4j/gis/spatial/TestIntersectsPathQueries.java +++ b/src/test/java/org/neo4j/gis/spatial/TestIntersectsPathQueries.java @@ -210,8 +210,8 @@ public String toString() { } @SuppressWarnings("SameParameterValue") - private void runTestPointSetGeoptimaIntersection(String tracePath, String dbRoot, String dbName, String layerName, - boolean testMultiPoint) { + private static void runTestPointSetGeoptimaIntersection(String tracePath, String dbRoot, String dbName, + String layerName, boolean testMultiPoint) { withDatabase(dbRoot, dbName, Neo4jTestCase.NORMAL_CONFIG, graphDb -> { SpatialDatabaseService spatial = new SpatialDatabaseService( new IndexManager((GraphDatabaseAPI) graphDb, SecurityContext.AUTH_DISABLED)); diff --git a/src/test/java/org/neo4j/gis/spatial/functions/SpatialFunctionsTest.java b/src/test/java/org/neo4j/gis/spatial/functions/SpatialFunctionsTest.java new file mode 100644 index 00000000..2cdb08f3 --- /dev/null +++ b/src/test/java/org/neo4j/gis/spatial/functions/SpatialFunctionsTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j Spatial. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.neo4j.gis.spatial.functions; + +import static org.hamcrest.Matchers.closeTo; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import java.util.Map; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Test; +import org.neo4j.exceptions.KernelException; +import org.neo4j.gis.spatial.AbstractApiTest; +import org.neo4j.graphdb.spatial.Geometry; + +public class SpatialFunctionsTest extends AbstractApiTest { + + @Override + protected void registerApiProceduresAndFunctions() throws KernelException { + registerProceduresAndFunctions(SpatialFunctions.class); + } + + @Test + public void create_point_and_pass_as_param() { + Geometry geom = (Geometry) executeObject("RETURN point({latitude: 5.0, longitude: 4.0}) as geometry", + "geometry"); + double distance = (Double) executeObject( + "WITH spatial.asGeometry($geom) AS geometry RETURN point.distance(geometry, point({latitude: 5.1, longitude: 4.0})) as distance", + Map.of("geom", geom), "distance"); + MatcherAssert.assertThat("Expected the geographic distance of 11132km", distance, closeTo(11132.0, 1.0)); + } + + @Test + public void create_point_geometry_return() { + Object geometry = executeObject( + "WITH point({latitude: 5.0, longitude: 4.0}) as geom RETURN spatial.asGeometry(geom) AS geometry", + "geometry"); + assertInstanceOf(Geometry.class, geometry, "Should be Geometry type"); + } + + @Test + public void create_point_geometry_and_distance() { + double distance = (double) executeObject( + "WITH point({latitude: 5.0, longitude: 4.0}) as geom WITH spatial.asGeometry(geom) AS geometry RETURN point.distance(geometry, point({latitude: 5.0, longitude: 4.0})) as distance", + "distance"); + System.out.println(distance); + } + + @Test + public void literal_geometry_return() { + Object geometry = executeObject( + "WITH spatial.asGeometry({latitude: 5.0, longitude: 4.0}) AS geometry RETURN geometry", "geometry"); + assertInstanceOf(Geometry.class, geometry, "Should be Geometry type"); + } +} diff --git a/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesPerformanceTest.java b/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesPerformanceTest.java index 1d98fe2b..f60d0bb3 100644 --- a/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesPerformanceTest.java +++ b/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesPerformanceTest.java @@ -37,8 +37,8 @@ public class GeoPipesPerformanceTest extends Neo4jTestCase { - private int records = 10000; - private int chunkSize = records / 10; + private final int records = 10000; + private final int chunkSize = records / 10; @BeforeEach public void setUp() throws Exception { @@ -90,9 +90,9 @@ public float average() { @Override public String toString() { if (count > 0) { - return "" + chunk + ": " + average() + "ms per record (" + count + " records over " + time + "ms)"; + return chunk + ": " + average() + "ms per record (" + count + " records over " + time + "ms)"; } - return "" + chunk + ": INVALID (" + count + " records over " + time + "ms)"; + return chunk + ": INVALID (" + count + " records over " + time + "ms)"; } } diff --git a/src/test/java/org/neo4j/gis/spatial/procedures/SpatialProceduresTest.java b/src/test/java/org/neo4j/gis/spatial/procedures/SpatialProceduresTest.java index dab6800a..a8562200 100644 --- a/src/test/java/org/neo4j/gis/spatial/procedures/SpatialProceduresTest.java +++ b/src/test/java/org/neo4j/gis/spatial/procedures/SpatialProceduresTest.java @@ -31,70 +31,44 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import static org.neo4j.configuration.GraphDatabaseSettings.DEFAULT_DATABASE_NAME; import static org.neo4j.gis.spatial.Constants.LABEL_LAYER; import static org.neo4j.gis.spatial.Constants.PROP_GEOMENCODER; import static org.neo4j.gis.spatial.Constants.PROP_GEOMENCODER_CONFIG; import static org.neo4j.gis.spatial.Constants.PROP_LAYER; import static org.neo4j.gis.spatial.Constants.PROP_LAYER_CLASS; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.function.Consumer; import org.hamcrest.MatcherAssert; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.neo4j.configuration.GraphDatabaseInternalSettings; -import org.neo4j.configuration.GraphDatabaseSettings; -import org.neo4j.dbms.api.DatabaseManagementService; import org.neo4j.exceptions.KernelException; +import org.neo4j.gis.spatial.AbstractApiTest; import org.neo4j.gis.spatial.Layer; import org.neo4j.gis.spatial.SpatialDatabaseService; import org.neo4j.gis.spatial.SpatialRelationshipTypes; +import org.neo4j.gis.spatial.functions.SpatialFunctions; import org.neo4j.gis.spatial.index.IndexManager; import org.neo4j.gis.spatial.utilities.ReferenceNodes; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.ResourceIterator; import org.neo4j.graphdb.Result; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.spatial.Geometry; import org.neo4j.graphdb.spatial.Point; -import org.neo4j.internal.helpers.collection.Iterators; -import org.neo4j.io.fs.FileUtils; import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.procedure.GlobalProcedures; import org.neo4j.kernel.impl.coreapi.InternalTransaction; import org.neo4j.kernel.internal.GraphDatabaseAPI; -import org.neo4j.test.TestDatabaseManagementServiceBuilder; -public class SpatialProceduresTest { +public class SpatialProceduresTest extends AbstractApiTest { - private DatabaseManagementService databases; - private GraphDatabaseService db; - - @BeforeEach - public void setUp() throws KernelException, IOException { - Path dbRoot = new File("target/procedures").toPath(); - FileUtils.deleteDirectory(dbRoot); - databases = new TestDatabaseManagementServiceBuilder(dbRoot) - .setConfig(GraphDatabaseSettings.procedure_unrestricted, List.of("spatial.*")) - .setConfig(GraphDatabaseInternalSettings.trace_cursors, true) - .impermanent().build(); - db = databases.database(DEFAULT_DATABASE_NAME); - registerProceduresAndFunctions(db, SpatialProcedures.class); - } - - @AfterEach - public void tearDown() { - databases.shutdown(); + @Override + protected void registerApiProceduresAndFunctions() throws KernelException { + registerProceduresAndFunctions(SpatialProcedures.class); + registerProceduresAndFunctions(SpatialFunctions.class); } public static void testCall(GraphDatabaseService db, String call, Consumer> consumer) { @@ -326,36 +300,12 @@ public void create_point_and_distance() { System.out.println(distance); } - @Test - // TODO: Support this once procedures are able to return Geometry types - public void create_point_geometry_and_distance() { - double distance = (double) executeObject( - "WITH point({latitude: 5.0, longitude: 4.0}) as geom WITH spatial.asGeometry(geom) AS geometry RETURN point.distance(geometry, point({latitude: 5.0, longitude: 4.0})) as distance", - "distance"); - System.out.println(distance); - } - @Test public void create_point_and_return() { Object geometry = executeObject("RETURN point({latitude: 5.0, longitude: 4.0}) as geometry", "geometry"); assertInstanceOf(Geometry.class, geometry, "Should be Geometry type"); } - @Test - public void create_point_geometry_return() { - Object geometry = executeObject( - "WITH point({latitude: 5.0, longitude: 4.0}) as geom RETURN spatial.asGeometry(geom) AS geometry", - "geometry"); - assertInstanceOf(Geometry.class, geometry, "Should be Geometry type"); - } - - @Test - public void literal_geometry_return() { - Object geometry = executeObject( - "WITH spatial.asGeometry({latitude: 5.0, longitude: 4.0}) AS geometry RETURN geometry", "geometry"); - assertInstanceOf(Geometry.class, geometry, "Should be Geometry type"); - } - @Test public void create_node_decode_to_geometry() { execute("CALL spatial.addWKTLayer('geom','geom')"); @@ -377,74 +327,6 @@ public void create_node_and_convert_to_geometry() { MatcherAssert.assertThat("Expected the cartesian distance of 1.0", distance, closeTo(1.0, 0.00001)); } - @Test - // TODO: Currently this only works for point geometries because Neo4j 3.4 can only return Point geometries from procedures - public void create_point_and_pass_as_param() { - Geometry geom = (Geometry) executeObject("RETURN point({latitude: 5.0, longitude: 4.0}) as geometry", - "geometry"); - double distance = (Double) executeObject( - "WITH spatial.asGeometry($geom) AS geometry RETURN point.distance(geometry, point({latitude: 5.1, longitude: 4.0})) as distance", - Map.of("geom", geom), "distance"); - MatcherAssert.assertThat("Expected the geographic distance of 11132km", distance, closeTo(11132.0, 1.0)); - } - - private long execute(String statement) { - try (Transaction tx = db.beginTx()) { - long count = Iterators.count(tx.execute(statement)); - tx.commit(); - return count; - } - } - - private long execute(String statement, Map params) { - try (Transaction tx = db.beginTx()) { - long count = Iterators.count(tx.execute(statement, params)); - tx.commit(); - return count; - } - } - - private void executeWrite(String call) { - try (Transaction tx = db.beginTx()) { - tx.execute(call).accept(v -> true); - tx.commit(); - } - } - - private Node createNode(String call, String column) { - Node node; - try (Transaction tx = db.beginTx()) { - ResourceIterator nodes = tx.execute(call).columnAs(column); - node = (Node) nodes.next(); - nodes.close(); - tx.commit(); - } - return node; - } - - private Object executeObject(String call, String column) { - Object obj; - try (Transaction tx = db.beginTx()) { - ResourceIterator values = tx.execute(call).columnAs(column); - obj = values.next(); - values.close(); - tx.commit(); - } - return obj; - } - - private Object executeObject(String call, Map params, String column) { - Object obj; - try (Transaction tx = db.beginTx()) { - Map p = (params == null) ? Map.of() : params; - ResourceIterator values = tx.execute(call, p).columnAs(column); - obj = values.next(); - values.close(); - tx.commit(); - } - return obj; - } - @Test public void create_a_pointlayer_with_x_and_y() { testCall(db, "CALL spatial.addPointLayerXY('geom','lon','lat')", @@ -1104,6 +986,7 @@ public void import_osm_twice_should_pass_with_different_layers() { } @Disabled + @Test public void import_cracow_to_layer() { execute("CALL spatial.addLayer('geom','OSM','')"); testCountQuery("importCracowToLayer", "CALL spatial.importOSMToLayer('geom','issue-347/cra.osm')", 256253,