/** * ***************************************************************************** * 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.input; import de.fub.agg2graph.structs.BoundedQueue; import de.fub.agg2graph.structs.CartesianCalc; 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 java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Prepocess GPS data by several means. See {@link CleaningOptions} for * supported cleaning options and their respective settings. * * @author Johannes Mitlmeier * */ public class GPSCleaner { private static final Logger LOG = Logger.getLogger(GPSCleaner.class.getName()); private CleaningOptions cleaningOptions = new CleaningOptions(); public GPSCleaner enableDefault() { cleaningOptions.filterBySegmentLength = true; cleaningOptions.filterByEdgeLength = true; cleaningOptions.filterByEdgeLengthIncrease = true; cleaningOptions.filterZigzag = true; cleaningOptions.filterFakeCircle = true; cleaningOptions.filterOutliers = true; return this; } public List<GPSSegment> clean(GPSSegment segment) { List<GPSPoint> pointList = segment; List<GPSSegment> result = new ArrayList<GPSSegment>(); GPSSegment currentSegment = new GPSSegment(); BoundedQueue<Double> lastLengths = new BoundedQueue<Double>(5); for (int i = 0; i < pointList.size(); i++) { GPSPoint point = pointList.get(i); ILocation lastPoint = currentSegment.size() > 0 ? currentSegment .get(currentSegment.size() - 1) : null; LOG.log(Level.FINEST, "Examining point {0}", point); double length = 0; if (cleaningOptions.filterByEdgeLength || cleaningOptions.filterByEdgeLengthIncrease) { if (lastPoint != null) { length = GPSCalc.getDistance(lastPoint, point); lastLengths.offer(length); } } // check edge length if (cleaningOptions.filterByEdgeLength && currentSegment.size() > 0) { if (length < cleaningOptions.minEdgeLength) { LOG.fine(String.format( "edge length %s to %s: %.2f < %.2f", lastPoint, point, length, cleaningOptions.minEdgeLength)); LOG.log(Level.FINEST, "short edge, dropping point {0}", point); continue; } if (length > cleaningOptions.maxEdgeLength) { LOG.fine(String.format( "edge length %s to %s: %.2f > %.2f", lastPoint, point, length, cleaningOptions.maxEdgeLength)); LOG.log(Level.FINEST, "long edge, still NOT dropping point {0}", point); // make new segment if (currentSegment.size() > cleaningOptions.minSegmentLength) { result.add(currentSegment); } currentSegment = new GPSSegment(); } } // check increase of edge length if (cleaningOptions.filterByEdgeLengthIncrease) { if (lastLengths.size() > 1) { if (length > cleaningOptions.minEdgeLengthAfterIncrease && length / lastLengths.get(lastLengths.size() - 2) > cleaningOptions.minEdgeLengthIncreaseFactor) { // make new segment if (currentSegment.size() > cleaningOptions.minSegmentLength) { result.add(currentSegment); } currentSegment = new GPSSegment(); } } } double angleHere = Double.MAX_VALUE; double angleBefore = Double.MAX_VALUE; // check zigzag if (cleaningOptions.filterZigzag) { if (currentSegment.size() > 1 && i < pointList.size() - 1) { GPSPoint nextToLastPoint = currentSegment .get(currentSegment.size() - 2); GPSPoint nextPoint = pointList.get(i + 1); angleHere = CartesianCalc.getAngleBetweenLines(lastPoint, point, point, nextPoint); angleBefore = CartesianCalc.getAngleBetweenLines( nextToLastPoint, lastPoint, lastPoint, point); if (!Double.isNaN(angleHere) && !Double.isNaN(angleBefore)) { // is it zigzagged? if (((angleHere > 180 - cleaningOptions.maxZigzagAngle) && (angleBefore < cleaningOptions.maxZigzagAngle)) || ((angleHere < cleaningOptions.maxZigzagAngle) && (angleBefore > 180 - cleaningOptions.maxZigzagAngle))) { LOG.log(Level.FINEST, "found zigzag"); LOG.log(Level.FINEST, String.format("%.3f <- -> %.3f", angleBefore, angleHere)); i++; continue; } } else { LOG.log(Level.FINEST, "something bad"); } } } // check fake circles if (cleaningOptions.filterFakeCircle) { if (currentSegment.size() > 1 && i < pointList.size() - 1) { if (!cleaningOptions.filterZigzag) { GPSPoint nextToLastPoint = currentSegment .get(currentSegment.size() - 2); GPSPoint nextPoint = pointList.get(i + 1); angleHere = CartesianCalc.getAngleBetweenLines( lastPoint, point, point, nextPoint); angleBefore = CartesianCalc.getAngleBetweenLines( nextToLastPoint, lastPoint, lastPoint, point); } if (!Double.isNaN(angleHere) && !Double.isNaN(angleBefore)) { // is it a fake circle? if ((angleHere > 180 - cleaningOptions.maxFakeCircleAngle) && (angleBefore > 180 - cleaningOptions.maxFakeCircleAngle)) { LOG.log(Level.FINEST, "found fake circle"); LOG.log(Level.FINEST, String.format("%.3f <- -> %.3f", angleBefore, angleHere)); // insert as second but last element GPSPoint oldLastPoint = currentSegment .remove(currentSegment.size() - 1); currentSegment.add(point); currentSegment.add(oldLastPoint); i++; continue; } } } } // segment length (split if necessary) if (cleaningOptions.filterBySegmentLength && currentSegment.size() >= cleaningOptions.maxSegmentLength) { // make new segment if (currentSegment.size() > cleaningOptions.minSegmentLength) { result.add(currentSegment); } currentSegment = new GPSSegment(); } LOG.log(Level.FINEST, "adding point {0}", point); currentSegment.add(point); } // save last segment if (!cleaningOptions.filterBySegmentLength || currentSegment.size() > cleaningOptions.minSegmentLength) { result.add(currentSegment); } return result; } public CleaningOptions getCleaningOptions() { return cleaningOptions; } public void setCleaningOptions(CleaningOptions options) { cleaningOptions = options; } }