package uk.ac.ox.zoo.seeg.abraid.mp.common.util;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.offset;
/**
* Tests the GeometryUtils class.
*
* Copyright (c) 2014 University of Oxford
*/
public class GeometryUtilsTest {
private static final double MAXIMUM_ORTHODROMIC_DISTANCE_OFFSET = 0.000005;
private static final double MAXIMUM_SPHERICAL_DISTANCE_OFFSET = 0.05;
@Test
public void createPointHasCorrectPrecisionAndSRID() {
Point point = GeometryUtils.createPoint(-1.2824895, 51.6743376);
assertThat(point.getX()).isEqualTo(-1.28249);
assertThat(point.getY()).isEqualTo(51.67434);
assertThat(point.getSRID()).isEqualTo(GeometryUtils.SRID_FOR_WGS_84);
}
@Test
public void findOrthodromicDistanceForTypicalParameters() {
// Arrange
// We compare the results with PostGIS's function ST_DISTANCE applied to geography objects, i.e.
// ST_DISTANCE(GEOGRAPHY(ST_SetSRID(ST_Point(-1.28383, 51.06529), 4326)),
// GEOGRAPHY(ST_SetSRID(ST_Point(-1.25, 51), 4326)))
Point point1 = GeometryUtils.createPoint(-1.28383, 51.06529);
Point point2 = GeometryUtils.createPoint(-1.25, 51);
double expectedDistance = 7.641287209854;
// Act
double distance = GeometryUtils.findOrthodromicDistance(point1, point2);
double distanceReversed = GeometryUtils.findOrthodromicDistance(point2, point1);
// Assert
assertThat(distance).isEqualTo(expectedDistance, offset(MAXIMUM_ORTHODROMIC_DISTANCE_OFFSET));
assertThat(distanceReversed).isEqualTo(expectedDistance, offset(MAXIMUM_ORTHODROMIC_DISTANCE_OFFSET));
}
@Test
public void findOrthodromicDistanceForTypicalParameters2() {
// Arrange
Point point1 = GeometryUtils.createPoint(8.75848, 53.13338);
Point point2 = GeometryUtils.createPoint(8.76167, 53.16090);
double expectedDistance = 3.070102641947;
// Act
double distance = GeometryUtils.findOrthodromicDistance(point1, point2);
// Assert
assertThat(distance).isEqualTo(expectedDistance, offset(MAXIMUM_ORTHODROMIC_DISTANCE_OFFSET));
}
@Test
public void findOrthodromicDistanceForZeroParameters() {
Point point = GeometryUtils.createPoint(0, 0);
double distance = GeometryUtils.findOrthodromicDistance(point, point);
assertThat(distance).isEqualTo(0);
}
@Test
public void findOrthodromicDistanceBetweenPoles() {
// Arrange
Point point1 = GeometryUtils.createPoint(0, 90);
Point point2 = GeometryUtils.createPoint(0, -90);
double expectedDistance = 20003.9314586236;
// Act
double distance = GeometryUtils.findOrthodromicDistance(point1, point2);
// Assert
assertThat(distance).isEqualTo(expectedDistance, offset(MAXIMUM_ORTHODROMIC_DISTANCE_OFFSET));
}
@Test
public void findOrthodromicDistanceAtEquator() {
// Arrange
Point point1 = GeometryUtils.createPoint(0, 0);
Point point2 = GeometryUtils.createPoint(100, 0);
double expectedDistance = 11131.9490779206;
// Act
double distance = GeometryUtils.findOrthodromicDistance(point1, point2);
// Assert
assertThat(distance).isEqualTo(expectedDistance, offset(MAXIMUM_ORTHODROMIC_DISTANCE_OFFSET));
}
@Test
public void findOrthodromicDistanceBetweenIdenticalPoints() {
Point point = GeometryUtils.createPoint(15, 15);
double distance = GeometryUtils.findOrthodromicDistance(point, point);
assertThat(distance).isEqualTo(0);
}
@Test
public void findOrthodromicDistanceWithoutGeodeticCalculatorConvergence() {
// This test fails when GeometryUtils.createPoint uses GeodeticCalculator.getOrthodromicDistance()
// (rather than the currently-used DefaultEllipsoid.WGS84.orthodromicDistance()).
// Arrange
Point point1 = GeometryUtils.createPoint(120.566670, 22.733330);
Point point2 = GeometryUtils.createPoint(-59.935130, -20);
double expectedDistance = 19697.5564126086;
// Act
double distance = GeometryUtils.findOrthodromicDistance(point1, point2);
// Assert
assertThat(distance).isEqualTo(expectedDistance, offset(MAXIMUM_ORTHODROMIC_DISTANCE_OFFSET));
}
@Test
public void findOrthodromicDistanceWithoutConvergence1() {
// This test fails to converge using Vincenty's method so falls back to the Haversine method.
//
// To compare it with PostGIS's ST_DISTANCE function, we have to set use_spheroid to FALSE i.e.
// ST_DISTANCE(GEOGRAPHY(ST_SetSRID(ST_Point(101.686530, 3.143090), 4326)),
// GEOGRAPHY(ST_SetSRID(ST_Point(-78.10016, -2.7031), 4326)), FALSE)
//
// This is despite PostGIS also apparently using Vincenty's method and falling back to Haversine
// (see lwspheroid.c's function "spheroid_distance" in the PostGIS source code).
//
// Spherical distance is more subject to error, so the maximum offset used below is much higher than for
// ellipsoid distance. Also note that the difference in X points is close to 180 degrees where error
// is more likely.
// Arrange
Point point1 = GeometryUtils.createPoint(101.686530, 3.143090);
Point point2 = GeometryUtils.createPoint(-78.10016, -2.7031);
double expectedDistance = 19960.7566965142;
// Act
double distance = GeometryUtils.findOrthodromicDistance(point1, point2);
// Assert
assertThat(distance).isEqualTo(expectedDistance, offset(MAXIMUM_SPHERICAL_DISTANCE_OFFSET));
}
@Test
public void findOrthodromicDistanceWithoutConvergence2() {
// See comment for findOrthodromicDistanceWithoutConvergence1
// Arrange
Point point1 = GeometryUtils.createPoint(-53.1, -26.3);
Point point2 = GeometryUtils.createPoint(126.953860, 26.566770);
double expectedDistance = 19984.9699692572;
// Act
double distance = GeometryUtils.findOrthodromicDistance(point1, point2);
// Assert
assertThat(distance).isEqualTo(expectedDistance, offset(MAXIMUM_SPHERICAL_DISTANCE_OFFSET));
}
@Test
public void concatenateMultiPolygonsReturnsAnEmptyMultiPolygonIfNoneSpecified() {
// Arrange
List<MultiPolygon> multiPolygonList = new ArrayList<>();
// Act
MultiPolygon concatenation = GeometryUtils.concatenate(multiPolygonList);
// Assert
assertThat(concatenation.getNumGeometries()).isEqualTo(0);
}
@Test
public void concatenateMultiPolygonsReturnsASingleNonNullMultiPolygonIfOnlyItIsSpecified() {
// Arrange
MultiPolygon onePolygon = GeometryUtils.createMultiPolygon(getTriangle());
List<MultiPolygon> multiPolygonList = Arrays.asList(onePolygon);
// Act
MultiPolygon concatenation = GeometryUtils.concatenate(multiPolygonList);
// Assert
assertThat(concatenation).isSameAs(onePolygon);
}
@Test
public void concatenateMultiPolygons() {
// Arrange
MultiPolygon onePolygon = GeometryUtils.createMultiPolygon(getTriangle());
MultiPolygon twoPolygons = GeometryUtils.createMultiPolygon(getSquare(), getFivePointedPolygon());
List<MultiPolygon> multiPolygonList = Arrays.asList(onePolygon, null, twoPolygons);
// Act
MultiPolygon concatenation = GeometryUtils.concatenate(multiPolygonList);
// Assert
assertThat(concatenation.getNumGeometries()).isEqualTo(3);
assertThat(concatenation.getGeometryN(0).equalsExact(getTriangle())).isTrue();
assertThat(concatenation.getGeometryN(1).equalsExact(getSquare())).isTrue();
assertThat(concatenation.getGeometryN(2).equalsExact(getFivePointedPolygon())).isTrue();
}
@Test
public void findClosestPointOnGeometryWhenRunOnItselfReturnsItself() {
// Arrange
Point point = GeometryUtils.createPoint(10, 20);
// Act
Point closestPoint = GeometryUtils.findClosestPointOnGeometry(point, point);
// Assert
assertThat(closestPoint.equalsExact(point)).isTrue();
}
@Test
public void findClosestPointOnGeometryWhenRunOnAPointReturnsThatPoint() {
// Arrange
Point point1 = GeometryUtils.createPoint(10, 20);
Point point2 = GeometryUtils.createPoint(40, 60);
// Act
Point closestPoint = GeometryUtils.findClosestPointOnGeometry(point1, point2);
// Assert
assertThat(closestPoint.equalsExact(point1)).isTrue();
}
@Test
public void findClosestPointOnGeometryReturnsVertex() {
// Arrange
Geometry geometry = getSquare();
Point point = GeometryUtils.createPoint(20, 20);
// Act
Point closestPoint = GeometryUtils.findClosestPointOnGeometry(geometry, point);
// Assert
assertThat(closestPoint.equalsExact(point)).isTrue();
}
@Test
public void findClosestPointOnGeometryReturnsInsidePoint() {
// Arrange
Geometry geometry = getSquare();
Point point = GeometryUtils.createPoint(20, 20);
// Act
Point closestPoint = GeometryUtils.findClosestPointOnGeometry(geometry, point);
// Assert
assertThat(closestPoint.equalsExact(point)).isTrue();
}
@Test
public void findClosestPointOnGeometrySnapsOutsidePointToVertex() {
// Arrange
Geometry geometry = getSquare();
Point point = GeometryUtils.createPoint(26, 24);
Point expectedClosestPoint = GeometryUtils.createPoint(20, 20);
// Act
Point closestPoint = GeometryUtils.findClosestPointOnGeometry(geometry, point);
// Assert
assertThat(closestPoint.equalsExact(expectedClosestPoint)).isTrue();
}
@Test
public void findClosestPointOnGeometrySnapsOutsidePointToClosestInterpolatedPoint() {
// Arrange
Geometry geometry = getSquare();
Point point = GeometryUtils.createPoint(25, 11);
Point expectedClosestPoint = GeometryUtils.createPoint(20, 11);
// Act
Point closestPoint = GeometryUtils.findClosestPointOnGeometry(geometry, point);
// Assert
assertThat(closestPoint.equalsExact(expectedClosestPoint)).isTrue();
}
@Test
public void findClosestPointOnGeometrySnapsDistantOutsidePointToClosestInterpolatedPoint() {
// Arrange
Geometry geometry = getSquare();
Point point = GeometryUtils.createPoint(15, -89.9);
Point expectedClosestPoint = GeometryUtils.createPoint(15, 10);
// Act
Point closestPoint = GeometryUtils.findClosestPointOnGeometry(geometry, point);
// Assert
assertThat(closestPoint.equalsExact(expectedClosestPoint)).isTrue();
}
@Test
public void findClosestPointOnGeometrySnapsOutsidePointWithCorrectRounding() {
// Arrange
Geometry geometry = GeometryUtils.createPolygon(false, 10.000003, 10.000002, 10, 20, 20, 20, 20, 10,
10.000003, 10.000002);
Point point = GeometryUtils.createPoint(9, 9);
Point expectedCorrectClosestPoint = GeometryUtils.createPoint(10.00001, 10.00001);
Point expectedIncorrectClosestPoint = GeometryUtils.createPoint(10, 10);
assertThat(geometry.intersects(expectedCorrectClosestPoint)).isTrue();
assertThat(geometry.intersects(expectedIncorrectClosestPoint)).isFalse();
// Act
Point closestPoint = GeometryUtils.findClosestPointOnGeometry(geometry, point);
// Assert
assertThat(closestPoint.equalsExact(expectedCorrectClosestPoint)).isTrue();
assertThat(closestPoint.equalsExact(expectedIncorrectClosestPoint)).isFalse();
}
@Test
public void findClosestPointOnGeometryReturnsNullIfClosestPointCannotBeFound() {
// Arrange
Geometry geometry = GeometryUtils.createPolygon(false, 10.000003, 10.000003, 10.000003, 10.000006,
10.000006, 10.000006, 10.000006, 10.000003, 10.000003, 10.000003);
Point point = GeometryUtils.createPoint(9, 9);
// Act
Point closestPoint = GeometryUtils.findClosestPointOnGeometry(geometry, point);
// Assert
assertThat(closestPoint).isNull();
}
// Asserted exception is in the @Test annotation - cannot use catchException() for static methods
@Test(expected = IllegalArgumentException.class)
public void createPolygonThrowsExceptionIfNumberParametersIsOdd() {
GeometryUtils.createPolygon(1, 1, 3, 2, 2, 3, 1);
}
private Polygon getTriangle() {
return GeometryUtils.createPolygon(1, 1, 3, 2, 2, 3, 1, 1);
}
private Polygon getSquare() {
return GeometryUtils.createPolygon(10, 10, 10, 20, 20, 20, 20, 10, 10, 10);
}
private Polygon getFivePointedPolygon() {
return GeometryUtils.createPolygon(3, 4, 5, 11, 12, 8, 9, 5, 5, 6, 3, 4);
}
}