/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2014 - 2015, 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 java.util.Arrays;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateFilter;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceComparator;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryComponentFilter;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.GeometryFilter;
import com.vividsolutions.jts.geom.IntersectionMatrix;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;
/**
* A CircularString is a sequence of zero or more connected circular arc segments. A circular arc
* segment is a curved segment defined by three points in a two-dimensional plane; the first point
* cannot be the same as the third point.
*
* @author Andrea Aime - GeoSolutions
*/
public class CircularString extends LineString implements SingleCurvedGeometry<LineString> {
/**
* Helper class that automates the scan of the list of CircularArc in the controlpoint sequence
*/
abstract class ArcScan {
public ArcScan() {
if (controlPoints.length == 3) {
// single arc case
CircularArc arc = new CircularArc(controlPoints);
visitArc(arc);
} else {
// go over each 3-points set and linearize it
double[] arcControlPoints = new double[6];
CircularArc arc = new CircularArc(arcControlPoints);
for (int i = 0; i <= controlPoints.length - 6; i += 4) {
// have the arc work off the new control points
System.arraycopy(controlPoints, i, arcControlPoints, 0, 6);
arc.reset();
visitArc(arc);
}
}
}
protected abstract void visitArc(CircularArc arc);
}
private static final long serialVersionUID = -5796254063449438787L;
/**
* This sequence is used as a fake to trick the constructor
*/
static final CoordinateSequence FAKE_STRING_2D = new CoordinateArraySequence(new Coordinate[] {
new Coordinate(0, 0), new Coordinate(1, 1) });
double[] controlPoints;
double tolerance;
LineString linearized;
public CircularString(CoordinateSequence points, GeometryFactory factory, double tolerance) {
super(FAKE_STRING_2D, factory);
this.tolerance = tolerance;
if (points.getDimension() != 2) {
throw new IllegalArgumentException("Circular strings are restricted to 2 dimensions "
+ "at the moment. Contributions to get ND support welcomed!");
}
int pointCount = points.size();
controlPoints = new double[pointCount * 2];
for (int i = 0; i < pointCount; i++) {
controlPoints[i * 2] = points.getX(i);
controlPoints[i * 2 + 1] = points.getY(i);
}
init(controlPoints, tolerance);
}
public CircularString(double[] controlPoints, GeometryFactory factory, double tolerance) {
super(FAKE_STRING_2D, factory);
init(controlPoints, tolerance);
}
private void init(double[] controlPoints, double tolerance) {
int length = controlPoints.length;
if (length % 2 != 0) {
throw new IllegalArgumentException(
"Invalid number of ordinates, must be even, but it is " + length + " instead");
}
int pointCount = length / 2;
if ((pointCount != 0 && pointCount < 3) || (pointCount > 3 && (pointCount % 2) == 0)) {
throw new IllegalArgumentException("Invalid number of points, a circular string"
+ "is always made of an odd number of points, with a mininum of 3, "
+ "and adding 2 for each extra circular arc in the sequence");
}
this.controlPoints = controlPoints;
this.tolerance = tolerance;
}
@Override
public double[] getControlPoints() {
return controlPoints;
}
@Override
public double getTolerance() {
return tolerance;
}
@Override
public int getNumArcs() {
return (controlPoints.length - 6) / 4 + 1;
}
@Override
public CircularArc getArcN(int arcIndex) {
int baseIdx = arcIndex * 4;
double[] arcControlPoints = new double[6];
System.arraycopy(controlPoints, baseIdx, arcControlPoints, 0, 6);
CircularArc arc = new CircularArc(arcControlPoints);
return arc;
}
@Override
public LineString linearize() {
return linearize(this.tolerance);
}
public LineString linearize(double tolerance) {
// use the cached one if we are asked for the default geometry tolerance
boolean isDefaultTolerance = CircularArc.equals(tolerance, this.tolerance);
if (linearized != null && isDefaultTolerance) {
return linearized;
}
CoordinateSequence cs = getLinearizedCoordinateSequence(tolerance);
LineString result = new LineString(cs, factory);
if (isDefaultTolerance) {
linearized = result;
}
return result;
}
public CoordinateSequence getLinearizedCoordinateSequence(final double tolerance) {
boolean isDefaultTolerance = CircularArc.equals(tolerance, this.tolerance);
if (linearized != null && isDefaultTolerance) {
return linearized.getCoordinateSequence();
}
final GrowableOrdinateArray gar = new GrowableOrdinateArray();
new ArcScan() {
protected void visitArc(CircularArc arc) {
// if it's not the first arc, we need to eliminate the last point,
// as the end point of the last arc is the start point of the new one
if (gar.size() > 0) {
gar.setSize(gar.size() - 2);
}
arc.linearize(tolerance, gar);
}
};
CoordinateSequence cs = gar.toCoordinateSequence(getFactory());
return cs;
}
/* Optimized overridden methods */
public boolean isClosed() {
return controlPoints[0] == controlPoints[controlPoints.length - 2]
&& controlPoints[1] == controlPoints[controlPoints.length - 1];
}
public int getDimension() {
return super.getDimension();
}
public int getBoundaryDimension() {
return super.getDimension();
}
public boolean isEmpty() {
return controlPoints.length == 0;
}
public String getGeometryType() {
return "CircularString";
}
public Geometry reverse() {
// reverse the control points
double[] reversed = new double[controlPoints.length];
System.arraycopy(controlPoints, 0, reversed, 0, controlPoints.length);
GrowableOrdinateArray array = new GrowableOrdinateArray();
array.addAll(controlPoints);
array.reverseOrdinates(0, array.size() - 1);
return new CircularString(array.getData(), getFactory(), tolerance);
}
public Point getInteriorPoint() {
int idx = controlPoints.length / 2;
return new Point(new CoordinateArraySequence(new Coordinate[] { new Coordinate(
controlPoints[idx], controlPoints[idx + 1]) }), getFactory());
}
public Geometry getEnvelope() {
return super.getEnvelope();
}
public Envelope getEnvelopeInternal() {
return super.getEnvelopeInternal();
}
@Override
protected Envelope computeEnvelopeInternal() {
final Envelope result = new Envelope();
new ArcScan() {
protected void visitArc(CircularArc arc) {
arc.expandEnvelope(result);
}
};
return result;
}
public int getNumGeometries() {
return 1;
}
public Geometry getGeometryN(int n) {
return this;
}
public void setUserData(Object userData) {
super.setUserData(userData);
}
public int getSRID() {
return super.getSRID();
}
public void setSRID(int SRID) {
super.setSRID(SRID);
}
public GeometryFactory getFactory() {
return super.getFactory();
}
public Object getUserData() {
return super.getUserData();
}
public PrecisionModel getPrecisionModel() {
return super.getPrecisionModel();
}
public boolean isRectangle() {
return false;
}
public boolean equalsExact(Geometry other) {
return equalsExact(other, 0);
}
public boolean equalsExact(Geometry other, double tolerance) {
if (other instanceof CircularString) {
CircularString csOther = (CircularString) other;
if (Arrays.equals(controlPoints, csOther.controlPoints)) {
return true;
}
}
return linearize(tolerance).equalsExact(other, tolerance);
}
public boolean equals(Geometry other) {
if (other instanceof CircularString) {
CircularString csOther = (CircularString) other;
if (Arrays.equals(controlPoints, csOther.controlPoints)) {
return true;
}
}
return linearize().equals(other);
}
public boolean equalsTopo(Geometry other) {
if (other instanceof CircularString) {
CircularString csOther = (CircularString) other;
if (Arrays.equals(controlPoints, csOther.controlPoints)) {
return true;
}
}
return linearize().equalsTopo(other);
}
public boolean equals(Object o) {
if (o instanceof Geometry) {
return equals((Geometry) o);
} else {
return false;
}
}
public int hashCode() {
return super.hashCode();
}
public String toString() {
return toCurvedText();
}
public String toCurvedText() {
StringBuilder sb = new StringBuilder("CIRCULARSTRING ");
if(isEmpty()) {
sb.append("EMPTY");
} else {
sb.append("(");
for (int i = 0; i < controlPoints.length;) {
sb.append(controlPoints[i++] + " " + controlPoints[i++]);
if (i < controlPoints.length) {
sb.append(", ");
}
}
sb.append(")");
}
return sb.toString();
}
public boolean equalsNorm(Geometry g) {
return super.equalsNorm(g);
}
public Point getPointN(int n) {
if (n == 0) {
return getStartPoint();
} else {
return linearize().getPointN(n);
}
}
public Point getStartPoint() {
return new Point(new CoordinateArraySequence(new Coordinate[] { new Coordinate(
controlPoints[0], controlPoints[1]) }), getFactory());
}
public Point getEndPoint() {
return new Point(
new CoordinateArraySequence(new Coordinate[] { new Coordinate(
controlPoints[controlPoints.length - 2],
controlPoints[controlPoints.length - 1]) }), getFactory());
}
/*
* Simple linearized delegate methods
*/
public Coordinate[] getCoordinates() {
return linearize().getCoordinates();
}
public CoordinateSequence getCoordinateSequence() {
return linearize().getCoordinateSequence();
}
public Coordinate getCoordinateN(int n) {
return linearize().getCoordinateN(n);
}
public Coordinate getCoordinate() {
return linearize().getCoordinate();
}
public int getNumPoints() {
return linearize().getNumPoints();
}
public boolean isRing() {
return linearize().isRing();
}
public double getLength() {
// todo: maybe compute the actual circular length?
return linearize().getLength();
}
public Geometry getBoundary() {
return linearize().getBoundary();
}
public boolean isCoordinate(Coordinate pt) {
return linearize().isCoordinate(pt);
}
public void apply(CoordinateFilter filter) {
linearize().apply(filter);
}
public void apply(CoordinateSequenceFilter filter) {
linearize().apply(filter);
}
public void apply(GeometryFilter filter) {
linearize().apply(filter);
}
public void apply(GeometryComponentFilter filter) {
linearize().apply(filter);
}
public void normalize() {
linearize().normalize();
}
public boolean isSimple() {
return linearize().isSimple();
}
public boolean isValid() {
return linearize().isValid();
}
public double distance(Geometry g) {
return linearize().distance(g);
}
public boolean isWithinDistance(Geometry geom, double distance) {
return linearize().isWithinDistance(geom, distance);
}
public double getArea() {
return linearize().getArea();
}
public Point getCentroid() {
return linearize().getCentroid();
}
public void geometryChanged() {
linearize().geometryChanged();
}
public boolean disjoint(Geometry g) {
return linearize().disjoint(g);
}
public boolean touches(Geometry g) {
return linearize().touches(g);
}
public boolean intersects(Geometry g) {
return linearize().intersects(g);
}
public boolean crosses(Geometry g) {
return linearize().crosses(g);
}
public boolean within(Geometry g) {
return linearize().within(g);
}
public boolean contains(Geometry g) {
return linearize().contains(g);
}
public boolean overlaps(Geometry g) {
return linearize().overlaps(g);
}
public boolean covers(Geometry g) {
return linearize().covers(g);
}
public boolean coveredBy(Geometry g) {
return linearize().coveredBy(g);
}
public boolean relate(Geometry g, String intersectionPattern) {
return linearize().relate(g, intersectionPattern);
}
public IntersectionMatrix relate(Geometry g) {
return linearize().relate(g);
}
public Geometry buffer(double distance) {
return linearize().buffer(distance);
}
public Geometry buffer(double distance, int quadrantSegments) {
return linearize().buffer(distance, quadrantSegments);
}
public Geometry buffer(double distance, int quadrantSegments, int endCapStyle) {
return linearize().buffer(distance, quadrantSegments, endCapStyle);
}
public Geometry convexHull() {
return linearize().convexHull();
}
public Geometry intersection(Geometry other) {
return linearize().intersection(other);
}
public Geometry union(Geometry other) {
return linearize().union(other);
}
public Geometry difference(Geometry other) {
return linearize().difference(other);
}
public Geometry symDifference(Geometry other) {
return linearize().symDifference(other);
}
public Geometry union() {
return linearize().union();
}
public Geometry norm() {
return linearize().norm();
}
public int compareTo(Object o) {
return linearize().compareTo(o);
}
public int compareTo(Object o, CoordinateSequenceComparator comp) {
return linearize().compareTo(o, comp);
}
@Override
public String toText() {
return linearize().toText();
}
@Override
public int getCoordinatesDimension() {
return 2;
}
}