/* * Licensed to GraphHopper GmbH under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. * * GraphHopper GmbH licenses this file to you 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 com.graphhopper.util; /** * Simplifies a list of 2D points which are not too far away. * http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm * <p> * Calling simplify is thread safe. * <p> * * @author Peter Karich */ public class DouglasPeucker { private double normedMaxDist; private DistanceCalc calc; private boolean approx; public DouglasPeucker() { setApproximation(true); // 1m setMaxDistance(1); } public void setApproximation(boolean a) { approx = a; if (approx) calc = Helper.DIST_PLANE; else calc = Helper.DIST_EARTH; } /** * maximum distance of discrepancy (from the normal way) in meter */ public DouglasPeucker setMaxDistance(double dist) { this.normedMaxDist = calc.calcNormalizedDist(dist); return this; } /** * This method removes points which are close to the line (defined by maxDist). * <p> * * @return removed nodes */ public int simplify(PointList points) { int removed = 0; int size = points.getSize(); if (approx) { int delta = 500; int segments = size / delta + 1; int start = 0; for (int i = 0; i < segments; i++) { // start of next is end of last segment, except for the last removed += simplify(points, start, Math.min(size - 1, start + delta)); start += delta; } } else { removed = simplify(points, 0, size - 1); } compressNew(points, removed); return removed; } /** * compress list: move points into EMPTY slots */ void compressNew(PointList points, int removed) { int freeIndex = -1; for (int currentIndex = 0; currentIndex < points.getSize(); currentIndex++) { if (Double.isNaN(points.getLatitude(currentIndex))) { if (freeIndex < 0) freeIndex = currentIndex; continue; } else if (freeIndex < 0) { continue; } points.set(freeIndex, points.getLatitude(currentIndex), points.getLongitude(currentIndex), points.getElevation(currentIndex)); points.set(currentIndex, Double.NaN, Double.NaN, Double.NaN); // find next free index int max = currentIndex; int searchIndex = freeIndex + 1; freeIndex = currentIndex; for (; searchIndex < max; searchIndex++) { if (Double.isNaN(points.getLatitude(searchIndex))) { freeIndex = searchIndex; break; } } } points.trimToSize(points.getSize() - removed); } // keep the points of fromIndex and lastIndex int simplify(PointList points, int fromIndex, int lastIndex) { if (lastIndex - fromIndex < 2) { return 0; } int indexWithMaxDist = -1; double maxDist = -1; double firstLat = points.getLatitude(fromIndex); double firstLon = points.getLongitude(fromIndex); double lastLat = points.getLatitude(lastIndex); double lastLon = points.getLongitude(lastIndex); for (int i = fromIndex + 1; i < lastIndex; i++) { double lat = points.getLatitude(i); if (Double.isNaN(lat)) { continue; } double lon = points.getLongitude(i); double dist = calc.calcNormalizedEdgeDistance(lat, lon, firstLat, firstLon, lastLat, lastLon); if (maxDist < dist) { indexWithMaxDist = i; maxDist = dist; } } if (indexWithMaxDist < 0) { throw new IllegalStateException("maximum not found in [" + fromIndex + "," + lastIndex + "]"); } int counter = 0; if (maxDist < normedMaxDist) { for (int i = fromIndex + 1; i < lastIndex; i++) { points.set(i, Double.NaN, Double.NaN, Double.NaN); counter++; } } else { counter = simplify(points, fromIndex, indexWithMaxDist); counter += simplify(points, indexWithMaxDist, lastIndex); } return counter; } }