/**
* *****************************************************************************
* Copyright 2013 Johannes Mitlmeier
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
* ****************************************************************************
*/
package de.fub.agg2graph.graph;
import de.fub.agg2graph.agg.AggConnection;
import de.fub.agg2graph.agg.AggContainer;
import de.fub.agg2graph.agg.AggNode;
import de.fub.agg2graph.roadgen.RoadNetwork;
import de.fub.agg2graph.structs.GPSCalc;
import de.fub.agg2graph.structs.GPSPoint;
import de.fub.agg2graph.structs.GPSSegment;
import de.fub.agg2graph.structs.ILocation;
import de.fub.agg2graph.structs.Path;
import java.util.ArrayList;
import java.util.List;
/**
* Filters data using Ramer-Douglas-Peucker algorithm with specified tolerance
*
* @author Rzeźnik, Johannes Mitlmeier
* @see <a
* href="http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm">Ramer-Douglas-Peucker
* algorithm</a>
* @see <a
* href="http://code.google.com/p/savitzky-golay-filter/source/browse/trunk/src/mr/go/sgfilter/RamerDouglasPeuckerFilter.java?r=15">adapted
* from here</a>
*/
public class RamerDouglasPeuckerFilter {
private double epsilon;
private double maxSegmentLength = 100; // meters
public RamerDouglasPeuckerFilter() {
}
public RamerDouglasPeuckerFilter(double epsilon) {
if (epsilon < 0) {
throw new IllegalArgumentException("Epsilon must be >= 0");
}
this.epsilon = epsilon;
}
public RamerDouglasPeuckerFilter(double epsilon, double maxSegmentLength) {
this(epsilon);
this.maxSegmentLength = maxSegmentLength;
}
public List<ILocation> getRemovablePoints(List<? extends ILocation> data) {
return getRemovablePoints(data, 0, data.size() - 1);
}
public double getEpsilon() {
return epsilon;
}
public double getMaxSegmentLength() {
return maxSegmentLength;
}
public void setMaxSegmentLength(double maxSegmentLength) {
this.maxSegmentLength = maxSegmentLength;
}
protected List<ILocation> getRemovablePoints(
List<? extends ILocation> points, int startIndex, int endIndex) {
double dmax = 0;
int idx = 0;
if (endIndex <= startIndex || epsilon == 0) {
return new ArrayList<ILocation>(0);
}
ILocation start = points.get(startIndex);
ILocation end = points.get(endIndex);
for (int i = startIndex + 1; i < endIndex; i++) {
double distance = GPSCalc.getDistancePointToEdge(points.get(i),
start, end);
// System.out.println(distance);
if (distance > dmax) {
idx = i;
dmax = distance;
}
}
if (dmax >= epsilon) {
List<ILocation> recursiveResult1 = getRemovablePoints(points,
startIndex, idx);
List<ILocation> recursiveResult2 = getRemovablePoints(points, idx,
endIndex);
List<ILocation> result = new ArrayList<ILocation>(
recursiveResult1.size() + recursiveResult2.size());
result.addAll(recursiveResult1);
result.addAll(recursiveResult2);
return result;
} else {
List<ILocation> result = new ArrayList<ILocation>(endIndex
- startIndex);
result.addAll(points.subList(startIndex + 1, endIndex));
return result;
}
}
/**
*
* @param epsilon maximum distance of a point in data between original curve
* and simplified curve
*/
public void setEpsilon(double epsilon) {
if (epsilon < 0) {
throw new IllegalArgumentException("Epsilon must be >= 0");
}
this.epsilon = epsilon;
}
/**
* Simplify a {@link GPSSegment} using this filter.
*
* @param cleanSegment
* @return Simplified {@link GPSSegment}.
*/
public GPSSegment simplify(GPSSegment cleanSegment) {
List<ILocation> removablePoints = getRemovablePoints(cleanSegment);
List<GPSPoint> points = cleanSegment;
for (ILocation point : removablePoints) {
points.remove(point);
}
return cleanSegment;
}
/**
* Simplify a {@link AggContainer} using this filter.
*
* @param aggPoints
* @param agg
* @return
*/
public List<AggNode> simplifyAgg(List<AggNode> aggPoints, AggContainer agg) {
List<ILocation> removablePoints = getRemovablePoints(aggPoints);
AggNode node = null;
for (ILocation point : removablePoints) {
node = (AggNode) point;
if (!node.isAggIntersection()) {
agg.extractNode(node);
aggPoints.remove(node);
}
}
// check for edges that are too long and fix them
List<AggNode> result = new ArrayList<AggNode>();
for (int i = 1; i < aggPoints.size(); i++) {
AggNode lastNode = aggPoints.get(i - 1);
AggNode thisNode = aggPoints.get(i);
if (result.size() > 1
&& !result.get(result.size() - 1).equals(lastNode)) {
result.add(lastNode);
}
double dist = GPSCalc.getDistance(lastNode, thisNode);
if (dist > maxSegmentLength) {
AggConnection conn = lastNode.getConnectionTo(thisNode);
if (conn != null) {
List<AggConnection> newConnections = agg.splitConnection(
conn, (int) Math.ceil(dist / maxSegmentLength));
for (AggConnection newConn : newConnections) {
result.add(newConn.getTo());
}
}
} else {
result.add(thisNode);
}
}
return result;
}
/**
* Simplify a {@link Path} using this filter. Typically used when making a
* {@link RoadNetwork}.
*
* @param path
* @return
*/
public List<ILocation> simplify(Path<? extends ILocation> path) {
List<ILocation> clonedNodes = path.getClonedNodes();
ILocation lastPoint = clonedNodes.get(clonedNodes.size() - 1);
List<ILocation> removablePoints = getRemovablePoints(clonedNodes);
for (ILocation point : removablePoints) {
clonedNodes.remove(point);
}
// make sure the last point is in the result, but only once
if (clonedNodes.lastIndexOf(lastPoint) != clonedNodes.size() - 1) {
clonedNodes.add(lastPoint);
}
return clonedNodes;
}
}