diff --git a/docs/docs/modules/ROOT/partials/generated/api/index.adoc b/docs/docs/modules/ROOT/partials/generated/api/index.adoc index db4f618d..a7df6d6a 100644 --- a/docs/docs/modules/ROOT/partials/generated/api/index.adoc +++ b/docs/docs/modules/ROOT/partials/generated/api/index.adoc @@ -169,7 +169,7 @@ Removes the given node from the layer, returns the geometry-node Removes the given nodes from the layer, returns the count of nodes removed |label:procedure[] -|xref:api/spatial/spatial.setFeatureAttributes.adoc[spatial.setFeatureAttributes icon:book[]] +|xref:api/spatial/spatial.setFeatureAttributes.adoc[spatial.setFeatureAttributes icon:book[]] label:deprecated[] Sets the feature attributes of the given layer |label:procedure[] diff --git a/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.getFeatureAttributes-examples.adoc b/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.getFeatureAttributes-examples.adoc index 45519b27..125f7054 100644 --- a/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.getFeatureAttributes-examples.adoc +++ b/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.getFeatureAttributes-examples.adoc @@ -65,11 +65,11 @@ CALL spatial.getFeatureAttributes('geom') .Result -[opts="header",cols="1"] +[opts="header",cols="2"] |=== -|name -|name -|type -|color +|className|name +|java.lang.String|color +|java.lang.String|name +|java.lang.String|type |=== diff --git a/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.getFeatureAttributes.adoc b/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.getFeatureAttributes.adoc index 110f3440..f58c997d 100644 --- a/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.getFeatureAttributes.adoc +++ b/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.getFeatureAttributes.adoc @@ -12,7 +12,7 @@ Returns feature attributes of the given layer [source] ---- -spatial.getFeatureAttributes(name :: STRING) :: (name :: STRING) +spatial.getFeatureAttributes(name :: STRING) :: (name :: STRING, className :: STRING) ---- == Input parameters @@ -30,5 +30,6 @@ a|The name of the layer |=== |Name|Type|Description |name|STRING| +|className|STRING| |=== diff --git a/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.setFeatureAttributes-examples.adoc b/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.setFeatureAttributes-examples.adoc index 45519b27..125f7054 100644 --- a/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.setFeatureAttributes-examples.adoc +++ b/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.setFeatureAttributes-examples.adoc @@ -65,11 +65,11 @@ CALL spatial.getFeatureAttributes('geom') .Result -[opts="header",cols="1"] +[opts="header",cols="2"] |=== -|name -|name -|type -|color +|className|name +|java.lang.String|color +|java.lang.String|name +|java.lang.String|type |=== diff --git a/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.setFeatureAttributes.adoc b/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.setFeatureAttributes.adoc index a9a3ae6e..008bee17 100644 --- a/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.setFeatureAttributes.adoc +++ b/docs/docs/modules/ROOT/partials/generated/api/spatial/spatial.setFeatureAttributes.adoc @@ -3,11 +3,17 @@ :description: This section contains reference documentation for the spatial.setFeatureAttributes procedure. -label:procedure[] +label:procedure[] label:deprecated[] [.emphasis] Sets the feature attributes of the given layer +[WARNING] +==== + +This procedure is deprecated by: feature attributes are now automatically discovered when a new node is added to the index +==== + == Signature [source] diff --git a/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jFeatureBuilder.java b/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jFeatureBuilder.java index f22bcdd5..aecea397 100644 --- a/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jFeatureBuilder.java +++ b/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jFeatureBuilder.java @@ -20,11 +20,9 @@ package org.geotools.data.neo4j; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; +import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import org.geotools.api.feature.simple.SimpleFeature; import org.geotools.api.feature.simple.SimpleFeatureType; import org.geotools.api.feature.type.AttributeDescriptor; @@ -53,11 +51,11 @@ public class Neo4jFeatureBuilder { private static final String FEATURE_PROP_GEOM = "the_geom"; private final SimpleFeatureBuilder builder; - private final List extraPropertyNames; + private final Map> extraProperties; - public Neo4jFeatureBuilder(SimpleFeatureType sft, List extraPropertyNames) { + public Neo4jFeatureBuilder(SimpleFeatureType sft, Map> extraProperties) { this.builder = new SimpleFeatureBuilder(sft); - this.extraPropertyNames = extraPropertyNames; + this.extraProperties = extraProperties == null ? Collections.emptyMap() : extraProperties; } /** @@ -65,18 +63,15 @@ public Neo4jFeatureBuilder(SimpleFeatureType sft, List extraPropertyName * builder */ public static Neo4jFeatureBuilder fromLayer(Transaction tx, Layer layer) { - return new Neo4jFeatureBuilder(getTypeFromLayer(tx, layer), Arrays.asList(layer.getExtraPropertyNames(tx))); + return new Neo4jFeatureBuilder(getTypeFromLayer(tx, layer), layer.getExtraProperties(tx)); } public SimpleFeature buildFeature(String id, Geometry geometry, Map properties) { builder.reset(); builder.set(FEATURE_PROP_GEOM, geometry); - if (extraPropertyNames != null) { - for (String name : extraPropertyNames) { - builder.set(name, properties.get(name)); - } + for (String name : extraProperties.keySet()) { + builder.set(name, properties.get(name)); } - return builder.buildFeature(id); } @@ -86,12 +81,12 @@ public SimpleFeature buildFeature(Transaction tx, SpatialRecord rec) { public static SimpleFeatureType getTypeFromLayer(Transaction tx, Layer layer) { return getType(layer.getName(), layer.getGeometryType(tx), layer.getCoordinateReferenceSystem(tx), - layer.getExtraPropertyNames(tx)); + layer.getExtraProperties(tx)); } public static SimpleFeatureType getType(String name, Integer geometryTypeId, CoordinateReferenceSystem crs, - String[] extraPropertyNames) { - List types = readAttributes(geometryTypeId, crs, extraPropertyNames); + Map> extraProperties) { + List types = readAttributes(geometryTypeId, crs, extraProperties); // find Geometry type SimpleFeatureType parent = null; @@ -121,7 +116,7 @@ public static SimpleFeatureType getType(String name, Integer geometryTypeId, Coo } private static List readAttributes(Integer geometryTypeId, CoordinateReferenceSystem crs, - String[] extraPropertyNames) { + Map> extraProperties) { Class geometryClass = SpatialDatabaseService.convertGeometryTypeToJtsClass(geometryTypeId); AttributeTypeBuilder build = new AttributeTypeBuilder(); @@ -135,21 +130,13 @@ private static List readAttributes(Integer geometryTypeId, List attributes = new ArrayList<>(); attributes.add(build.buildDescriptor(BasicFeatureTypes.GEOMETRY_ATTRIBUTE_NAME, geometryType)); - if (extraPropertyNames != null) { - Set usedNames = new HashSet<>(); + if (extraProperties != null) { // record names in case of duplicates - usedNames.add(BasicFeatureTypes.GEOMETRY_ATTRIBUTE_NAME); - - for (String propertyName : extraPropertyNames) { - if (!usedNames.contains(propertyName)) { - usedNames.add(propertyName); - - build.setNillable(true); - build.setBinding(String.class); - - attributes.add(build.buildDescriptor(propertyName)); - } - } + extraProperties.forEach((propertyName, aClass) -> { + build.setNillable(true); + build.setBinding(aClass); + attributes.add(build.buildDescriptor(propertyName)); + }); } return attributes; diff --git a/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jSpatialDataStore.java b/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jSpatialDataStore.java index 3ad4ed03..fb8bd921 100644 --- a/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jSpatialDataStore.java +++ b/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jSpatialDataStore.java @@ -162,7 +162,7 @@ public void clearCache() { protected ContentFeatureSource createFeatureSource(ContentEntry contentEntry) throws IOException { Layer layer; ArrayList records = new ArrayList<>(); - String[] extraPropertyNames; + Map> extraProperties; try (Transaction tx = database.beginTx()) { layer = spatialDatabase.getLayer(tx, contentEntry.getTypeName(), false); SearchRecords results = layer.getIndex().search(tx, new SearchAll()); @@ -171,11 +171,11 @@ protected ContentFeatureSource createFeatureSource(ContentEntry contentEntry) th for (SpatialDatabaseRecord record : results) { records.add(record); } - extraPropertyNames = layer.getExtraPropertyNames(tx); + extraProperties = layer.getExtraProperties(tx); tx.commit(); } Neo4jSpatialFeatureSource source = new Neo4jSpatialFeatureSource(contentEntry, database, layer, - buildFeatureType(contentEntry.getTypeName()), records, extraPropertyNames); + buildFeatureType(contentEntry.getTypeName()), records, extraProperties.keySet()); if (layer instanceof EditableLayer) { return new Neo4jSpatialFeatureStore(contentEntry, database, (EditableLayer) layer, source); } diff --git a/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jSpatialFeatureSource.java b/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jSpatialFeatureSource.java index 613ba81b..d305a780 100644 --- a/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jSpatialFeatureSource.java +++ b/server-plugin/src/main/java/org/geotools/data/neo4j/Neo4jSpatialFeatureSource.java @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Set; import org.geotools.api.data.FeatureReader; import org.geotools.api.data.Query; import org.geotools.api.feature.simple.SimpleFeature; @@ -51,10 +52,10 @@ public class Neo4jSpatialFeatureSource extends ContentFeatureSource { private final SimpleFeatureType featureType; private final SimpleFeatureBuilder builder; private final Iterable results; - private final String[] extraPropertyNames; + private final Set extraPropertyNames; public Neo4jSpatialFeatureSource(ContentEntry contentEntry, GraphDatabaseService database, Layer layer, - SimpleFeatureType featureType, Iterable results, String[] extraPropertyNames) { + SimpleFeatureType featureType, Iterable results, Set extraPropertyNames) { super(contentEntry, Query.ALL); this.database = database; this.layer = layer; diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/AbstractGeometryEncoder.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/AbstractGeometryEncoder.java index 50f858e1..8aa4008d 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/AbstractGeometryEncoder.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/AbstractGeometryEncoder.java @@ -19,6 +19,7 @@ */ package org.neo4j.gis.spatial; +import java.util.Set; import org.apache.commons.lang3.ArrayUtils; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; @@ -127,6 +128,11 @@ public String getSignature() { return "GeometryEncoder(bbox='" + bboxProperty + "')"; } + @Override + public Set getEncoderProperties() { + return Set.of(bboxProperty); + } + // Attributes protected Layer layer; diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/Constants.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/Constants.java index b0effea4..41e418a5 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/Constants.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/Constants.java @@ -30,6 +30,7 @@ public interface Constants { String PROP_BBOX = "bbox"; String PROP_LAYER = "layer"; + String PROP_PREFIX_EXTRA_PROP_V2 = "extraProp."; String PROP_LAYERNODEEXTRAPROPS = "layerprops"; String PROP_CRS = "layercrs"; String PROP_GEOMENCODER = "geomencoder"; diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/DefaultLayer.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/DefaultLayer.java index de8d4bd1..67318be3 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/DefaultLayer.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/DefaultLayer.java @@ -25,10 +25,13 @@ import static org.neo4j.gis.spatial.Constants.PROP_INDEX_CLASS; import static org.neo4j.gis.spatial.Constants.PROP_INDEX_CONFIG; import static org.neo4j.gis.spatial.Constants.PROP_LAYERNODEEXTRAPROPS; +import static org.neo4j.gis.spatial.Constants.PROP_PREFIX_EXTRA_PROP_V2; import static org.neo4j.gis.spatial.Constants.PROP_TYPE; import java.util.Collections; import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; import javax.annotation.Nonnull; import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.locationtech.jts.geom.Geometry; @@ -69,6 +72,8 @@ public class DefaultLayer implements Layer, SpatialDataset { /** * The constructor is protected because we should not construct this class + * directly but use the factory methods to create Layers based on + * configurations * directly but use the factory methods to create Layers based on configurations */ protected DefaultLayer() { @@ -178,16 +183,35 @@ public Integer getGeometryType(Transaction tx) { return geomTypeSearch.firstFoundType; } + @Nonnull @Override - public String[] getExtraPropertyNames(Transaction tx) { + public Map> getExtraProperties(Transaction tx) { Node layerNode = getLayerNode(tx); - String[] extraPropertyNames; + var extraProps = new TreeMap>(); + layerNode.getAllProperties().forEach((name, o) -> { + if (!name.startsWith(PROP_PREFIX_EXTRA_PROP_V2)) { + return; + } + Class clazz = String.class; + if (o instanceof String className) { + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException ignore) { + } + } + + var key = name.substring(PROP_PREFIX_EXTRA_PROP_V2.length()); + extraProps.put(key, clazz); + }); if (layerNode.hasProperty(PROP_LAYERNODEEXTRAPROPS)) { - extraPropertyNames = (String[]) layerNode.getProperty(PROP_LAYERNODEEXTRAPROPS); - } else { - extraPropertyNames = new String[]{}; + Object legacyProps = layerNode.getProperty(PROP_LAYERNODEEXTRAPROPS); + if (legacyProps instanceof String[] props) { + for (String s : props) { + extraProps.putIfAbsent(s, String.class); + } + } } - return extraPropertyNames; + return extraProps; } /** diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/DynamicLayerConfig.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/DynamicLayerConfig.java index 3ae70a78..1167df85 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/DynamicLayerConfig.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/DynamicLayerConfig.java @@ -22,6 +22,8 @@ import java.io.File; import java.util.LinkedHashMap; import java.util.Map; +import java.util.TreeMap; +import javax.annotation.Nonnull; import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.geotools.filter.text.cql2.CQLException; import org.locationtech.jts.geom.GeometryFactory; @@ -101,12 +103,17 @@ public SpatialDataset getDataset() { return parent.getDataset(); } + @Nonnull @Override - public String[] getExtraPropertyNames(Transaction tx) { + public Map> getExtraProperties(Transaction tx) { if (propertyNames != null && propertyNames.length > 0) { - return propertyNames; + var result = new TreeMap>(); + for (String propertyName : propertyNames) { + result.put(propertyName, String.class); + } + return result; } - return parent.getExtraPropertyNames(tx); + return parent.getExtraProperties(tx); } private static class PropertyUsageSearch implements SearchFilter { @@ -163,7 +170,7 @@ public void restrictLayerProperties(Transaction tx) { System.out.println("Restricted property names already exists - will be overwritten"); } System.out.println( - "Before property scan we have " + getExtraPropertyNames(tx).length + " known attributes for layer " + "Before property scan we have " + getExtraProperties(tx).size() + " known attributes for layer " + getName()); PropertyUsageSearch search = new PropertyUsageSearch(this); @@ -171,8 +178,8 @@ public void restrictLayerProperties(Transaction tx) { setExtraPropertyNames(tx, search.getNames()); System.out.println( - "After property scan of " + search.getNodeCount() + " nodes, we have " + getExtraPropertyNames( - tx).length + " known attributes for layer " + getName()); + "After property scan of " + search.getNodeCount() + " nodes, we have " + getExtraProperties( + tx).size() + " known attributes for layer " + getName()); } public Node configNode(Transaction tx) { diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/EditableLayerImpl.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/EditableLayerImpl.java index f5eb9664..2d893cfd 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/EditableLayerImpl.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/EditableLayerImpl.java @@ -23,9 +23,11 @@ import static org.neo4j.gis.spatial.Constants.GTYPE_POINT; import static org.neo4j.gis.spatial.Constants.PROP_CRS; import static org.neo4j.gis.spatial.Constants.PROP_LAYERNODEEXTRAPROPS; +import static org.neo4j.gis.spatial.Constants.PROP_PREFIX_EXTRA_PROP_V2; import static org.neo4j.gis.spatial.Constants.PROP_TYPE; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -41,6 +43,8 @@ public class EditableLayerImpl extends DefaultLayer implements EditableLayer { protected SpatialIndexWriter indexWriter; + private final Map> seenProperties = new HashMap<>(); + private Set encoderProps; @Override public void initialize(Transaction tx, IndexManager indexManager, String name, Node layerNode, boolean readOnly) { @@ -50,6 +54,7 @@ public void initialize(Transaction tx, IndexManager indexManager, String name, N } else { throw new SpatialDatabaseException("Index writer could not be initialized"); } + encoderProps = getGeometryEncoder().getEncoderProperties(); } /** @@ -63,8 +68,7 @@ public SpatialDatabaseRecord add(Transaction tx, Node geomNode) { // add BBOX to Node if it's missing getGeometryEncoder().ensureIndexable(geometry, geomNode); - indexWriter.add(tx, geomNode); - return new SpatialDatabaseRecord(this, geomNode, geometry); + return addToIndex(tx, geometry, geomNode); } @Override @@ -76,6 +80,7 @@ public int addAll(Transaction tx, List geomNodes) { Geometry geometry = geometryEncoder.decodeGeometry(geomNode); // add BBOX to Node if it's missing geometryEncoder.encodeGeometry(tx, geometry, geomNode); + memorizeNodeMeta(geomNode); } indexWriter.add(tx, geomNodes); return geomNodes.size(); @@ -96,8 +101,27 @@ public SpatialDatabaseRecord add(Transaction tx, Geometry geometry) { public SpatialDatabaseRecord add(Transaction tx, Geometry geometry, Map properties) { checkWritable(); Node geomNode = addGeomNode(tx, geometry, properties); - indexWriter.add(tx, geomNode); - return new SpatialDatabaseRecord(this, geomNode, geometry); + return addToIndex(tx, geometry, geomNode); + } + + protected SpatialDatabaseRecord addToIndex(Transaction tx, Geometry geometry, Node node) { + indexWriter.add(tx, node); + memorizeNodeMeta(node); + return new SpatialDatabaseRecord(this, node, geometry); + } + + protected void memorizeNodeMeta(Node node) { + node.getAllProperties().forEach((name, value) -> { + if (encoderProps.contains(name)) { + return; + } + seenProperties.compute(name, (s, aClass) -> { + if (aClass == null && value != null) { + return value.getClass(); + } + return aClass; + }); + }); } @Override @@ -145,6 +169,20 @@ protected Node addGeomNode(Transaction tx, Geometry geom, Map pr return geomNode; } + void mergeExtraPropertyNames(Transaction tx, Set names) { + Node layerNode = getLayerNode(tx); + if (layerNode.hasProperty(PROP_LAYERNODEEXTRAPROPS)) { + String[] actualNames = (String[]) layerNode.getProperty(PROP_LAYERNODEEXTRAPROPS); + + Set mergedNames = new HashSet<>(names); + Collections.addAll(mergedNames, actualNames); + + layerNode.setProperty(PROP_LAYERNODEEXTRAPROPS, mergedNames.toArray(new String[0])); + } else { + layerNode.setProperty(PROP_LAYERNODEEXTRAPROPS, names.toArray(new String[0])); + } + } + @Override public String getSignature() { return "Editable" + super.getSignature(); @@ -190,8 +228,22 @@ void mergeExtraPropertyNames(Transaction tx, String[] names) { @Override public void finalizeTransaction(Transaction tx) { - if (!isReadOnly()){ + if (!isReadOnly()) { + saveAttributeMeta(tx); getIndex().finalizeTransaction(tx); } } + + private void saveAttributeMeta(Transaction tx) { + var node = getLayerNode(tx); + seenProperties.forEach((s, aClass) -> { + var key = PROP_PREFIX_EXTRA_PROP_V2 + s; + if (node.hasProperty(key)) { + return; + } + node.setProperty(key, aClass == null ? null : aClass.getName()); + }); + mergeExtraPropertyNames(tx, seenProperties.keySet()); + } + } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/GeometryEncoder.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/GeometryEncoder.java index 59acaf6c..026a8de0 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/GeometryEncoder.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/GeometryEncoder.java @@ -19,6 +19,7 @@ */ package org.neo4j.gis.spatial; +import java.util.Set; import org.locationtech.jts.geom.Geometry; import org.neo4j.gis.spatial.rtree.EnvelopeDecoder; import org.neo4j.graphdb.Entity; @@ -100,4 +101,10 @@ public interface GeometryEncoder extends EnvelopeDecoder { * @return descriptive signature of encoder, type and configuration */ String getSignature(); + + /** + * + * @return the properties used by the encoder + */ + Set getEncoderProperties(); } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/Layer.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/Layer.java index bc97580d..e2e7d6b2 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/Layer.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/Layer.java @@ -19,6 +19,8 @@ */ package org.neo4j.gis.spatial; +import java.util.Map; +import javax.annotation.Nonnull; import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.locationtech.jts.geom.GeometryFactory; import org.neo4j.gis.spatial.attributes.PropertyMappingManager; @@ -91,7 +93,8 @@ public interface Layer { * @param tx the transaction * @return String array of all attribute names */ - String[] getExtraPropertyNames(Transaction tx); + @Nonnull + Map> getExtraProperties(Transaction tx); /** * The layer conforms with the Geotools pattern of only allowing a single geometry per layer. diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/ShapefileImporter.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/ShapefileImporter.java index a7e95064..e62695e1 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/ShapefileImporter.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/ShapefileImporter.java @@ -149,8 +149,6 @@ public List importFile(String dataset, EditableLayerImpl layer, Charset ch } layer.setGeometryType(tx, geometryType); - - layer.mergeExtraPropertyNames(tx, fieldsName); tx.commit(); } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/SpatialDatabaseRecord.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/SpatialDatabaseRecord.java index b8a38209..9bb465d2 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/SpatialDatabaseRecord.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/SpatialDatabaseRecord.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Set; import org.geotools.api.referencing.crs.CoordinateReferenceSystem; import org.locationtech.jts.geom.Geometry; import org.neo4j.gis.spatial.attributes.PropertyMapper; @@ -108,27 +109,15 @@ private boolean hasGeometryProperty(String name) { } @Override - public String[] getPropertyNames(Transaction tx) { - return layer.getExtraPropertyNames(tx); - } - - public Object[] getPropertyValues(Transaction tx) { - String[] names = getPropertyNames(tx); - if (names == null) { - return null; - } - Object[] values = new Object[names.length]; - for (int i = 0; i < names.length; i++) { - values[i] = getProperty(tx, names[i]); - } - return values; + public Set getPropertyNames(Transaction tx) { + return layer.getExtraProperties(tx).keySet(); } @Override public Map getProperties(Transaction tx) { Map result = new HashMap<>(); - String[] names = getPropertyNames(tx); + Set names = getPropertyNames(tx); for (String name : names) { result.put(name, getProperty(tx, name)); } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/SpatialRecord.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/SpatialRecord.java index 0e2b15eb..d4890837 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/SpatialRecord.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/SpatialRecord.java @@ -20,6 +20,7 @@ package org.neo4j.gis.spatial; import java.util.Map; +import java.util.Set; import org.locationtech.jts.geom.Geometry; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Transaction; @@ -32,7 +33,7 @@ public interface SpatialRecord { boolean hasProperty(Transaction tx, String name); - String[] getPropertyNames(Transaction tx); + Set getPropertyNames(Transaction tx); Object getProperty(Transaction tx, String name); diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/AbstractSinglePropertyEncoder.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/AbstractSinglePropertyEncoder.java index 0ae0dd94..2bfba554 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/AbstractSinglePropertyEncoder.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/AbstractSinglePropertyEncoder.java @@ -20,6 +20,7 @@ package org.neo4j.gis.spatial.encoders; +import java.util.Set; import org.neo4j.gis.spatial.AbstractGeometryEncoder; public abstract class AbstractSinglePropertyEncoder extends AbstractGeometryEncoder implements Configurable { @@ -48,4 +49,10 @@ public String getConfiguration() { public String getSignature() { return "GeometryEncoder(geom='" + geomProperty + "', bbox='" + bboxProperty + "')"; } + + + @Override + public Set getEncoderProperties() { + return Set.of(bboxProperty, geomProperty, PROP_TYPE); + } } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/NativePointEncoder.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/NativePointEncoder.java index 54bf1581..7478af17 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/NativePointEncoder.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/NativePointEncoder.java @@ -19,6 +19,7 @@ */ package org.neo4j.gis.spatial.encoders; +import java.util.Set; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Point; @@ -93,4 +94,9 @@ public String getSignature() { return "NativePointEncoder(geometry='" + locationProperty + "', bbox='" + bboxProperty + "', crs=" + crs.getCode() + ")"; } + + @Override + public Set getEncoderProperties() { + return Set.of(bboxProperty, locationProperty, PROP_TYPE); + } } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/NativePointsEncoder.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/NativePointsEncoder.java index 1c4da84e..d0c18b8d 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/NativePointsEncoder.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/NativePointsEncoder.java @@ -20,6 +20,7 @@ package org.neo4j.gis.spatial.encoders; import java.util.Arrays; +import java.util.Set; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.LineString; @@ -103,4 +104,9 @@ public String getSignature() { return "NativePointEncoder(geometry='" + property + "', bbox='" + bboxProperty + "', crs=" + crs.getCode() + ")"; } + + @Override + public Set getEncoderProperties() { + return Set.of(bboxProperty, property, PROP_TYPE); + } } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimpleGraphEncoder.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimpleGraphEncoder.java index 7c243493..630db453 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimpleGraphEncoder.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimpleGraphEncoder.java @@ -19,6 +19,7 @@ */ package org.neo4j.gis.spatial.encoders; +import java.util.Set; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.CoordinateList; import org.locationtech.jts.geom.Geometry; @@ -40,6 +41,10 @@ // TODO: Consider generalizing this code and making a general linked list geometry store available in the library public class SimpleGraphEncoder extends AbstractGeometryEncoder { + private static final String PROPERTY_X_COORD = "x"; + private static final String PROPERTY_Y_COORD = "y"; + private static final String PROPERTY_Z_COORD = "z"; + protected enum SimpleRelationshipTypes implements RelationshipType { FIRST, NEXT } @@ -58,9 +63,9 @@ protected void encodeGeometryShape(Transaction tx, Geometry geometry, Entity con Node prev = null; for (Coordinate coord : geometry.getCoordinates()) { Node point = tx.createNode(); - point.setProperty("x", coord.x); - point.setProperty("y", coord.y); - point.setProperty("z", coord.z); + point.setProperty(PROPERTY_X_COORD, coord.x); + point.setProperty(PROPERTY_Y_COORD, coord.y); + point.setProperty(PROPERTY_Z_COORD, coord.z); if (prev == null) { node.createRelationshipTo(point, SimpleRelationshipTypes.FIRST); } else { @@ -79,9 +84,18 @@ public Geometry decodeGeometry(Entity container) { .relationships(SimpleRelationshipTypes.NEXT, Direction.OUTGOING).breadthFirst() .evaluator(Evaluators.excludeStartPosition()); for (Node point : td.traverse(node).nodes()) { - coordinates.add(new Coordinate((Double) point.getProperty("x"), (Double) point.getProperty("y"), - (Double) point.getProperty("z")), false); + coordinates.add(new Coordinate( + (Double) point.getProperty(PROPERTY_X_COORD), + (Double) point.getProperty(PROPERTY_Y_COORD), + (Double) point.getProperty(PROPERTY_Z_COORD)), + false + ); } return getGeometryFactory().createLineString(coordinates.toCoordinateArray()); } + + @Override + public Set getEncoderProperties() { + return Set.of(bboxProperty, PROPERTY_X_COORD, PROPERTY_Y_COORD, PROPERTY_Z_COORD, PROP_TYPE); + } } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimplePointEncoder.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimplePointEncoder.java index a3e7eb52..acd24ad7 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimplePointEncoder.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimplePointEncoder.java @@ -19,6 +19,7 @@ */ package org.neo4j.gis.spatial.encoders; +import java.util.Set; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.neo4j.gis.spatial.AbstractGeometryEncoder; @@ -79,4 +80,9 @@ public void setConfiguration(String configuration) { public String getSignature() { return "SimplePointEncoder(x='" + xProperty + "', y='" + yProperty + "', bbox='" + bboxProperty + "')"; } + + @Override + public Set getEncoderProperties() { + return Set.of(bboxProperty, xProperty, yProperty, PROP_TYPE); + } } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimplePropertyEncoder.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimplePropertyEncoder.java index 79adc47a..1fc65dcc 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimplePropertyEncoder.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/encoders/SimplePropertyEncoder.java @@ -19,6 +19,7 @@ */ package org.neo4j.gis.spatial.encoders; +import java.util.Set; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.neo4j.gis.spatial.AbstractGeometryEncoder; @@ -34,6 +35,8 @@ // TODO: Consider switching from Float to Double according to Davide Savazzi public class SimplePropertyEncoder extends AbstractGeometryEncoder { + private static final String PROPERTY_DATA = "data"; + @Override protected void encodeGeometryShape(Transaction tx, Geometry geometry, Entity container) { container.setProperty(PROP_TYPE, SpatialDatabaseService.convertJtsClassToGeometryType(geometry.getClass())); @@ -44,16 +47,21 @@ protected void encodeGeometryShape(Transaction tx, Geometry geometry, Entity con data[i * 2 + 1] = (float) coords[i].y; } - container.setProperty("data", data); + container.setProperty(PROPERTY_DATA, data); } @Override public Geometry decodeGeometry(Entity container) { - float[] data = (float[]) container.getProperty("data"); + float[] data = (float[]) container.getProperty(PROPERTY_DATA); Coordinate[] coordinates = new Coordinate[data.length / 2]; for (int i = 0; i < data.length / 2; i++) { coordinates[i] = new Coordinate(data[2 * i], data[2 * i + 1]); } return getGeometryFactory().createLineString(coordinates); } + + @Override + public Set getEncoderProperties() { + return Set.of(bboxProperty, PROPERTY_DATA, PROP_TYPE); + } } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/osm/OSMGeometryEncoder.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/osm/OSMGeometryEncoder.java index f32ddaac..65d7ee6a 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/osm/OSMGeometryEncoder.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/osm/OSMGeometryEncoder.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Set; import org.locationtech.jts.algorithm.ConvexHull; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; @@ -50,6 +51,9 @@ public class OSMGeometryEncoder extends AbstractGeometryEncoder { + private static final String PROPERTY_VERTICES = "vertices"; + private static final String PROPERTY_LAT = "lat"; + private static final String PROPERTY_LON = "lon"; private static int decodedCount = 0; private static int overrunCount = 0; private static int nodeId = 0; @@ -144,14 +148,14 @@ private static Node testIsNode(Entity container) { @Override public Envelope decodeEnvelope(Entity container) { Node geomNode = testIsNode(container); - double[] bbox = (double[]) geomNode.getProperty(PROP_BBOX); + double[] bbox = (double[]) geomNode.getProperty(bboxProperty); // double xmin, double xmax, double ymin, double ymax return new Envelope(bbox[0], bbox[1], bbox[2], bbox[3]); } @Override public void encodeEnvelope(Envelope mbb, Entity container) { - container.setProperty(PROP_BBOX, new double[]{mbb.getMinX(), mbb.getMaxX(), mbb.getMinY(), mbb.getMaxY()}); + container.setProperty(bboxProperty, new double[]{mbb.getMinX(), mbb.getMaxX(), mbb.getMinY(), mbb.getMaxY()}); } public static Node getOSMNodeFromGeometryNode(Node geomNode) { @@ -206,11 +210,12 @@ public Geometry decodeGeometry(Entity container) { GeometryFactory geomFactory = layer.getGeometryFactory(); Node osmNode = getOSMNodeFromGeometryNode(geomNode); if (osmNode.hasProperty("node_osm_id")) { - return geomFactory.createPoint(new Coordinate((Double) osmNode.getProperty("lon", 0.0), (Double) osmNode - .getProperty("lat", 0.0))); + return geomFactory.createPoint( + new Coordinate((Double) osmNode.getProperty(PROPERTY_LON, 0.0), (Double) osmNode + .getProperty(PROPERTY_LAT, 0.0))); } if (osmNode.hasProperty("way_osm_id")) { - int vertices = (Integer) geomNode.getProperty("vertices"); + int vertices = (Integer) geomNode.getProperty(PROPERTY_VERTICES); int gtype = (Integer) geomNode.getProperty(PROP_TYPE); return decodeGeometryFromWay(osmNode, gtype, vertices, geomFactory); } @@ -335,7 +340,8 @@ private Geometry decodeGeometryFromWay(Node wayNode, int gtype, int vertices, Ge overrunCount++; break; } - coordinates.add(new Coordinate((Double) node.getProperty("lon"), (Double) node.getProperty("lat"))); + coordinates.add( + new Coordinate((Double) node.getProperty(PROPERTY_LON), (Double) node.getProperty(PROPERTY_LAT))); } decodedCount++; if (overrun) { @@ -402,7 +408,7 @@ protected void encodeGeometryShape(Transaction tx, Geometry geometry, Entity con default: throw new SpatialDatabaseException("Unsupported geometry: " + geometry.getClass()); } - geomNode.setProperty("vertices", vertices); + geomNode.setProperty(PROPERTY_VERTICES, vertices); } private Node makeOSMNode(Transaction tx, Geometry geometry, Node geomNode) { @@ -417,8 +423,8 @@ private Node makeOSMNode(Transaction tx, Coordinate coordinate) { Node node = tx.createNode(); // TODO: Generate a valid osm id node.setProperty(OSMId.NODE.toString(), nodeId); - node.setProperty("lat", coordinate.y); - node.setProperty("lon", coordinate.x); + node.setProperty(PROPERTY_LAT, coordinate.y); + node.setProperty(PROPERTY_LON, coordinate.x); node.setProperty("timestamp", getTimestamp()); // TODO: Add other common properties, like changeset, uid, user, version return node; @@ -560,4 +566,9 @@ public String toString() { return name; } } + + @Override + public Set getEncoderProperties() { + return Set.of(bboxProperty, PROPERTY_VERTICES, PROPERTY_LAT, PROPERTY_LON, PROP_TYPE); + } } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/osm/OSMLayer.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/osm/OSMLayer.java index 897c84c7..12d9d7e6 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/osm/OSMLayer.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/osm/OSMLayer.java @@ -96,6 +96,7 @@ public Node addWay(Transaction tx, Node way, boolean verifyGeom) { if (verifyGeom) { getGeometryEncoder().decodeGeometry(geomNode); } + memorizeNodeMeta(geomNode); indexWriter.add(tx, geomNode); } catch (Exception e) { System.err.println( diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/GeoPipeFlow.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/GeoPipeFlow.java index 7825b9f2..b1f16bd9 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/GeoPipeFlow.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/GeoPipeFlow.java @@ -20,9 +20,11 @@ package org.neo4j.gis.spatial.pipes; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; import org.neo4j.gis.spatial.SpatialDatabaseRecord; @@ -104,8 +106,8 @@ public boolean hasProperty(Transaction tx, String name) { } @Override - public String[] getPropertyNames(Transaction tx) { - return properties.keySet().toArray(new String[]{}); + public Set getPropertyNames(Transaction tx) { + return Collections.unmodifiableSet(properties.keySet()); } @Override diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/GeoPipeline.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/GeoPipeline.java index ced87ec6..d18380ab 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/GeoPipeline.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/GeoPipeline.java @@ -20,11 +20,11 @@ package org.neo4j.gis.spatial.pipes; import java.util.ArrayList; -import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +import java.util.Set; import org.geotools.api.feature.simple.SimpleFeature; import org.geotools.api.feature.simple.SimpleFeatureType; import org.geotools.data.neo4j.Neo4jFeatureBuilder; @@ -371,7 +371,7 @@ public GeoPipeline copyDatabaseRecordProperties(Transaction tx) { /** * @see CopyDatabaseRecordProperties */ - public GeoPipeline copyDatabaseRecordProperties(Transaction tx, String[] keys) { + public GeoPipeline copyDatabaseRecordProperties(Transaction tx, Set keys) { return addPipe(new CopyDatabaseRecordProperties(tx, keys)); } @@ -884,8 +884,7 @@ public FeatureCollection toFeatureCollection(f final Iterator recordsIterator = records.iterator(); final ReferencedEnvelope refBounds = new ReferencedEnvelope(bounds, layer.getCoordinateReferenceSystem(tx)); - final Neo4jFeatureBuilder featureBuilder = new Neo4jFeatureBuilder(featureType, - Arrays.asList(layer.getExtraPropertyNames(tx))); + final Neo4jFeatureBuilder featureBuilder = new Neo4jFeatureBuilder(featureType, layer.getExtraProperties(tx)); return new AbstractFeatureCollection(featureType) { @Override public int size() { diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/processing/CopyDatabaseRecordProperties.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/processing/CopyDatabaseRecordProperties.java index cbc676a4..d32e134b 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/processing/CopyDatabaseRecordProperties.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/pipes/processing/CopyDatabaseRecordProperties.java @@ -19,6 +19,7 @@ */ package org.neo4j.gis.spatial.pipes.processing; +import java.util.Set; import org.neo4j.gis.spatial.pipes.AbstractGeoPipe; import org.neo4j.gis.spatial.pipes.GeoPipeFlow; import org.neo4j.graphdb.Transaction; @@ -32,7 +33,7 @@ */ public class CopyDatabaseRecordProperties extends AbstractGeoPipe { - private final String[] keys; + private final Set keys; private final Transaction tx; public CopyDatabaseRecordProperties(Transaction tx) { @@ -42,17 +43,17 @@ public CopyDatabaseRecordProperties(Transaction tx) { public CopyDatabaseRecordProperties(Transaction tx, String key) { this.tx = tx; - this.keys = new String[]{key}; + this.keys = Set.of(key); } - public CopyDatabaseRecordProperties(Transaction tx, String[] keys) { + public CopyDatabaseRecordProperties(Transaction tx, Set keys) { this.tx = tx; this.keys = keys; } @Override protected GeoPipeFlow process(GeoPipeFlow flow) { - String[] names = keys != null ? keys : flow.getRecord().getPropertyNames(tx); + Set names = keys != null ? keys : flow.getRecord().getPropertyNames(tx); for (String name : names) { flow.getProperties().put(name, flow.getRecord().getProperty(tx, name)); } diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java index 1a0460f5..ed369e64 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java @@ -126,7 +126,7 @@ public record NameResult(String name, String signature) { } - public record StringResult(String name) { + public record FeatureAttributeResult(String name, String className) { } @@ -511,13 +511,19 @@ public Stream getLayer(@Name(value = "name", description = "the name @Procedure(value = "spatial.getFeatureAttributes", mode = READ) @Description("Returns feature attributes of the given layer") - public Stream getFeatureAttributes( + public Stream getFeatureAttributes( @Name(value = "name", description = DOC_LAYER_NAME) String name) { Layer layer = getLayerOrThrow(tx, spatial(), name, true); - return Arrays.stream(layer.getExtraPropertyNames(tx)).map(StringResult::new); + return layer.getExtraProperties(tx) + .entrySet() + .stream() + .map(entry -> new FeatureAttributeResult(entry.getKey(), entry.getValue().getName())); } - @Procedure(value = "spatial.setFeatureAttributes", mode = WRITE) + @Procedure( + value = "spatial.setFeatureAttributes", mode = WRITE, + deprecatedBy = "feature attributes are now automatically discovered when a new node is added to the index" + ) @Description("Sets the feature attributes of the given layer") public Stream setFeatureAttributes(@Name(value = "name", description = DOC_LAYER_NAME) String name, @Name(value = "attributeNames", description = "The attributes to set") List attributeNames) { diff --git a/server-plugin/src/main/java/org/neo4j/gis/spatial/rtree/RTreeImageExporter.java b/server-plugin/src/main/java/org/neo4j/gis/spatial/rtree/RTreeImageExporter.java index 0b2db897..d198714b 100644 --- a/server-plugin/src/main/java/org/neo4j/gis/spatial/rtree/RTreeImageExporter.java +++ b/server-plugin/src/main/java/org/neo4j/gis/spatial/rtree/RTreeImageExporter.java @@ -29,6 +29,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; @@ -226,8 +227,8 @@ private void drawBounds(MapContent mapContent, ReferencedEnvelope bounds, Color private MemoryFeatureCollection makeEnvelopeFeatures(List envelopes) { SimpleFeatureType featureType = Neo4jFeatureBuilder.getType("Polygon", Constants.GTYPE_POLYGON, crs, - new String[]{}); - Neo4jFeatureBuilder featureBuilder = new Neo4jFeatureBuilder(featureType, new ArrayList<>()); + Collections.emptyMap()); + Neo4jFeatureBuilder featureBuilder = new Neo4jFeatureBuilder(featureType, Collections.emptyMap()); MemoryFeatureCollection features = new MemoryFeatureCollection(featureType); for (Envelope envelope : envelopes) { @@ -246,8 +247,8 @@ private MemoryFeatureCollection makeEnvelopeFeatures(List envelopes) { private MemoryFeatureCollection makeEnvelopeFeatures(Coordinate min, Coordinate max) { SimpleFeatureType featureType = Neo4jFeatureBuilder.getType("Polygon", Constants.GTYPE_POLYGON, crs, - new String[]{}); - Neo4jFeatureBuilder featureBuilder = new Neo4jFeatureBuilder(featureType, new ArrayList<>()); + Collections.emptyMap()); + Neo4jFeatureBuilder featureBuilder = new Neo4jFeatureBuilder(featureType, Collections.emptyMap()); MemoryFeatureCollection features = new MemoryFeatureCollection(featureType); Coordinate[] coordinates = new Coordinate[]{ new Coordinate(min.x, min.y), @@ -263,8 +264,8 @@ private MemoryFeatureCollection makeEnvelopeFeatures(Coordinate min, Coordinate private MemoryFeatureCollection makeIndexNodeFeatures(List nodes) { SimpleFeatureType featureType = Neo4jFeatureBuilder.getType("Polygon", Constants.GTYPE_POLYGON, crs, - new String[]{}); - Neo4jFeatureBuilder featureBuilder = new Neo4jFeatureBuilder(featureType, new ArrayList<>()); + Collections.emptyMap()); + Neo4jFeatureBuilder featureBuilder = new Neo4jFeatureBuilder(featureType, Collections.emptyMap()); MemoryFeatureCollection features = new MemoryFeatureCollection(featureType); for (RTreeIndex.NodeWithEnvelope node : nodes) { Envelope envelope = node.envelope; @@ -282,7 +283,7 @@ private MemoryFeatureCollection makeIndexNodeFeatures(List nodes, SimpleFeatureType featureType) { - Neo4jFeatureBuilder featureBuilder = new Neo4jFeatureBuilder(featureType, new ArrayList<>()); + Neo4jFeatureBuilder featureBuilder = new Neo4jFeatureBuilder(featureType, Collections.emptyMap()); MemoryFeatureCollection features = new MemoryFeatureCollection(featureType); for (Node node : nodes) { Geometry geometry = geometryEncoder.decodeGeometry(node); diff --git a/server-plugin/src/test/java/org/neo4j/doc/domain/examples/ExampleCypher.java b/server-plugin/src/test/java/org/neo4j/doc/domain/examples/ExampleCypher.java index 2ded3d2f..c07f6131 100644 --- a/server-plugin/src/test/java/org/neo4j/doc/domain/examples/ExampleCypher.java +++ b/server-plugin/src/test/java/org/neo4j/doc/domain/examples/ExampleCypher.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.TreeSet; import org.neo4j.values.storable.Value; /** @@ -137,7 +138,7 @@ private String generateResult() { } StringBuilder writer = new StringBuilder(); writer.append(".Result\n\n"); - var columns = result.get(0).keySet(); + var columns = new TreeSet<>(result.get(0).keySet()); writer.append("[opts=\"header\",cols=\"") .append(columns.size()) diff --git a/server-plugin/src/test/java/org/neo4j/gis/spatial/LayersTest.java b/server-plugin/src/test/java/org/neo4j/gis/spatial/LayersTest.java index 7688185d..92b87095 100644 --- a/server-plugin/src/test/java/org/neo4j/gis/spatial/LayersTest.java +++ b/server-plugin/src/test/java/org/neo4j/gis/spatial/LayersTest.java @@ -189,6 +189,7 @@ private void testDeleteGeometry(Class encoderClass) { assertNotNull(record); // try to remove the geometry layer.delete(tx, record.getNodeId()); + layer.finalizeTransaction(tx); }); } diff --git a/server-plugin/src/test/java/org/neo4j/gis/spatial/OsmAnalysisTest.java b/server-plugin/src/test/java/org/neo4j/gis/spatial/OsmAnalysisTest.java index 96c99ad1..e74cc910 100644 --- a/server-plugin/src/test/java/org/neo4j/gis/spatial/OsmAnalysisTest.java +++ b/server-plugin/src/test/java/org/neo4j/gis/spatial/OsmAnalysisTest.java @@ -354,9 +354,6 @@ private static SortedMap exportPoints(Transaction tx, String laye EditableLayerImpl layer = (EditableLayerImpl) spatialService.createLayer(tx, name, WKBGeometryEncoder.class, EditableLayerImpl.class, ""); - layer.setExtraPropertyNames( - new String[]{"user_id", "user_name", "year", "month", "dayOfMonth", "weekOfYear"}, tx); - layers.put(name, layer); } } diff --git a/server-plugin/src/test/java/org/neo4j/gis/spatial/TestSpatialUtils.java b/server-plugin/src/test/java/org/neo4j/gis/spatial/TestSpatialUtils.java index f5a26f8b..c97487e4 100644 --- a/server-plugin/src/test/java/org/neo4j/gis/spatial/TestSpatialUtils.java +++ b/server-plugin/src/test/java/org/neo4j/gis/spatial/TestSpatialUtils.java @@ -148,8 +148,6 @@ public void testSnapping() throws Exception { GeometryFactory factory = osmLayer.getGeometryFactory(); EditableLayerImpl resultsLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "testSnapping_results", null, null, false); - String[] fieldsNames = new String[]{"snap-id", "description", "distance"}; - resultsLayer.setExtraPropertyNames(fieldsNames, tx); Point point = factory.createPoint(new Coordinate(12.9777, 56.0555)); resultsLayer.add(tx, point, Map.of("snap-id", 0L, "description", "Point to snap", "distance", 0L) diff --git a/server-plugin/src/test/java/org/neo4j/gis/spatial/index/LayerIndexTestBase.java b/server-plugin/src/test/java/org/neo4j/gis/spatial/index/LayerIndexTestBase.java index f41e9ef6..32319233 100644 --- a/server-plugin/src/test/java/org/neo4j/gis/spatial/index/LayerIndexTestBase.java +++ b/server-plugin/src/test/java/org/neo4j/gis/spatial/index/LayerIndexTestBase.java @@ -172,6 +172,7 @@ private SimplePointLayer makeTestPointLayer() { try (Transaction tx = graph.beginTx()) { SimplePointLayer layer = spatial.createPointLayer(tx, "test", getIndexClass(), getEncoderClass(), null ); + layer.finalizeTransaction(tx); tx.commit(); return layer; } diff --git a/server-plugin/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesDocTest.java b/server-plugin/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesDocTest.java index ee2c19e3..efb87c81 100644 --- a/server-plugin/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesDocTest.java +++ b/server-plugin/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesDocTest.java @@ -132,7 +132,7 @@ public void filter_by_window_intersection() { @Title("Filter by cql using bbox") @Documented(""" This pipe is filtering according to a CQL Bounding Box description. - + Example: @@s_filter_by_cql_using_bbox """) @@ -149,7 +149,7 @@ public void filter_by_cql_using_bbox() throws CQLException { This pipe performs a search within a geometry in this example, both OSM street geometries should be found in when searching with an enclosing rectangle Envelope. - + Example: @@s_search_within_geometry """) @@ -177,7 +177,7 @@ public void filter_by_cql_using_property() throws CQLException { @Title("Filter by cql using complex cql") @Documented(""" This pipe is filtering according to a complex CQL description. - + Example: @@s_filter_by_cql_using_complex_cql """) @@ -201,10 +201,10 @@ public void filter_by_cql_using_complex_cql() throws CQLException { @Title("Affine Transformation") @Documented(""" This pipe applies an affine transformation to every geometry. - + Example: @@s_affine_transformation - + Output: @@affine_transformation """) @@ -269,10 +269,10 @@ public void get_boundary_length() { @Title("Buffer") @Documented(""" This pipe applies a buffer to geometries. - + Example: @@s_buffer - + Output: @@buffer """) @@ -293,10 +293,10 @@ public void get_buffer() { @Title("Centroid") @Documented(""" This pipe calculates geometry centroid. - + Example: @@s_centroid - + Output: @@centroid """) @@ -318,10 +318,10 @@ public void get_centroid() { @Title("Export to GML") @Documented(""" This pipe exports every geometry as a http://en.wikipedia.org/wiki/Geography_Markup_Language[GML] snippet. - + Example: @@s_export_to_gml - + Output: @@exportgml """) @@ -343,10 +343,10 @@ public void export_to_GML() { @Title("Convex Hull") @Documented(""" This pipe calculates geometry convex hull. - + Example: @@s_convex_hull - + Output: @@convex_hull """) @@ -367,10 +367,10 @@ public void get_convex_hull() { @Documented(""" This pipe inserts extra vertices along the line segments in the geometry. The densified geometry contains no line segment which is longer than the given distance tolerance. - + Example: @@s_densify - + Output: @@densify """) @@ -403,10 +403,10 @@ public void json() { @Title("Max") @Documented(""" The Max pipe computes the maximum value of the specified property and discard items with a value less than the maximum. - + Example: @@s_max - + Output: @@max """) @@ -427,10 +427,10 @@ public void get_max_area() { @Title("Boundary") @Documented(""" The boundary pipe calculates boundary of every geometry in the pipeline. - + Example: @@s_boundary - + Output: @@boundary """) @@ -447,10 +447,10 @@ public void boundary() { @Title("Difference") @Documented(""" The Difference pipe computes a geometry representing the points making up item geometry that do not make up the given geometry. - + Example: @@s_difference - + Output: @@difference """) @@ -469,10 +469,10 @@ public void difference() throws Exception { @Title("Intersection") @Documented(""" The Intersection pipe computes a geometry representing the intersection between item geometry and the given geometry. - + Example: @@s_intersection - + Output: @@intersection """) @@ -491,10 +491,10 @@ public void intersection() throws Exception { @Title("Union") @Documented(""" The Union pipe unites item geometry with a given geometry. - + Example: @@s_union - + Output: @@union """) @@ -514,10 +514,10 @@ public void union() throws Exception { @Title("Min") @Documented(""" The Min pipe computes the minimum value of the specified property and discard items with a value greater than the minimum. - + Example: @@s_min - + Output: @@min """) @@ -553,22 +553,22 @@ public void extract_osm_points() { @Title("Break up all geometries into points and make density islands") @Documented(""" This example demonstrates the some pipes chained together to make a full geoprocessing pipeline. - + Example: @@s_break_up_all_geometries_into_points_and_make_density_islands - + Step 1 - startOsm: @@step1_break_up_all_geometries_into_points_and_make_density_islands - + Step 2 - extractOsmPoints: @@step2_break_up_all_geometries_into_points_and_make_density_islands - + Step 3 - groupByDensityIslands: @@step3_break_up_all_geometries_into_points_and_make_density_islands - + Step 4 - toConvexHull: @@step4_break_up_all_geometries_into_points_and_make_density_islands - + Step 5- toBuffer: @@step5_break_up_all_geometries_into_points_and_make_density_islands """) @@ -608,10 +608,10 @@ public void break_up_all_geometries_into_points_and_make_density_islands_and_get @Title("Extract Points") @Documented(""" This pipe extracts every point from a geometry. - + Example: @@s_extract_points - + Output: @@extract_points """) @@ -669,10 +669,10 @@ public void compute_distance() throws ParseException { The Union All pipe unites geometries of every item contained in the pipeline. This pipe groups every item in the pipeline in a single item containing the geometry output of the union. - + Example: @@s_unite_all - + Output: @@unite_all """) @@ -702,10 +702,10 @@ public void unite_all() { The Intersect All pipe intersects geometries of every item contained in the pipeline. This pipe groups every item in the pipeline in a single item containing the geometry output of the intersection. - + Example: @@s_intersect_all - + Output: @@intersect_all """) @@ -732,10 +732,10 @@ public void intersect_all() { @Title("Intersecting Windows") @Documented(""" The FilterIntersectWindow pipe finds geometries that intersects a given rectangle. - + Example: @@s_intersecting_windows - + Output: @@intersecting_windows """) @@ -754,10 +754,10 @@ public void intersecting_windows() { @Title("Start Point") @Documented(""" The StartPoint pipe finds the starting point of item geometry. - + Example: @@s_start_point - + Output: @@start_point""") public void start_point() { @@ -781,10 +781,10 @@ public void start_point() { @Title("End Point") @Documented(""" The EndPoint pipe finds the ending point of item geometry. - + Example: @@s_end_point - + Output: @@end_point """) @@ -809,10 +809,10 @@ public void end_point() { @Title("Envelope") @Documented(""" The Envelope pipe computes the minimum bounding box of item geometry. - + Example: @@s_envelope - + Output: @@envelope """) @@ -903,7 +903,7 @@ private void addImageSnippet( } else { pipelineCollection = pipeline.toFeatureCollection(tx, Neo4jFeatureBuilder.getType(layer.getName(), geomType, layer.getCoordinateReferenceSystem(tx), - layer.getExtraPropertyNames(tx))); + layer.getExtraProperties(tx))); } ReferencedEnvelope bounds = layerCollection.getBounds(); @@ -937,7 +937,6 @@ private static void load() throws Exception { osmLayer = spatial.getLayer(tx, "two-street.osm", false); boxesLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "boxes", null, null, false); - boxesLayer.setExtraPropertyNames(new String[]{"name"}, tx); boxesLayer.setCoordinateReferenceSystem(tx, DefaultEngineeringCRS.GENERIC_2D); WKTReader reader = new WKTReader(boxesLayer.getGeometryFactory()); boxesLayer.add(tx, @@ -961,24 +960,29 @@ private static void load() throws Exception { intersectionLayer.add(tx, reader.read("POLYGON ((2 2, 2 6, 6 6, 6 2, 2 2))")); equalLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "equal", null, null, false); - equalLayer.setExtraPropertyNames(new String[]{"id", "name"}, tx); equalLayer.setCoordinateReferenceSystem(tx, DefaultEngineeringCRS.GENERIC_2D); reader = new WKTReader(intersectionLayer.getGeometryFactory()); equalLayer.add(tx, reader.read("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))"), - Map.of("id", 1,"name", "equal" )); + Map.of("id", 1, "name", "equal")); equalLayer.add(tx, reader.read("POLYGON ((0 0, 0.1 5, 5 5, 5 0, 0 0))"), - Map.of("id", 2,"name", "tolerance" )); + Map.of("id", 2, "name", "tolerance")); equalLayer.add(tx, reader.read("POLYGON ((0 5, 5 5, 5 0, 0 0, 0 5))"), - Map.of("id", 3,"name", "different order" )); + Map.of("id", 3, "name", "different order")); equalLayer.add(tx, reader.read("POLYGON ((0 0, 0 2, 0 4, 0 5, 5 5, 5 3, 5 2, 5 0, 0 0))"), - Map.of("id", 4,"name", "topo equal" )); + Map.of("id", 4, "name", "topo equal")); linesLayer = (EditableLayerImpl) spatial.getOrCreateEditableLayer(tx, "lines", null, null, false); 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)")); + boxesLayer.finalizeTransaction(tx); + concaveLayer.finalizeTransaction(tx); + intersectionLayer.finalizeTransaction(tx); + equalLayer.finalizeTransaction(tx); + linesLayer.finalizeTransaction(tx); + tx.commit(); } } diff --git a/server-plugin/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesPerformanceTest.java b/server-plugin/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesPerformanceTest.java index e6d48e84..5c4137af 100644 --- a/server-plugin/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesPerformanceTest.java +++ b/server-plugin/src/test/java/org/neo4j/gis/spatial/pipes/GeoPipesPerformanceTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; @@ -104,7 +105,7 @@ public void testQueryPerformance() { try (Transaction tx = graphDb().beginTx()) { Layer layer = spatial.getLayer(tx, "GeoPipesPerformanceTest", true); // String[] keys = {"id","name","address","city","state","zip"}; - String[] keys = {"id", "name"}; + Set keys = Set.of("id", "name"); Coordinate loc = new Coordinate(15.0, 15.0); GeoPipeline flowList = GeoPipeline.startNearestNeighborLatLonSearch(tx, layer, loc, records) .copyDatabaseRecordProperties(tx, keys); @@ -151,7 +152,7 @@ public void testPagingPerformance() { try (Transaction tx = graphDb().beginTx()) { Layer layer = spatial.getLayer(tx, "GeoPipesPerformanceTest", true); // String[] keys = {"id","name","address","city","state","zip"}; - String[] keys = {"id", "name"}; + Set keys = Set.of("id", "name"); Coordinate loc = new Coordinate(15.0, 15.0); ArrayList totals = new ArrayList(); long prevTime = System.currentTimeMillis(); diff --git a/server-plugin/src/test/java/org/neo4j/gis/spatial/rtree/RTreeTests.java b/server-plugin/src/test/java/org/neo4j/gis/spatial/rtree/RTreeTests.java index 4de48edd..abe02e1f 100644 --- a/server-plugin/src/test/java/org/neo4j/gis/spatial/rtree/RTreeTests.java +++ b/server-plugin/src/test/java/org/neo4j/gis/spatial/rtree/RTreeTests.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import org.geotools.api.feature.simple.SimpleFeatureType; import org.geotools.data.neo4j.Neo4jFeatureBuilder; import org.junit.jupiter.api.AfterEach; @@ -57,7 +58,7 @@ public void setup() { } if (exportImages) { SimpleFeatureType featureType = Neo4jFeatureBuilder.getType("test", Constants.GTYPE_POINT, null, - new String[]{}); + Collections.emptyMap()); imageExporter = new RTreeImageExporter(new GeometryFactory(), new SimplePointEncoder(), null, featureType, rtree); try (Transaction tx = db.beginTx()) {