/*
* The JTS Topology Suite is a collection of Java classes that
* implement the fundamental operations required to validate a given
* geo-spatial data set to a known topological specification.
*
* Copyright (C) 2001 Vivid Solutions
*
* 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; either
* version 2.1 of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.revolsys.geometry.linearref;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Lineal;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.impl.PointDoubleXYZ;
import com.revolsys.geometry.model.segment.LineSegment;
import com.revolsys.geometry.model.segment.LineSegmentDouble;
/**
* Represents a location along a {@link Lineal}.
* The referenced geometry is not maintained within
* this location, but must be provided for operations which require it.
* Various methods are provided to manipulate the location value
* and query the geometry it references.
*/
public class LinearLocation implements Comparable {
/**
* Compares two sets of location values for order.
*
* @param componentIndex0 a component index
* @param segmentIndex0 a segment index
* @param segmentFraction0 a segment fraction
* @param componentIndex1 another component index
* @param segmentIndex1 another segment index
* @param segmentFraction1 another segment fraction
*@return a negative integer, zero, or a positive integer
* as the first set of location values
* is less than, equal to, or greater than the second set of locationValues
*/
public static int compareLocationValues(final int componentIndex0, final int segmentIndex0,
final double segmentFraction0, final int componentIndex1, final int segmentIndex1,
final double segmentFraction1) {
// compare component indices
if (componentIndex0 < componentIndex1) {
return -1;
}
if (componentIndex0 > componentIndex1) {
return 1;
}
// compare segments
if (segmentIndex0 < segmentIndex1) {
return -1;
}
if (segmentIndex0 > segmentIndex1) {
return 1;
}
// same segment, so compare segment fraction
if (segmentFraction0 < segmentFraction1) {
return -1;
}
if (segmentFraction0 > segmentFraction1) {
return 1;
}
// same location
return 0;
}
/**
* Gets a location which refers to the end of a linear {@link Geometry}.
* @param linear the linear geometry
* @return a new <tt>LinearLocation</tt>
*/
public static LinearLocation getEndLocation(final Geometry linear) {
// assert: linear is LineString or MultiLineString
final LinearLocation loc = new LinearLocation();
loc.setToEnd(linear);
return loc;
}
/**
* Computes the {@link Coordinates} of a point a given fraction
* along the line segment <tt>(p0, p1)</tt>.
* If the fraction is greater than 1.0 the last
* point of the segment is returned.
* If the fraction is less than or equal to 0.0 the first point
* of the segment is returned.
* The Z ordinate is interpolated from the Z-ordinates of the given points,
* if they are specified.
*
* @param p0 the first point of the line segment
* @param p1 the last point of the line segment
* @param frac the length to the desired point
* @return the <tt>Coordinate</tt> of the desired point
*/
public static Point pointAlongSegmentByFraction(final Point p0, final Point p1,
final double frac) {
if (frac <= 0.0) {
return p0;
}
if (frac >= 1.0) {
return p1;
}
final double x = (p1.getX() - p0.getX()) * frac + p0.getX();
final double y = (p1.getY() - p0.getY()) * frac + p0.getY();
// interpolate Z value. If either input Z is NaN, result z will be NaN as
// well.
final double z = (p1.getZ() - p0.getZ()) * frac + p0.getZ();
return new PointDoubleXYZ(x, y, z);
}
private int componentIndex = 0;
private double segmentFraction = 0.0;
private int segmentIndex = 0;
/**
* Creates a location referring to the start of a linear geometry
*/
public LinearLocation() {
}
public LinearLocation(final int segmentIndex, final double segmentFraction) {
this(0, segmentIndex, segmentFraction);
}
public LinearLocation(final int componentIndex, final int segmentIndex,
final double segmentFraction) {
this.componentIndex = componentIndex;
this.segmentIndex = segmentIndex;
this.segmentFraction = segmentFraction;
normalize();
}
private LinearLocation(final int componentIndex, final int segmentIndex,
final double segmentFraction, final boolean doNormalize) {
this.componentIndex = componentIndex;
this.segmentIndex = segmentIndex;
this.segmentFraction = segmentFraction;
if (doNormalize) {
normalize();
}
}
/**
* Creates a new location equal to a given one.
*
* @param loc a LinearLocation
*/
public LinearLocation(final LinearLocation loc) {
this.componentIndex = loc.componentIndex;
this.segmentIndex = loc.segmentIndex;
this.segmentFraction = loc.segmentFraction;
}
/**
* Ensures the indexes are valid for a given linear {@link Geometry}.
*
* @param linear a linear geometry
*/
public void clamp(final Geometry linear) {
if (this.componentIndex >= linear.getGeometryCount()) {
setToEnd(linear);
return;
}
if (this.segmentIndex >= linear.getVertexCount()) {
final LineString line = (LineString)linear.getGeometry(this.componentIndex);
this.segmentIndex = line.getVertexCount() - 1;
this.segmentFraction = 1.0;
}
}
/**
* Copies this location
*
* @return a copy of this location
*/
@Override
public Object clone() {
return new LinearLocation(this.componentIndex, this.segmentIndex, this.segmentFraction);
}
/**
* Compares this object with the specified index values for order.
*
* @param componentIndex1 a component index
* @param segmentIndex1 a segment index
* @param segmentFraction1 a segment fraction
* @return a negative integer, zero, or a positive integer as this <code>LineStringLocation</code>
* is less than, equal to, or greater than the specified locationValues
*/
public int compareLocationValues(final int componentIndex1, final int segmentIndex1,
final double segmentFraction1) {
// compare component indices
if (this.componentIndex < componentIndex1) {
return -1;
}
if (this.componentIndex > componentIndex1) {
return 1;
}
// compare segments
if (this.segmentIndex < segmentIndex1) {
return -1;
}
if (this.segmentIndex > segmentIndex1) {
return 1;
}
// same segment, so compare segment fraction
if (this.segmentFraction < segmentFraction1) {
return -1;
}
if (this.segmentFraction > segmentFraction1) {
return 1;
}
// same location
return 0;
}
/**
* Compares this object with the specified object for order.
*
*@param o the <code>LineStringLocation</code> with which this <code>Coordinate</code>
* is being compared
*@return a negative integer, zero, or a positive integer as this <code>LineStringLocation</code>
* is less than, equal to, or greater than the specified <code>LineStringLocation</code>
*/
@Override
public int compareTo(final Object o) {
final LinearLocation other = (LinearLocation)o;
// compare component indices
if (this.componentIndex < other.componentIndex) {
return -1;
}
if (this.componentIndex > other.componentIndex) {
return 1;
}
// compare segments
if (this.segmentIndex < other.segmentIndex) {
return -1;
}
if (this.segmentIndex > other.segmentIndex) {
return 1;
}
// same segment, so compare segment fraction
if (this.segmentFraction < other.segmentFraction) {
return -1;
}
if (this.segmentFraction > other.segmentFraction) {
return 1;
}
// same location
return 0;
}
/**
* Gets the component index for this location.
*
* @return the component index
*/
public int getComponentIndex() {
return this.componentIndex;
}
/**
* Gets the {@link Coordinates} along the
* given linear {@link Geometry} which is
* referenced by this location.
*
* @param linearGeom the linear geometry referenced by this location
* @return the <tt>Coordinate</tt> at the location
*/
public Point getCoordinate(final Geometry linearGeom) {
final LineString lineComp = (LineString)linearGeom.getGeometry(this.componentIndex);
final Point p0 = lineComp.getPoint(this.segmentIndex);
if (this.segmentIndex >= lineComp.getVertexCount() - 1) {
return p0;
}
final Point p1 = lineComp.getPoint(this.segmentIndex + 1);
return pointAlongSegmentByFraction(p0, p1, this.segmentFraction);
}
/**
* Gets a {@link LineSegmentDouble} representing the segment of the
* given linear {@link Geometry} which contains this location.
*
* @param linearGeom a linear geometry
* @return the <tt>LineSegmentDouble</tt> containing the location
*/
public LineSegment getSegment(final Geometry linearGeom) {
final LineString lineComp = (LineString)linearGeom.getGeometry(this.componentIndex);
final Point p0 = lineComp.getPoint(this.segmentIndex);
// check for endpoint - return last segment of the line if so
if (this.segmentIndex >= lineComp.getVertexCount() - 1) {
final Point prev = lineComp.getPoint(lineComp.getVertexCount() - 2);
return new LineSegmentDouble(prev, p0);
}
final Point p1 = lineComp.getPoint(this.segmentIndex + 1);
return new LineSegmentDouble(p0, p1);
}
/**
* Gets the segment fraction for this location
*
* @return the segment fraction
*/
public double getSegmentFraction() {
return this.segmentFraction;
}
/**
* Gets the segment index for this location
*
* @return the segment index
*/
public int getSegmentIndex() {
return this.segmentIndex;
}
/**
* Gets the length of the segment in the given
* Geometry containing this location.
*
* @param linearGeom a linear geometry
* @return the length of the segment
*/
public double getSegmentLength(final Geometry linearGeom) {
final LineString lineComp = (LineString)linearGeom.getGeometry(this.componentIndex);
// ensure segment index is valid
int segIndex = this.segmentIndex;
if (this.segmentIndex >= lineComp.getVertexCount() - 1) {
segIndex = lineComp.getVertexCount() - 2;
}
final Point p0 = lineComp.getPoint(segIndex);
final Point p1 = lineComp.getPoint(segIndex + 1);
return p0.distancePoint(p1);
}
/**
* Tests whether this location is an endpoint of
* the linear component it refers to.
*
* @param linearGeom the linear geometry referenced by this location
* @return true if the location is a component endpoint
*/
public boolean isEndpoint(final Geometry linearGeom) {
final LineString lineComp = (LineString)linearGeom.getGeometry(this.componentIndex);
// check for endpoint
final int nseg = lineComp.getVertexCount() - 1;
return this.segmentIndex >= nseg || this.segmentIndex == nseg && this.segmentFraction >= 1.0;
}
/**
* Tests whether two locations
* are on the same segment in the parent {@link Geometry}.
*
* @param loc a location on the same geometry
* @return true if the locations are on the same segment of the parent geometry
*/
public boolean isOnSameSegment(final LinearLocation loc) {
if (this.componentIndex != loc.componentIndex) {
return false;
}
if (this.segmentIndex == loc.segmentIndex) {
return true;
}
if (loc.segmentIndex - this.segmentIndex == 1 && loc.segmentFraction == 0.0) {
return true;
}
if (this.segmentIndex - loc.segmentIndex == 1 && this.segmentFraction == 0.0) {
return true;
}
return false;
}
/**
* Tests whether this location refers to a valid
* location on the given linear {@link Geometry}.
*
* @param linearGeom a linear geometry
* @return true if this location is valid
*/
public boolean isValid(final Geometry linearGeom) {
if (this.componentIndex < 0 || this.componentIndex >= linearGeom.getGeometryCount()) {
return false;
}
final LineString lineComp = (LineString)linearGeom.getGeometry(this.componentIndex);
if (this.segmentIndex < 0 || this.segmentIndex > lineComp.getVertexCount()) {
return false;
}
if (this.segmentIndex == lineComp.getVertexCount() && this.segmentFraction != 0.0) {
return false;
}
if (this.segmentFraction < 0.0 || this.segmentFraction > 1.0) {
return false;
}
return true;
}
/**
* Tests whether this location refers to a vertex
*
* @return true if the location is a vertex
*/
public boolean isVertex() {
return this.segmentFraction <= 0.0 || this.segmentFraction >= 1.0;
}
/**
* Ensures the individual values are locally valid.
* Does <b>not</b> ensure that the indexes are valid for
* a particular linear geometry.
*
* @see clamp
*/
private void normalize() {
if (this.segmentFraction < 0.0) {
this.segmentFraction = 0.0;
}
if (this.segmentFraction > 1.0) {
this.segmentFraction = 1.0;
}
if (this.componentIndex < 0) {
this.componentIndex = 0;
this.segmentIndex = 0;
this.segmentFraction = 0.0;
}
if (this.segmentIndex < 0) {
this.segmentIndex = 0;
this.segmentFraction = 0.0;
}
if (this.segmentFraction == 1.0) {
this.segmentFraction = 0.0;
this.segmentIndex += 1;
}
}
/**
* Sets the value of this location to
* refer to the end of a linear geometry.
*
* @param linear the linear geometry to use to set the end
*/
public void setToEnd(final Geometry linear) {
this.componentIndex = linear.getGeometryCount() - 1;
final LineString lastLine = (LineString)linear.getGeometry(this.componentIndex);
this.segmentIndex = lastLine.getVertexCount() - 1;
this.segmentFraction = 1.0;
}
/**
* Snaps the value of this location to
* the nearest vertex on the given linear {@link Geometry},
* if the vertex is closer than <tt>minDistance</tt>.
*
* @param linearGeom a linear geometry
* @param minDistance the minimum allowable distance to a vertex
*/
public void snapToVertex(final Geometry linearGeom, final double minDistance) {
if (this.segmentFraction <= 0.0 || this.segmentFraction >= 1.0) {
return;
}
final double segLen = getSegmentLength(linearGeom);
final double lenToStart = this.segmentFraction * segLen;
final double lenToEnd = segLen - lenToStart;
if (lenToStart <= lenToEnd && lenToStart < minDistance) {
this.segmentFraction = 0.0;
} else if (lenToEnd <= lenToStart && lenToEnd < minDistance) {
this.segmentFraction = 1.0;
}
}
/**
* Converts a linear location to the lowest equivalent location index.
* The lowest index has the lowest possible component and segment indices.
* <p>
* Specifically:
* <ul>
* <li>if the location point is an endpoint, a location value is returned as (nseg-1, 1.0)
* <li>if the location point is ambiguous (i.e. an endpoint and a startpoint), the lowest endpoint location is returned
* </ul>
* If the location index is already the lowest possible value, the original location is returned.
*
* @param linearGeom the linear geometry referenced by this location
* @return the lowest equivalent location
*/
public LinearLocation toLowest(final Geometry linearGeom) {
// TODO: compute lowest component index
final LineString lineComp = (LineString)linearGeom.getGeometry(this.componentIndex);
final int nseg = lineComp.getVertexCount() - 1;
// if not an endpoint can be returned directly
if (this.segmentIndex < nseg) {
return this;
}
return new LinearLocation(this.componentIndex, nseg, 1.0, false);
}
@Override
public String toString() {
return "LinearLoc[" + this.componentIndex + ", " + this.segmentIndex + ", "
+ this.segmentFraction + "]";
}
}