/*
* 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.simplify;
import java.util.List;
import com.revolsys.geometry.algorithm.LineIntersector;
import com.revolsys.geometry.algorithm.RobustLineIntersector;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.coordinates.LineSegmentUtil;
import com.revolsys.geometry.model.segment.LineSegment;
import com.revolsys.geometry.model.segment.LineSegmentDouble;
/**
* Simplifies a TaggedLineString, preserving topology
* (in the sense that no new intersections are introduced).
* Uses the recursive Douglas-Peucker algorithm.
*
* @author Martin Davis
* @version 1.7
*/
public class TaggedLineStringSimplifier {
/**
* Tests whether a segment is in a section of a TaggedLineString
* @param taggedLine
* @param sectionIndex
* @param seg
* @return
*/
private static boolean isInLineSection(final TaggedLineString line, final int[] sectionIndex,
final TaggedLineSegment seg) {
// not in this taggedLine
if (seg.getParent() != line.getParent()) {
return false;
}
final int segIndex = seg.getIndex();
if (segIndex >= sectionIndex[0] && segIndex < sectionIndex[1]) {
return true;
}
return false;
}
private double distanceTolerance = 0.0;
private LineSegmentIndex inputIndex = new LineSegmentIndex();
private final LineIntersector li = new RobustLineIntersector();
private LineString line;
private LineSegmentIndex outputIndex = new LineSegmentIndex();
private TaggedLineString taggedLine;
public TaggedLineStringSimplifier(final LineSegmentIndex inputIndex,
final LineSegmentIndex outputIndex) {
this.inputIndex = inputIndex;
this.outputIndex = outputIndex;
}
private int findFurthestPoint(final int i, final int j, final double[] maxDistance) {
final Point p0 = this.line.getVertex(i);
final Point p1 = this.line.getVertex(j);
double maxDist = -1.0;
int maxIndex = i;
for (int k = i + 1; k < j; k++) {
final Point midPt = this.line.getVertex(k);
final double distance = LineSegmentUtil.distanceLinePoint(p0, p1, midPt);
if (distance > maxDist) {
maxDist = distance;
maxIndex = k;
}
}
maxDistance[0] = maxDist;
return maxIndex;
}
/**
* Flattens a section of the taggedLine between
* indexes <code>start</code> and <code>end</code>,
* replacing them with a taggedLine between the endpoints.
* The input and output indexes are updated
* to reflect this.
*
* @param start the start index of the flattened section
* @param end the end index of the flattened section
* @return the new segment created
*/
private LineSegment flatten(final int start, final int end) {
// make a new segment for the simplified geometry
final Point p0 = this.line.getVertex(start);
final Point p1 = this.line.getVertex(end);
final LineSegment newSeg = new LineSegmentDouble(p0, p1);
// update the indexes
remove(this.taggedLine, start, end);
this.outputIndex.add(newSeg);
return newSeg;
}
private boolean hasBadInputIntersection(final TaggedLineString parentLine,
final int[] sectionIndex, final LineSegment candidateSeg) {
final List<TaggedLineSegment> querySegs = this.inputIndex.query(candidateSeg);
for (final TaggedLineSegment querySeg : querySegs) {
if (hasInteriorIntersection(querySeg, candidateSeg)) {
if (isInLineSection(parentLine, sectionIndex, querySeg)) {
continue;
}
return true;
}
}
return false;
}
private boolean hasBadIntersection(final TaggedLineString parentLine, final int[] sectionIndex,
final LineSegment candidateSeg) {
if (hasBadOutputIntersection(candidateSeg)) {
return true;
}
if (hasBadInputIntersection(parentLine, sectionIndex, candidateSeg)) {
return true;
}
return false;
}
private boolean hasBadOutputIntersection(final LineSegment candidateSeg) {
final List<LineSegment> querySegs = this.outputIndex.query(candidateSeg);
for (final LineSegment querySeg : querySegs) {
if (hasInteriorIntersection(querySeg, candidateSeg)) {
return true;
}
}
return false;
}
private boolean hasInteriorIntersection(final LineSegment seg0, final LineSegment seg1) {
this.li.computeIntersectionPoints(seg0.getP0(), seg0.getP1(), seg1.getP0(), seg1.getP1());
return this.li.isInteriorIntersection();
}
/**
* Remove the segs in the section of the taggedLine
* @param taggedLine
* @param pts
* @param sectionStartIndex
* @param sectionEndIndex
*/
private void remove(final TaggedLineString line, final int start, final int end) {
for (int i = start; i < end; i++) {
final TaggedLineSegment seg = line.getSegment(i);
this.inputIndex.remove(seg);
}
}
/**
* Sets the distance tolerance for the simplification.
* All vertices in the simplified geometry will be within this
* distance of the original geometry.
*
* @param distanceTolerance the approximation tolerance to use
*/
public void setDistanceTolerance(final double distanceTolerance) {
this.distanceTolerance = distanceTolerance;
}
/**
* Simplifies the given {@link TaggedLineString}
* using the distance tolerance specified.
*
* @param taggedLine the linestring to simplify
*/
void simplify(final TaggedLineString taggedLine) {
this.taggedLine = taggedLine;
this.line = taggedLine.getParent();
simplifySection(0, this.line.getVertexCount() - 1, 0);
}
private void simplifySection(final int i, final int j, int depth) {
depth += 1;
final int[] sectionIndex = new int[2];
if (i + 1 == j) {
final LineSegment newSeg = this.taggedLine.getSegment(i);
this.taggedLine.addToResult(newSeg);
// leave this segment in the input index, for efficiency
return;
}
boolean isValidToSimplify = true;
/**
* Following logic ensures that there is enough points in the output taggedLine.
* If there is already more points than the minimum, there's nothing to check.
* Otherwise, if in the worst case there wouldn't be enough points,
* don't flatten this segment (which avoids the worst case scenario)
*/
if (this.taggedLine.getResultSize() < this.taggedLine.getMinimumSize()) {
final int worstCaseSize = depth + 1;
if (worstCaseSize < this.taggedLine.getMinimumSize()) {
isValidToSimplify = false;
}
}
final double[] distance = new double[1];
final int furthestPtIndex = findFurthestPoint(i, j, distance);
// flattening must be less than distanceTolerance
if (distance[0] > this.distanceTolerance) {
isValidToSimplify = false;
}
// test if flattened section would cause intersection
// final LineSegment candidateSeg = new LineSegmentDouble();
final Point p0 = this.line.getVertex(i);
final Point p1 = this.line.getVertex(j);
final LineSegment candidateSeg = new LineSegmentDouble(p0, p1);
sectionIndex[0] = i;
sectionIndex[1] = j;
if (hasBadIntersection(this.taggedLine, sectionIndex, candidateSeg)) {
isValidToSimplify = false;
}
if (isValidToSimplify) {
final LineSegment newSeg = flatten(i, j);
this.taggedLine.addToResult(newSeg);
return;
}
simplifySection(i, furthestPtIndex, depth);
simplifySection(furthestPtIndex, j, depth);
}
}