Skip to content

Commit

Permalink
Updated to 3.0.3, improved procedures with better return types
Browse files Browse the repository at this point in the history
  • Loading branch information
craigtaverner committed Jul 1, 2016
1 parent c0d9134 commit 178b0ef
Show file tree
Hide file tree
Showing 4 changed files with 355 additions and 16 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<properties>
<neo4j.version>3.0.1</neo4j.version>
<neo4j.version>3.0.3</neo4j.version>
<neo4j.java.version>1.7</neo4j.java.version>
<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
<maven-gpg-plugin.version>1.4</maven-gpg-plugin.version>
Expand All @@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>neo4j-spatial</artifactId>
<groupId>org.neo4j</groupId>
<version>0.17-neo4j-3.0.2</version>
<version>0.18-neo4j-3.0.3</version>
<name>Neo4j - Spatial Components</name>
<description>Spatial utilities and components for Neo4j</description>
<url>http://components.neo4j.org/${project.artifactId}/${project.version}</url>
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/org/neo4j/gis/spatial/DefaultLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import java.util.Iterator;
import java.util.Set;

import com.vividsolutions.jts.geom.PrecisionModel;
import org.geotools.factory.FactoryRegistryException;
import org.geotools.referencing.CRS;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.neo4j.gis.spatial.rtree.Envelope;
import org.neo4j.gis.spatial.rtree.Listener;
Expand Down Expand Up @@ -266,8 +268,19 @@ public void initialize(SpatialDatabaseService spatialDatabase, String name, Node
this.name = name;
this.layerNode = layerNode;

// TODO read Precision Model and SRID from layer properties and use them to construct GeometryFactory
this.geometryFactory = new GeometryFactory();
CoordinateReferenceSystem crs = getCoordinateReferenceSystem();
if (crs != null) {
// TODO: Verify this code works for general cases to read SRID from layer properties and use them to construct GeometryFactory
try {
Integer code = CRS.lookupEpsgCode(crs, true);
if (code != null) {
this.geometryFactory = new GeometryFactory(new PrecisionModel(), code);
}
} catch (FactoryException e) {
System.err.println("Failed to lookup CRS: " + e.getMessage());
}
}

if (layerNode.hasProperty(PROP_GEOMENCODER)) {
String encoderClassName = (String) layerNode.getProperty(PROP_GEOMENCODER);
Expand Down
256 changes: 247 additions & 9 deletions src/main/java/org/neo4j/gis/spatial/procedures/SpatialProcedures.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
package org.neo4j.gis.spatial.procedures;

import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import org.neo4j.cypher.internal.compiler.v3_0.commands.expressions.GeographicPoint;
import org.neo4j.cypher.internal.compiler.v3_0.GeographicPoint;
import org.neo4j.gis.spatial.*;
import org.neo4j.gis.spatial.encoders.SimpleGraphEncoder;
import org.neo4j.gis.spatial.encoders.SimplePointEncoder;
Expand All @@ -35,6 +38,7 @@
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.spatial.CRS;
import org.neo4j.kernel.api.proc.ProcedureSignature;
import org.neo4j.kernel.impl.proc.Procedures;
import org.neo4j.kernel.internal.GraphDatabaseAPI;
Expand All @@ -43,13 +47,14 @@
import org.neo4j.procedure.Name;
import org.neo4j.procedure.PerformsWrites;
import org.neo4j.procedure.Procedure;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/*
Expand Down Expand Up @@ -95,6 +100,14 @@ public NodeDistanceResult(Node node, double distance) {
}
}

public static class GeometryResult {
public final Object geometry;

public GeometryResult(Object geometry) {
this.geometry = geometry;
}
}

private static Map<String, Class> encoderClasses = new HashMap<>();

static {
Expand Down Expand Up @@ -404,6 +417,37 @@ public Stream<NodeDistanceResult> findGeometriesWithinDistance(
});
}

@Procedure("spatial.decodeGeometry")
// TODO: This currently returns an internal Cypher type, in order to be able to pass back into
// other procedures that only accept internal cypher types due to a bug in Neo4j 3.0
// If you need to return Geometries outside (eg. RETURN geometry), then consider spatial.asExternalGeometry(geometry)
public Stream<GeometryResult> decodeGeometry(
@Name("layerName") String name,
@Name("coordinate") Node node) {

Layer layer = getLayerOrThrow(name);
return Stream.of(layer.getGeometryEncoder().decodeGeometry(node)).map(geom -> new GeometryResult(toCypherGeometry(layer, geom)));
}

@Procedure("spatial.asGeometry")
// TODO: This currently returns an internal Cypher type, in order to be able to pass back into
// other procedures that only accept internal cypher types due to a bug in Neo4j 3.0
// If you need to return Geometries outside (eg. RETURN geometry), then consider spatial.asExternalGeometry(geometry)
public Stream<GeometryResult> asGeometry(
@Name("geometry") Object geometry) {

return Stream.of(geometry).map(geom -> new GeometryResult(toCypherGeometry(null, geom)));
}

@Procedure("spatial.asExternalGeometry")
// TODO: This method only exists (and differs from spatial.asGeometry()) because of a bug in Cypher 3.0
// Cypher will emit external geometry types but can only consume internal types. Once that bug is fixed,
// We can make both asGeometry() and asExternalGeometry() return the same public type, and deprecate this procedure.
public Stream<GeometryResult> asExternalGeometry(
@Name("geometry") Object geometry) {

return Stream.of(geometry).map(geom -> new GeometryResult(toNeo4jGeometry(null, geom)));
}

@Procedure("spatial.intersects")
@PerformsWrites // TODO FIX
Expand All @@ -413,15 +457,16 @@ public Stream<NodeResult> findGeometriesIntersecting(

Layer layer = getLayerOrThrow(name);
return GeoPipeline
.startIntersectSearch(layer, toGeometry(layer, geometry))
.startIntersectSearch(layer, toJTSGeometry(layer, geometry))
.stream().map(GeoPipeFlow::getGeomNode).map(NodeResult::new);
}

private Geometry toGeometry(Layer layer, Object value) {
private Geometry toJTSGeometry(Layer layer, Object value) {
GeometryFactory factory = layer.getGeometryFactory();
if (value instanceof org.neo4j.cypher.internal.compiler.v3_0.commands.expressions.Point) {
org.neo4j.cypher.internal.compiler.v3_0.commands.expressions.Point point = (org.neo4j.cypher.internal.compiler.v3_0.commands.expressions.Point) value;
return factory.createPoint(new Coordinate(point.x(), point.y()));
if (value instanceof org.neo4j.graphdb.spatial.Point) {
org.neo4j.graphdb.spatial.Point point = (org.neo4j.graphdb.spatial.Point) value;
List<Double> coord = point.getCoordinate().getCoordinate();
return factory.createPoint(new Coordinate(coord.get(0), coord.get(1)));
}
if (value instanceof String) {
WKTReader reader = new WKTReader(factory);
Expand All @@ -441,6 +486,199 @@ private Geometry toGeometry(Layer layer, Object value) {
throw new RuntimeException("Can't convert " + value + " to a geometry");
}

public static class Neo4jGeometry implements org.neo4j.graphdb.spatial.Geometry {
protected final String geometryType;
protected final CRS crs;
protected final List<org.neo4j.graphdb.spatial.Coordinate> coordinates;

public Neo4jGeometry(String geometryType, List<org.neo4j.graphdb.spatial.Coordinate> coordinates, CRS crs) {
this.geometryType = geometryType;
this.coordinates = coordinates;
this.crs = crs;
}

public String getGeometryType() {
return this.geometryType;
}

public List<org.neo4j.graphdb.spatial.Coordinate> getCoordinates() {
return this.coordinates;
}

public CRS getCRS() {
return this.crs;
}

public static String coordinateString(List<org.neo4j.graphdb.spatial.Coordinate> coordinates) {
return coordinates.stream().map(c -> c.getCoordinate().stream().map(v -> v.toString()).collect(Collectors.joining(", "))).collect(Collectors.joining(", "));
}

public String toString() {
return geometryType + "(" + coordinateString(coordinates) + ")[" + crs + "]";
}
}

public static class Neo4jPoint extends Neo4jGeometry implements org.neo4j.graphdb.spatial.Point {
private final org.neo4j.graphdb.spatial.Coordinate coordinate;

public Neo4jPoint(double x, double y, CRS crs) {
super("Point", new ArrayList(), crs);
this.coordinate = new org.neo4j.graphdb.spatial.Coordinate(new double[]{x, y});
this.coordinates.add(this.coordinate);
}
}

private org.neo4j.graphdb.spatial.Coordinate toNeo4jCoordinate(Coordinate coordinate) {
if (coordinate.z == Coordinate.NULL_ORDINATE) {
return new org.neo4j.graphdb.spatial.Coordinate(coordinate.x, coordinate.y);
} else {
return new org.neo4j.graphdb.spatial.Coordinate(coordinate.x, coordinate.y, coordinate.z);
}
}

private List<org.neo4j.graphdb.spatial.Coordinate> toNeo4jCoordinates(Coordinate[] coordinates) {
ArrayList<org.neo4j.graphdb.spatial.Coordinate> converted = new ArrayList<>();
for (Coordinate coordinate : coordinates) {
converted.add(toNeo4jCoordinate(coordinate));
}
return converted;
}

private org.neo4j.graphdb.spatial.Geometry toNeo4jGeometry(Layer layer, Object value) {
if (value instanceof org.neo4j.graphdb.spatial.Geometry) {
return (org.neo4j.graphdb.spatial.Geometry) value;
}
CRS crs = findCRS("Cartesian");
if (layer != null) {
CoordinateReferenceSystem layerCRS = layer.getCoordinateReferenceSystem();
if (layerCRS != null) {
ReferenceIdentifier crsRef = layer.getCoordinateReferenceSystem().getName();
crs = findCRS(crsRef.toString());
}
}
if (value instanceof Point) {
Point point = (Point) value;
return new Neo4jPoint(point.getX(), point.getY(), crs);
}
if (value instanceof Geometry) {
Geometry geometry = (Geometry) value;
return new Neo4jGeometry(geometry.getGeometryType(), toNeo4jCoordinates(geometry.getCoordinates()), crs);
}
if (value instanceof String && layer != null) {
GeometryFactory factory = layer.getGeometryFactory();
WKTReader reader = new WKTReader(factory);
try {
Geometry geometry = reader.read((String) value);
return new Neo4jGeometry(geometry.getGeometryType(), toNeo4jCoordinates(geometry.getCoordinates()), crs);
} catch (ParseException e) {
throw new IllegalArgumentException("Invalid WKT: " + e.getMessage());
}
}
Map<String, Object> latLon = null;
if (value instanceof PropertyContainer) {
latLon = ((PropertyContainer) value).getProperties("latitude", "longitude", "lat", "lon");
}
if (value instanceof Map) latLon = (Map<String, Object>) value;
Coordinate coord = toCoordinate(latLon);
if (coord != null) return new Neo4jPoint(coord.x, coord.y, crs);
throw new RuntimeException("Can't convert " + value + " to a geometry");
}

private org.neo4j.cypher.internal.compiler.v3_0.Geometry makeCypherGeometry(double x, double y, org.neo4j.cypher.internal.compiler.v3_0.CRS crs) {
if (crs.equals(org.neo4j.cypher.internal.compiler.v3_0.CRS.Cartesian())) {
return new org.neo4j.cypher.internal.compiler.v3_0.CartesianPoint(x, y, crs);
} else {
return new org.neo4j.cypher.internal.compiler.v3_0.GeographicPoint(x, y, crs);
}
}

private org.neo4j.cypher.internal.compiler.v3_0.Geometry makeCypherGeometry(Geometry geometry, org.neo4j.cypher.internal.compiler.v3_0.CRS crs) {
if (geometry.getGeometryType().toLowerCase().equals("point")) {
Coordinate coordinate = geometry.getCoordinates()[0];
return makeCypherGeometry(coordinate.getOrdinate(0), coordinate.getOrdinate(1), crs);
} else {
throw new RuntimeException("Cypher only accepts POINT geometries, not " + geometry.getGeometryType());
}
}

private org.neo4j.cypher.internal.compiler.v3_0.Geometry toCypherGeometry(Layer layer, Object value) {
if (value instanceof org.neo4j.cypher.internal.compiler.v3_0.Geometry) {
return (org.neo4j.cypher.internal.compiler.v3_0.Geometry) value;
}
if ( value instanceof org.neo4j.graphdb.spatial.Point) {
org.neo4j.graphdb.spatial.Point point = (org.neo4j.graphdb.spatial.Point) value;
List<Double> coord = point.getCoordinate().getCoordinate();
return makeCypherGeometry(coord.get(0), coord.get(1), org.neo4j.cypher.internal.compiler.v3_0.CRS.fromSRID(point.getCRS().getCode()));
}
org.neo4j.cypher.internal.compiler.v3_0.CRS crs = org.neo4j.cypher.internal.compiler.v3_0.CRS.Cartesian();
if (layer != null) {
CoordinateReferenceSystem layerCRS = layer.getCoordinateReferenceSystem();
if (layerCRS != null) {
ReferenceIdentifier crsRef = layer.getCoordinateReferenceSystem().getName();
crs = org.neo4j.cypher.internal.compiler.v3_0.CRS.fromName(crsRef.toString());
}
}
if (value instanceof Geometry) {
Geometry geometry = (Geometry) value;
if (geometry.getSRID() > 0) {
crs = org.neo4j.cypher.internal.compiler.v3_0.CRS.fromSRID(geometry.getSRID());
}
if (geometry instanceof Point) {
Point point = (Point) geometry;
return makeCypherGeometry(point.getX(), point.getY(), crs);
}
return makeCypherGeometry(geometry, crs);
}
if (value instanceof String) {
GeometryFactory factory = (layer == null) ? new GeometryFactory() : layer.getGeometryFactory();
WKTReader reader = new WKTReader(factory);
try {
Geometry geometry = reader.read((String) value);
return makeCypherGeometry(geometry, crs);
} catch (ParseException e) {
throw new IllegalArgumentException("Invalid WKT: " + e.getMessage());
}
}
Map<String, Object> latLon = null;
if (value instanceof PropertyContainer) {
latLon = ((PropertyContainer) value).getProperties("latitude", "longitude", "lat", "lon");
if (layer == null) {
crs = org.neo4j.cypher.internal.compiler.v3_0.CRS.WGS84();
}
}
if (value instanceof Map) latLon = (Map<String, Object>) value;
Coordinate coord = toCoordinate(latLon);
if (coord != null) return makeCypherGeometry(coord.x, coord.y, crs);
throw new RuntimeException("Can't convert " + value + " to a geometry");
}

private static CRS findCRS(String crs) {
switch (crs) {
case "WGS-84":
return makeCRS(4326, "WGS-84", "http://spatialreference.org/ref/epsg/4326/");
case "Cartesian":
return makeCRS(7203, "cartesian", "http://spatialreference.org/ref/sr-org/7203/");
default:
throw new IllegalArgumentException("Cypher type system does not support CRS: " + crs);
}
}

private static CRS makeCRS(final int code, final String type, final String href) {
return new CRS() {
public int getCode() {
return code;
}

public String getType() {
return type;
}

public String getHref() {
return href;
}
};
}

private Coordinate toCoordinate(Object value) {
if (value instanceof GeographicPoint) {
GeographicPoint point = (GeographicPoint) value;
Expand Down
Loading

0 comments on commit 178b0ef

Please sign in to comment.