/** * ***************************************************************************** * 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.agg.strategy; import de.fub.agg2graph.agg.AggNode; import de.fub.agg2graph.agg.IMergeHandler; import de.fub.agg2graph.agg.ITraceDistance; import de.fub.agg2graph.structs.CartesianCalc; import de.fub.agg2graph.structs.ClassObjectEditor; import de.fub.agg2graph.structs.GPSCalc; import de.fub.agg2graph.structs.GPSPoint; import de.fub.agg2graph.structs.ILocation; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; public class DefaultTraceDistance implements ITraceDistance { private static final Logger logger = Logger.getLogger("agg2graph.agg.default.dist"); private double aggReflectionFactor = 4; private int maxOutliners = 10; private double maxDistance = 60; private int maxLookahead = 10; private double maxPathDifference = 100; private int minLengthFirstSegment = 1; private double maxAngle = 37; public double getAggReflectionFactor() { return aggReflectionFactor; } public void setAggReflectionFactor(double aggReflectionFactor) { this.aggReflectionFactor = aggReflectionFactor; } public int getMaxOutliners() { return maxOutliners; } public void setMaxOutliners(int maxOutliners) { this.maxOutliners = maxOutliners; } public double getMaxDistance() { return maxDistance; } public void setMaxDistance(double maxDistance) { this.maxDistance = maxDistance; } public int getMaxLookahead() { return maxLookahead; } public void setMaxLookahead(int maxLookahead) { this.maxLookahead = maxLookahead; } public double getMaxPathDifference() { return maxPathDifference; } public void setMaxPathDifference(double maxPathDifference) { this.maxPathDifference = maxPathDifference; } public int getMinLengthFirstSegment() { return minLengthFirstSegment; } public void setMinLengthFirstSegment(int minLengthFirstSegment) { this.minLengthFirstSegment = minLengthFirstSegment; } public double getMaxAngle() { return maxAngle; } public void setMaxAngle(double maxAngle) { this.maxAngle = maxAngle; } /** * Compute the difference of a path to the aggregation. This measure only * guarantees relative correctness when questioned repeatedly for different * paths. A difference of 0 indicates equality while larger values indicate * increasingly different paths. * * @param aggPath * @param tracePoints * @param startIndex * @param dmh * @return Object[] { double bestValue, int bestValueLength } */ @Override public Object[] getPathDifference(List<AggNode> aggPath, List<GPSPoint> tracePoints, int startIndex, IMergeHandler dmh) { double bestValue = Double.MAX_VALUE; double bestValueLength = 0; // List<AggNode> internalPath = new ArrayList<AggNode>(); // internalPath.addAll(aggPath); for (int i = startIndex; i < Math.min(startIndex + 20, tracePoints.size()); i++) { logger.log( Level.FINER, String.format("Testing %s and %s...", aggPath, tracePoints.subList(startIndex, i + 1))); // GPSPoint point = tracePoints.get(i); List<AggNode> aggLocations = aggPath; List<GPSPoint> traceLocations = tracePoints.subList(startIndex, i + 1); // one-element similarity is not interesting if (traceLocations.size() <= minLengthFirstSegment || aggLocations.size() <= minLengthFirstSegment) { logger.log(Level.FINE, "Too short path"); continue; } // angle okay? double angle = 180; if (dmh != null && dmh.getGpsPoints().size() > 0) { angle = GPSCalc.getAngleBetweenEdges(dmh.getGpsPoints().get(0), tracePoints.get(i), dmh.getAggNodes().get(0), aggPath.get(aggPath.size() - 1)); } else { angle = GPSCalc.getAngleBetweenEdges( tracePoints.get(startIndex), tracePoints.get(i), aggPath.get(0), aggPath.get(aggPath.size() - 1)); } if (!CartesianCalc.isAngleMax(angle, maxAngle)) { logger.log(Level.FINE, String.format("Angle is not good: %.1f", angle)); continue; } /* * for the new point in the new trace: find minimal distance to all * possibly matching edges of the aggregation */ double dist = Double.MAX_VALUE / 2; double[] traceToAggDistances = getPointToLineDistances( traceLocations, aggLocations); if (!outlinersOkay(traceToAggDistances)) { continue; } dist = Math.min(dist, getAverageDistance(traceToAggDistances)); double[] aggToTraceDistances = getPointToLineDistances( aggLocations, traceLocations); dist = Math.max(dist, dist + 1.0 / aggReflectionFactor * getAverageDistance(aggToTraceDistances)); // value formula logger.log(Level.FINE, MessageFormat.format("dist: {0}", dist)); // consider length double value = dist * Math.pow(0.95, aggLocations.size() + traceLocations.size()); logger.log(Level.FINE, MessageFormat.format("value: {0}", value)); if (value > maxPathDifference) { value = Double.MAX_VALUE; } logger.log(Level.FINE, String.format("Value of path: %.3f", value)); if (value < bestValue) { logger.log(Level.FINE, "best"); bestValue = value; bestValueLength = traceLocations.size(); } } return new Object[]{bestValue, bestValueLength}; } private double[] getPointToLineDistances(List<? extends ILocation> from, List<? extends ILocation> to) { double[] result = new double[from.size()]; ILocation loc; for (int i = 0; i < from.size(); i++) { loc = from.get(i); result[i] = GPSCalc.distancePointToTrace(loc, to)[0]; } return result; } private boolean outlinersOkay(double[] distances) { int outliers = 0; double distance; for (int dIndex = 0; dIndex < distances.length; dIndex++) { distance = distances[dIndex]; if (distance > maxDistance) { if (outliers > maxOutliners) { // this path is not good logger.log(Level.FINE, String.format( "Too many outliners (%d), limit is %d in a row.", outliers + 1, outliers)); return false; } else if (dIndex == 0) { logger.log( Level.FINE, String.format( "Outliner point at the start. Distance is %.3f, it should be below %.3f.", distance, maxDistance)); return false; } else if (dIndex == distances.length - 1) { logger.log( Level.FINE, String.format( "Outliner point at the end. Distance is %.3f, it should be below %.3f.", distance, maxDistance)); return false; } outliers++; } else { outliers = 0; } } return true; } private double getAverageDistance(double[] aggToTraceDistances) { double sum = 0; for (double d : aggToTraceDistances) { sum += d; } return sum / aggToTraceDistances.length; } @Override public List<ClassObjectEditor> getSettings() { List<ClassObjectEditor> result = new ArrayList<ClassObjectEditor>(); result.add(new ClassObjectEditor(this)); return result; } }