/**
* *****************************************************************************
* 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.AggConnection;
import de.fub.agg2graph.agg.AggNode;
import de.fub.agg2graph.agg.AggregationStrategyFactory;
import de.fub.agg2graph.agg.IMergeHandler;
import de.fub.agg2graph.agg.MergeHandlerFactory;
import de.fub.agg2graph.agg.TraceDistanceFactory;
import de.fub.agg2graph.structs.BoundedQueue;
import de.fub.agg2graph.structs.GPSPoint;
import de.fub.agg2graph.structs.GPSSegment;
import de.fub.agg2graph.structs.ILocation;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DefaultAggregationStrategy extends AbstractAggregationStrategy {
private static final Logger logger = Logger.getLogger("agg2graph.agg.default.strategy");
private int maxLookahead = 7;
private double maxPathDifference = 1000;
private double maxInitDistance = 150;
public enum State {
NO_MATCH, IN_MATCH
}
private State state = State.NO_MATCH;
/**
* Preferably use the {@link AggregationStrategyFactory} for creating
* instances of this class.
*/
public DefaultAggregationStrategy() {
traceDistance = TraceDistanceFactory.getObject();
baseMergeHandler = MergeHandlerFactory.getObject();
}
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 double getMaxInitDistance() {
return maxInitDistance;
}
public void setMaxInitDistance(double maxInitDistance) {
this.maxInitDistance = maxInitDistance;
}
@Override
public void aggregate(GPSSegment segment, boolean isAgg) {
logger.setLevel(Level.OFF); // Level.ALL);
// reset all attributes
lastNode = null;
mergeHandler = null;
matches = new ArrayList<IMergeHandler>();
state = State.NO_MATCH;
// insert first segment without changes (assuming somewhat cleaned
// data!)
// attention: node counter is not necessarily accurate!
if (aggContainer.getCachingStrategy() == null
|| aggContainer.getCachingStrategy().getNodeCount() == 0) {
int i = 0;
clear(); // we have to clear the previouse last node to avoid unwanted connection from the previouse added segment.
while (i < segment.size()) {
AggNode node = new AggNode(segment.get(i), aggContainer);
node.setID(MessageFormat.format("A-{0}", segment.get(i).getID()));
addNodeToAgg(aggContainer, node);
lastNode = node;
i++;
}
return;
}
BoundedQueue<ILocation> lastParsedCurrentPoints = new BoundedQueue<ILocation>(5);
int i = 0;
while (i < segment.size()) {
// step 1: find starting point
// get close points, within 10 meters (merge candidates)
Set<AggNode> nearPoints = null;
GPSPoint currentPoint = segment.get(i);
logger.log(Level.FINE, "current point: {0}", currentPoint);
// no progress? (should not be necessary)
if (lastParsedCurrentPoints.size() > 2
&& lastParsedCurrentPoints.get(
lastParsedCurrentPoints.size() - 1).equals(
currentPoint)
&& lastParsedCurrentPoints.get(
lastParsedCurrentPoints.size() - 2).equals(
currentPoint)) {
i++;
continue;
}
lastParsedCurrentPoints.offer(currentPoint);
State lastState = state;
// get all close points, but none that are already in the current
// match (because we would kinda search backwards)
nearPoints = aggContainer.getCachingStrategy().getCloseNodes(
currentPoint, maxInitDistance);
if (mergeHandler != null) {
List<AggNode> nodes = mergeHandler.getAggNodes();
for (int j = 0; j < nodes.size() - 1; j++) {
nearPoints.remove(nodes.get(j));
}
}
logger.log(Level.FINE, "near points: {0}", nearPoints);
boolean isMatch = true;
if (nearPoints.isEmpty()) {
isMatch = false;
} else {
// there is candidates for a match start
List<List<AggNode>> paths = getPathsByDepth(nearPoints, 1, maxLookahead);
// evaluate paths, pick best, continue
logger.log(Level.FINE, "Paths from {0} in agg: {1}", new Object[]{nearPoints, paths});
double bestDifference = Double.MAX_VALUE, difference;
int length;
List<AggNode> bestPath = null;
int bestPathLength = 0;
for (List<AggNode> path : paths) {
Object[] returnValues = traceDistance.getPathDifference(
path,
segment,
i,
mergeHandler);
difference = (Double) returnValues[0];
length = (int) Math.round(Double.valueOf(returnValues[1].toString()));
logger.info(String.format("Difference of path %s and %s is %.3f",
path,
segment.subList(i, i + length),
difference));
if (difference < bestDifference
|| (difference == bestDifference && length > bestPathLength)) {
bestDifference = difference;
logger.log(Level.FINE, "This is the new best path.");
bestPathLength = length;
bestPath = path;
if (bestPath.isEmpty()) {
int j = i;
i = j;
}
}
}
// do we have a successful match?
if (bestDifference >= maxPathDifference || bestPath == null) {
// i++;
logger.log(Level.FINE, "Best path not good enough (anymore)");
isMatch = false;
} else if (bestPath.size() <= 1 && bestPathLength <= 1) {
isMatch = false;
}
state = isMatch ? State.IN_MATCH : State.NO_MATCH;
if (isMatch) {
// make a merge handler if the match would start here
if (lastState == State.NO_MATCH) {
mergeHandler = baseMergeHandler.getCopy();
mergeHandler.setAggContainer(aggContainer);
}
logger.log(Level.FINE,
String.format(
"best path found: %s, value: %.8f\ncomsuming %d GPS points: %s",
bestPath, bestDifference, bestPathLength,
segment.subList(i, i + bestPathLength)));
mergeHandler.addAggNodes(bestPath);
mergeHandler.addGPSPoints(segment.subList(i, i
+ bestPathLength));
mergeHandler.setDistance(bestDifference);
logger.log(Level.FINE, "Path so far: {0}", mergeHandler);
i = i + bestPathLength - 1;
}
}
if (!isMatch && (lastState == State.IN_MATCH
&& (state == State.NO_MATCH
|| i == segment.size() - 1))) {
finishMatch();
} else if (!isMatch && lastState == State.NO_MATCH) {
// if there is no close points or no valid match, add it to the
// aggregation
AggNode node = new AggNode(currentPoint, aggContainer);
node.setID(MessageFormat.format("A-{0}", currentPoint.getID()));
addNodeToAgg(aggContainer, node);
lastNode = node;
i++;
}
}
// step 2 and 3 of 3: ghost points, merge everything
for (IMergeHandler match : matches) {
if (!match.isEmpty()) {
match.mergePoints();
}
}
}
protected void finishMatch() {
// last match is over now
matches.add(mergeHandler);
mergeHandler.processSubmatch();
/*
* connect to previous node lastNode is the last non-matched node or the
* outNode of the last match
*/
aggContainer.connect(lastNode, mergeHandler.getInNode());
mergeHandler.setBeforeNode(lastNode);
// remember outgoing node (for later connection)
lastNode = mergeHandler.getOutNode();
}
/*
* reverse paths
*/
private List<List<AggNode>> getPathsByDepth(Set<AggNode> nearPoints,
int minDepth, int maxDepth) {
List<List<AggNode>> paths = new ArrayList<List<AggNode>>();
for (AggNode startNode : nearPoints) {
List<AggNode> path = new ArrayList<AggNode>();
path.add(startNode);
addPaths(paths, path, 1, minDepth, maxDepth);
}
return paths;
}
private void addPaths(List<List<AggNode>> paths, List<AggNode> path,
int depth, int minDepth, int maxDepth) {
if (depth > maxDepth) {
return;
}
// add out nodes
// TODO load node if necessary instead of null check
if (path.get(depth - 1).getOut() != null) {
for (AggConnection outConn : path.get(depth - 1).getOut()) {
AggNode outNode = outConn.getTo();
path.add(outNode);
if (depth >= minDepth) {
ArrayList<AggNode> pathCopy = new ArrayList<AggNode>();
pathCopy.addAll(path);
paths.add(pathCopy);
}
addPaths(paths, path, depth + 1, minDepth, maxDepth);
path.remove(path.size() - 1);
}
}
}
}