package business;
import com.vividsolutions.jts.geom.*;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureSource;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geometry.jts.FactoryFinder;
import org.geotools.geometry.jts.JTS;
import org.geotools.graph.build.feature.FeatureGraphGenerator;
import org.geotools.graph.build.line.LineStringGraphGenerator;
import org.geotools.graph.path.DijkstraShortestPathFinder;
import org.geotools.graph.path.Path;
import org.geotools.graph.structure.Graph;
import org.geotools.graph.structure.Node;
import org.geotools.graph.structure.basic.BasicEdge;
import org.geotools.graph.traverse.standard.DijkstraIterator.EdgeWeighter;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import utilities.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
public class CalculateRoutes {
private Graph networkGraph;
private LineStringGraphGenerator lineStringGen = new LineStringGraphGenerator();
private FeatureCollection<SimpleFeatureType, SimpleFeature> routeFeatures = FeatureCollections.newCollection();
private DijkstraShortestPathFinder shortestPathFinder;
private ArrayList<Double> Costs = new ArrayList<Double>();
private Double distanceOriginToGraph = 0.0;
private static CoordinateReferenceSystem coordinateReferenceSystem;
private NodeHelper nodeHelper = new NodeHelper();
// Compiles successfully when using geotools-2.7-RC2
// http://sourceforge.net/projects/geotools/files/GeoTools%202.7%20Releases/
public void buildNetwork(FeatureCollection networkFC, Point originPoint) throws TransformException {
coordinateReferenceSystem = networkFC.getSchema().getCoordinateReferenceSystem();
//get the object to generate the graph from line string objects
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 featureIterator = networkFC.features();
try {
while (featureIterator.hasNext()) {
Feature feature = featureIterator.next();
featureGen.add(feature);
}
} finally {
featureIterator.close();
}
//build the graph
networkGraph = featureGen.getGraph();
//find the node of the graph closest to the origin point and returns the node
Node source = nodeHelper.getNearestGraphNode(lineStringGen, networkGraph, originPoint, coordinateReferenceSystem);
//distance between the origin location and the nearest graph node
distanceOriginToGraph = nodeHelper.getDistanceFromGraphNode();
//weight the edges of the graph using the distance of each linestring
EdgeWeighter edgeWeighter = new EdgeWeighter() {
public double getWeight(org.geotools.graph.structure.Edge edge) {
SimpleFeature aLineString = (SimpleFeature) edge.getObject();
Geometry geom = (Geometry) aLineString.getDefaultGeometry();
return geom.getLength();
}
};
//calculate the cost(distance) of each graph node to the node closest to the origin
shortestPathFinder = new DijkstraShortestPathFinder(networkGraph, source, edgeWeighter);
shortestPathFinder.calculate();
}
public boolean calculateRouteFeature(Point originPoint, Point destinationPoint, int id) throws SchemaException, TransformException {
//this calculates the route and builds a FeatureCollection of route objects, to do this the geometries of the various
//linestrings contained within a path object (e.g. each road that makes up the route) must be merged to make one feature
//get the graph node closest to the destination point object
Node destination = nodeHelper.getNearestGraphNode(lineStringGen, networkGraph, destinationPoint, coordinateReferenceSystem);
//get the path (route) from origin to destination
Logger.d("Calc route feature, destination: " + destination);
Path path = shortestPathFinder.getPath(destination);
//Logger.d("Calc route feature, path: " + path);
//this happens if the closest node is the endpoint of a disconnected line
if (path == null) {
Logger.w("Could not calculate the route to this destination" +
". There is probably a problem with the network data set (dangles etc ...)");
return false;
}
//create a vector object to store all the edges
Vector result = new Vector();
Node previous = null;
Node node;
//iterate through the path object getting each node and it's neighbour and build edge objects from them
for (Iterator iterator = path.riterator(); iterator.hasNext();) {
node = (Node) iterator.next();
if (previous != null) {
// Adds the resulting edge into the vector
result.add(node.getEdge(previous));
}
previous = node;
}
//create an array to store the geometry of each Edge linestring that will need to be merged into one Route object
Geometry[] geomArray = new Geometry[result.size()];
//populate the geometry array with the route edges
for (int i = 0; i < result.size(); i++) {
BasicEdge eg = (BasicEdge) result.get(i);
SimpleFeature feature = (SimpleFeature) eg.getObject();
Geometry geom = (Geometry) feature.getDefaultGeometry();
geomArray[i] = geom;
}
//call the function to merge the geometries into one feature
Geometry routeGeometry = buildRouteGeometry(geomArray);
//checks to see if the Route object is valid, If the origin and destinaton are so close to each other
//that they share the same closest graph node then no path is created and the resulting geometry is null.
//This causes all sorts of errors when the FeatureStore is built i.e. it returns nothing when the
//getBounds() function is called on it which a map renderer needs to draw the layer. Therefore a straight line
//between the two objects is drawn and the cost is the straight line distance
if (shortestPathFinder.getCost(destination) != 0) {
//path is not null
Double distanceDestinationToGraph = nodeHelper.getDistanceFromGraphNode();
SimpleFeature routeFeature = buildNewRouteFeature(routeGeometry, shortestPathFinder, destination, distanceDestinationToGraph + distanceOriginToGraph, id);
routeFeatures.add(routeFeature);
Logger.i("Shortest path exists " + routeFeatures.size());
} else {
Logger.w("Shortest path NULL");
//path is null
SimpleFeature routeFeature = buildNullFeature(originPoint, destinationPoint, id);
routeFeatures.add(routeFeature);
}
return true;
}
//function that combines the geometries of the path into one single route geometry
private Geometry buildRouteGeometry(Geometry[] geomArray) {
Geometry all = null;
int length = geomArray.length;
Geometry geomToAddTemp;
for (int i = 0; i < length; i++) {
geomToAddTemp = geomArray[i];
if (all == null) {
all = geomToAddTemp;
} else {
all = all.union(geomToAddTemp);
}
}
return all;
}
//function that builds the route feature
private static SimpleFeature buildNewRouteFeature(Geometry routeGeometry, DijkstraShortestPathFinder dijkstraShortestPathFinder,
Node destination, Double distanceToGraph, int objectId) throws SchemaException {
//builds a feature containing geometry, Cost and ID of the Route with a Swiss SRID
final SimpleFeatureType TYPE = DataUtilities.createType("Location",
"the_geom:MultiLineString:srid=4326," + // <- the geometry attribute: Polyline type
"travel_distance_m:Double," + "objectid:Integer"// <- a String attribute
);
//populates the new feature object, builds it and returns it
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
featureBuilder.add(routeGeometry);
//Cost is calculates here using the cost (Distance) of the route plus the straight line distances between the closest graph nodes
//and the origin and destination points
featureBuilder.add(dijkstraShortestPathFinder.getCost(destination) + distanceToGraph);
featureBuilder.add(objectId);
return featureBuilder.buildFeature(null);
}
private static SimpleFeature buildNullFeature(Point originPoint, Point destinationPoint, int objectId) throws SchemaException, TransformException {
//builds a feature containing geometry, Cost and ID of the Route with a Swiss SRID when no path is returned because the objects are too close
final SimpleFeatureType TYPE = DataUtilities.createType("Location",
"the_geom:MultiLineString:srid=4326," + // <- the geometry attribute: Polyline type
"travel_distance_m:Double," + "objectid:String"// <- a String attribute
);
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
//builds the line between the two locations
GeometryFactory factory = FactoryFinder.getGeometryFactory(null);
Coordinate[] coordList = new Coordinate[2];
coordList[0] = new Coordinate(originPoint.getX(), originPoint.getY());
coordList[1] = new Coordinate(destinationPoint.getX(), destinationPoint.getY());
LineString routeLs = factory.createLineString(coordList);
//populates the new feature object, builds it and returns it
featureBuilder.add(routeLs);
featureBuilder.add(calculateDistance(originPoint.getX(), originPoint.getY(), destinationPoint.getCoordinate().x, destinationPoint.getCoordinate().y, coordinateReferenceSystem));
featureBuilder.add(objectId);
return featureBuilder.buildFeature(null);
}
public FeatureSource<SimpleFeatureType, SimpleFeature> getRouteFeatureSource() throws IOException {
// returns the feature collection of routes as a feature source
return DataUtilities.source(routeFeatures);
}
public void calculateCostArrayList(Point originPoint, Point destinationPoint) throws SchemaException, TransformException {
//calculates the cost between an origin and destination point
Node destination = nodeHelper.getNearestGraphNode(lineStringGen, networkGraph, destinationPoint, coordinateReferenceSystem);
Costs.add(shortestPathFinder.getCost(destination));
}
public ArrayList<Double> getCostArray() {
//returns the costarray
return (ArrayList<Double>) Costs.clone();
}
private static double calculateDistance(double xOrig, double yOrig, double xDest, double yDest,
CoordinateReferenceSystem crs) throws TransformException {
// Calculates straight line distance
Coordinate start = new Coordinate(xOrig, yOrig);
Coordinate end = new Coordinate(xDest, yDest);
double distance = JTS.orthodromicDistance(start, end, crs);
Logger.i("Straight distance: " + distance);
return distance;
}
}