/**
* This file is part of Relation Analyzer for OSM.
* Copyright (c) 2001 by Adrian Stabiszewski, as@grundid.de
*
* Relation Analyzer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Relation Analyzer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Relation Analyzer. If not, see <http://www.gnu.org/licenses/>.
*/
package org.osmtools.ra.graph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.osmtools.ra.data.Node;
import org.osmtools.ra.segment.ConnectableSegment;
import org.osmtools.ra.segment.SegmentNodes;
/**
* <p>
* GraphCreator creates a graph of nodes and returns the leaves of this graph. The leaves are nodes with only one edge.
* They are basically the entry points to the graph.
* </p>
*
* <p>
* The edges of graph contain only the nodes that are needed to connect the two nodes of the edge. This has only a
* meaning if one deals with a roundabout. The edge will only contain the nodes between the entry and exit point of the
* roundabout.
* </p>
*
* <p>
* If the web is some kind of a interconnected ring, where all nodes are connected with at least two edges, the web
* creator will return one leaf. It will be the single entry point to the ring.
* </p>
*
* <p>
* It is for the traverser to generate a useful route through the web. Returning the leaves gives the traverser a chance
* to traverse from A to B.
* </p>
*
* <p>
* The analyzer uses the amount of leaves to decide if the relation is OK. For example for a route relation two leaves
* are expected.
* </p>
*
*/
public class GraphCreator {
private Map<Node, IntersectionNode> knownNodes = new HashMap<Node, IntersectionNode>();
private List<ConnectableSegment> segments;
private Collection<Edge> edges = new ArrayList<Edge>();
public GraphCreator(List<ConnectableSegment> segments) {
this.segments = segments;
generateGraph();
}
public Graph getGraph() {
Set<IntersectionNode> leaves = new HashSet<IntersectionNode>();
for (IntersectionNode node : knownNodes.values()) {
if (node.isLeaf())
leaves.add(node);
}
if (leaves.isEmpty() && !knownNodes.isEmpty())
leaves.add(knownNodes.values().iterator().next());
double length = sumSegmentLength();
return new Graph(leaves, edges, length);
}
private double sumSegmentLength() {
double sum = 0;
for (ConnectableSegment connectableSegment : segments) {
sum += connectableSegment.getLength();
}
return sum;
}
private void generateGraph() {
for (ConnectableSegment segment : segments) {
List<ConnectableSegment> connectingSegments = findConnectingSegments(segment);
if (connectingSegments.isEmpty())
handleSingleSegmentGraph(segment);
else if (connectingSegments.size() == 1) {
createLeafEdge(segment);
}
else {
for (ConnectableSegment firstSegment : connectingSegments) {
for (ConnectableSegment secondSegment : connectingSegments.subList(
connectingSegments.indexOf(firstSegment) + 1, connectingSegments.size())) {
Set<Node> commonNodes1 = findCommonNode(segment, firstSegment);
Set<Node> commonNodes2 = findCommonNode(segment, secondSegment);
if (commonNodes1.containsAll(commonNodes2) && commonNodes1.size() == 1) {
createLeafEdge(segment);
}
else {
if (commonNodes1.size() == 1) {
createEdges(segment, commonNodes1.iterator().next(), commonNodes2);
}
else if (commonNodes2.size() == 1) {
createEdges(segment, commonNodes2.iterator().next(), commonNodes1);
}
else {
// TODO: cannot handle this.
/* Multiple common nodes from several segments. We would need to get the
* common nodes in order along the current segment and then chop the
* segment from node to node. Avoid creating edges that "jump" across
* common nodes */
}
}
}
}
}
}
}
private void createEdges(ConnectableSegment segment, Node node, Set<Node> commonNodes) {
for (Node commonNode : commonNodes) {
createEdge(segment, node, commonNode);
}
}
private void handleSingleSegmentGraph(ConnectableSegment segment) {
SegmentNodes segmentNodes = segment.getSegmentNodes();
createEdge(segment, segmentNodes.getThisNode(), segmentNodes.getOtherNode());
}
private void createLeafEdge(ConnectableSegment segment) {
SegmentNodes segmentNodes = segment.getSegmentNodes();
createEdge(segment, segmentNodes.getThisNode(), segmentNodes.getOtherNode());
}
private void createEdge(ConnectableSegment segment, Node firstNode, Node secondNode) {
IntersectionNode firstIntersectionNode = createIntersectionNode(firstNode);
IntersectionNode secondIntersectionNode = createIntersectionNode(secondNode);
firstIntersectionNode.addEdge(secondIntersectionNode);
secondIntersectionNode.addEdge(firstIntersectionNode);
if (segment.canConnectNodesInDirection(firstNode, secondNode)) {
addEdge(firstIntersectionNode, secondIntersectionNode);
}
if (segment.canConnectNodesInDirection(secondNode, firstNode)) {
addEdge(secondIntersectionNode, firstIntersectionNode);
}
}
private void addEdge(IntersectionNode intersectionNode1, IntersectionNode intersectionNode2) {
Edge edge = new Edge(intersectionNode1, intersectionNode2);
edges.add(edge);
}
private Set<Node> findCommonNode(ConnectableSegment segment, ConnectableSegment segmentToConntent) {
return segment.getCommonNode(segmentToConntent);
}
private List<ConnectableSegment> findConnectingSegments(ConnectableSegment segmentToConnect) {
List<ConnectableSegment> result = new ArrayList<ConnectableSegment>();
for (ConnectableSegment segment : segments) {
if (segment != segmentToConnect && segment.canConnect(segmentToConnect))
result.add(segment);
}
return result;
}
private IntersectionNode createIntersectionNode(Node node) {
IntersectionNode intersectionNode = knownNodes.get(node);
if (intersectionNode == null) {
intersectionNode = new IntersectionNode(node);
knownNodes.put(node, intersectionNode);
}
return intersectionNode;
}
}