The gt-graph package defines the concept of a graph (or network) made up of geotools Features.
Maven:
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-graph</artifactId>
<version>${geotools.version}</version>
</dependency>
The graph module provides a convenient, flexible and efficient API for graph construction and query. In additional to generic Graph support, it implements directed and undirected networks.
This code was originally developed at Refractions Research and has been used in Jump before being ported to GeoTools.
Use of GeoTools
The Graph module makes use of concepts and (classes) from the GeoTools core:
In addition to the Feature API from core, the graph module makes use of relationships. Usually relationships are based on spatial comparisons between features, although you may be able to quickly establish a relationship using your feature attributes.
Graph constructed from LineStrings based on “shared end points”
Example a road network used for navigation.
Graph constructed from Polygons based on “touches”
Example graph of watersheds used for ground water analysis.
The basic layout of a Graph is a collection of edges joined together at nodes.
Graph
The core graph data structure is a collection of nodes and edges, a range of methods are available allowing access using visitors.
Node
Represents a node in the graph.
Edge
Represents the graph edge between two nodes; with a range of methods to both access and compare.
Graphable
We store the following common information for both nodes and edges:
visited: Used during traversal to mark visited components
count: In a similar fashion count is stored
object: Used to store the Feature used during creation.
Depending on the graph constructed you may end up storing the features along the edges (say for a road network) or at each node (say for a watershed network).
A Graph supports simple direct access is supported using:
You can also pass in a visitor in order to traverse the graph contents:
The visitor can also control when to stop the process using:
Graph.queryNodes( visitor )
Graph.queryEdges( visitor )
To provide control your visitor can indicate when it wants to stop, backtrack or continue using:
An alternative to direct access is the configure a GraphTraversal using:
GraphIterator: iterator specifying the order in which to visit components of the graph during the traversal.
GraphWalker: walker being iterated over the graph (usually to accomplish a specific goal)
GeoTools provides out of the box implementations for many common problems: finding the the shortest path, partition a graph into sections, or visiting all the elements.
We also have a straight extension of these ideas to represent a directed graph in which each edge has a direction in which it can be traversed. This is the difference between considering each edge a simple connection vs thinking of each edge as an arrow.
At a low level graph creation is handled using a GraphBuilder. We have a range of implementations available. The important point is that you control them by calling buildNode, and buildEdge repeatedly allowing it to build up an internal representation of your Graph.
When you are satisfied with the result you can call getGraph() to retrieve the result.
final LineGraphGenerator generator = new BasicLineGraphGenerator(); SimpleFeatureCollection fc = featureSource.getFeatures(); fc.accepts(new FeatureVisitor() { public void visit(Feature feature) { generator.add(feature); } }, null); Graph graph = generator.getGraph();
To make use of your graph we will use a GraphVisitor:
The following OrphanVistor is called for “each” GraphComponent where it has a chance to check if the GraphComponent is an orphan (ie has no relationships) or not.
class OrphanVisitor implements GraphVisitor { private int count = 0; public int getCount() { return count; } public int visit(Graphable component) { Iterator related = component.getRelated(); if( related.hasNext() == false ){ // no related components makes this an orphan count++; } return GraphTraversal.CONTINUE; } } OrphanVisitor graphVisitor = new OrphanVisitor(); SimpleGraphWalker sgv = new SimpleGraphWalker(graphVisitor); GraphIterator iterator = new BreadthFirstIterator(); BasicGraphTraversal bgt = new BasicGraphTraversal(graph, sgv, iterator); bgt.traverse(); System.out.println("Found orphans: " + graphVisitor.getCount());
For those familiar with the Builder Pattern (GOF Design Patterns) this will look familiar.
The other approach is we have a number of generators which will automatically create a Graph for you based on information you feed in. The GraphGenerators use a GraphBuilder to build up each node and edge internally; so you will need to be careful to construct them with the correct builder for the problem you are wishing to solve.
Each one of these implementations is set up to handle different kinds of data (Features, LineStrings, etc...) so please be sure to read the javadocs.
We have a number of generators that can be used to process a feature collection in different ways in order to build up an appropriate Graph.
This example can be used if you want to build a graph from a feature collection made up of linear features:
// get a feature collection somehow
SimpleFeatureCollection fCollection = featureSource.getFeatures();
//create a linear graph generate
LineStringGraphGenerator lineStringGen = new LineStringGraphGenerator();
//wrap it in a feature graph generator
FeatureGraphGenerator featureGen = new FeatureGraphGenerator( lineStringGen );
//throw all the features into the graph generator
FeatureIterator iter = fCollection.features();
try {
while(iter.hasNext()){
Feature feature = iter.next();
featureGen.add( feature );
}
} finally {
iter.close();
}
Graph graph = featureGen.getGraph()
This example can be used to build a graph from just a set of line segments:
//we have some line segments
LineSegment[] lines = ...
//create the graph generator
BasicLineGraphGenerator graphGen = new BasicLineGraphGenerator();
//add the lines to the graph
for ( int i = 0; i < lines.length; i++ ) {
graphGen.add( lines[i] );
}
Graph graph = graphGen.getGraph()
Once the graph is built each, edge#getObject() will hold the original feature used to built it.
You can traverse your graph and build up FeatureCollection as you go.:
SimpleFeatureCollection features = FeatureCollections.newInstance();
for ( Iterator e = graph.getEdges().iterator(); e.hasNext(); ) {
Edge edge = (Edge) e.next();
SimpleFeature feature = (SimpleFeature) e.getObject();
features.add( feature );
}
We have a number of ways to calculate the shortest path between two nodes:
The class DijkstraShortestPathFinder can be used to calculate a path using Dijkstra’s Shortest Path algorithm.:
//reference to a graph, already built
Graph graph = ...see above...
//find a source node (usually your user chooses one)
Node start = ..
// create a strategy for weighting edges in the graph
// in this case we are using geometry length
DijkstraIterator.EdgeWeigter weighter = new DijkstraIterator.EdgeWeighter() {
public double getWeight(Edge e) {
SimpleFeature feature = (SimpleFeature) e.getObject();
Geometry geometry = (Geometry) feature.getDefaultGeometry();
return gometry.getLength();
}
}
// Create GraphWalker - in this case DijkstraShortestPathFinder
DijkstraShortestPathFinder pf = new DijkstraShortestPathFinder( graph, start, weighter );
pf.calculate();
//find some destinations to calculate paths to
List/*<Node>*/ destinations = ...
//calculate the paths
for ( Iterator d = destinations.iterator(); d.hasNext(); ) {
Node destination = (Node) d.next();
Path path = pf.getPath( destination );
//do something with the path
}
AStarShortestPathFinder can be used in a similar fashion (and is often quicker)