/**
* *****************************************************************************
* Copyright (c) 2012 Johannes Mitlmeier. All rights reserved. This program and
* the accompanying materials are made available under the terms of the GNU
* Affero Public License v3.0 which accompanies this distribution, and is
* available at http://www.gnu.org/licenses/agpl-3.0.html
*
* Contributors: Johannes Mitlmeier - initial API and implementation
* ****************************************************************************
*/
package de.fub.agg2graph.agg.strategy;
import de.fub.agg2graph.agg.AggCleaner;
import de.fub.agg2graph.agg.AggConnection;
import de.fub.agg2graph.agg.AggContainer;
import de.fub.agg2graph.agg.AggNode;
import de.fub.agg2graph.agg.IMergeHandler;
import de.fub.agg2graph.agg.PointGhostPointPair;
import de.fub.agg2graph.graph.RamerDouglasPeuckerFilter;
import de.fub.agg2graph.input.Globals;
import de.fub.agg2graph.structs.CartesianCalc;
import de.fub.agg2graph.structs.ClassObjectEditor;
import de.fub.agg2graph.structs.GPSCalc;
import de.fub.agg2graph.structs.GPSEdge;
import de.fub.agg2graph.structs.GPSPoint;
import de.fub.agg2graph.structs.ILocation;
import de.fub.agg2graph.ui.gui.RenderingOptions;
import de.fub.agg2graph.ui.gui.jmv.Layer;
import de.fub.agg2graph.ui.gui.jmv.TestUI;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jscience.mathematics.vector.Float64Vector;
public class WeightedClosestPointMerge implements IMergeHandler {
private static final Logger logger = Logger
.getLogger("agg2graph.agg.default.merge");
// contains only matched points/nodes
private List<AggNode> aggNodes = null;
private List<GPSPoint> gpsPoints = null;
private int maxLookahead = 10;
private double minContinuationAngle = 45;
// helper stuff
private Map<AggConnection, List<PointGhostPointPair>> newNodesPerConn;
private List<PointGhostPointPair> pointGhostPointPairs;
private AggNode inNode;
private AggNode outNode;
private AggContainer aggContainer;
private RenderingOptions roMatchGPS;
// cleaning stuff
private final RamerDouglasPeuckerFilter rdpf = new RamerDouglasPeuckerFilter(0, 50);
private static AggCleaner cleaner = new AggCleaner().enableDefault();
private double maxPointGhostDist = 40; //10 meters
private double distance = 40;
private AggNode beforeNode;
public WeightedClosestPointMerge() {
// debugging
logger.setLevel(Level.ALL);
roMatchGPS = new RenderingOptions();
roMatchGPS.setColor(Color.PINK);
logger.setLevel(Level.OFF);
aggNodes = new ArrayList<AggNode>();
gpsPoints = new ArrayList<GPSPoint>();
}
public WeightedClosestPointMerge(AggContainer aggContainer) {
this();
this.aggContainer = aggContainer;
}
public WeightedClosestPointMerge(AggContainer aggContainer,
List<AggNode> aggNodes, List<GPSPoint> gpsPoints) {
this(aggContainer);
this.aggNodes = aggNodes;
this.gpsPoints = gpsPoints;
}
/**
* @return the maxLookahead
*/
public int getMaxLookahead() {
return maxLookahead;
}
/**
* @param maxLookahead the maxLookahead to set
*/
public void setMaxLookahead(int maxLookahead) {
this.maxLookahead = maxLookahead;
}
/**
* @return the minContinuationAngle
*/
public double getMinContinuationAngle() {
return minContinuationAngle;
}
/**
* @param minContinuationAngle the minContinuationAngle to set
*/
public void setMinContinuationAngle(double minContinuationAngle) {
this.minContinuationAngle = minContinuationAngle;
}
/**
* @return the maxPointGhostDist
*/
public double getMaxPointGhostDist() {
return maxPointGhostDist;
}
/**
* @param maxPointGhostDist the maxPointGhostDist to set
*/
public void setMaxPointGhostDist(double maxPointGhostDist) {
this.maxPointGhostDist = maxPointGhostDist;
}
@Override
public AggContainer getAggContainer() {
return aggContainer;
}
@Override
public void setAggContainer(AggContainer aggContainer) {
this.aggContainer = aggContainer;
}
@Override
public List<AggNode> getAggNodes() {
return this.aggNodes;
}
@Override
public void addAggNode(AggNode aggNode) {
if (this.aggNodes.size() > 0
&& this.aggNodes.get(this.aggNodes.size() - 1).equals(aggNode)) {
this.aggNodes.remove(this.aggNodes.size() - 1);
}
this.aggNodes.add(aggNode);
}
@Override
public void addAggNodes(List<AggNode> aggNodes) {
int i = 0;
while (aggNodes.size() > i
&& this.aggNodes.size() > 0
&& this.aggNodes.get(this.aggNodes.size() - 1).equals(
aggNodes.get(i))) {
this.aggNodes.remove(this.aggNodes.size() - 1);
i++;
}
this.aggNodes.addAll(aggNodes);
}
@Override
public List<GPSPoint> getGpsPoints() {
return gpsPoints;
}
@Override
public void addGPSPoints(List<GPSPoint> gpsPoints) {
int i = 0;
while (gpsPoints.size() > i
&& this.gpsPoints.size() > 0
&& this.gpsPoints.get(this.gpsPoints.size() - 1).equals(
gpsPoints.get(i))) {
this.gpsPoints.remove(this.gpsPoints.size() - 1);
i++;
}
this.gpsPoints.addAll(gpsPoints);
}
@Override
public void addGPSPoint(GPSPoint gpsPoint) {
if (this.gpsPoints.size() > 0
&& this.gpsPoints.get(this.gpsPoints.size() - 1).equals(
gpsPoint)) {
return;
}
this.gpsPoints.add(gpsPoint);
}
@Override
public void processSubmatch() {
newNodesPerConn = new HashMap<AggConnection, List<PointGhostPointPair>>();
pointGhostPointPairs = new ArrayList<PointGhostPointPair>();
// projections of the trace to the aggregation
int start = 0;
GPSPoint lastPoint = null, point = null;
// projections of the aggregation to the trace
for (int pointIndex = 0; pointIndex < getAggNodes().size(); pointIndex++) {
AggNode node = getAggNodes().get(pointIndex);
logger.log(Level.FINER, "agg node {0}", node);
// loop over all possible opposing lines
List<GPSPoint> internalGpsPoints = getGpsPoints();
boolean afterHit = false;
int iMax;
for (int i = start; i < Math.min(start + getMaxLookahead(),
internalGpsPoints.size() - 1); i++) {
iMax = Math.min(start + getMaxLookahead(),
internalGpsPoints.size() - 1);
point = getGpsPoints().get(i);
GPSPoint startNode = internalGpsPoints.get(i);
GPSPoint endNode = internalGpsPoints.get(i + 1);
ILocation newPoint = GPSCalc.getProjectionPoint(node,
startNode, endNode);
newPoint = testLength(point, newPoint);
if (newPoint == null) {
if (afterHit || pointIndex == 0) {
break;
}
continue;
} else {
if (!afterHit) {
start = Math.max(0, i - 1);
}
}
GPSPoint newNode = new GPSPoint(newPoint);
newNode.setID(String.format("%s+", node.getID()));
logger.log(Level.FINER, String.format(
"made ghost point %s at %s%s", newNode, startNode,
endNode));
AggNode addNode = node;
if (afterHit) {
addNode = new AggNode(node);
addNode.setID("dup-" + node.getID());
}
PointGhostPointPair pair = PointGhostPointPair
.createAggToTrace(addNode, newNode, pointIndex,
afterHit);
pointGhostPointPairs.add(pair);
if (afterHit && pointIndex < getAggNodes().size() - 1) {
AggConnection conn = getAggNodes().get(pointIndex)
.getConnectionTo(getAggNodes().get(pointIndex + 1));
if (!newNodesPerConn.containsKey(conn)) {
newNodesPerConn.put(conn,
new ArrayList<PointGhostPointPair>());
}
// remove (now) invalid ghost points of earlier trace points
PointGhostPointPair loopPair = null;
List<PointGhostPointPair> nodesOnThisConn = newNodesPerConn
.get(conn);
for (int m = nodesOnThisConn.size() - 1; m >= 0; m--) {
loopPair = nodesOnThisConn.get(m);
// is it okay?
if (!loopPair.getPoint().equals(lastPoint)
&& !loopPair.getPoint().equals(point)) {
nodesOnThisConn.remove(m);
pointGhostPointPairs.remove(loopPair);
}
}
newNodesPerConn.get(conn).add(pair);
}
afterHit = true;
// agg to trace
if (i < iMax - 1) {
GPSPoint nextNode = internalGpsPoints.get(i + 2);
double nextAngle = GPSCalc.getAngleBetweenEdges(startNode,
endNode, endNode, nextNode);
if (CartesianCalc.isAngleMax(nextAngle, getMinContinuationAngle())) {
break;
}
}
}
}
}
private ILocation testLength(GPSPoint point, ILocation newPoint) {
if (newPoint != null) {
if (GPSCalc.getDistanceTwoPointsMeter(point, newPoint) > getMaxPointGhostDist()) {
return null;
}
}
return newPoint;
}
private void showDebugInfo() {
// debug
TestUI ui = (TestUI) Globals.get("ui");
if (ui == null) {
return;
}
Layer matchingLayer = ui.getLayerManager().getLayer("matching");
Layer mergingLayer = ui.getLayerManager().getLayer("merging");
// clone the lists
List<ILocation> aggNodesClone = new ArrayList<ILocation>(
aggNodes.size());
for (ILocation loc : aggNodes) {
if (!loc.isRelevant()) {
aggNodesClone.add(new GPSPoint(loc));
matchingLayer.addObject(aggNodesClone);
aggNodesClone = new ArrayList<ILocation>(
aggNodes.size());
} else {
aggNodesClone.add(new GPSPoint(loc));
}
}
if (aggNodesClone.size() > 0) {
matchingLayer.addObject(aggNodesClone);
}
matchingLayer.addObject(gpsPoints); // , roMatchGPS);
// for debugging highlight trace to agg with an arrow
for (PointGhostPointPair pgpp : pointGhostPointPairs) {
List<ILocation> line = new ArrayList<ILocation>(2);
line.add(new GPSPoint(pgpp.point));
line.add(new GPSPoint(pgpp.ghostPoint));
mergingLayer.addObject(line);
}
}
@Override
public double getDistance() {
return distance;
}
@Override
public void setDistance(double bestDifference) {
this.distance = bestDifference;
}
@Override
public void mergePoints() {
showDebugInfo();
List<AggConnection> changedAggConnections = new ArrayList<AggConnection>(
10);
List<AggConnection> newAggConnections;
// add nodes
AggNode lastNode = null;
AggConnection conn = null;
for (AggNode node : getAggNodes()) {
if (lastNode == null) {
lastNode = node;
continue;
}
/* Make sure that they are connected */
conn = lastNode.getConnectionTo(node);
if (conn == null) {
continue;
}
conn.tryToFill();
List<AggNode> aggNodeList = new ArrayList<AggNode>();
if (newNodesPerConn.get(conn) != null) {
for (PointGhostPointPair pair : newNodesPerConn.get(conn)) {
aggNodeList.add(pair.getAggNode());
}
newAggConnections = aggContainer.insertNodesOrdered(
conn.getFrom(), conn.getTo(), aggNodeList);
changedAggConnections.addAll(newAggConnections);
} else {
// edge without
changedAggConnections.add(conn);
}
lastNode = node;
}
// merge points
for (PointGhostPointPair pgpp : pointGhostPointPairs) {
mergePointPair(pgpp.getAggNode(), pgpp.getGPSPoint());
}
List<AggNode> changedAggPoints = AggConnection
.listToPoints(changedAggConnections);
// update distance and weights
for (AggConnection loopConn : changedAggConnections) {
if (loopConn == null) {
continue;
}
float oldWeight = loopConn.getWeight();
double oldAvgDist = loopConn.getAvgDist();
loopConn.setAvgDist(((oldWeight - 1) * oldAvgDist + distance)
/ oldWeight);
loopConn.setWeight(oldWeight + 1);
}
for (AggNode node : changedAggPoints) {
node.refreshWeight();
}
// // add turns
List<AggNode> turnNodes = new ArrayList<AggNode>();
turnNodes.addAll(changedAggPoints);
if (turnNodes.size() > 1) {
turnNodes.remove(0);
turnNodes.remove(turnNodes.size() - 1);
}
turnNodes.add(0, inNode);
turnNodes.add(0, beforeNode);
turnNodes.add(outNode);
// AggNode node;
for (int i = 2; i < changedAggPoints.size(); i++) {
changedAggPoints.get(i - 1).addTurn(changedAggPoints.get(i - 2),
changedAggPoints.get(i - 0));
}
// clean like in the GPSCleaner
cleaner.clean(changedAggPoints);
// simplify
rdpf.simplifyAgg(changedAggPoints, aggContainer);
}
private void mergePointPair(AggNode aggNode, GPSPoint gpsPoint) {
double factor = gpsPoint.getWeight()
/ (aggNode.getWeight() + gpsPoint.getWeight()); // -1
// because
// it is
// already
// connected?
// factor = 0.5;
// // DEBUGGING
Float64Vector newPos = GPSCalc.getDistanceTwoPointsFloat64(aggNode)
.plus(GPSCalc.getDistanceTwoPointsFloat64(aggNode, gpsPoint)
.times(factor * 1 / aggNode.getK()));
logger.log(Level.FINER, "moving {0} {1}, {2} and {3}, {4} to {5}, {6}", new Object[]{aggNode, aggNode.getLat(), aggNode.getLon(), gpsPoint.getLat(), gpsPoint.getLon(), newPos.getValue(0), newPos.getValue(1)});
ILocation newPosCopy = new GPSPoint(newPos.getValue(0),
newPos.getValue(1));
aggNode.setK(aggNode.getK() + 1);
aggContainer.moveNodeTo(aggNode, newPosCopy);
}
@Override
public AggNode getInNode() {
return inNode;
}
@Override
public AggNode getOutNode() {
return outNode;
}
// TODO FAUL
@Override
public String toString() {
StringBuilder gps = new StringBuilder();
for (GPSPoint point : gpsPoints) {
gps.append(point).append(", ");
}
StringBuilder agg = new StringBuilder();
for (AggNode node : aggNodes) {
agg.append(node).append(", ");
}
return String.format("MergeHandler:\n\tGPS: %s\n\tAgg: %s", gps, agg);
}
@Override
public boolean isEmpty() {
return gpsPoints.isEmpty() && gpsPoints.isEmpty();
}
@Override
public void setBeforeNode(AggNode lastNode) {
this.beforeNode = lastNode;
}
@Override
public void addAggNodes(AggConnection bestConn) {
List<AggNode> agg = new ArrayList<AggNode>();
agg.add(bestConn.getFrom());
agg.add(bestConn.getTo());
addAggNodes(agg);
}
@Override
public void addGPSPoints(GPSEdge edge) {
List<GPSPoint> tra = new ArrayList<GPSPoint>();
tra.add(edge.getFrom());
tra.add(edge.getTo());
addGPSPoints(tra);
}
@Override
public IMergeHandler getCopy() {
WeightedClosestPointMerge object = new WeightedClosestPointMerge();
object.aggContainer = this.aggContainer;
object.setMaxLookahead(this.getMaxLookahead());
object.setMinContinuationAngle(this.getMinContinuationAngle());
object.setMaxPointGhostDist(this.getMaxPointGhostDist());
return object;
}
@Override
public List<ClassObjectEditor> getSettings() {
List<ClassObjectEditor> result = new ArrayList<ClassObjectEditor>();
result.add(new ClassObjectEditor(this, Arrays.asList(new String[]{
"aggContainer", "distance", "rdpf"})));
result.add(new ClassObjectEditor(this.rdpf));
return result;
}
@Override
public List<PointGhostPointPair> getPointGhostPointPairs() {
return null;
}
}