GeometryTestCase.java
/*
* Copyright (c) 2016 Vivid Solutions.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
*
* http://www.eclipse.org/org/documents/edl-v10.php.
*/
package test.jts;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory;
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.Ordinate;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
import junit.framework.TestCase;
/**
* A base class for Geometry tests which provides various utility methods.
*
* @author mbdavis
*
*/
public abstract class GeometryTestCase extends TestCase{
private static final String CHECK_EQUAL_FAIL = "FAIL - Expected = %s -- Actual = %s\n";
private static final String CHECK_EQUAL_FAIL_MSG = "FAIL - %s: Expected = %s -- Actual = %s\n";
private static final String CHECK_NO_AlIAS_FAIL = "FAIL - geometries have aliased coordinates\n";
final GeometryFactory geomFactory;
final WKTReader readerWKT;
final WKTWriter writerZ = new WKTWriter(3);
protected GeometryTestCase(String name)
{
this(name, CoordinateArraySequenceFactory.instance());
}
protected GeometryTestCase(String name, CoordinateSequenceFactory coordinateSequenceFactory) {
super(name);
geomFactory = new GeometryFactory(coordinateSequenceFactory);
readerWKT = new WKTReader(geomFactory);
}
protected GeometryFactory getGeometryFactory() {
return geomFactory;
}
protected void checkValid(Geometry geom) {
assertTrue(geom.isValid());
}
/**
* Checks that the normalized values of the expected and actual
* geometries are exactly equal.
*
* @param expected the expected value
* @param actual the actual value
*/
protected void checkEqual(Geometry expected, Geometry actual) {
checkEqual("", expected, actual);
}
/**
* Checks that the normalized values of the expected and actual
* geometries are exactly equal.
*
* @param expected the expected value
* @param actual the actual value
*/
protected void checkEqual(String msg, Geometry expected, Geometry actual) {
Geometry actualNorm = actual == null ? null : actual.norm();
Geometry expectedNorm = expected == null ? null : expected.norm();
boolean equal;
if (actualNorm == null || expectedNorm == null) {
equal = actualNorm == null && expectedNorm == null;
}
else {
equal = actualNorm.equalsExact(expectedNorm);
}
if (! equal) {
System.out.format(CHECK_EQUAL_FAIL_MSG, msg, expectedNorm, actualNorm );
}
assertTrue(equal);
}
/**
* Checks that the values of the expected and actual
* geometries are exactly equal.
*
* @param expected the expected value
* @param actual the actual value
*/
protected void checkEqualExact(Geometry expected, Geometry actual) {
boolean equal = actual.equalsExact(expected);
if (! equal) {
System.out.format(CHECK_EQUAL_FAIL, expected, actual );
}
assertTrue(equal);
}
protected void checkEqual(Geometry expected, Geometry actual, double tolerance) {
Geometry actualNorm = actual.norm();
Geometry expectedNorm = expected.norm();
boolean equal = actualNorm.equalsExact(expectedNorm, tolerance);
if (! equal) {
System.out.format(CHECK_EQUAL_FAIL, expectedNorm, actualNorm );
}
assertTrue(equal);
}
protected void checkEqualXYZ(Geometry expected, Geometry actual) {
Geometry actualNorm = actual.norm();
Geometry expectedNorm = expected.norm();
boolean equal = equalsExactMultipleDimension(actualNorm, expectedNorm, 3);
if (! equal) {
System.out.format(CHECK_EQUAL_FAIL,
writerZ.write(expectedNorm),
writerZ.write(actualNorm) );
}
assertTrue(equal);
}
protected void checkEqualXYZM(Geometry expected, Geometry actual) {
Geometry actualNorm = actual.norm();
Geometry expectedNorm = expected.norm();
boolean equal = equalsExactMultipleDimension(actualNorm, expectedNorm, 4);
if (! equal) {
System.out.format(CHECK_EQUAL_FAIL,
writerZ.write(expectedNorm),
writerZ.write(actualNorm) );
}
assertTrue(equal);
}
private boolean equalsExactMultipleDimension(Geometry a, Geometry b, int dimension) {
if (a.getClass() != b.getClass()) return false;
if (a.getNumGeometries() != b.getNumGeometries()) return false;
if (a instanceof Point) {
return isEqualDim(((Point) a).getCoordinateSequence(), ((Point) b).getCoordinateSequence(), dimension);
}
else if (a instanceof LineString) {
return isEqualDim(((LineString) a).getCoordinateSequence(), ((LineString) b).getCoordinateSequence(), dimension);
}
else if (a instanceof Polygon) {
return equalsExactMultipleDimensionPolygon( (Polygon) a, (Polygon) b, dimension);
}
else if (a instanceof GeometryCollection) {
for (int i = 0; i < a.getNumGeometries(); i++) {
if (! equalsExactMultipleDimension(a.getGeometryN(i), b.getGeometryN(i), dimension))
return false;
}
return true;
}
return false;
}
private boolean equalsExactMultipleDimensionPolygon(Polygon a, Polygon b, int dimension) {
LinearRing aShell = a.getExteriorRing();
LinearRing bShell = b.getExteriorRing();
if (! isEqualDim(aShell.getCoordinateSequence(), bShell.getCoordinateSequence(), dimension))
return false;
if (a.getNumInteriorRing() != b.getNumInteriorRing())
return false;
for (int i = 0; i < a.getNumInteriorRing(); i++) {
LinearRing aHole = a.getInteriorRingN(i);
LinearRing bHole = b.getInteriorRingN(i);
if (! isEqualDim(aHole.getCoordinateSequence(), bHole.getCoordinateSequence(), dimension))
return false;
}
return true;
}
protected void checkEqual(Geometry[] expected, Geometry[] actual) {
assertEquals("Array length", expected.length, actual.length);
for (int i = 0; i < expected.length; i++) {
checkEqual("element " + i, expected[i], actual[i]);
}
}
protected void checkEqual(Collection expected, Collection actual) {
checkEqual(toGeometryCollection(expected),toGeometryCollection(actual) );
}
GeometryCollection toGeometryCollection(Collection geoms) {
return geomFactory.createGeometryCollection(GeometryFactory.toGeometryArray(geoms));
}
protected void checkEqualXY(Coordinate expected, Coordinate actual) {
assertEquals("Coordinate X", expected.getX(), actual.getX() );
assertEquals("Coordinate Y", expected.getY(), actual.getY() );
}
protected void checkEqualXYZ(Coordinate expected, Coordinate actual) {
assertEquals("Coordinate X", expected.getX(), actual.getX() );
assertEquals("Coordinate Y", expected.getY(), actual.getY() );
assertEquals("Coordinate Z", expected.getZ(), actual.getZ() );
}
protected void checkEqualXY(String message, Coordinate expected, Coordinate actual) {
assertEquals(message + " X", expected.getX(), actual.getX() );
assertEquals(message + " Y", expected.getY(), actual.getY() );
}
protected void checkEqualXY(Coordinate expected, Coordinate actual, double tolerance) {
assertEquals("Coordinate X", expected.getX(), actual.getX(), tolerance);
assertEquals("Coordinate Y", expected.getY(), actual.getY(), tolerance);
}
protected void checkEqualXY(String message, Coordinate expected, Coordinate actual, double tolerance) {
assertEquals(message + " X", expected.getX(), actual.getX(), tolerance);
assertEquals(message + " Y", expected.getY(), actual.getY(), tolerance);
}
protected void checkNoAlias(Geometry geom, Geometry geom2) {
Geometry geom2Copy = geom2.copy();
geom.apply(new CoordinateFilter() {
@Override
public void filter(Coordinate coord) {
coord.x = coord.x + 1;
}
});
boolean equal = geom2.equalsExact(geom2Copy);
if (! equal) {
System.out.println(CHECK_NO_AlIAS_FAIL);
fail();
}
}
/**
* Reads a {@link Geometry} from a WKT string using a custom {@link GeometryFactory}.
*
* @param geomFactory the custom factory to use
* @param wkt the WKT string
* @return the geometry read
*/
protected static Geometry read(GeometryFactory geomFactory, String wkt) {
WKTReader reader = new WKTReader(geomFactory);
try {
return reader.read(wkt);
} catch (ParseException e) {
throw new RuntimeException(e.getMessage());
}
}
protected Geometry read(String wkt) {
//return read(readerWKT, wkt);
return WKTorBReader.read(wkt, geomFactory);
}
public static Geometry read(WKTReader reader, String wkt) {
try {
return reader.read(wkt);
} catch (ParseException e) {
throw new RuntimeException(e.getMessage());
}
}
protected List readList(String[] wkt) {
ArrayList geometries = new ArrayList(wkt.length);
for (int i = 0; i < wkt.length; i++) {
geometries.add(read(wkt[i]));
}
return geometries;
}
public static List readList(WKTReader reader, String[] wkt) {
ArrayList geometries = new ArrayList(wkt.length);
for (int i = 0; i < wkt.length; i++) {
geometries.add(read(reader, wkt[i]));
}
return geometries;
}
protected Geometry[] readArray(String... wkt) {
Geometry[] geometries = new Geometry[wkt.length];
for (int i = 0; i < wkt.length; i++) {
geometries[i] = (wkt[i] == null) ? null : read(wkt[i]);
}
return geometries;
}
/**
* Gets a {@link WKTReader} to read geometries from WKT with expected ordinates.
*
* @param ordinateFlags a set of expected ordinates
* @return a {@code WKTReader}
*/
public static WKTReader getWKTReader(EnumSet<Ordinate> ordinateFlags) {
return getWKTReader(ordinateFlags, new PrecisionModel());
}
/**
* Gets a {@link WKTReader} to read geometries from WKT with expected ordinates.
*
* @param ordinateFlags a set of expected ordinates
* @param scale a scale value to create a {@link PrecisionModel}
*
* @return a {@code WKTReader}
*/
public static WKTReader getWKTReader(EnumSet<Ordinate> ordinateFlags, double scale) {
return getWKTReader(ordinateFlags, new PrecisionModel(scale));
}
/**
* Gets a {@link WKTReader} to read geometries from WKT with expected ordinates.
*
* @param ordinateFlags a set of expected ordinates
* @param precisionModel a precision model
*
* @return a {@code WKTReader}
*/
public static WKTReader getWKTReader(EnumSet<Ordinate> ordinateFlags, PrecisionModel precisionModel) {
WKTReader result;
if (!ordinateFlags.contains(Ordinate.X)) ordinateFlags.add(Ordinate.X);
if (!ordinateFlags.contains(Ordinate.Y)) ordinateFlags.add(Ordinate.Y);
if (ordinateFlags.size() == 2)
{
result = new WKTReader(new GeometryFactory(precisionModel, 0, CoordinateArraySequenceFactory.instance()));
result.setIsOldJtsCoordinateSyntaxAllowed(false);
}
else if (ordinateFlags.contains(Ordinate.Z))
result = new WKTReader(new GeometryFactory(precisionModel, 0, CoordinateArraySequenceFactory.instance()));
else if (ordinateFlags.contains(Ordinate.M)) {
result = new WKTReader(new GeometryFactory(precisionModel, 0,
PackedCoordinateSequenceFactory.DOUBLE_FACTORY));
result.setIsOldJtsCoordinateSyntaxAllowed(false);
}
else
result = new WKTReader(new GeometryFactory(precisionModel, 0, PackedCoordinateSequenceFactory.DOUBLE_FACTORY));
return result;
}
/**
* Tests two {@link CoordinateSequence}s for equality. The following items are checked:
* <ul>
* <li>size</li><li>dimension</li><li>ordinate values</li>
* </ul>
* @param seq1 a sequence
* @param seq2 another sequence
* @return {@code true} if both sequences are equal
*/
public static boolean isEqual(CoordinateSequence seq1, CoordinateSequence seq2) {
return isEqualTol(seq1, seq2, 0d);
}
/**
* Tests two {@link CoordinateSequence}s for equality. The following items are checked:
* <ul>
* <li>size</li><li>dimension</li><li>ordinate values with {@code tolerance}</li>
* </ul>
* @param seq1 a sequence
* @param seq2 another sequence
* @return {@code true} if both sequences are equal
*/
public static boolean isEqualTol(CoordinateSequence seq1, CoordinateSequence seq2, double tolerance) {
if (seq1.getDimension() != seq2.getDimension())
return false;
return isEqual(seq1, seq2, seq1.getDimension(), tolerance);
}
/**
* Tests two {@link CoordinateSequence}s for equality. The following items are checked:
* <ul>
* <li>size</li><li>dimension up to {@code dimension}</li><li>ordinate values</li>
* </ul>
* @param seq1 a sequence
* @param seq2 another sequence
* @return {@code true} if both sequences are equal
*/
public static boolean isEqualDim(CoordinateSequence seq1, CoordinateSequence seq2, int dimension) {
return isEqual(seq1, seq2, dimension, 0d);
}
/**
* Tests two {@link CoordinateSequence}s for equality. The following items are checked:
* <ul>
* <li>size</li><li>dimension up to {@code dimension}</li><li>ordinate values with {@code tolerance}</li>
* </ul>
* @param seq1 a sequence
* @param seq2 another sequence
* @return {@code true} if both sequences are equal
*/
public static boolean isEqual(CoordinateSequence seq1, CoordinateSequence seq2, int dimension, double tolerance) {
if (seq1 != null && seq2 == null) return false;
if (seq1 == null && seq2 != null) return false;
if (seq1.size() != seq2.size()) return false;
if (seq1.getDimension() < dimension)
throw new IllegalArgumentException("dimension too high for seq1");
if (seq2.getDimension() < dimension)
throw new IllegalArgumentException("dimension too high for seq2");
for (int i = 0; i < seq1.size(); i++) {
for (int j = 0; j < dimension; j++) {
double val1 = seq1.getOrdinate(i, j);
double val2 = seq2.getOrdinate(i, j);
if (Double.isNaN(val1) || Double.isNaN(val2)) {
return Double.isNaN(val1) && Double.isNaN(val2);
}
else if (Math.abs(val1 - val2) > tolerance)
return false;
}
}
return true;
}
/**
* Gets a {@link CoordinateSequenceFactory} that can create sequences
* for ordinates defined in the provided bit-pattern.
* @param ordinateFlags a bit-pattern of ordinates
* @return a {@code CoordinateSequenceFactory}
*/
public static CoordinateSequenceFactory getCSFactory(EnumSet<Ordinate> ordinateFlags)
{
if (ordinateFlags.contains(Ordinate.M))
return PackedCoordinateSequenceFactory.DOUBLE_FACTORY;
return CoordinateArraySequenceFactory.instance();
}
}