/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2014, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotools.geometry.jts;
import static org.junit.Assert.*;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.vividsolutions.jts.algorithm.CGAlgorithms;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
public class CircularArcTest {
static final Coordinate ORIGIN = new Coordinate(0, 0);
static final int COUNTER_CLOCKWISE = 1;
static final int COLLINEAR = 0;
static final int CLOCKWISE = -1;
@BeforeClass
public static void setupBaseSegmentsQuadrant() {
// we want to run the test at a higher precision
CircularArc.setBaseSegmentsQuadrant(32);
}
@AfterClass
public static void resetBaseSegmentsQuadrant() {
// we want to run the test at a higher precision
CircularArc.setBaseSegmentsQuadrant(12);
}
Envelope envelopeFrom(CircularArc arc, double... otherPoints) {
Envelope env = new Envelope();
// add the control points
env.expandToInclude(arc.controlPoints[0], arc.controlPoints[1]);
env.expandToInclude(arc.controlPoints[2], arc.controlPoints[3]);
env.expandToInclude(arc.controlPoints[4], arc.controlPoints[5]);
if (otherPoints != null) {
// add the other points
for (int i = 0; i < otherPoints.length;) {
env.expandToInclude(otherPoints[i++], otherPoints[i++]);
}
}
return env;
}
static void assertCoordinateEquals(Coordinate expected, Coordinate actual) {
if (expected == null) {
assertNull(actual);
} else {
assertEquals(expected.x, actual.x, Circle.EPS);
assertEquals(expected.y, actual.y, Circle.EPS);
}
}
@Test
public void testCollinear() {
CircularArc arc = new CircularArc(0, 0, 0, 10, 0, 20);
assertEquals(CircularArc.COLLINEARS, arc.getRadius(), 0d);
assertCoordinateEquals(null, arc.getCenter());
double[] linearized = arc.linearize(0);
assertArrayEquals(new double[] { 0, 0, 0, 10, 0, 20 }, linearized, 0d);
assertEquals(envelopeFrom(arc), arc.getEnvelope());
}
@Test
public void testSamePoints() {
CircularArc arc = new CircularArc(0, 0, 0, 0, 0, 0);
assertEquals(0, arc.getRadius(), 0d);
assertCoordinateEquals(ORIGIN, arc.getCenter());
double[] linearized = arc.linearize(0);
assertArrayEquals(new double[] { 0, 0, 0, 0, 0, 0 }, linearized, 0d);
assertEquals(envelopeFrom(arc), arc.getEnvelope());
assertEquals(0, arc.getEnvelope().getArea(), 0d);
}
@Test
public void testMinuscule() {
Circle circle = new Circle(100);
CircularArc arc = circle.getCircularArc(0, CircularArc.HALF_PI / 128,
CircularArc.HALF_PI / 64);
assertEquals(100, arc.getRadius(), 1e-9);
assertCoordinateEquals(ORIGIN, arc.getCenter());
// linearize with a large tolerance, we should get back just the control points
assertArrayEquals(arc.getControlPoints(), arc.linearize(10), 0d);
assertEquals(envelopeFrom(arc), arc.getEnvelope());
}
@Test
public void testMatchingSequence() {
Circle circle = new Circle(100);
// create control points that will match exactly the points the algo should generate
CircularArc arc = circle.getCircularArc(0, CircularArc.HALF_PI / 32,
CircularArc.HALF_PI / 16);
assertEquals(100, arc.getRadius(), 1e-9);
assertCoordinateEquals(ORIGIN, arc.getCenter());
// linearize with a large tolerance, we should get back just the control points
assertArrayEquals(arc.getControlPoints(), arc.linearize(10), 0d);
}
@Test
public void testOutsideSequence() {
Circle circle = new Circle(100);
// create control points that will match exactly the points the algo should generate
double halfStep = CircularArc.HALF_PI / 64;
CircularArc arc = circle.getCircularArc(halfStep, halfStep * 3, halfStep * 5);
assertEquals(100, arc.getRadius(), 1e-9);
assertCoordinateEquals(ORIGIN, arc.getCenter());
// linearize, we should get back the control points, plus the regular points in the middle
double[] expected = circle.samplePoints(halfStep, halfStep * 2, halfStep * 3, halfStep * 4,
halfStep * 5);
assertArrayEquals(expected, arc.linearize(0.1), Circle.EPS);
}
@Test
public void testOutsideSequenceClockwise() {
Circle circle = new Circle(100);
// create control points that will match exactly the points the algo should generate
double halfStep = CircularArc.HALF_PI / 64;
CircularArc arc = circle.getCircularArc(halfStep * 5, halfStep * 3, halfStep);
assertEquals(100, arc.getRadius(), 1e-9);
assertCoordinateEquals(ORIGIN, arc.getCenter());
// linearize, we should get back the control points, plus the regular points in the middle
double[] expected = circle.samplePoints(halfStep * 5, halfStep * 4, halfStep * 3,
halfStep * 2, halfStep);
assertArrayEquals(expected, arc.linearize(0.1), Circle.EPS);
assertEquals(envelopeFrom(arc), arc.getEnvelope());
}
@Test
public void testStartMatchSequence() {
Circle circle = new Circle(100);
// create control points that will match exactly the points the algo should generate
double halfStep = CircularArc.HALF_PI / 64;
CircularArc arc = circle.getCircularArc(0, halfStep * 3, halfStep * 5);
assertEquals(100, arc.getRadius(), 1e-9);
assertCoordinateEquals(ORIGIN, arc.getCenter());
// linearize, we should get back the control points, plus the regular points in the middle
double[] expected = circle.samplePoints(0, halfStep * 2, halfStep * 3, halfStep * 4,
halfStep * 5);
assertArrayEquals(expected, arc.linearize(0.2), Circle.EPS);
}
@Test
public void testMidMatchSequence() {
Circle circle = new Circle(100);
// create control points that will match exactly the points the algo should generate
double halfStep = CircularArc.HALF_PI / 64;
CircularArc arc = circle.getCircularArc(halfStep, halfStep * 2, halfStep * 5);
assertEquals(100, arc.getRadius(), 1e-9);
assertCoordinateEquals(ORIGIN, arc.getCenter());
// linearize, we should get back the control points, plus the regular points in the middle
double[] expected = circle.samplePoints(halfStep, halfStep * 2, halfStep * 4, halfStep * 5);
assertArrayEquals(expected, arc.linearize(0.2), Circle.EPS);
}
@Test
public void testEndMatchSequence() {
Circle circle = new Circle(100);
// create control points that will match exactly the points the algo should generate
double halfStep = CircularArc.HALF_PI / 64;
CircularArc arc = circle.getCircularArc(halfStep, halfStep * 3, halfStep * 4);
assertEquals(100, arc.getRadius(), 1e-9);
assertCoordinateEquals(ORIGIN, arc.getCenter());
// linearize, we should get back only control points, plus the regular points in the middle
double[] expected = circle.samplePoints(halfStep, halfStep * 3, halfStep * 4);
assertArrayEquals(expected, arc.linearize(10), Circle.EPS);
}
@Test
public void testMatchTolerance() {
Circle circle = new Circle(100);
CircularArc arc = circle.getCircularArc(0, Math.PI / 2, Math.PI);
// test with subsequently smaller tolerances, but avoid going too low or
// numerical issues will byte us, this one stops roughly at 2.4e-7
double tolerance = 1;
for (int i = 0; i < 12; i++) {
double[] linearized = arc.linearize(tolerance);
// System.out.println(tolerance + " --> " + linearized.length);
assertTrue(linearized.length >= 64);
circle.assertTolerance(linearized, tolerance);
tolerance /= 4;
}
}
@Test
public void testMatchToleranceClockwise() {
Circle circle = new Circle(100);
CircularArc arc = circle.getCircularArc(Math.PI, Math.PI / 2, 0);
// test with subsequently smaller tolerances, but avoid going too low or
// numerical issues will byte us, this one stops roughly at 2.4e-7
double tolerance = 1;
for (int i = 0; i < 12; i++) {
double[] linearized = arc.linearize(tolerance);
// System.out.println(tolerance + " --> " + linearized.length);
assertTrue(linearized.length >= 64);
circle.assertTolerance(linearized, tolerance);
tolerance /= 4;
}
}
@Test
public void testCrossPIPI() {
Circle circle = new Circle(100);
// create control points that will match exactly the points the algo should generate
double step = CircularArc.HALF_PI / 32;
CircularArc arc = circle.getCircularArc(-step * 2, step, step * 2);
assertEquals(100, arc.getRadius(), 1e-9);
assertCoordinateEquals(ORIGIN, arc.getCenter());
// linearize, we should get back the control points, plus the regular points in the middle
double[] expected = circle.samplePoints(-step * 2, -step, 0, step, step * 2);
assertArrayEquals(expected, arc.linearize(0.2), Circle.EPS);
assertEquals(envelopeFrom(arc, 100, 0), arc.getEnvelope());
}
@Test
public void testFullCircle() {
Circle circle = new Circle(100);
// create control points that will match exactly the points the algo should generate
CircularArc arc = circle.getCircularArc(0, Math.PI, 0);
assertEquals(envelopeFrom(arc, 100, 0, 0, 100, -100, 0, 0, -100), arc.getEnvelope());
}
@Test
public void testOrientations() {
Circle circle = new Circle(100);
// half circle up
assertEquals(COUNTER_CLOCKWISE,
getOrientationIndex(getLinearizedArc(circle, 0, Math.PI / 2, Math.PI)));
assertEquals(CLOCKWISE,
getOrientationIndex(getLinearizedArc(circle, Math.PI, Math.PI / 2, 0)));
assertEquals(CLOCKWISE,
getOrientationIndex(getLinearizedArc(circle, -Math.PI, Math.PI / 2, 0)));
// half circle down
assertEquals(COUNTER_CLOCKWISE,
getOrientationIndex(getLinearizedArc(circle, Math.PI, Math.PI * 3 / 2, 0)));
assertEquals(CLOCKWISE,
getOrientationIndex(getLinearizedArc(circle, 0, Math.PI * 3 / 2, Math.PI)));
assertEquals(CLOCKWISE,
getOrientationIndex(getLinearizedArc(circle, 0, -Math.PI / 2, -Math.PI)));
// end between start and mid, wrapping
assertEquals(
COUNTER_CLOCKWISE,
getOrientationIndex(getLinearizedArc(circle, Math.PI / 2, Math.PI / 4,
Math.PI * 3 / 8)));
assertEquals(
CLOCKWISE,
getOrientationIndex(getLinearizedArc(circle, Math.PI * 3 / 8, Math.PI / 4,
Math.PI / 2)));
}
private int getOrientationIndex(LineString ls) {
return CGAlgorithms.orientationIndex(ls.getCoordinateN(0), ls.getCoordinateN(1),
ls.getCoordinateN(2));
}
private LineString getLinearizedArc(Circle c, double startAngle, double midAngle,
double endAngle) {
CircularArc arc = c.getCircularArc(startAngle, midAngle, endAngle);
double[] linearized = arc.linearize(Double.MAX_VALUE);
Coordinate[] coords = new Coordinate[linearized.length / 2];
for (int i = 0; i < coords.length; i++) {
coords[i] = new Coordinate(linearized[i * 2], linearized[i * 2 + 1]);
}
CoordinateArraySequence cs = new CoordinateArraySequence(coords);
return new LineString(cs, new GeometryFactory());
}
}