/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-2008, 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.iso.coordinate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.geotools.geometry.iso.primitive.CurveImpl;
import org.geotools.geometry.iso.primitive.PointImpl;
import org.geotools.geometry.iso.util.DoubleOperation;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.coordinate.LineSegment;
import org.opengis.geometry.coordinate.LineString;
import org.opengis.geometry.coordinate.ParamForPoint;
import org.opengis.geometry.coordinate.PointArray;
import org.opengis.geometry.coordinate.Position;
import org.opengis.geometry.primitive.CurveInterpolation;
/**
* A LineString consists of sequence of line segments, each having a parameterization like the one
* for LineSegment. The class essentially combines a Sequence<LineSegments> into a single object,
* with the obvious savings of storage space.
*
* @author Jackson Roehrig & Sanjay Jena
*
* @source $URL$
*/
public class LineStringImpl extends CurveSegmentImpl implements LineString {
private static final long serialVersionUID = -4463400288452707656L;
/**
* The controlPoints of a LineString are a sequence of positions between which the curve is
* linearly interpolated. The first position in the sequence is the startPoint of the
* LineString, and the last point in the sequence is the endPoint of the LineString.
* LineString::controlPoint : PointArray
*/
private PointArrayImpl controlPoints;
/**
* Envelope of the LineString Has to be calculated when creating the instance
*/
private EnvelopeImpl envelope = null;
public LineStringImpl( List<Position> positions ) {
this(new PointArrayImpl(positions), 0.0 );
}
/**
* Constructor by another LineString The constructor for LineString takes a sequence of points
* and constructs a LineString with those points as controlPoints. The constructor of a
* LineString takes two or more positions and creates the appropriate line string joining them.
* LineString::LineString(points[2..n]:Position):LineString
*
* @param lineString
*/
public LineStringImpl( LineStringImpl lineString ) {
super(lineString);
super.setInterpolation(CurveInterpolation.LINEAR);
// this.controlPoints = new PointArrayImpl(lineString.controlPoints);
this.controlPoints = new PointArrayImpl(lineString.controlPoints);
this.endParam = lineString.endParam;
// this.envelope = new EnvelopeImpl(lineString.getEnvelope());
this.envelope = new EnvelopeImpl(lineString.getEnvelope());
}
/**
* Constructor by a PointArray and a StartParam
*
* @param pointArray
* @param startPar
*/
public LineStringImpl( PointArrayImpl pointArray, double startPar ) {
super(startPar);
super.setInterpolation(CurveInterpolation.LINEAR);
if (pointArray.length() >= 2) {
this.controlPoints = pointArray;
} else {
throw new IllegalArgumentException("A LineString needs at least two control points."); //$NON-NLS-1$
}
/*
* Set length of PointArray (which represents the LineString) as EndParam. The StartParam
* and EndParam will be updated when creating a curve. The parametrisation of this
* LineString should be continous according to the Curve
*/
this.setEndParam(DoubleOperation.add(startPar, this.controlPoints.getDistanceSum()));
// this.setEndParam(startPar + this.controlPoints.getDistanceSum());
/* Create envelope */
this.envelope = this.controlPoints.getEnvelope();
}
/**
* Merges this LineString with another LineString, forming a new LineString The input
* LineStrings will not be modified by this operation. The used Lists of the pointArray will be
* cloned. The Positions which represents the control points will not be cloned.
*
* @param other LineString
*/
public LineStringImpl merge( LineStringImpl other ) {
// Test ok (SJ)
LineStringImpl result;
if (other.getEndPoint().equals(this.getStartPoint())) {
LinkedList<Position> posToAdd = new LinkedList<Position>(other.getControlPoints());
posToAdd.removeLast();
posToAdd.addAll(getControlPoints());
result = new LineStringImpl( posToAdd );
} else if (this.getEndPoint().equals(other.getStartPoint())) {
LinkedList<Position> posToAdd = new LinkedList<Position>(getControlPoints());
posToAdd.removeLast();
posToAdd.addAll(other.getControlPoints());
result = new LineStringImpl( posToAdd );
} else {
throw new IllegalArgumentException(
"The LineString do not agree in a start and end point");
}
return result;
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.coordinate.LineString#getControlPoints()
*/
public PointArray getControlPoints() {
// ok
return this.controlPoints;
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.coordinate.LineString#asLineSegments()
* @version Not verified whether getLineSegments() in PointArray is correctly implemented
*/
// TODO: This code depends on a specific implementation of PointArray, PointArrayImpl has very
// little to do with its inner class LineSegmentsSequence (it can be factored out)
//
public List<LineSegment> asLineSegments() {
// test OK (SJ)
// Returns the control points in the PointArray as a sequence of LineSegments
return (List<LineSegment>) this.controlPoints.getLineSegments(this.getCurve());
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.coordinate.GenericCurve#getStartPoint()
*/
public DirectPosition getStartPoint() {
// ok
/* Return DirectPosition of first Control Point */
return controlPoints.getFirst().getPosition();
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.coordinate.GenericCurve#getEndPoint()
* @version Implementation OK
*/
public DirectPosition getEndPoint() {
// ok
/* Return Direct Position of last Control Point */
return controlPoints.getLast().getPosition();
}
/*
* (non-Javadoc)
*
* @see org.geotools.geometry.featgeom.coordinate.CurveSegmentImpl#getStartPosition()
*/
public Position getStartPosition() {
// ok
// Returns the first Point of the control points PointArray
return controlPoints.getFirst();
}
/*
* (non-Javadoc)
*
* @see org.geotools.geometry.featgeom.coordinate.CurveSegmentImpl#getEndPosition()
*/
public Position getEndPosition() {
// ok
// Returns the last Point of the control points PointArray
return controlPoints.getLast();
}
/*
* (non-Javadoc)
*
* @see org.geotools.geometry.featgeom.coordinate.CurveSegmentImpl#split(double)
*/
public void split( double maxSpacing ) {
// TODO semantic SJ, JR
// TODO implementation
// TODO test
// TODO documentation
// gdavis: this method seems to be broken, needs fixing
//this.controlPoints.split(maxSpacing);
throw new UnsupportedOperationException("not implemented yet.");
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.primitive.CurveSegment#getNumDerivativesAtStart()
*/
public int getNumDerivativesAtStart() {
// Return 0, because linestrings can not support continuity above C^0
return 0;
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.primitive.CurveSegment#getNumDerivativesInterior()
*/
public int getNumDerivativesInterior() {
// Return 0, because linestrings can not support continuity above C^0
return 0;
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.primitive.CurveSegment#getNumDerivativesAtEnd()
*/
public int getNumDerivativesAtEnd() {
// Return 0, because linestrings can not support continuity above C^0
return 0;
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.primitive.CurveSegment#getSamplePoints()
* @version Implementation OK
*/
public PointArray getSamplePoints() {
// ok
// Return the control points PointArray as sample points of this LineString
return this.controlPoints;
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.primitive.CurveSegment#reverse()
*/
public CurveSegmentImpl reverse() {
// Test OK
// Reverse the order of control points which define this line string
// The parametrisation does not need to be corrected, because the PointArray will calculate
// the parametrisation at runtime
Collections.reverse( controlPoints );
return this;
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.coordinate.GenericCurve#getTangent(double)
*/
public double[] getTangent( double distance ) {
// Test OK - Valid for n dimensional space
if (distance < this.getStartParam() || distance > this.getEndParam())
throw new IllegalArgumentException("Distance parameter not in parametrisation range.");
/* Search segment at distance */
List<LineSegment> segments = this.asLineSegments();
int i = 0;
while( segments.get(i).getEndParam() < distance && i < segments.size() ) {
i++;
}
/* Delegate work to according LineSegment */
return ((LineSegmentImpl) segments.get(i)).getTangentInSegment(distance);
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.coordinate.GenericCurve#forConstructiveParam(double)
*/
public DirectPosition forConstructiveParam( double cp ) {
// Test ok - valid for n dimensional space
// Return the Position at param (cp * lengthOfLineString)
double par = DoubleOperation.mult(cp, this.getCurve().length());
return this.forParam(par);
}
/*
* (non-Javadoc)
*
* @see org.geotools.geometry.featgeom.coordinate.CurveSegmentImpl#getEnvelope()
*/
public EnvelopeImpl getEnvelope() {
// Returns the envelope of this LineString
return this.envelope;
// Alternate solution is to let the point array calculate the envelope on the fly:
// return this.controlPoints.getEnvelope();
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.coordinate.GenericCurve#forParam(double)
*/
public DirectPosition forParam( double distance ) {
// Test OK - Valid for n dimensional space
if (distance < this.getStartParam() || distance > this.getEndParam())
throw new IllegalArgumentException("Distance parameter not in parametrisation range.");
/* Search segment at distance */
List<LineSegment> segments = this.asLineSegments();
int i = 0;
while( ((LineSegmentImpl) segments.get(i)).endParam < distance && i < segments.size() ) {
i++;
}
/* Delegate work to according LineSegment */
return ((LineSegmentImpl) segments.get(i)).forParamInSegment(distance);
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.coordinate.GenericCurve#getParamForPoint(org.opengis.geometry.coordinate.DirectPosition)
*/
public ParamForPoint getParamForPoint( DirectPosition p ) {
// TODO semantic SJ, JR
// TODO implementation
// TODO test
// TODO documentation
// TO DO JR: check with a discussion in geotools/codehaus about the
// return parameters.
throw new UnsupportedOperationException("not implemented yet.");
//assert false;
//return null;
// /* Receive LineString as LineSegments */
// List<LineSegmentImpl> segments = this.asLineSegments();
//
// /* ArrayList for storing all found minimum distance positions */
// ArrayList<DirectPositionImpl> rPositions = new
// ArrayList<DirectPositionImpl>();
//
// ParamForPointImpl rParams = new ParamForPointImpl(this);
//
// /* Get closest Point on first Segment */
// DirectPositionImpl actPos = segments.get(0).closestPoint(p);
// /* Add to List of all minimum distance positions */
// rPositions.add(actPos);
// // JR: Sanjay, das entspricht rParams.add(segments[0].startParam() +
// d), mit d aus closestPoint
// double dist =
// DirectPositionImpl.distance(segments.get(0).getStartPoint(),actPos);
// rParams.add(segments.get(0).getStartParam() + dist);
// /* Calculate distance between those two points */
// double minDistance = DirectPositionImpl.distance(p,actPos);
//
// /* Loop all other segments and compare minimum distance */
// for (int i=1, n=segments.size(); i<n; i++) {
//
// /* Check distance to next segment */
// actPos = segments.get(i).closestPoint(p);
// double actDistance = actDistance =
// DirectPositionImpl.distance(p,actPos);
//
// /* If distance smaller or equal than previous minDistance, then safe
// this as new minDistance */
// if (actDistance <= minDistance) {
// /* If actual distance is smaller than previous, clear list of all
// minimum distance positions, because a new minium distance was found
// */
// if (actDistance < minDistance) {
// rPositions.clear();
// rParams.clear();
// }
// minDistance = actDistance;
// rPositions.add(actPos);
// rParams.add(segments.get(i).getStartParam()
// + DirectPositionImpl.distance(segments.get(i).getStartPoint(),
// actPos));
// }
//
// }
// assert false;
// return rParams;
}
/*
* (non-Javadoc)
*
* @see org.opengis.geometry.coordinate.GenericCurve#asLineString(double, double)
*/
public LineStringImpl asLineString( double maxSpacing, double maxOffset ) {
// TODO semantic SJ, JR
// TODO implementation
// TODO test
// TODO documentation
//assert false;
//return null;
// TO DO the ArrayList<Position> approach must disappear, maybe
// substituted by controlPoint.clone()
/* If parameters of method are zero, return this object itself (with
same controlPòints) */
if (maxSpacing == 0 && maxOffset == 0)
return this;
/* ArrayList of Positions, which will be used as the controlPoints of
the new LineString */
//ArrayList<Position> positions = new ArrayList<Position>();
List<Position> positions = new ArrayList<Position>();
/* Add Start Point to collection of LineString Positions */
//positions.add(new Position(this.startPoint()));
/* The actualParam represents the actual Position on the LineString
*/
double actualParam = this.startParam;
/* The newSpacing represents the distance between the Posistion at the actualParam and the next Position;
* if the maxOffset is small, it will be necessary to define a smaller distance */
double newSpacing = maxSpacing;
/* All segments of the actual LineString (this-object) */
List<LineSegment> segments = this.asLineSegments();
/* First Segment after actualParam (>=) and LastSegment before actualParam+newSpacing (<=)
* Define the area which has to be inspected depending the maxOffset
* In the beginning, the both point at the first Segment */
int firstSegment = 0;
int lastSegment = 0;
double maxDistance = 0;
//CoordinateFactoryImpl cf = this.getCurve().getGeometryFactory().getCoordinateFactory();
/* Loop until end of LineString reached */
while (actualParam < this.endParam) {
/* If actualParam + newSpacing is greater than the endParam,
* set the newSpacing to that value, that next Position is the at
* the endParam of the original LineString */
if (actualParam + newSpacing > this.endParam) {
newSpacing = this.endParam - actualParam;
}
/* The maximum distance between the the line (actualParam to actualParam+newSpacing) and the Segments (between firstSegment and lastSegment) will be calculated after */
maxDistance = 0;
/* Search first segment after position start in LineString:
* If position is on ControlPoint, gets segment with position as startParam
* If position is on first segment, get first segment
* If position is on last Segment, gets last segment */
int i = firstSegment;
while ((segments.get(i).getStartParam() < actualParam) && (i<segments.size() -1)) {
i++;
}
firstSegment = i;
do {
/* If start and end point on same LineSegment, then return a distance of 0.0 */
// if ((segments[firstSegment].startParam() > actualParam && segments[firstSegment].startParam() < actualParam+newSpacing)
// || (segments[firstSegment].endParam() > actualParam && segments[firstSegment].endParam() < actualParam+newSpacing)) {
// TO DO numerical precision
//assert false;
double EPSILON = 0.00001;
if ((segments.get(firstSegment).getStartParam() > actualParam &&
segments.get(firstSegment).getStartParam() + EPSILON < actualParam +
newSpacing) ||
(segments.get(firstSegment).getEndParam() > actualParam &&
segments.get(firstSegment).getEndParam() + EPSILON < actualParam +
newSpacing)) {
/* If not, the segments betweens the both positions have to be
checked */
/* Search last segment before position start in LineString
* If position is on ControlPoint, get segment with position as endParam
* If position is on first segment, get first segment
* If position is on last segment, get last segment */
i = firstSegment;
while ((i<segments.size()) && (segments.get(i).getEndParam() <= actualParam+newSpacing)){
i++;
}
lastSegment = i-1;
DirectPosition p1 = this.forParam(actualParam);
DirectPosition p2 = new DirectPositionImpl(this.forParam(actualParam+newSpacing));
LineSegmentImpl seg = new LineSegmentImpl(p1, p2, 0);
//(LineSegmentImpl)cf.createLineSegment(cf.createPosition(this.forParam(actualParam)),cf.createPosition(this.forParam(actualParam+newSpacing)));
/* Search maximum of all distances */
CurveImpl curve = new CurveImpl(seg);
maxDistance = curve.distance( new PointImpl(segments.get(firstSegment).getStartPoint()) );
//maxDistance = seg.distance(segments.get(firstSegment).getStartPoint());
double actDistance = 0;
for (i=firstSegment; i<lastSegment; i++) {
actDistance = curve.distance( new PointImpl(segments.get(i).getEndPoint()) );
//actDistance = seg.distance(segments.get(i).getEndPoint());
if (actDistance > maxDistance) {
maxDistance = actDistance;
}
}
/* Test, ob Offset bei Param [actualParam+newSpacing] eingehalten
wird */
if (maxDistance > maxOffset) {
// System.out.println("Testausgabe: Musste den Abstand verkleinern. Neuer Abstand: " + (newSpacing/2));
newSpacing /= 2;
}
} else {
maxDistance = 0;
/* Both positions are located at the same segment;
set the maxDistance to 0 to leave loop */
}
} while (maxDistance > maxOffset); /* Until Offset is accepted */
/* Add newSpacing to actualParam */
actualParam += newSpacing;
/* Reset newSpacing to the original value */
newSpacing = maxSpacing;
/* Add Position at constrParam position of actualParam */
positions.add( new DirectPositionImpl(this.forParam(actualParam)) );
//positions.add(cf.createPosition(this.forParam(actualParam)));
}
// /* Transform Arraylist to Array of Position´s */
// Position posArray[] = new Position[positions.size()];
// for (int i=0; i<positions.size(); i++) {
// posArray[i] = (Position)positions.get(i);
// }
/* Construct new LineString, giving the positions, curve and
startParam */
// TO DO JR check: I think there was an error here because the new LineString
// should not belong to any curve
// LineString rLineString = new LineString (posArray, this.getCurve(), this.startParam);
LineStringImpl rLineString = new LineStringImpl(positions);
//LineStringImpl rLineString = cf.createLineString(positions, this.startParam);
rLineString.setEndParam(this.getEndParam());
return rLineString;
}
public String toString() {
return "[LineString: " + this.controlPoints + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((controlPoints == null) ? 0 : controlPoints.hashCode());
result = PRIME * result + ((envelope == null) ? 0 : envelope.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof LineStringImpl))
return false;
// if (getClass() != obj.getClass())
// return false;
final LineStringImpl other = (LineStringImpl) obj;
if (controlPoints == null) {
if (other.controlPoints != null)
return false;
} else if (!controlPoints.equals(other.controlPoints))
return false;
if (envelope == null) {
if (other.envelope != null)
return false;
} else if (!envelope.equals(other.envelope))
return false;
return true;
}
// Not used!
// /**
// * @param minSpacing
// */
// public void merge(double minSpacing) {
// // TO DO test
// // TO DO documentation
// this.controlPoints.merge(minSpacing);
// }
}