Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/main/java/org/neo4j/gis/spatial/AbstractGeometryEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import org.apache.commons.lang3.ArrayUtils;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.neo4j.gis.spatial.rtree.Envelope;
import org.neo4j.graphdb.Entity;
import org.neo4j.graphdb.Node;
Expand All @@ -30,6 +31,15 @@ public abstract class AbstractGeometryEncoder implements GeometryEncoder, Consta

protected String bboxProperty = PROP_BBOX;

private GeometryFactory geometryFactory;

protected GeometryFactory getGeometryFactory() {
if (geometryFactory == null) {
geometryFactory = new GeometryFactory();
}
return geometryFactory;
}

// Public methods

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.locationtech.jts.geom.Polygon;
import org.neo4j.gis.spatial.encoders.Configurable;
import org.neo4j.gis.spatial.encoders.NativePointEncoder;
import org.neo4j.gis.spatial.encoders.NativePointsEncoder;
import org.neo4j.gis.spatial.encoders.SimplePointEncoder;
import org.neo4j.gis.spatial.index.IndexManager;
import org.neo4j.gis.spatial.index.LayerGeohashPointIndex;
Expand Down Expand Up @@ -526,6 +527,8 @@ String getSignature() {
"longitude:latitude"));
addRegisteredLayerType(new RegisteredLayerType("NativePoint", NativePointEncoder.class,
SimplePointLayer.class, DefaultGeographicCRS.WGS84, LayerRTreeIndex.class, "location"));
addRegisteredLayerType(new RegisteredLayerType("NativePoints", NativePointsEncoder.class,
EditableLayerImpl.class, DefaultGeographicCRS.WGS84, LayerRTreeIndex.class, "geometry"));
addRegisteredLayerType(new RegisteredLayerType("NativeGeohash", NativePointEncoder.class,
SimplePointLayer.class, DefaultGeographicCRS.WGS84, LayerGeohashPointIndex.class, "location"));
addRegisteredLayerType(new RegisteredLayerType("NativeZOrder", NativePointEncoder.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.neo4j.gis.spatial.AbstractGeometryEncoder;
import org.neo4j.gis.spatial.SpatialDatabaseService;
Expand All @@ -36,22 +35,14 @@
public class NativePointEncoder extends AbstractGeometryEncoder implements Configurable {

private static final String DEFAULT_GEOM = "location";
private static GeometryFactory geometryFactory;
private String locationProperty = DEFAULT_GEOM;
private Neo4jCRS crs = Neo4jCRS.findCRS("WGS-84");

protected static GeometryFactory getGeometryFactory() {
if (geometryFactory == null) {
geometryFactory = new GeometryFactory();
}
return geometryFactory;
}

@Override
protected void encodeGeometryShape(Transaction tx, Geometry geometry, Entity container) {
int gtype = SpatialDatabaseService.convertJtsClassToGeometryType(geometry.getClass());
if (gtype == GTYPE_POINT) {
container.setProperty("gtype", gtype);
container.setProperty(PROP_TYPE, gtype);
Neo4jPoint neo4jPoint = new Neo4jPoint((Point) geometry, crs);
container.setProperty(locationProperty, neo4jPoint);
} else {
Expand Down
106 changes: 106 additions & 0 deletions src/main/java/org/neo4j/gis/spatial/encoders/NativePointsEncoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j Spatial.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.gis.spatial.encoders;

import java.util.Arrays;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.neo4j.gis.spatial.AbstractGeometryEncoder;
import org.neo4j.gis.spatial.SpatialDatabaseService;
import org.neo4j.gis.spatial.encoders.neo4j.Neo4jCRS;
import org.neo4j.gis.spatial.encoders.neo4j.Neo4jPoint;
import org.neo4j.graphdb.Entity;
import org.neo4j.graphdb.Transaction;

/**
* Simple encoder that stores line strings as an array of points.
*/
public class NativePointsEncoder extends AbstractGeometryEncoder implements Configurable {

private String property = "geometry";
private Neo4jCRS crs = Neo4jCRS.findCRS("WGS-84");

@Override
protected void encodeGeometryShape(Transaction tx, Geometry geometry, Entity container) {
var gtype = SpatialDatabaseService.convertJtsClassToGeometryType(geometry.getClass());
if (geometry instanceof LineString lineString) {
var neo4jPoints = Arrays.stream(lineString.getCoordinates())
.map(coordinate -> new Neo4jPoint(coordinate, crs))
.toArray(org.neo4j.graphdb.spatial.Point[]::new);
container.setProperty(PROP_TYPE, gtype);
container.setProperty(property, neo4jPoints);
} else {
throw new IllegalArgumentException(
"Can only store point-arrays as linestring: " + SpatialDatabaseService.convertGeometryTypeToName(
gtype));
}

}

@Override
public Geometry decodeGeometry(Entity container) {
var points = ((org.neo4j.graphdb.spatial.Point[]) container.getProperty(property));
var factory = getGeometryFactory();
var coordinates = Arrays.stream(points)
.map(point -> {
if (point.getCRS().getCode() != crs.getCode()) {
throw new IllegalStateException(
"Trying to decode geometry with wrong CRS: layer configured to crs=" + crs.getCode()
+ ", but geometry has crs=" + point.getCRS().getCode());
}
double[] coordinate = point.getCoordinate().getCoordinate();
if (crs.dimensions() == 3) {
return new Coordinate(coordinate[0], coordinate[1], coordinate[2]);
} else {
return new Coordinate(coordinate[0], coordinate[1]);
}
})
.toArray(Coordinate[]::new);
return factory.createLineString(coordinates);
}

@Override
public String getConfiguration() {
return property + ":" + bboxProperty + ": " + crs.getCode();
}

@Override
public void setConfiguration(String configuration) {
if (configuration != null && !configuration.trim().isEmpty()) {
String[] fields = configuration.split(":");
if (fields.length > 0) {
property = fields[0];
}
if (fields.length > 1) {
bboxProperty = fields[1];
}
if (fields.length > 2) {
crs = Neo4jCRS.findCRS(fields[2]);
}
}
}

@Override
public String getSignature() {
return "NativePointEncoder(geometry='" + property + "', bbox='" + bboxProperty + "', crs=" + crs.getCode()
+ ")";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateList;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.neo4j.gis.spatial.AbstractGeometryEncoder;
import org.neo4j.gis.spatial.SpatialDatabaseException;
import org.neo4j.graphdb.Direction;
Expand All @@ -41,19 +40,10 @@
// TODO: Consider generalizing this code and making a general linked list geometry store available in the library
public class SimpleGraphEncoder extends AbstractGeometryEncoder {

private GeometryFactory geometryFactory;

protected enum SimpleRelationshipTypes implements RelationshipType {
FIRST, NEXT
}

private GeometryFactory getGeometryFactory() {
if (geometryFactory == null) {
geometryFactory = new GeometryFactory();
}
return geometryFactory;
}

private static Node testIsNode(Entity container) {
if (!(container instanceof Node)) {
throw new SpatialDatabaseException("Cannot decode non-node geometry: " + container);
Expand All @@ -64,7 +54,7 @@ private static Node testIsNode(Entity container) {
@Override
protected void encodeGeometryShape(Transaction tx, Geometry geometry, Entity container) {
Node node = testIsNode(container);
node.setProperty("gtype", GTYPE_LINESTRING);
node.setProperty(PROP_TYPE, GTYPE_LINESTRING);
Node prev = null;
for (Coordinate coord : geometry.getCoordinates()) {
Node point = tx.createNode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.neo4j.gis.spatial.AbstractGeometryEncoder;
import org.neo4j.gis.spatial.SpatialDatabaseService;
import org.neo4j.graphdb.Entity;
Expand All @@ -34,21 +33,13 @@ public class SimplePointEncoder extends AbstractGeometryEncoder implements Confi

public static final String DEFAULT_X = "longitude";
public static final String DEFAULT_Y = "latitude";
protected GeometryFactory geometryFactory;
protected String xProperty = DEFAULT_X;
protected String yProperty = DEFAULT_Y;

protected GeometryFactory getGeometryFactory() {
if (geometryFactory == null) {
geometryFactory = new GeometryFactory();
}
return geometryFactory;
}

@Override
protected void encodeGeometryShape(Transaction tx, Geometry geometry, Entity container) {
container.setProperty(
"gtype",
PROP_TYPE,
SpatialDatabaseService.convertJtsClassToGeometryType(geometry.getClass()));
Coordinate[] coords = geometry.getCoordinates();
container.setProperty(xProperty, coords[0].x);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.neo4j.gis.spatial.AbstractGeometryEncoder;
import org.neo4j.gis.spatial.SpatialDatabaseService;
import org.neo4j.graphdb.Entity;
Expand All @@ -35,18 +34,9 @@
// TODO: Consider switching from Float to Double according to Davide Savazzi
public class SimplePropertyEncoder extends AbstractGeometryEncoder {

protected GeometryFactory geometryFactory;

protected GeometryFactory getGeometryFactory() {
if (geometryFactory == null) {
geometryFactory = new GeometryFactory();
}
return geometryFactory;
}

@Override
protected void encodeGeometryShape(Transaction tx, Geometry geometry, Entity container) {
container.setProperty("gtype", SpatialDatabaseService.convertJtsClassToGeometryType(geometry.getClass()));
container.setProperty(PROP_TYPE, SpatialDatabaseService.convertJtsClassToGeometryType(geometry.getClass()));
Coordinate[] coords = geometry.getCoordinates();
float[] data = new float[coords.length * 2];
for (int i = 0; i < coords.length; i++) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/neo4j/gis/spatial/osm/OSMImporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,7 @@ protected void addNodeGeometry(WrappedNode node, int gtype, Envelope bbox, int v
gtype = vertices > 1 ? GTYPE_MULTIPOINT : GTYPE_POINT;
}
Node geomNode = tx.createNode();
geomNode.setProperty("gtype", gtype);
geomNode.setProperty(PROP_TYPE, gtype);
geomNode.setProperty("vertices", vertices);
geomNode.setProperty(PROP_BBOX,
new double[]{bbox.getMinX(), bbox.getMaxX(), bbox.getMinY(), bbox.getMaxY()});
Expand Down Expand Up @@ -1207,7 +1207,7 @@ protected void updateGeometryMetaDataFromMember(WrappedNode member, GeometryMeta
try (var relationships = member.getRelationships(OSMRelation.GEOM)) {
for (Relationship rel : relationships) {
nodeProps = getNodeProperties(WrappedNode.fromNode(rel.getEndNode()));
metaGeom.checkSupportedGeometry((Integer) nodeProps.get("gtype"));
metaGeom.checkSupportedGeometry((Integer) nodeProps.get(PROP_TYPE));
metaGeom.expandToIncludeBBox(nodeProps);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.neo4j.gis.spatial.Constants.LABEL_LAYER;
Expand All @@ -54,6 +55,7 @@
import org.neo4j.gis.spatial.utilities.ReferenceNodes;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.QueryExecutionException;
import org.neo4j.graphdb.Result;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.spatial.Geometry;
Expand Down Expand Up @@ -1244,6 +1246,56 @@ public void find_no_geometries_using_closest_on_empty_layer() {
testCallCount(db, "CALL spatial.closest('geom',{lon:15.2, lat:60.1}, 1.0)", null, 0);
}

@Test
public void testNativePoints() {
execute("CREATE (node:Foo { points: [point({latitude: 5.0, longitude: 4.0}), point({latitude: 6.0, longitude: 5.0})]})");
execute("CALL spatial.addLayer('line','NativePoints','points') YIELD node" +
" MATCH (n:Foo)" +
" WITH collect(n) AS nodes" +
" CALL spatial.addNodes('line', nodes) YIELD count RETURN count");
testCallCount(db, "CALL spatial.closest('line',{lon:5.1, lat:4.1}, 1.0)", null, 1);
}

@Test
public void testNativePoints3D() {
execute("CREATE (node:Foo { points: [point({latitude: 5.0, longitude: 4.0, height: 1.0}), point({latitude: 6.0, longitude: 5.0, height: 2.0})]})");
Exception exception = assertThrows(QueryExecutionException.class, () -> {
execute("CALL spatial.addLayer('line','NativePoints','points:bbox:Cartesian') YIELD node" +
" MATCH (n:Foo)" +
" WITH collect(n) AS nodes" +
" CALL spatial.addNodes('line', nodes) YIELD count RETURN count");
});

assertEquals(
"Failed to invoke procedure `spatial.addNodes`: Caused by: java.lang.IllegalStateException: Trying to decode geometry with wrong CRS: layer configured to crs=7203, but geometry has crs=4979",
exception.getMessage());
}

@Test
public void testNativePointsCartesian() {
execute("CREATE (node:Foo { points: [point({x: 5.0, y: 4.0}), point({x: 6.0, y: 5.0})]})");
execute("CALL spatial.addLayer('line','NativePoints','points:bbox:Cartesian') YIELD node" +
" MATCH (n:Foo)" +
" WITH collect(n) AS nodes" +
" CALL spatial.addNodes('line', nodes) YIELD count RETURN count");
testCallCount(db, "CALL spatial.closest('line',point({x:5.1, y:4.1}), 1.0)", null, 1);
}

@Test
public void testNativePointsCartesian3D() {
execute("CREATE (node:Foo { points: [point({x: 5.0, y: 4.0, z: 1}), point({x: 6.0, y: 5.0, z: 2})]})");
Exception exception = assertThrows(QueryExecutionException.class, () -> {
execute("CALL spatial.addLayer('line','NativePoints','points:bbox:Cartesian') YIELD node" +
" MATCH (n:Foo)" +
" WITH collect(n) AS nodes" +
" CALL spatial.addNodes('line', nodes) YIELD count RETURN count");
});

assertEquals(
"Failed to invoke procedure `spatial.addNodes`: Caused by: java.lang.IllegalStateException: Trying to decode geometry with wrong CRS: layer configured to crs=7203, but geometry has crs=9157",
exception.getMessage());
}

/*

@Test
Expand Down