/*
* 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.Point;
/**
* Computes the {@link LinearLocation} for a given length
* along a linear {@link Geometry}.
* Negative lengths are measured in reverse from end of the linear geometry.
* Out-of-range values are clamped.
*/
public class LengthLocationMap {
// TODO: cache computed cumulative length for each vertex
// TODO: support user-defined measures
// TODO: support measure index for fast mapping to a location
/**
* Computes the length for a given {@link LinearLocation}
* on a linear {@link Geometry}.
*
* @param linearGeom the linear geometry to use
* @param loc the {@link LinearLocation} index of the location
* @return the length for the {@link LinearLocation}
*/
public static double getLength(final Geometry linearGeom, final LinearLocation loc) {
final LengthLocationMap locater = new LengthLocationMap(linearGeom);
return locater.getLength(loc);
}
/**
* Computes the {@link LinearLocation} for a
* given length along a linear {@link Geometry}.
*
* @param linearGeom the linear geometry to use
* @param length the length index of the location
* @return the {@link LinearLocation} for the length
*/
public static LinearLocation getLocation(final Geometry linearGeom, final double length) {
final LengthLocationMap locater = new LengthLocationMap(linearGeom);
return locater.getLocation(length);
}
/**
* Computes the {@link LinearLocation} for a
* given length along a linear {@link Geometry},
* with control over how the location
* is resolved at component endpoints.
*
* @param linearGeom the linear geometry to use
* @param length the length index of the location
* @param resolveLower if true lengths are resolved to the lowest possible index
* @return the {@link LinearLocation} for the length
*/
public static LinearLocation getLocation(final Geometry linearGeom, final double length,
final boolean resolveLower) {
final LengthLocationMap locater = new LengthLocationMap(linearGeom);
return locater.getLocation(length, resolveLower);
}
private final Geometry linearGeom;
public LengthLocationMap(final Geometry linearGeom) {
this.linearGeom = linearGeom;
}
public double getLength(final LinearLocation loc) {
double totalLength = 0.0;
final LinearIterator it = new LinearIterator(this.linearGeom);
while (it.hasNext()) {
if (!it.isEndOfLine()) {
final Point p0 = it.getSegmentStart();
final Point p1 = it.getSegmentEnd();
final double segLen = p1.distancePoint(p0);
// length falls in this segment
if (loc.getComponentIndex() == it.getComponentIndex()
&& loc.getSegmentIndex() == it.getVertexIndex()) {
return totalLength + segLen * loc.getSegmentFraction();
}
totalLength += segLen;
}
it.next();
}
return totalLength;
}
/**
* Compute the {@link LinearLocation} corresponding to a length.
* Negative lengths are measured in reverse from end of the linear geometry.
* Out-of-range values are clamped.
* Ambiguous indexes are resolved to the lowest possible location value.
*
* @param length the length index
* @return the corresponding LinearLocation
*/
public LinearLocation getLocation(final double length) {
return getLocation(length, true);
}
/**
* Compute the {@link LinearLocation} corresponding to a length.
* Negative lengths are measured in reverse from end of the linear geometry.
* Out-of-range values are clamped.
* Ambiguous indexes are resolved to the lowest or highest possible location value,
* depending on the value of <tt>resolveLower</tt>
*
* @param length the length index
* @return the corresponding LinearLocation
*/
public LinearLocation getLocation(final double length, final boolean resolveLower) {
double forwardLength = length;
// negative values are measured from end of geometry
if (length < 0.0) {
final double lineLen = this.linearGeom.getLength();
forwardLength = lineLen + length;
}
final LinearLocation loc = getLocationForward(forwardLength);
if (resolveLower) {
return loc;
}
return resolveHigher(loc);
}
private LinearLocation getLocationForward(final double length) {
if (length <= 0.0) {
return new LinearLocation();
}
double totalLength = 0.0;
final LinearIterator it = new LinearIterator(this.linearGeom);
while (it.hasNext()) {
/**
* Special handling is required for the situation when the
* length references exactly to a component endpoint.
* In this case, the endpoint location of the current component
* is returned,
* rather than the startpoint location of the next component.
* This produces consistent behaviour with the project method.
*/
if (it.isEndOfLine()) {
if (totalLength == length) {
final int compIndex = it.getComponentIndex();
final int segIndex = it.getVertexIndex();
return new LinearLocation(compIndex, segIndex, 0.0);
}
} else {
final Point p0 = it.getSegmentStart();
final Point p1 = it.getSegmentEnd();
final double segLen = p1.distancePoint(p0);
// length falls in this segment
if (totalLength + segLen > length) {
final double frac = (length - totalLength) / segLen;
final int compIndex = it.getComponentIndex();
final int segIndex = it.getVertexIndex();
return new LinearLocation(compIndex, segIndex, frac);
}
totalLength += segLen;
}
it.next();
}
// length is longer than line - return end location
return LinearLocation.getEndLocation(this.linearGeom);
}
private LinearLocation resolveHigher(final LinearLocation loc) {
if (!loc.isEndpoint(this.linearGeom)) {
return loc;
}
int compIndex = loc.getComponentIndex();
// if last component can't resolve any higher
if (compIndex >= this.linearGeom.getGeometryCount() - 1) {
return loc;
}
do {
compIndex++;
} while (compIndex < this.linearGeom.getGeometryCount() - 1
&& this.linearGeom.getGeometry(compIndex).getLength() == 0);
// resolve to next higher location
return new LinearLocation(compIndex, 0, 0.0);
}
}