diff --git a/pom.xml b/pom.xml index fce635a9..85900e21 100644 --- a/pom.xml +++ b/pom.xml @@ -339,7 +339,6 @@ org.geotools gt-geojson ${geotools.version} - provided org.geotools.xsd diff --git a/src/main/java/org/neo4j/gis/spatial/ShapefileImporter.java b/src/main/java/org/neo4j/gis/spatial/ShapefileImporter.java index daebac3e..012da835 100644 --- a/src/main/java/org/neo4j/gis/spatial/ShapefileImporter.java +++ b/src/main/java/org/neo4j/gis/spatial/ShapefileImporter.java @@ -103,7 +103,7 @@ public List importFile(String dataset, String layerName, Charset charset) EditableLayerImpl layer; try (Transaction tx = database.beginTx()) { layer = (EditableLayerImpl) spatialDatabase.getOrCreateLayer(tx, layerName, WKBGeometryEncoder.class, - layerClass); + layerClass, null); tx.commit(); } return importFile(dataset, layer, charset); diff --git a/src/main/java/org/neo4j/gis/spatial/SpatialDatabaseService.java b/src/main/java/org/neo4j/gis/spatial/SpatialDatabaseService.java index 61433229..cbe77bf9 100644 --- a/src/main/java/org/neo4j/gis/spatial/SpatialDatabaseService.java +++ b/src/main/java/org/neo4j/gis/spatial/SpatialDatabaseService.java @@ -61,6 +61,9 @@ */ public class SpatialDatabaseService implements Constants { + public static final String RTREE_INDEX_NAME = "rtree"; + public static final String GEOHASH_INDEX_NAME = "geohash"; + public final IndexManager indexManager; public SpatialDatabaseService(IndexManager indexManager) { @@ -178,30 +181,29 @@ public DynamicLayer asDynamicLayer(Transaction tx, Layer layer) { return (DynamicLayer) LayerUtilities.makeLayerFromNode(tx, indexManager, node); } - public DefaultLayer getOrCreateDefaultLayer(Transaction tx, String name) { - return (DefaultLayer) getOrCreateLayer(tx, name, WKBGeometryEncoder.class, EditableLayerImpl.class, ""); + public DefaultLayer getOrCreateDefaultLayer(Transaction tx, String name, String indexConfig) { + return (DefaultLayer) getOrCreateLayer(tx, name, WKBGeometryEncoder.class, EditableLayerImpl.class, "", + indexConfig); } - public EditableLayer getOrCreateEditableLayer(Transaction tx, String name, String format, - String propertyNameConfig) { + public EditableLayer getOrCreateEditableLayer(Transaction tx, String name, String format, String propertyNameConfig, + String indexConfig) { Class geClass = WKBGeometryEncoder.class; if (format != null && format.toUpperCase().startsWith("WKT")) { geClass = WKTGeometryEncoder.class; } - return (EditableLayer) getOrCreateLayer(tx, name, geClass, EditableLayerImpl.class, propertyNameConfig); + return (EditableLayer) getOrCreateLayer(tx, name, geClass, EditableLayerImpl.class, propertyNameConfig, + indexConfig); } - public EditableLayer getOrCreateEditableLayer(Transaction tx, String name) { - return getOrCreateEditableLayer(tx, name, "WKB", ""); + public EditableLayer getOrCreateEditableLayer(Transaction tx, String name, String indexConfig) { + return getOrCreateEditableLayer(tx, name, "WKB", "", indexConfig); } - public EditableLayer getOrCreateEditableLayer(Transaction tx, String name, String wktProperty) { - return getOrCreateEditableLayer(tx, name, "WKT", wktProperty); + public EditableLayer getOrCreateEditableLayer(Transaction tx, String name, String wktProperty, String indexConfig) { + return getOrCreateEditableLayer(tx, name, "WKT", wktProperty, indexConfig); } - public static final String RTREE_INDEX_NAME = "rtree"; - public static final String GEOHASH_INDEX_NAME = "geohash"; - public static Class resolveIndexClass(String index) { if (index == null) { return LayerRTreeIndex.class; @@ -216,23 +218,25 @@ public static Class resolveIndexClass(String index) } public EditableLayer getOrCreateSimplePointLayer(Transaction tx, String name, String index, String xProperty, - String yProperty) { - return getOrCreatePointLayer(tx, name, resolveIndexClass(index), SimplePointEncoder.class, xProperty, + String yProperty, String indexConfig) { + return getOrCreatePointLayer(tx, name, resolveIndexClass(index), SimplePointEncoder.class, indexConfig, + xProperty, yProperty); } public EditableLayer getOrCreateNativePointLayer(Transaction tx, String name, String index, - String locationProperty) { - return getOrCreatePointLayer(tx, name, resolveIndexClass(index), SimplePointEncoder.class, locationProperty); + String locationProperty, String indexConfig) { + return getOrCreatePointLayer(tx, name, resolveIndexClass(index), SimplePointEncoder.class, indexConfig, + locationProperty); } public EditableLayer getOrCreatePointLayer(Transaction tx, String name, Class indexClass, Class encoderClass, - String... encoderConfig) { + String indexConfig, String... encoderConfig) { Layer layer = getLayer(tx, name); if (layer == null) { return (EditableLayer) createLayer(tx, name, encoderClass, SimplePointLayer.class, indexClass, - makeEncoderConfig(encoderConfig), DefaultGeographicCRS.WGS84); + makeEncoderConfig(encoderConfig), indexConfig, DefaultGeographicCRS.WGS84); } if (layer instanceof EditableLayer) { return (EditableLayer) layer; @@ -242,10 +246,10 @@ public EditableLayer getOrCreatePointLayer(Transaction tx, String name, } public Layer getOrCreateLayer(Transaction tx, String name, Class geometryEncoder, - Class layerClass, String config) { + Class layerClass, String encoderConfig, String indexConfig) { Layer layer = getLayer(tx, name); if (layer == null) { - layer = createLayer(tx, name, geometryEncoder, layerClass, null, config); + layer = createLayer(tx, name, geometryEncoder, layerClass, null, encoderConfig, indexConfig); } else if (!(layerClass == null || layerClass.isInstance(layer))) { throw new SpatialDatabaseException( "Existing layer '" + layer + "' is not of the expected type: " + layerClass); @@ -254,8 +258,8 @@ public Layer getOrCreateLayer(Transaction tx, String name, Class geometryEncoder, - Class layerClass) { - return getOrCreateLayer(tx, name, geometryEncoder, layerClass, ""); + Class layerClass, String indexConfig) { + return getOrCreateLayer(tx, name, geometryEncoder, layerClass, "", indexConfig); } /** @@ -298,8 +302,8 @@ public boolean containsLayer(Transaction tx, String name) { return getLayer(tx, name) != null; } - public Layer createWKBLayer(Transaction tx, String name) { - return createLayer(tx, name, WKBGeometryEncoder.class, EditableLayerImpl.class); + public Layer createWKBLayer(Transaction tx, String name, String indexConfig) { + return createLayer(tx, name, WKBGeometryEncoder.class, EditableLayerImpl.class, indexConfig); } public SimplePointLayer createSimplePointLayer(Transaction tx, String name) { @@ -311,7 +315,7 @@ public SimplePointLayer createSimplePointLayer(Transaction tx, String name, Stri } public SimplePointLayer createSimplePointLayer(Transaction tx, String name, String... xybProperties) { - return createPointLayer(tx, name, LayerRTreeIndex.class, SimplePointEncoder.class, xybProperties); + return createPointLayer(tx, name, LayerRTreeIndex.class, SimplePointEncoder.class, null, xybProperties); } public SimplePointLayer createNativePointLayer(Transaction tx, String name) { @@ -324,13 +328,14 @@ public SimplePointLayer createNativePointLayer(Transaction tx, String name, Stri } public SimplePointLayer createNativePointLayer(Transaction tx, String name, String... encoderConfig) { - return createPointLayer(tx, name, LayerRTreeIndex.class, NativePointEncoder.class, encoderConfig); + return createPointLayer(tx, name, LayerRTreeIndex.class, NativePointEncoder.class, null, encoderConfig); } public SimplePointLayer createPointLayer(Transaction tx, String name, Class indexClass, - Class encoderClass, String... encoderConfig) { + Class encoderClass, String indexConfig, String... encoderConfig + ) { return (SimplePointLayer) createLayer(tx, name, encoderClass, SimplePointLayer.class, indexClass, - makeEncoderConfig(encoderConfig), org.geotools.referencing.crs.DefaultGeographicCRS.WGS84); + makeEncoderConfig(encoderConfig), indexConfig, org.geotools.referencing.crs.DefaultGeographicCRS.WGS84); } public static String makeEncoderConfig(String... args) { @@ -349,19 +354,27 @@ public static String makeEncoderConfig(String... args) { } public Layer createLayer(Transaction tx, String name, Class geometryEncoderClass, - Class layerClass) { - return createLayer(tx, name, geometryEncoderClass, layerClass, null, null); - } - - public Layer createLayer(Transaction tx, String name, Class geometryEncoderClass, - Class layerClass, Class indexClass, - String encoderConfig) { - return createLayer(tx, name, geometryEncoderClass, layerClass, indexClass, encoderConfig, null); + Class layerClass, String indexConfig) { + return createLayer(tx, name, geometryEncoderClass, layerClass, null, null, indexConfig); } public Layer createLayer(Transaction tx, String name, Class geometryEncoderClass, Class layerClass, Class indexClass, - String encoderConfig, CoordinateReferenceSystem crs) { + String encoderConfig, + String indexConfig + ) { + return createLayer(tx, name, geometryEncoderClass, layerClass, indexClass, encoderConfig, indexConfig, null); + } + + public Layer createLayer(Transaction tx, + String name, + Class geometryEncoderClass, + Class layerClass, + Class indexClass, + String encoderConfig, + String indexConfig, + CoordinateReferenceSystem crs + ) { if (containsLayer(tx, name)) { throw new SpatialDatabaseException("Layer " + name + " already exists"); } @@ -379,6 +392,17 @@ public Layer createLayer(Transaction tx, String name, Class jtsCla * @return new Layer with copy of all geometries */ public Layer createResultsLayer(Transaction tx, String layerName, List results) { - EditableLayer layer = (EditableLayer) createWKBLayer(tx, layerName); + EditableLayer layer = (EditableLayer) createWKBLayer(tx, layerName, ""); for (SpatialDatabaseRecord record : results) { layer.add(tx, record.getGeometry()); } @@ -544,15 +568,16 @@ private static void addRegisteredLayerType(RegisteredLayerType type) { registeredLayerTypes.put(type.typeName.toLowerCase(), type); } - public Layer getOrCreateRegisteredTypeLayer(Transaction tx, String name, String type, String config) { + public Layer getOrCreateRegisteredTypeLayer(Transaction tx, String name, String type, String encoderConfig, + String indexConfig) { RegisteredLayerType registeredLayerType = registeredLayerTypes.get(type.toLowerCase()); - return getOrCreateRegisteredTypeLayer(tx, name, registeredLayerType, config); + return getOrCreateRegisteredTypeLayer(tx, name, registeredLayerType, encoderConfig, indexConfig); } public Layer getOrCreateRegisteredTypeLayer(Transaction tx, String name, RegisteredLayerType registeredLayerType, - String config) { + String encoderConfig, String indexConfig) { return getOrCreateLayer(tx, name, registeredLayerType.geometryEncoder, registeredLayerType.layerClass, - (config == null) ? registeredLayerType.defaultConfig : config); + (encoderConfig == null) ? registeredLayerType.defaultConfig : encoderConfig, indexConfig); } public static Map getRegisteredLayerTypes() { diff --git a/src/main/java/org/neo4j/gis/spatial/osm/OSMImporter.java b/src/main/java/org/neo4j/gis/spatial/osm/OSMImporter.java index 22557f21..f343d209 100644 --- a/src/main/java/org/neo4j/gis/spatial/osm/OSMImporter.java +++ b/src/main/java/org/neo4j/gis/spatial/osm/OSMImporter.java @@ -250,7 +250,7 @@ public long reIndex(GraphDatabaseService database, int commitInterval, boolean i OSMDataset dataset; try (Transaction tx = beginTx(database)) { layer = (OSMLayer) spatialDatabase.getOrCreateLayer(tx, layerName, OSMGeometryEncoder.class, - OSMLayer.class); + OSMLayer.class, null); dataset = OSMDataset.withDatasetId(tx, layer, osm_dataset); tx.commit(); } 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 02f7fc2e..b6aa9328 100644 --- a/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java +++ b/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java @@ -211,13 +211,15 @@ public Stream getAllLayerTypes() { public Stream addSimplePointLayer( @Name("name") String name, @Name(value = "indexType", defaultValue = RTREE_INDEX_NAME) String indexType, - @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName) { + @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig + ) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { return streamNode(sdb.createLayer(tx, name, SimplePointEncoder.class, SimplePointLayer.class, - SpatialDatabaseService.resolveIndexClass(indexType), null, - selectCRS(crsName)).getLayerNode(tx)); + SpatialDatabaseService.resolveIndexClass(indexType), null, indexConfig, selectCRS(crsName)) + .getLayerNode(tx)); } throw new IllegalArgumentException("Cannot create existing layer: " + name); } @@ -226,37 +228,44 @@ public Stream addSimplePointLayer( @Description("Adds a new simple point layer with geohash based index, returns the layer root node") public Stream addSimplePointLayerGeohash( @Name("name") String name, - @Name(value = "crsName", defaultValue = WGS84_CRS_NAME) String crsName) { + @Name(value = "crsName", defaultValue = WGS84_CRS_NAME) String crsName, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { return streamNode(sdb.createLayer(tx, name, SimplePointEncoder.class, SimplePointLayer.class, - LayerGeohashPointIndex.class, null, - selectCRS(crsName)).getLayerNode(tx)); + LayerGeohashPointIndex.class, null, indexConfig, selectCRS(crsName)) + .getLayerNode(tx)); } throw new IllegalArgumentException("Cannot create existing layer: " + name); } @Procedure(value = "spatial.addPointLayerZOrder", mode = WRITE) @Description("Adds a new simple point layer with z-order curve based index, returns the layer root node") - public Stream addSimplePointLayerZOrder(@Name("name") String name) { + public Stream addSimplePointLayerZOrder(@Name("name") String name, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { return streamNode(sdb.createLayer(tx, name, SimplePointEncoder.class, SimplePointLayer.class, - LayerZOrderPointIndex.class, null, DefaultGeographicCRS.WGS84).getLayerNode(tx)); + LayerZOrderPointIndex.class, null, indexConfig, DefaultGeographicCRS.WGS84) + .getLayerNode(tx)); } throw new IllegalArgumentException("Cannot create existing layer: " + name); } @Procedure(value = "spatial.addPointLayerHilbert", mode = WRITE) @Description("Adds a new simple point layer with hilbert curve based index, returns the layer root node") - public Stream addSimplePointLayerHilbert(@Name("name") String name) { + public Stream addSimplePointLayerHilbert( + @Name("name") String name, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig + ) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { return streamNode(sdb.createLayer(tx, name, SimplePointEncoder.class, SimplePointLayer.class, - LayerHilbertPointIndex.class, null, DefaultGeographicCRS.WGS84).getLayerNode(tx)); + LayerHilbertPointIndex.class, null, indexConfig, DefaultGeographicCRS.WGS84) + .getLayerNode(tx)); } throw new IllegalArgumentException("Cannot create existing layer: " + name); } @@ -268,15 +277,17 @@ public Stream addSimplePointLayer( @Name("xProperty") String xProperty, @Name("yProperty") String yProperty, @Name(value = "indexType", defaultValue = RTREE_INDEX_NAME) String indexType, - @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName) { + @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { if (xProperty != null && yProperty != null) { return streamNode(sdb.createLayer(tx, name, SimplePointEncoder.class, SimplePointLayer.class, - SpatialDatabaseService.resolveIndexClass(indexType), - SpatialDatabaseService.makeEncoderConfig(xProperty, yProperty), - selectCRS(hintCRSName(crsName, yProperty))).getLayerNode(tx)); + SpatialDatabaseService.resolveIndexClass(indexType), + SpatialDatabaseService.makeEncoderConfig(xProperty, yProperty), indexConfig, + selectCRS(hintCRSName(crsName, yProperty))) + .getLayerNode(tx)); } throw new IllegalArgumentException( "Cannot create layer '" + name + "': Missing encoder config values: xProperty[" + xProperty @@ -291,14 +302,16 @@ public Stream addSimplePointLayerWithConfig( @Name("name") String name, @Name("encoderConfig") String encoderConfig, @Name(value = "indexType", defaultValue = RTREE_INDEX_NAME) String indexType, - @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName) { + @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { if (encoderConfig.indexOf(':') > 0) { return streamNode(sdb.createLayer(tx, name, SimplePointEncoder.class, SimplePointLayer.class, - SpatialDatabaseService.resolveIndexClass(indexType), encoderConfig, - selectCRS(hintCRSName(crsName, encoderConfig))).getLayerNode(tx)); + SpatialDatabaseService.resolveIndexClass(indexType), encoderConfig, indexConfig, + selectCRS(hintCRSName(crsName, encoderConfig))) + .getLayerNode(tx)); } throw new IllegalArgumentException( "Cannot create layer '" + name + "': invalid encoder config '" + encoderConfig + "'"); @@ -311,12 +324,14 @@ public Stream addSimplePointLayerWithConfig( public Stream addNativePointLayer( @Name("name") String name, @Name(value = "indexType", defaultValue = RTREE_INDEX_NAME) String indexType, - @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName) { + @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { return streamNode(sdb.createLayer(tx, name, NativePointEncoder.class, SimplePointLayer.class, - SpatialDatabaseService.resolveIndexClass(indexType), null, selectCRS(crsName)).getLayerNode(tx)); + SpatialDatabaseService.resolveIndexClass(indexType), null, indexConfig, selectCRS(crsName)) + .getLayerNode(tx)); } throw new IllegalArgumentException("Cannot create existing layer: " + name); } @@ -325,36 +340,43 @@ public Stream addNativePointLayer( @Description("Adds a new native point layer with geohash based index, returns the layer root node") public Stream addNativePointLayerGeohash( @Name("name") String name, - @Name(value = "crsName", defaultValue = WGS84_CRS_NAME) String crsName) { + @Name(value = "crsName", defaultValue = WGS84_CRS_NAME) String crsName, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { return streamNode(sdb.createLayer(tx, name, NativePointEncoder.class, SimplePointLayer.class, - LayerGeohashPointIndex.class, null, selectCRS(crsName)).getLayerNode(tx)); + LayerGeohashPointIndex.class, null, indexConfig, selectCRS(crsName)) + .getLayerNode(tx)); } throw new IllegalArgumentException("Cannot create existing layer: " + name); } @Procedure(value = "spatial.addNativePointLayerZOrder", mode = WRITE) @Description("Adds a new native point layer with z-order curve based index, returns the layer root node") - public Stream addNativePointLayerZOrder(@Name("name") String name) { + public Stream addNativePointLayerZOrder(@Name("name") String name, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { return streamNode(sdb.createLayer(tx, name, NativePointEncoder.class, SimplePointLayer.class, - LayerZOrderPointIndex.class, null, DefaultGeographicCRS.WGS84).getLayerNode(tx)); + LayerZOrderPointIndex.class, null, indexConfig, DefaultGeographicCRS.WGS84) + .getLayerNode(tx)); } throw new IllegalArgumentException("Cannot create existing layer: " + name); } @Procedure(value = "spatial.addNativePointLayerHilbert", mode = WRITE) @Description("Adds a new native point layer with hilbert curve based index, returns the layer root node") - public Stream addNativePointLayerHilbert(@Name("name") String name) { + public Stream addNativePointLayerHilbert(@Name("name") String name, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig + ) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { return streamNode(sdb.createLayer(tx, name, NativePointEncoder.class, SimplePointLayer.class, - LayerHilbertPointIndex.class, null, DefaultGeographicCRS.WGS84).getLayerNode(tx)); + LayerHilbertPointIndex.class, null, indexConfig, DefaultGeographicCRS.WGS84) + .getLayerNode(tx)); } throw new IllegalArgumentException("Cannot create existing layer: " + name); } @@ -366,15 +388,17 @@ public Stream addNativePointLayer( @Name("xProperty") String xProperty, @Name("yProperty") String yProperty, @Name(value = "indexType", defaultValue = RTREE_INDEX_NAME) String indexType, - @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName) { + @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { if (xProperty != null && yProperty != null) { return streamNode(sdb.createLayer(tx, name, NativePointEncoder.class, SimplePointLayer.class, - SpatialDatabaseService.resolveIndexClass(indexType), - SpatialDatabaseService.makeEncoderConfig(xProperty, yProperty), - selectCRS(hintCRSName(crsName, yProperty))).getLayerNode(tx)); + SpatialDatabaseService.resolveIndexClass(indexType), + SpatialDatabaseService.makeEncoderConfig(xProperty, yProperty), indexConfig, + selectCRS(hintCRSName(crsName, yProperty))) + .getLayerNode(tx)); } throw new IllegalArgumentException( "Cannot create layer '" + name + "': Missing encoder config values: xProperty[" + xProperty @@ -389,14 +413,16 @@ public Stream addNativePointLayerWithConfig( @Name("name") String name, @Name("encoderConfig") String encoderConfig, @Name(value = "indexType", defaultValue = RTREE_INDEX_NAME) String indexType, - @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName) { + @Name(value = "crsName", defaultValue = UNSET_CRS_NAME) String crsName, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { if (encoderConfig.indexOf(':') > 0) { return streamNode(sdb.createLayer(tx, name, NativePointEncoder.class, SimplePointLayer.class, - SpatialDatabaseService.resolveIndexClass(indexType), encoderConfig, - selectCRS(hintCRSName(crsName, encoderConfig))).getLayerNode(tx)); + SpatialDatabaseService.resolveIndexClass(indexType), encoderConfig, indexConfig, + selectCRS(hintCRSName(crsName, encoderConfig))) + .getLayerNode(tx)); } throw new IllegalArgumentException( "Cannot create layer '" + name + "': invalid encoder config '" + encoderConfig + "'"); @@ -405,6 +431,7 @@ public Stream addNativePointLayerWithConfig( } public static final String UNSET_CRS_NAME = ""; + public static final String UNSET_INDEX_CONFIG = ""; public static final String WGS84_CRS_NAME = "wgs84"; /** @@ -437,15 +464,17 @@ private static String hintCRSName(String crsName, String hint) { public Stream addLayerWithEncoder( @Name("name") String name, @Name("encoder") String encoderClassName, - @Name("encoderConfig") String encoderConfig) { + @Name("encoderConfig") String encoderConfig, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { Class encoderClass = encoderClasses.get(encoderClassName); Class layerClass = SpatialDatabaseService.suggestLayerClassForEncoder(encoderClass); if (encoderClass != null) { - return streamNode( - sdb.createLayer(tx, name, encoderClass, layerClass, null, encoderConfig).getLayerNode(tx)); + return streamNode(sdb + .createLayer(tx, name, encoderClass, layerClass, null, encoderConfig, indexConfig) + .getLayerNode(tx)); } throw new IllegalArgumentException( "Cannot create layer '" + name + "': invalid encoder class '" + encoderClassName + "'"); @@ -458,13 +487,15 @@ public Stream addLayerWithEncoder( public Stream addLayerOfType( @Name("name") String name, @Name("type") String type, - @Name("encoderConfig") String encoderConfig) { + @Name("encoderConfig") String encoderConfig, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { SpatialDatabaseService sdb = spatial(); Layer layer = sdb.getLayer(tx, name); if (layer == null) { Map knownTypes = SpatialDatabaseService.getRegisteredLayerTypes(); if (knownTypes.containsKey(type.toLowerCase())) { - return streamNode(sdb.getOrCreateRegisteredTypeLayer(tx, name, type, encoderConfig).getLayerNode(tx)); + return streamNode(sdb.getOrCreateRegisteredTypeLayer(tx, name, type, encoderConfig, indexConfig) + .getLayerNode(tx)); } throw new IllegalArgumentException( "Cannot create layer '" + name + "': unknown type '" + type + "' - supported types are " @@ -484,8 +515,9 @@ private static Stream streamNode(String nodeId) { @Procedure(value = "spatial.addWKTLayer", mode = WRITE) @Description("Adds a new WKT layer with the given node property to hold the WKT string, returns the layer root node") public Stream addWKTLayer(@Name("name") String name, - @Name("nodePropertyName") String nodePropertyName) { - return addLayerOfType(name, "WKT", nodePropertyName); + @Name("nodePropertyName") String nodePropertyName, + @Name(value = "indexConfig", defaultValue = UNSET_INDEX_CONFIG) String indexConfig) { + return addLayerOfType(name, "WKT", nodePropertyName, indexConfig); } @Procedure(value = "spatial.layer", mode = WRITE) @@ -671,7 +703,7 @@ public Stream importOSM( // Delegate creating the layer to the inner thread, so we do not pollute the procedure transaction with anything that might conflict. // Since the procedure transaction starts before, and ends after, all inner transactions. BiFunction layerMaker = (tx, name) -> (OSMLayer) spatial().getOrCreateLayer(tx, - name, OSMGeometryEncoder.class, OSMLayer.class); + name, OSMGeometryEncoder.class, OSMLayer.class, ""); return Stream.of(new CountResult(importOSMToLayer(uri, layerName, layerMaker))); } diff --git a/src/main/java/org/neo4j/gis/spatial/rtree/RTreeIndex.java b/src/main/java/org/neo4j/gis/spatial/rtree/RTreeIndex.java index 11b357d7..b7c50b30 100644 --- a/src/main/java/org/neo4j/gis/spatial/rtree/RTreeIndex.java +++ b/src/main/java/org/neo4j/gis/spatial/rtree/RTreeIndex.java @@ -67,6 +67,7 @@ public class RTreeIndex implements SpatialIndexWriter, Configurable { public static final String KEY_MAX_NODE_REFERENCES = "maxNodeReferences"; public static final String KEY_SHOULD_MERGE_TREES = "shouldMergeTrees"; + public static final String REFERENCE_RELATIONSHIP_TYPE = "referenceRelationshipType"; public static final int MIN_MAX_NODE_REFERENCES = 10; public static final int MAX_MAX_NODE_REFERENCES = 1000000; public static final int DEFAULT_MAX_NODE_REFERENCES = 100; @@ -77,6 +78,7 @@ public class RTreeIndex implements SpatialIndexWriter, Configurable { private int maxNodeReferences; private String splitMode = GREENES_SPLIT; private boolean shouldMergeTrees = false; + private RelationshipType referenceRelationshipType = RTreeRelationshipTypes.RTREE_REFERENCE; private int totalGeometryCount = 0; private boolean countSaved = false; @@ -106,6 +108,9 @@ public EnvelopeDecoder getEnvelopeDecoder() { @Override public void setConfiguration(String jsonConfig) { + if (jsonConfig == null || jsonConfig.isBlank()) { + return; + } JSONObject jsonObject = (JSONObject) JSONValue.parse(jsonConfig); HashMap config = new HashMap<>(); for (Object key : jsonObject.keySet()) { @@ -120,26 +125,28 @@ public String getConfiguration() { config.put(KEY_SPLIT, this.splitMode); config.put(KEY_MAX_NODE_REFERENCES, this.maxNodeReferences); config.put(KEY_SHOULD_MERGE_TREES, this.shouldMergeTrees); + config.put(REFERENCE_RELATIONSHIP_TYPE, this.referenceRelationshipType.name()); return JSONObject.toJSONString(config); } @Override public void configure(Map config) { - for (String key : config.keySet()) { + config.forEach((key, rawValue) -> { switch (key) { case KEY_SPLIT: - String value = config.get(key).toString(); + String value = rawValue.toString(); switch (value) { case QUADRATIC_SPLIT: case GREENES_SPLIT: splitMode = value; break; default: - throw new IllegalArgumentException("No such RTreeIndex value for '" + key + "': " + value); + throw new IllegalArgumentException( + "No such RTreeIndex value for '" + key + "': " + rawValue); } break; case KEY_MAX_NODE_REFERENCES: - int intValue = Integer.parseInt(config.get(key).toString()); + int intValue = Integer.parseInt(rawValue.toString()); if (intValue < MIN_MAX_NODE_REFERENCES) { throw new IllegalArgumentException( "RTreeIndex does not allow " + key + " less than " + MIN_MAX_NODE_REFERENCES); @@ -151,12 +158,15 @@ public void configure(Map config) { this.maxNodeReferences = intValue; break; case KEY_SHOULD_MERGE_TREES: - this.shouldMergeTrees = Boolean.parseBoolean(config.get(key).toString()); + this.shouldMergeTrees = Boolean.parseBoolean(rawValue.toString()); + break; + case REFERENCE_RELATIONSHIP_TYPE: + this.referenceRelationshipType = RelationshipType.withName(rawValue.toString()); break; default: throw new IllegalArgumentException("No such RTreeIndex configuration key: " + key); } - } + }); } @Override @@ -177,7 +187,7 @@ private void addBelow(Transaction tx, Node parent, Node geomNode) { parent = chooseSubTree(parent, geomNode); } // bbox enlargement needed - if (countChildren(parent, RTreeRelationshipTypes.RTREE_REFERENCE) >= maxNodeReferences) { + if (countChildren(parent, referenceRelationshipType) >= maxNodeReferences) { insertInLeaf(parent, geomNode); splitAndAdjustPathBoundingBox(tx, parent); } else if (insertInLeaf(parent, geomNode)) { @@ -404,7 +414,7 @@ private List bulkInsertion(Transaction tx, Node rootNode, int int newHeight = getHeight(newRootNode, 0); if (newHeight == 1) { monitor.addCase("h_i > l_t (d==1)"); - try (var relationships = newRootNode.getRelationships(RTreeRelationshipTypes.RTREE_REFERENCE)) { + try (var relationships = newRootNode.getRelationships(referenceRelationshipType)) { for (Relationship geom : relationships) { addBelow(tx, child.node, geom.getEndNode()); geom.delete(); @@ -615,7 +625,7 @@ public void remove(Transaction tx, String geomNodeId, boolean deleteGeomNode, bo // remove the entry final Relationship geometryRtreeReference = geomNode.getSingleRelationship( - RTreeRelationshipTypes.RTREE_REFERENCE, Direction.INCOMING); + referenceRelationshipType, Direction.INCOMING); if (geometryRtreeReference != null) { geometryRtreeReference.delete(); } @@ -624,11 +634,11 @@ public void remove(Transaction tx, String geomNodeId, boolean deleteGeomNode, bo } // reorganize the tree if needed - if (countChildren(indexNode, RTreeRelationshipTypes.RTREE_REFERENCE) == 0) { - indexNode = deleteEmptyTreeNodes(indexNode, RTreeRelationshipTypes.RTREE_REFERENCE); + if (countChildren(indexNode, referenceRelationshipType) == 0) { + indexNode = deleteEmptyTreeNodes(indexNode, referenceRelationshipType); adjustParentBoundingBox(indexNode, RTreeRelationshipTypes.RTREE_CHILD); } else { - adjustParentBoundingBox(indexNode, RTreeRelationshipTypes.RTREE_REFERENCE); + adjustParentBoundingBox(indexNode, referenceRelationshipType); } adjustPathBoundingBox(indexNode); @@ -670,7 +680,7 @@ public boolean needsToVisit(Envelope indexNodeEnvelope) { @Override public void onIndexReference(Node geomNode) { - geomNode.getSingleRelationship(RTreeRelationshipTypes.RTREE_REFERENCE, Direction.INCOMING).delete(); + geomNode.getSingleRelationship(referenceRelationshipType, Direction.INCOMING).delete(); if (deleteGeomNodes) { deleteNode(geomNode); } @@ -791,7 +801,7 @@ public Evaluation evaluate(Path path, BranchState SearchFilter.EnvelopFilterResult.FILTER) .relationships(RTreeRelationshipTypes.RTREE_CHILD, Direction.OUTGOING) - .relationships(RTreeRelationshipTypes.RTREE_REFERENCE, Direction.OUTGOING) + .relationships(referenceRelationshipType, Direction.OUTGOING) .evaluator(searchEvaluator); Traverser traverser = td.traverse(getIndexRoot(tx)); return new SearchResults(traverser.nodes()); @@ -840,9 +850,9 @@ public void visit(Transaction tx, SpatialIndexVisitor visitor, Node indexNode) { } } } else // Node is a leaf - if (indexNode.hasRelationship(Direction.OUTGOING, RTreeRelationshipTypes.RTREE_REFERENCE)) { + if (indexNode.hasRelationship(Direction.OUTGOING, referenceRelationshipType)) { try (var relationships = indexNode.getRelationships(Direction.OUTGOING, - RTreeRelationshipTypes.RTREE_REFERENCE)) { + referenceRelationshipType)) { for (Relationship rel : relationships) { visitor.onIndexReference(rel.getEndNode()); } @@ -862,7 +872,7 @@ public Node getIndexRoot(Transaction tx) { * know whether the child is a leaf or an index node. */ private Envelope getChildNodeEnvelope(Node child, RelationshipType relType) { - if (relType.name().equals(RTreeRelationshipTypes.RTREE_REFERENCE.name())) { + if (relType.name().equals(referenceRelationshipType.name())) { return getLeafNodeEnvelope(child); } return getIndexNodeEnvelope(child); @@ -892,7 +902,7 @@ public static Envelope getIndexNodeEnvelope(Node indexNode) { return new Envelope(bbox[0], bbox[2], bbox[1], bbox[3]); } - private static void visitInTx(Transaction tx, SpatialIndexVisitor visitor, String indexNodeId) { + private void visitInTx(Transaction tx, SpatialIndexVisitor visitor, String indexNodeId) { Node indexNode = tx.getNodeByElementId(indexNodeId); if (!visitor.needsToVisit(getIndexNodeEnvelope(indexNode))) { return; @@ -915,9 +925,9 @@ private static void visitInTx(Transaction tx, SpatialIndexVisitor visitor, Strin visitInTx(tx, visitor, child); } } else // Node is a leaf - if (indexNode.hasRelationship(Direction.OUTGOING, RTreeRelationshipTypes.RTREE_REFERENCE)) { + if (indexNode.hasRelationship(Direction.OUTGOING, referenceRelationshipType)) { try (var relationships = indexNode.getRelationships(Direction.OUTGOING, - RTreeRelationshipTypes.RTREE_REFERENCE)) { + referenceRelationshipType)) { for (Relationship rel : relationships) { visitor.onIndexReference(rel.getEndNode()); } @@ -1071,7 +1081,7 @@ private static int countChildren(Node indexNode, RelationshipType relationshipTy * @return is enlargement needed? */ private boolean insertInLeaf(Node indexNode, Node geomRootNode) { - return addChild(indexNode, RTreeRelationshipTypes.RTREE_REFERENCE, geomRootNode); + return addChild(indexNode, referenceRelationshipType, geomRootNode); } private void splitAndAdjustPathBoundingBox(Transaction tx, Node indexNode) { @@ -1098,14 +1108,14 @@ private void splitAndAdjustPathBoundingBox(Transaction tx, Node indexNode) { private Node quadraticSplit(Transaction tx, Node indexNode) { if (nodeIsLeaf(indexNode)) { - return quadraticSplit(tx, indexNode, RTreeRelationshipTypes.RTREE_REFERENCE); + return quadraticSplit(tx, indexNode, referenceRelationshipType); } return quadraticSplit(tx, indexNode, RTreeRelationshipTypes.RTREE_CHILD); } private Node greenesSplit(Transaction tx, Node indexNode) { if (nodeIsLeaf(indexNode)) { - return greenesSplit(tx, indexNode, RTreeRelationshipTypes.RTREE_REFERENCE); + return greenesSplit(tx, indexNode, referenceRelationshipType); } return greenesSplit(tx, indexNode, RTreeRelationshipTypes.RTREE_CHILD); } @@ -1425,12 +1435,12 @@ private static void deleteRecursivelySubtree(Node node, Relationship incoming) { node.delete(); } - protected static boolean isGeometryNodeIndexed(Node geomNode) { - return geomNode.hasRelationship(Direction.INCOMING, RTreeRelationshipTypes.RTREE_REFERENCE); + protected boolean isGeometryNodeIndexed(Node geomNode) { + return geomNode.hasRelationship(Direction.INCOMING, referenceRelationshipType); } - protected static Node findLeafContainingGeometryNode(Node geomNode) { - return geomNode.getSingleRelationship(RTreeRelationshipTypes.RTREE_REFERENCE, Direction.INCOMING) + protected Node findLeafContainingGeometryNode(Node geomNode) { + return geomNode.getSingleRelationship(referenceRelationshipType, Direction.INCOMING) .getStartNode(); } @@ -1488,7 +1498,7 @@ public void onIndexReference(Node geomNode) { * the objects from one type to another without loading all into memory, * we need to use this ugly java-magic. Man, I miss Ruby right now! */ - private static class IndexNodeToGeometryNodeIterable implements Iterable { + private class IndexNodeToGeometryNodeIterable implements Iterable { private final Iterator allIndexNodeIterator; @@ -1512,7 +1522,7 @@ private void checkGeometryNodeIterator() { MonoDirectionalTraversalDescription traversal = new MonoDirectionalTraversalDescription(); TraversalDescription td = traversal .depthFirst() - .relationships(RTreeRelationshipTypes.RTREE_REFERENCE, Direction.OUTGOING) + .relationships(referenceRelationshipType, Direction.OUTGOING) .evaluator(Evaluators.excludeStartPosition()) .evaluator(Evaluators.toDepth(1)); while ((geometryNodeIterator == null || !geometryNodeIterator.hasNext()) && diff --git a/src/test/java/org/neo4j/gis/spatial/LayerSignatureTest.java b/src/test/java/org/neo4j/gis/spatial/LayerSignatureTest.java index b0a367f7..5ae2056c 100644 --- a/src/test/java/org/neo4j/gis/spatial/LayerSignatureTest.java +++ b/src/test/java/org/neo4j/gis/spatial/LayerSignatureTest.java @@ -64,19 +64,19 @@ public void testDefaultSimplePointLayer() { @Test public void testSimpleWKBLayer() { testLayerSignature("EditableLayer(name='test', encoder=WKBGeometryEncoder(geom='geometry', bbox='bbox'))", - tx -> spatial.createWKBLayer(tx, "test")); + tx -> spatial.createWKBLayer(tx, "test", null)); } @Test public void testWKBLayer() { testLayerSignature("EditableLayer(name='test', encoder=WKBGeometryEncoder(geom='wkb', bbox='bbox'))", - tx -> spatial.getOrCreateEditableLayer(tx, "test", "wkb", "wkb")); + tx -> spatial.getOrCreateEditableLayer(tx, "test", "wkb", "wkb", null)); } @Test public void testWKTLayer() { testLayerSignature("EditableLayer(name='test', encoder=WKTGeometryEncoder(geom='wkt', bbox='bbox'))", - tx -> spatial.getOrCreateEditableLayer(tx, "test", "wkt", "wkt")); + tx -> spatial.getOrCreateEditableLayer(tx, "test", "wkt", "wkt", null)); } private Layer testLayerSignature(String signature, Function layerMaker) { @@ -100,7 +100,7 @@ private void inTx(Consumer txFunction) { public void testDynamicLayer() { Layer layer = testLayerSignature( "EditableLayer(name='test', encoder=WKTGeometryEncoder(geom='wkt', bbox='bbox'))", - tx -> spatial.getOrCreateEditableLayer(tx, "test", "wkt", "wkt")); + tx -> spatial.getOrCreateEditableLayer(tx, "test", "wkt", "wkt", null)); inTx(tx -> { DynamicLayer dynamic = spatial.asDynamicLayer(tx, layer); assertEquals("EditableLayer(name='test', encoder=WKTGeometryEncoder(geom='wkt', bbox='bbox'))", diff --git a/src/test/java/org/neo4j/gis/spatial/LayersTest.java b/src/test/java/org/neo4j/gis/spatial/LayersTest.java index 7eca583f..c3bb3a40 100644 --- a/src/test/java/org/neo4j/gis/spatial/LayersTest.java +++ b/src/test/java/org/neo4j/gis/spatial/LayersTest.java @@ -92,7 +92,7 @@ public void testBasicLayerOperations() { assertNull(layer); }); inTx(tx -> { - Layer layer = spatial.createWKBLayer(tx, layerName); + Layer layer = spatial.createWKBLayer(tx, layerName, null); assertNotNull(layer); assertThat("Should be a default layer", layer instanceof DefaultLayer); }); @@ -128,7 +128,7 @@ private void testPointLayer(Class indexClass, new IndexManager((GraphDatabaseAPI) graphDb, SecurityContext.AUTH_DISABLED)); inTx(tx -> { EditableLayer layer = (EditableLayer) spatial.createLayer(tx, layerName, encoderClass, - EditableLayerImpl.class, indexClass, null); + EditableLayerImpl.class, indexClass, null, null); assertNotNull(layer); }); inTx(tx -> { @@ -178,7 +178,7 @@ private void testDeleteGeometry(Class encoderClass) { new IndexManager((GraphDatabaseAPI) graphDb, SecurityContext.AUTH_DISABLED)); inTx(tx -> { EditableLayer layer = (EditableLayer) spatial.createLayer(tx, layerName, encoderClass, - EditableLayerImpl.class, null, null); + EditableLayerImpl.class, null, null, null); assertNotNull(layer); }); inTx(tx -> { @@ -197,7 +197,7 @@ public void testEditableLayer() { SpatialDatabaseService spatial = new SpatialDatabaseService( new IndexManager((GraphDatabaseAPI) graphDb, SecurityContext.AUTH_DISABLED)); inTx(tx -> { - EditableLayer layer = spatial.getOrCreateEditableLayer(tx, layerName); + EditableLayer layer = spatial.getOrCreateEditableLayer(tx, layerName, null, null); assertNotNull(layer); }); inTx(tx -> { @@ -234,7 +234,7 @@ public void testSnapToLine() { SpatialDatabaseService spatial = new SpatialDatabaseService( new IndexManager((GraphDatabaseAPI) graphDb, SecurityContext.AUTH_DISABLED)); inTx(tx -> { - EditableLayer layer = spatial.getOrCreateEditableLayer(tx, "roads"); + EditableLayer layer = spatial.getOrCreateEditableLayer(tx, "roads", null, null); Coordinate crossing_bygg_forstadsgatan = new Coordinate(13.0171471, 55.6074148); Coordinate[] waypoints_forstadsgatan = {new Coordinate(13.0201511, 55.6066846), crossing_bygg_forstadsgatan}; @@ -272,7 +272,7 @@ private String testSpecificEditableLayer(String layerName, Class { - Layer layer = spatial.createLayer(tx, layerName, geometryEncoderClass, layerClass); + Layer layer = spatial.createLayer(tx, layerName, geometryEncoderClass, layerClass, null); assertNotNull(layer); assertInstanceOf(EditableLayer.class, layer, "Should be an editable layer"); }); @@ -350,7 +350,7 @@ public void testIndexAccessAfterBulkInsertion() { // GraphDatabaseService db = new GraphDatabaseFactory().newEmbeddedDatabase(dbPath.getCanonicalPath()); SpatialDatabaseService spatial = new SpatialDatabaseService( new IndexManager((GraphDatabaseAPI) graphDb, SecurityContext.AUTH_DISABLED)); - inTx(tx -> spatial.getOrCreateSimplePointLayer(tx, "Coordinates", "rtree", "lat", "lon")); + inTx(tx -> spatial.getOrCreateSimplePointLayer(tx, "Coordinates", "rtree", "lat", "lon", null)); Random rand = new Random(); diff --git a/src/test/java/org/neo4j/gis/spatial/OsmAnalysisTest.java b/src/test/java/org/neo4j/gis/spatial/OsmAnalysisTest.java index b64bbeac..3bc5ed65 100644 --- a/src/test/java/org/neo4j/gis/spatial/OsmAnalysisTest.java +++ b/src/test/java/org/neo4j/gis/spatial/OsmAnalysisTest.java @@ -353,7 +353,7 @@ private static SortedMap exportPoints(Transaction tx, String laye } EditableLayerImpl layer = (EditableLayerImpl) spatialService.createLayer(tx, name, - WKBGeometryEncoder.class, EditableLayerImpl.class); + WKBGeometryEncoder.class, EditableLayerImpl.class, ""); layer.setExtraPropertyNames( new String[]{"user_id", "user_name", "year", "month", "dayOfMonth", "weekOfYear"}, tx); diff --git a/src/test/java/org/neo4j/gis/spatial/RTreeBulkInsertTest.java b/src/test/java/org/neo4j/gis/spatial/RTreeBulkInsertTest.java index c0019de6..de9177af 100644 --- a/src/test/java/org/neo4j/gis/spatial/RTreeBulkInsertTest.java +++ b/src/test/java/org/neo4j/gis/spatial/RTreeBulkInsertTest.java @@ -179,7 +179,7 @@ private EditableLayer getOrCreateSimplePointLayer(String name, String index, Str CoordinateReferenceSystem crs = DefaultEngineeringCRS.GENERIC_2D; try (Transaction tx = db.beginTx()) { SpatialDatabaseService sdbs = spatial(); - EditableLayer layer = sdbs.getOrCreateSimplePointLayer(tx, name, index, xProperty, yProperty); + EditableLayer layer = sdbs.getOrCreateSimplePointLayer(tx, name, index, xProperty, yProperty, null); layer.setCoordinateReferenceSystem(tx, crs); tx.commit(); return layer; @@ -1255,7 +1255,7 @@ public void shouldPerformRTreeBulkInsertion() { System.out.println("BulkLoadingTestRun " + j); try (Transaction tx = db.beginTx()) { - EditableLayer layer = sdbs.getOrCreateSimplePointLayer(tx, "BulkLoader", "rtree", "lon", "lat"); + EditableLayer layer = sdbs.getOrCreateSimplePointLayer(tx, "BulkLoader", "rtree", "lon", "lat", null); List coords = new ArrayList<>(N); for (int i = 0; i < N; i++) { Node n = tx.createNode(Label.label("Coordinate")); diff --git a/src/test/java/org/neo4j/gis/spatial/TestOSMImportBase.java b/src/test/java/org/neo4j/gis/spatial/TestOSMImportBase.java index 4f97943f..cdb8bd58 100644 --- a/src/test/java/org/neo4j/gis/spatial/TestOSMImportBase.java +++ b/src/test/java/org/neo4j/gis/spatial/TestOSMImportBase.java @@ -66,7 +66,7 @@ protected static void checkOSMLayer(GraphDatabaseService db, String layerName) t SpatialDatabaseService spatial = new SpatialDatabaseService( new IndexManager((GraphDatabaseAPI) db, SecurityContext.AUTH_DISABLED)); OSMLayer layer = (OSMLayer) spatial.getOrCreateLayer(tx, layerName, OSMGeometryEncoder.class, - OSMLayer.class); + OSMLayer.class, null); Assertions.assertNotNull(layer.getIndex(), "OSM Layer index should not be null"); Assertions.assertNotNull(layer.getIndex().getBoundingBox(tx), "OSM Layer index envelope should not be null"); diff --git a/src/test/java/org/neo4j/gis/spatial/TestRemove.java b/src/test/java/org/neo4j/gis/spatial/TestRemove.java index d0daaa3f..2eda1ea8 100644 --- a/src/test/java/org/neo4j/gis/spatial/TestRemove.java +++ b/src/test/java/org/neo4j/gis/spatial/TestRemove.java @@ -37,7 +37,7 @@ public void testAddMoreThanMaxNodeRefThenDeleteAll() { new IndexManager((GraphDatabaseAPI) graphDb(), SecurityContext.AUTH_DISABLED)); try (Transaction tx = graphDb().beginTx()) { - spatial.createLayer(tx, layerName, WKTGeometryEncoder.class, EditableLayerImpl.class); + spatial.createLayer(tx, layerName, WKTGeometryEncoder.class, EditableLayerImpl.class, ""); tx.commit(); } diff --git a/src/test/java/org/neo4j/gis/spatial/TestSpatialQueries.java b/src/test/java/org/neo4j/gis/spatial/TestSpatialQueries.java index f04c6b22..6995c7ec 100644 --- a/src/test/java/org/neo4j/gis/spatial/TestSpatialQueries.java +++ b/src/test/java/org/neo4j/gis/spatial/TestSpatialQueries.java @@ -53,7 +53,7 @@ public void testSearchClosestWithShortLongLineStrings() throws ParseException { Geometry longLineString; Geometry point; try (Transaction tx = graphDb().beginTx()) { - EditableLayer layer = spatial.getOrCreateEditableLayer(tx, layerName, "WKT"); + EditableLayer layer = spatial.getOrCreateEditableLayer(tx, layerName, "WKT", null); WKTReader wkt = new WKTReader(layer.getGeometryFactory()); shortLineString = wkt.read("LINESTRING(16.3493032 48.199882,16.3479487 48.1997337)"); longLineString = wkt.read( diff --git a/src/test/java/org/neo4j/gis/spatial/TestSpatialUtils.java b/src/test/java/org/neo4j/gis/spatial/TestSpatialUtils.java index 964da8de..aca18f19 100644 --- a/src/test/java/org/neo4j/gis/spatial/TestSpatialUtils.java +++ b/src/test/java/org/neo4j/gis/spatial/TestSpatialUtils.java @@ -48,7 +48,7 @@ public void testJTSLinearRef() { new IndexManager((GraphDatabaseAPI) graphDb(), SecurityContext.AUTH_DISABLED)); Geometry geometry; try (Transaction tx = graphDb().beginTx()) { - EditableLayer layer = spatial.getOrCreateEditableLayer(tx, "jts"); + EditableLayer layer = spatial.getOrCreateEditableLayer(tx, "jts", null, null); Coordinate[] coordinates = new Coordinate[]{new Coordinate(0, 0), new Coordinate(0, 1), new Coordinate(1, 1)}; geometry = layer.getGeometryFactory().createLineString(coordinates); @@ -145,7 +145,7 @@ public void testSnapping() throws Exception { OSMDataset.fromLayer(tx, osmLayer); // cache for future usage below GeometryFactory factory = osmLayer.getGeometryFactory(); EditableLayerImpl resultsLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, - "testSnapping_results"); + "testSnapping_results", null, null); String[] fieldsNames = new String[]{"snap-id", "description", "distance"}; resultsLayer.setExtraPropertyNames(fieldsNames, tx); Point point = factory.createPoint(new Coordinate(12.9777, 56.0555)); diff --git a/src/test/java/org/neo4j/gis/spatial/index/LayerIndexTestBase.java b/src/test/java/org/neo4j/gis/spatial/index/LayerIndexTestBase.java index 3f42774c..36e1237b 100644 --- a/src/test/java/org/neo4j/gis/spatial/index/LayerIndexTestBase.java +++ b/src/test/java/org/neo4j/gis/spatial/index/LayerIndexTestBase.java @@ -170,7 +170,7 @@ public void shouldCreateAndFindAndDeleteIndexViaLayer() { private SimplePointLayer makeTestPointLayer() { try (Transaction tx = graph.beginTx()) { - SimplePointLayer layer = spatial.createPointLayer(tx, "test", getIndexClass(), getEncoderClass()); + SimplePointLayer layer = spatial.createPointLayer(tx, "test", getIndexClass(), getEncoderClass(), null); tx.commit(); return layer; } diff --git a/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesDocTest.java b/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesDocTest.java index 58d4313d..7cdfa91d 100644 --- a/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesDocTest.java +++ b/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesDocTest.java @@ -946,7 +946,7 @@ private static void load() throws Exception { loadTestOsmData("two-street.osm", 100); osmLayer = spatial.getLayer(tx, "two-street.osm"); - boxesLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "boxes"); + boxesLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "boxes", null, null); boxesLayer.setExtraPropertyNames(new String[]{"name"}, tx); boxesLayer.setCoordinateReferenceSystem(tx, DefaultEngineeringCRS.GENERIC_2D); WKTReader reader = new WKTReader(boxesLayer.getGeometryFactory()); @@ -957,19 +957,19 @@ private static void load() throws Exception { reader.read("POLYGON ((2 3, 2 5, 6 5, 6 3, 2 3))"), new String[]{"name"}, new Object[]{"B"}); - concaveLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "concave"); + concaveLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "concave", null, null); concaveLayer.setCoordinateReferenceSystem(tx, DefaultEngineeringCRS.GENERIC_2D); reader = new WKTReader(concaveLayer.getGeometryFactory()); concaveLayer.add(tx, reader.read("POLYGON ((0 0, 2 5, 0 10, 10 10, 10 0, 0 0))")); - intersectionLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "intersection"); + intersectionLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "intersection", null, null); intersectionLayer.setCoordinateReferenceSystem(tx, DefaultEngineeringCRS.GENERIC_2D); reader = new WKTReader(intersectionLayer.getGeometryFactory()); intersectionLayer.add(tx, reader.read("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))")); intersectionLayer.add(tx, reader.read("POLYGON ((4 4, 4 10, 10 10, 10 4, 4 4))")); intersectionLayer.add(tx, reader.read("POLYGON ((2 2, 2 6, 6 6, 6 2, 2 2))")); - equalLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "equal"); + equalLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "equal", null, null); equalLayer.setExtraPropertyNames(new String[]{"id", "name"}, tx); equalLayer.setCoordinateReferenceSystem(tx, DefaultEngineeringCRS.GENERIC_2D); reader = new WKTReader(intersectionLayer.getGeometryFactory()); @@ -984,7 +984,7 @@ private static void load() throws Exception { reader.read("POLYGON ((0 0, 0 2, 0 4, 0 5, 5 5, 5 3, 5 2, 5 0, 0 0))"), new String[]{"id", "name"}, new Object[]{4, "topo equal"}); - linesLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "lines"); + linesLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "lines", null, null); linesLayer.setCoordinateReferenceSystem(tx, DefaultEngineeringCRS.GENERIC_2D); reader = new WKTReader(intersectionLayer.getGeometryFactory()); linesLayer.add(tx, reader.read("LINESTRING (12 26, 15 27, 18 32, 20 38, 23 34)")); 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 a8562200..6a1811c6 100644 --- a/src/test/java/org/neo4j/gis/spatial/procedures/SpatialProceduresTest.java +++ b/src/test/java/org/neo4j/gis/spatial/procedures/SpatialProceduresTest.java @@ -141,15 +141,13 @@ public static void registerProceduresAndFunctions(GraphDatabaseService db, Class private static Layer makeLayerOfVariousTypes(SpatialDatabaseService spatial, Transaction tx, String name, int index) { - switch (index % 3) { - case 0: - return spatial.getOrCreateSimplePointLayer(tx, name, SpatialDatabaseService.RTREE_INDEX_NAME, "x", "y"); - case 1: - return spatial.getOrCreateNativePointLayer(tx, name, SpatialDatabaseService.RTREE_INDEX_NAME, - "location"); - default: - return spatial.getOrCreateDefaultLayer(tx, name); - } + return switch (index % 3) { + case 0 -> spatial.getOrCreateSimplePointLayer(tx, name, SpatialDatabaseService.RTREE_INDEX_NAME, "x", "y", + null); + case 1 -> spatial.getOrCreateNativePointLayer(tx, name, SpatialDatabaseService.RTREE_INDEX_NAME, "location", + null); + default -> spatial.getOrCreateDefaultLayer(tx, name, null); + }; } private void makeOldSpatialModel(Transaction tx, String... layers) { @@ -568,7 +566,7 @@ public void list_spatial_procedures() { procs.get("spatial.layers")); assertEquals("spatial.layer(name :: STRING) :: (node :: NODE)", procs.get("spatial.layer")); assertEquals( - "spatial.addLayer(name :: STRING, type :: STRING, encoderConfig :: STRING) :: (node :: NODE)", + "spatial.addLayer(name :: STRING, type :: STRING, encoderConfig :: STRING, indexConfig = :: STRING) :: (node :: NODE)", procs.get("spatial.addLayer")); assertEquals("spatial.addNode(layerName :: STRING, node :: NODE) :: (node :: NODE)", procs.get("spatial.addNode")); @@ -868,6 +866,41 @@ public void add_many_nodes_to_the_native_point_layer_using_addNodes() { testRemoveNodes("native_poi", count); } + @Test + public void add_node_to_multiple_indexes_in_chunks() { + // Playing with this number in both tests leads to rough benchmarking of the addNode/addNodes comparison + int count = 100; + execute(""" + UNWIND range(1,$count) as i + CREATE (n:Point { + id: i, + point1: point( { latitude: 56.0, longitude: 12.0 } ), + point2: point( { latitude: 57.0, longitude: 13.0 } ) + })""", Map.of("count", count)); + execute("CALL spatial.addLayer('point1','NativePoint','point1:point1BB', '{\"referenceRelationshipType\": \"RTREE_P1_TYPE\"}')"); + execute("CALL spatial.addLayer('point2','NativePoint','point2:point2BB', '{\"referenceRelationshipType\": \"RTREE_P2_TYPE\"}')"); + db.executeTransactionally(""" + MATCH (p:Point) + WITH (count(p) / 10) AS pages, collect(p) AS nodes + UNWIND range(0, pages) AS i CALL { + WITH i, nodes + CALL spatial.addNodes('point1', nodes[(i * 10)..((i + 1) * 10)]) YIELD count + RETURN count AS count + } IN TRANSACTIONS OF 1 ROWS + RETURN sum(count) AS count + """); + db.executeTransactionally(""" + MATCH (p:Point) + WITH (count(p) / 10) AS pages, collect(p) AS nodes + UNWIND range(0, pages) AS i CALL { + WITH i, nodes + CALL spatial.addNodes('point2', nodes[(i * 10)..((i + 1) * 10)]) YIELD count + RETURN count AS count + } IN TRANSACTIONS OF 1 ROWS + RETURN sum(count) AS count + """); + } + @Test public void add_many_nodes_to_the_native_point_layer_using_addNode() { // Playing with this number in both tests leads to rough benchmarking of the addNode/addNodes comparison