Skip to content

Commit

Permalink
Procedures and user defined functions for withinPolygon
Browse files Browse the repository at this point in the history
  • Loading branch information
craigtaverner committed May 3, 2018
1 parent 1866637 commit f9171e1
Show file tree
Hide file tree
Showing 8 changed files with 372 additions and 50 deletions.
29 changes: 1 addition & 28 deletions algo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.amanzi</groupId>
<artifactId>spatial-3d-parent</artifactId>
<version>1.0.0</version>
<version>0.1.0</version>
</parent>

<artifactId>spatial-3d-algo</artifactId>
Expand All @@ -22,33 +22,6 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>net.biville.florent</groupId>
<artifactId>neo4j-sproc-compiler</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-kernel</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-io</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
41 changes: 29 additions & 12 deletions algo/src/main/java/org/amanzi/spatial/algo/Within.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@

import org.amanzi.spatial.core.Point;
import org.amanzi.spatial.core.Polygon;
import org.apache.commons.lang3.tuple.Pair;

import java.util.ArrayList;
import java.util.Arrays;

public class Within {
public static boolean within(Polygon polygon, Point point) {
return within(polygon, point, false);
}

public static boolean within(Polygon polygon, Point point, boolean touching) {
for (Polygon.SimplePolygon shell : polygon.getShells()) {
if (!within(shell, point)) {
if (!within(shell, point, touching)) {
return false;
}
}
for (Polygon.SimplePolygon hole : polygon.getShells()) {
if (within(hole, point)) {
for (Polygon.SimplePolygon hole : polygon.getHoles()) {
if (within(hole, point, touching)) {
return false;
}
}
return true;
}

public static boolean within(Polygon.SimplePolygon shell, Point point) {
return within(shell, point, false);
}

public static boolean within(Polygon.SimplePolygon shell, Point point, boolean touching) {
int fixedDim = 0;
int compareDim = 1;
Point[] points = shell.getPoints();
Expand All @@ -34,8 +40,10 @@ public static boolean within(Polygon.SimplePolygon shell, Point point) {
Integer compare2 = ternaryComparePointsIgnoringOneDimension(p2.getCoordinate(), point.getCoordinate(), fixedDim);
if (compare1 == null || compare2 == null) {
// Ignore?
} else if (compare1 * compare2 >= 0) {
} else if (compare1 * compare2 > 0) {
// both on same side - ignore
} else if (compare1 * compare2 == 0 && !touching) {
// point touches one or both end points, but we are ignoring touching points
} else {
Integer compare = ternaryComparePointsIgnoringOneDimension(p1.getCoordinate(), p2.getCoordinate(), fixedDim);
if (compare < 0) {
Expand All @@ -47,22 +55,31 @@ public static boolean within(Polygon.SimplePolygon shell, Point point) {
}
int intersections = 0;
for (Point[] side : sides) {
if (crosses(side, point, fixedDim, compareDim)) {
double crossingValue = crossingAt(side, point, fixedDim, compareDim);
if (touching && crossingValue == 0) {
return true;
}
if (crossingValue >= 0) {
intersections += 1;
}
}
return intersections % 2 == 1;
}

public static boolean crosses(Point[] side, Point point, int fixedDim, int compareDim) {
static double crossingAt(Point[] side, Point point, int fixedDim, int compareDim) {
double[] c = point.getCoordinate();
double[] min = new double[]{side[0].getCoordinate()[fixedDim], side[0].getCoordinate()[compareDim]};
double[] max = new double[]{side[1].getCoordinate()[fixedDim], side[1].getCoordinate()[compareDim]};
double[] diff = new double[]{max[0] - min[0], max[1] - min[1]};
double ratio = (c[1] - min[1]) / diff[1];
double offset = ratio * diff[0];
double crossingValue = min[0] + offset;
return crossingValue >= c[0];
if (diff[1] == 0) {
// touching a line that runs along the fixed dimension
return 0;
} else {
double ratio = (c[1] - min[1]) / diff[1];
double offset = ratio * diff[0];
double crossingValue = min[0] + offset;
return crossingValue - c[0];
}
}

public static Integer ternaryComparePointsIgnoringOneDimension(double[] c1, double[] c2, int ignoreDim) {
Expand Down
19 changes: 19 additions & 0 deletions algo/src/test/java/org/amanzi/spatial/algo/WithinTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.amanzi.spatial.core.Point;
import org.amanzi.spatial.core.Polygon;
import org.junit.Ignore;
import org.junit.Test;

import java.util.Arrays;
Expand All @@ -21,6 +22,24 @@ public void shouldBeWithinSquare() {
assertThat(Within.within(square, new Point(0, 20)), equalTo(false));
}

@Ignore
// TODO still some bugs with touching logic
public void shouldBeTouchingSquare() {
Polygon.SimplePolygon square = makeSquare(new double[]{-10, -10}, 20);
for (boolean touching : new boolean[]{false, true}) {
assertThat(Within.within(square, new Point(-10, -20), touching), equalTo(false));
assertThat(Within.within(square, new Point(-10, -10), touching), equalTo(touching));
assertThat(Within.within(square, new Point(-10, 0), touching), equalTo(touching));
assertThat(Within.within(square, new Point(-10, 10), touching), equalTo(touching));
assertThat(Within.within(square, new Point(-10, 20), touching), equalTo(false));
assertThat(Within.within(square, new Point(-20, -10), touching), equalTo(false));
assertThat(Within.within(square, new Point(-10, -10), touching), equalTo(touching));
assertThat(Within.within(square, new Point(0, -10), touching), equalTo(touching));
assertThat(Within.within(square, new Point(10, -10), touching), equalTo(touching));
assertThat(Within.within(square, new Point(20, -10), touching), equalTo(false));
}
}

private static double[] move(double[] coords, int dim, double move) {
double[] moved = Arrays.copyOf(coords, coords.length);
moved[dim] += move;
Expand Down
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.amanzi</groupId>
<artifactId>spatial-3d-parent</artifactId>
<version>1.0.0</version>
<version>0.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion doc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>spatial-3d-parent</artifactId>
<groupId>org.amanzi</groupId>
<version>1.0.0</version>
<version>0.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
126 changes: 126 additions & 0 deletions neo4j/src/main/java/org/amanzi/spatial/neo4j/UserDefinedFunctions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.amanzi.spatial.neo4j;

import org.amanzi.spatial.algo.Within;
import org.amanzi.spatial.core.Polygon;
import org.neo4j.graphdb.spatial.CRS;
import org.neo4j.graphdb.spatial.Coordinate;
import org.neo4j.graphdb.spatial.Point;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;
import org.neo4j.procedure.UserFunction;
import org.neo4j.values.storable.CoordinateReferenceSystem;

import java.util.*;
import java.util.stream.Stream;

public class UserDefinedFunctions {

@Procedure("amanzi.polygon")
public Stream<PolygonResult> makePolygon(@Name("points") List<Point> points) {
if (points == null || points.size() < 3) {
throw new IllegalArgumentException("Invalid 'points', should be a list of at least 3, but was: " + (points == null ? "null" : points.size()));
} else if (points.get(0).equals(points.get(points.size() - 1))) {
return Stream.of(new PolygonResult(points));
} else {
ArrayList<Point> polygon = new ArrayList<>(points.size() + 1);
polygon.addAll(points);
polygon.add(points.get(0));
return Stream.of(new PolygonResult(polygon));
}
}

@UserFunction("amanzi.boundingBoxFor")
public Map<String, Point> boundingBoxFor(@Name("polygon") List<Point> polygon) {
if (polygon == null || polygon.size() < 4) {
throw new IllegalArgumentException("Invalid 'polygon', should be a list of at least 4, but was: " + (polygon == null ? "null" : polygon.size()));
} else if (!polygon.get(0).equals(polygon.get(polygon.size() - 1))) {
throw new IllegalArgumentException("Invalid 'polygon', first and last point should be the same, but were: " + polygon.get(0) + " and " + polygon.get(polygon.size() - 1));
} else {
CRS crs = polygon.get(0).getCRS();
double[] min = asPoint(polygon.get(0)).getCoordinate();
double[] max = asPoint(polygon.get(0)).getCoordinate();
for (Point p : polygon) {
double[] vertex = asPoint(p).getCoordinate();
for (int i = 0; i < vertex.length; i++) {
if (vertex[i] < min[i]) {
min[i] = vertex[i];
}
if (vertex[i] > max[i]) {
max[i] = vertex[i];
}
}
}
HashMap<String, Point> bbox = new HashMap<>();
bbox.put("min", asPoint(crs, min));
bbox.put("max", asPoint(crs, max));
return bbox;
}
}

@UserFunction("amanzi.withinPolygon")
public boolean withinPolygon(@Name("point") Point point, @Name("polygon") List<Point> polygon, @Name(value = "touching", defaultValue = "false") boolean touching) {
if (polygon == null || polygon.size() < 4) {
throw new IllegalArgumentException("Invalid 'polygon', should be a list of at least 4, but was: " + polygon.size());
} else if (!polygon.get(0).equals(polygon.get(polygon.size() - 1))) {
throw new IllegalArgumentException("Invalid 'polygon', first and last point should be the same, but were: " + polygon.get(0) + " and " + polygon.get(polygon.size() - 1));
} else {
CRS polyCrs = polygon.get(0).getCRS();
CRS pointCrs = point.getCRS();
if (!polyCrs.equals(pointCrs)) {
throw new IllegalArgumentException("Cannot compare geometries of different CRS: " + polyCrs + " !+ " + pointCrs);
} else {
Polygon geometry = Polygon.simple(asPoints(polygon));
return Within.within(geometry, asPoint(point), touching);
}
}
}

private org.amanzi.spatial.core.Point[] asPoints(List<Point> polygon) {
org.amanzi.spatial.core.Point[] points = new org.amanzi.spatial.core.Point[polygon.size()];
for (int i = 0; i < points.length; i++) {
points[i] = asPoint(polygon.get(i));
}
return points;
}

private org.amanzi.spatial.core.Point asPoint(Point point) {
List<Double> coordinates = point.getCoordinate().getCoordinate();
double[] coords = new double[coordinates.size()];
for (int i = 0; i < coords.length; i++) {
coords[i] = coordinates.get(i);
}
return new org.amanzi.spatial.core.Point(coords);
}

private Point asPoint(CRS crs, double[] coords) {
return new Neo4jPoint(crs, new Coordinate(coords));
}

private class Neo4jPoint implements Point {
private final List<Coordinate> coordinates;
private final CRS crs;

private Neo4jPoint(CRS crs, Coordinate coordinate) {
this.crs = crs;
this.coordinates = Arrays.asList(coordinate);
}

@Override
public List<Coordinate> getCoordinates() {
return coordinates;
}

@Override
public CRS getCRS() {
return crs;
}
}

public class PolygonResult {
public List<Point> polygon;

private PolygonResult(List<Point> points) {
this.polygon = points;
}
}
}
Loading

0 comments on commit f9171e1

Please sign in to comment.