/**
* Copyright (C) 2012 Google, Inc.
*
* 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 org.onebusaway.uk.network_rail.gtfs_realtime.graph;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import javax.inject.Inject;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.Parser;
import org.apache.commons.cli.PosixParser;
import org.onebusaway.collections.Min;
import org.onebusaway.uk.atoc.timetable_parser.StationElement;
import org.onebusaway.uk.network_rail.gtfs_realtime.NetworkRailGtfsRealtimeModule;
import org.onebusaway.uk.network_rail.gtfs_realtime.TimetableService;
import org.onebusaway.uk.network_rail.gtfs_realtime.graph.RailwayGraph.Node;
import org.onebusaway.uk.network_rail.gtfs_realtime.graph.RailwayGraph.RailwayPath;
import org.onebusaway.uk.network_rail.gtfs_realtime.graph.RawGraph.BerthPath;
import org.onebusaway.uk.parser.ProjectionSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scala.actors.threadpool.Arrays;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
public class PositionBerthToStanoxGraphMain {
private static Logger _log = LoggerFactory.getLogger(PositionBerthToStanoxGraphMain.class);
private static final String ARG_ATOC_TIMETABLE_PATH = "atocTimetablePath";
private static final String ARG_GRAPH_PATH = "graphPath";
private static final String ARG_STATIONS_PATH = "stationsPath";
private static final String ARG_OSM_SHAPES_PATH = "osmShapesPath";
public static void main(String[] args) throws ParseException, IOException {
PositionBerthToStanoxGraphMain m = new PositionBerthToStanoxGraphMain();
m.run(args);
}
private RawGraph _graph = new RawGraph();
private TimetableService _timetableService;
private RailwayShapeService _railwayShapeService;
private GraphLayout _graphLayout;
private Map<RawStanoxNode, Location> _stanoxLocations = new HashMap<RawStanoxNode, Location>();
private Map<RawBerthNode, Location> _berthNodesToLocations = new HashMap<RawBerthNode, Location>();
private Map<RawBerthNode, List<Point2D.Double>> _berthNodesToPotentialLocations = new HashMap<RawBerthNode, List<Point2D.Double>>();
@Inject
public void setTimetableService(TimetableService timetableService) {
_timetableService = timetableService;
}
@Inject
public void setRailwayShapeService(RailwayShapeService railwayShapeService) {
_railwayShapeService = railwayShapeService;
}
@Inject
public void setGraphLayout(GraphLayout graphLayout) {
_graphLayout = graphLayout;
}
private void run(String[] args) throws ParseException, IOException {
Options options = new Options();
buildOptions(options);
Parser parser = new PosixParser();
CommandLine cli = parser.parse(options, args);
Set<Module> modules = new HashSet<Module>();
NetworkRailGtfsRealtimeModule.addModuleAndDependencies(modules);
Injector injector = Guice.createInjector(modules);
injector.injectMembers(this);
_timetableService.readScheduleData(new File(
cli.getOptionValue(ARG_ATOC_TIMETABLE_PATH)));
if (cli.hasOption(ARG_STATIONS_PATH)) {
_timetableService.readStationLocations(new File(
cli.getOptionValue(ARG_STATIONS_PATH)));
}
if (cli.hasOption(ARG_OSM_SHAPES_PATH)) {
_railwayShapeService.readOsmShapeData(new File(
cli.getOptionValue(ARG_OSM_SHAPES_PATH)));
}
_graph.read(new File(cli.getOptionValue(ARG_GRAPH_PATH)));
buildStanoxLocations();
setBerthLocationsFromStanox();
loop();
exportLocationsToKml();
}
private Location averagePoints(List<Point2D.Double> points) {
double xTotal = 0;
double yTotal = 0;
for (Point2D.Double point : points) {
xTotal += point.x;
yTotal += point.y;
}
Location l = new Location();
l.x = xTotal / points.size();
l.y = yTotal / points.size();
Point2D.Double p = ProjectionSupport.convertToLatLon(l.x, l.y);
l.lat = p.y;
l.lon = p.x;
return l;
}
private void buildOptions(Options options) {
options.addOption(ARG_ATOC_TIMETABLE_PATH, true, "atoc timetable path");
options.addOption(ARG_GRAPH_PATH, true, "graph path");
options.addOption(ARG_STATIONS_PATH, true, "");
options.addOption(ARG_OSM_SHAPES_PATH, true, "");
}
private void buildStanoxLocations() {
long stanoxCount = 0;
long stanoxWithLocation = 0;
for (RawNode node : _graph.getNodes()) {
if (node instanceof RawStanoxNode) {
stanoxCount++;
RawStanoxNode stanoxNode = (RawStanoxNode) node;
Point2D.Double point = _timetableService.getBestLocationForStanox(stanoxNode.getId().getStanox());
if (point != null) {
Location location = new Location(point);
_stanoxLocations.put(stanoxNode, location);
stanoxWithLocation++;
}
}
}
_log.info("stanox with location=" + stanoxWithLocation + "/" + stanoxCount);
}
private void setBerthLocationsFromStanox() {
long total = 0;
long hits = 0;
for (RawBerthNode node : _graph.getBerthNodes()) {
if (node.getId().toString().equals("WH_0347-WH_COUT")) {
System.out.println("here");
}
Location location = getStanoxLocationForBerth(node);
if (location != null) {
hits++;
_berthNodesToLocations.put(node, location);
}
total++;
}
_log.info("berth nodes attached directly to stanox=" + hits + "/" + total);
}
private Location getStanoxLocationForBerth(RawBerthNode node) {
for (RawStanoxNode stanoxNode : node.getStanox()) {
Location location = _stanoxLocations.get(stanoxNode);
if (location != null) {
return location;
}
}
return null;
}
private void loop() {
while (true) {
_log.info("nodes with locations=" + _berthNodesToLocations.size());
interpolateBerthLocations();
if (_berthNodesToPotentialLocations.isEmpty()) {
return;
}
averageBerthLocations();
}
}
private void interpolateBerthLocations() {
int index = 0;
for (RawBerthNode rootNode : _berthNodesToLocations.keySet()) {
if (index % 100 == 0) {
_log.info("node=" + index + "/"
+ _berthNodesToLocations.keySet().size());
}
index++;
Location fromLocation = _berthNodesToLocations.get(rootNode);
Queue<OrderedRawBerthNode> queue = new PriorityQueue<OrderedRawBerthNode>();
queue.add(new OrderedRawBerthNode(rootNode, null, 0.0));
Map<RawBerthNode, RawBerthNode> parents = new HashMap<RawBerthNode, RawBerthNode>();
Set<RawBerthNode> visited = new HashSet<RawBerthNode>();
while (!queue.isEmpty()) {
OrderedRawBerthNode currentNode = queue.poll();
RawBerthNode node = currentNode.getNode();
if (!visited.add(node)) {
continue;
}
parents.put(node, currentNode.getParent());
Location toLocation = _berthNodesToLocations.get(node);
if (currentNode.getParent() != null && toLocation != null) {
List<RawBerthNode> path = new ArrayList<RawBerthNode>();
RawBerthNode last = node;
while (last != null) {
path.add(last);
last = parents.get(last);
}
if (path.size() <= 2) {
break;
}
Collections.reverse(path);
BerthPath berthPath = new BerthPath(path, currentNode.getDistance());
double d = fromLocation.getDistance(toLocation);
if (d > 30000) {
continue;
}
RailwayPath railwayPath = _railwayShapeService.getPath(
fromLocation.getPoint(), toLocation.getPoint());
if (railwayPath != null) {
snapBerthsToRailwayPath(berthPath, railwayPath);
}
break;
} else {
for (Map.Entry<RawBerthNode, List<Integer>> entry : node.getOutgoing().entrySet()) {
RawBerthNode outgoing = entry.getKey();
int avgDuration = RawNode.average(entry.getValue());
queue.add(new OrderedRawBerthNode(outgoing, node,
currentNode.getDistance() + avgDuration));
}
}
}
}
}
private void averageBerthLocations() {
for (Map.Entry<RawBerthNode, List<Point2D.Double>> entry : _berthNodesToPotentialLocations.entrySet()) {
Location p = averagePoints(entry.getValue());
_berthNodesToLocations.put(entry.getKey(), p);
}
_berthNodesToPotentialLocations.clear();
}
private void explore2(RawStanoxNode stanoxNode) {
Location p = _stanoxLocations.get(stanoxNode);
if (p == null) {
return;
}
for (RawNode edge : stanoxNode.getOutgoing().keySet()) {
if (!(edge instanceof RawStanoxNode)) {
continue;
}
RawStanoxNode nearbyStanoxNode = (RawStanoxNode) edge;
Location pTo = _stanoxLocations.get(nearbyStanoxNode);
if (pTo == null) {
continue;
}
double distance = p.getDistance(pTo);
if (distance > 50000) {
continue;
}
_log.info("from=" + stanoxNode + " to=" + nearbyStanoxNode + " distance="
+ distance);
_log.info(p.lat + " " + p.lon + " " + pTo.lat + " " + pTo.lon);
BerthPath berthPath = getShortestPathBetweenNodes(stanoxNode,
nearbyStanoxNode, distance);
if (berthPath != null) {
_log.info("railway path");
RailwayPath railwayPath = _railwayShapeService.getPath(p.getPoint(),
pTo.getPoint());
if (railwayPath != null) {
snapBerthsToRailwayPath(berthPath, railwayPath);
}
}
}
}
private BerthPath getShortestPathBetweenNodes(RawStanoxNode fromStanoxNode,
RawStanoxNode toStanoxNode, double distance) {
double maxTime = (distance * 1.5 /* meters */) / (50 /* m/s */);
Min<BerthPath> minPath = new Min<RawGraph.BerthPath>();
for (RawBerthNode fromNode : fromStanoxNode.getBerthConnections()) {
for (RawBerthNode toNode : toStanoxNode.getBerthConnections()) {
BerthPath path = _graph.getShortestPath(fromNode, toNode, maxTime);
if (path != null) {
minPath.add(path.distance, path);
}
}
}
return minPath.getMinElement();
}
private void snapBerthsToRailwayPath(BerthPath berthPath,
RailwayPath railwayPath) {
double[] relativeBerthDistance = computeRelativeDistance(berthPath);
double[] relativeShapeDistance = computeRelativeDistance(railwayPath);
for (int i = 0; i < relativeBerthDistance.length; ++i) {
double d = relativeBerthDistance[i];
Point2D.Double p = getBestPoint(railwayPath.nodes, relativeShapeDistance,
d);
RawBerthNode node = berthPath.nodes.get(i);
List<Point2D.Double> locations = _berthNodesToPotentialLocations.get(node);
if (locations == null) {
locations = new ArrayList<Point2D.Double>();
_berthNodesToPotentialLocations.put(node, locations);
}
locations.add(p);
}
}
private Point2D.Double getBestPoint(List<Node> nodes,
double[] relativeShapeDistance, double d) {
int index = Arrays.binarySearch(relativeShapeDistance, d);
if (index >= 0) {
return nodes.get(index).getPoint();
}
if (index < 0) {
index = -(index + 1);
}
if (index == 0) {
return nodes.get(0).getPoint();
}
if (index >= nodes.size()) {
return nodes.get(nodes.size() - 1).getPoint();
}
Node nodeFrom = nodes.get(index - 1);
Node nodeTo = nodes.get(index);
double dFrom = relativeShapeDistance[index - 1];
double dTo = relativeShapeDistance[index];
double ratio = (d - dFrom) / (dTo - dFrom);
double x = nodeFrom.getX() + ratio * (nodeTo.getX() - nodeFrom.getX());
double y = nodeFrom.getY() + ratio * (nodeTo.getY() - nodeFrom.getY());
return new Point2D.Double(x, y);
}
private double[] computeRelativeDistance(BerthPath berthPath) {
double[] distances = new double[berthPath.nodes.size()];
double totalDistance = berthPath.distance;
double cumulativeDistance = 0.0;
for (int i = 0; i < berthPath.nodes.size(); ++i) {
RawBerthNode node = berthPath.nodes.get(i);
if (i > 0) {
RawBerthNode prev = berthPath.nodes.get(i - 1);
double duration = prev.getOutgoingAverageDuration(node);
cumulativeDistance += duration;
}
distances[i] = cumulativeDistance / totalDistance;
}
return distances;
}
private double[] computeRelativeDistance(RailwayPath railwayPath) {
double[] distances = new double[railwayPath.nodes.size()];
double totalDistance = railwayPath.distance;
double cumulativeDistance = 0.0;
for (int i = 0; i < railwayPath.nodes.size(); ++i) {
RailwayGraph.Node node = railwayPath.nodes.get(i);
if (i > 0) {
RailwayGraph.Node prev = railwayPath.nodes.get(i - 1);
double distance = prev.getDistance(node);
cumulativeDistance += distance;
}
distances[i] = cumulativeDistance / totalDistance;
}
return distances;
}
private void explore(RawStanoxNode stanoxNode) {
Set<RawBerthNode> connections = stanoxNode.getBerthConnections();
if (connections.isEmpty()) {
return;
}
explore(connections, stanoxNode);
}
private void explore(Set<RawBerthNode> connections, RawStanoxNode stanoxNode) {
Queue<OrderedRawBerthNode> queue = new PriorityQueue<OrderedRawBerthNode>();
Set<RawBerthNode> visited = new HashSet<RawBerthNode>();
int openCount = 0;
for (RawBerthNode connection : connections) {
queue.add(new OrderedRawBerthNode(connection, null, 0.0));
openCount++;
}
Map<RawBerthNode, RawBerthNode> parents = new HashMap<RawBerthNode, RawBerthNode>();
while (!queue.isEmpty()) {
OrderedRawBerthNode currentNode = queue.poll();
RawBerthNode node = currentNode.getNode();
boolean isOpen = currentNode.isOpen();
if (isOpen) {
openCount--;
} else if (openCount == 0) {
return;
}
if (visited.contains(node)) {
continue;
}
visited.add(node);
parents.put(node, currentNode.getParent());
Set<RawStanoxNode> stanoxes = node.getStanox();
if (stanoxes.size() > 0 && !stanoxes.contains(stanoxNode)) {
_log.info(node + " stanoxes=" + stanoxes + " "
+ currentNode.getDistance() + " open=" + openCount);
RawBerthNode c = node;
while (c != null) {
_log.info(" " + c);
c = parents.get(c);
}
isOpen = false;
}
for (Map.Entry<RawBerthNode, List<Integer>> entry : node.getOutgoing().entrySet()) {
RawBerthNode outgoing = entry.getKey();
int avgDuration = RawNode.average(entry.getValue());
queue.add(new OrderedRawBerthNode(outgoing, node,
currentNode.getDistance() + avgDuration, isOpen));
if (isOpen) {
openCount++;
}
}
}
}
private void fillLocationsForKnownStanox(
Map<RawBerthNode, Location> nodesToLocations) {
}
private Location getLocationForStanox(int stanox) {
double xTotal = 0;
double yTotal = 0;
int count = 0;
for (String tiploc : _timetableService.getTiplocsForStanox(stanox)) {
StationElement station = _timetableService.getStationForTiploc(tiploc);
if (station != null) {
xTotal += station.getEasting();
yTotal += station.getNorthing();
count++;
}
}
if (count == 0) {
return null;
}
Location location = new Location();
location.fixed = true;
location.x = xTotal / count;
location.y = yTotal / count;
return location;
}
private void fillLocationsForUnknownStanox(
Map<RawBerthNode, Location> nodesToLocations) {
int total = 0;
int problem = 0;
Map<RawBerthNode, Location> updates = new HashMap<RawBerthNode, Location>();
_log.info("updates=" + updates.size());
nodesToLocations.putAll(updates);
}
private Map<Location, Integer> getNearbyNodesWithLocation(
Map<RawBerthNode, Location> nodesToLocations, RawBerthNode source,
int minCount) {
Map<Location, Integer> locationsAndTime = new HashMap<Location, Integer>();
PriorityQueue<OrderedNode> queue = new PriorityQueue<OrderedNode>();
queue.add(new OrderedNode(source, 0));
Set<RawBerthNode> visited = new HashSet<RawBerthNode>();
visited.add(source);
Map<RawBerthNode, Integer> minTimeToSource = new HashMap<RawBerthNode, Integer>();
while (!queue.isEmpty()) {
OrderedNode orderedNode = queue.poll();
RawBerthNode node = orderedNode.node;
if (minTimeToSource.containsKey(node)) {
continue;
}
int time = orderedNode.value;
minTimeToSource.put(node, time);
if (nodesToLocations.containsKey(node)) {
locationsAndTime.put(nodesToLocations.get(node), time);
if (locationsAndTime.size() >= minCount) {
return locationsAndTime;
}
}
for (Edge edge : node.getEdges()) {
RawBerthNode to = edge.getTo();
int proposedTime = edge.getAverageDuration() + time;
if (!minTimeToSource.containsKey(to)) {
queue.add(new OrderedNode(to, proposedTime));
}
}
}
return locationsAndTime;
}
private void exportLocationsToKml() throws FileNotFoundException {
PrintWriter out = new PrintWriter(
"/Users/bdferris/Documents/uk-rail/graph.kml");
out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
out.println("<kml xmlns=\"http://www.opengis.net/kml/2.2\">");
out.println("<Document>");
out.println(" <Style id=\"stanox\">");
out.println(" <IconStyle>");
out.println(" <color>ff000000</color>");
out.println(" </IconStyle>");
out.println(" </Style>");
for (Map.Entry<RawStanoxNode, Location> entry : _stanoxLocations.entrySet()) {
RawStanoxNode node = entry.getKey();
Location location = entry.getValue();
out.println(" <Placemark>");
out.println(" <name>" + node.getId() + "</name>");
out.println(" <styleUrl>stanox</styleUrl>");
out.println(" <Point>");
out.println(" <coordinates>" + location.lon + "," + location.lat
+ ",0</coordinates>");
out.println(" </Point>");
out.println(" </Placemark>");
}
for (Map.Entry<RawBerthNode, Location> entry : _berthNodesToLocations.entrySet()) {
RawBerthNode node = entry.getKey();
Location location = entry.getValue();
out.println(" <Placemark>");
out.println(" <name>" + node.getId() + "</name>");
out.println(" <MultiGeometry>");
out.println(" <Point>");
out.println(" <coordinates>" + location.lon + "," + location.lat
+ ",0</coordinates>");
out.println(" </Point>");
for (RawBerthNode edge : node.getOutgoing().keySet()) {
Location edgeLocation = _berthNodesToLocations.get(edge);
if (edgeLocation != null) {
out.println(" <LineString>");
out.println(" <coordinates>" + location.lon + ","
+ location.lat + ",0 " + edgeLocation.lon + ","
+ edgeLocation.lat + ",0</coordinates>");
out.println(" </LineString>");
}
}
out.println(" </MultiGeometry>");
out.println(" </Placemark>");
}
out.println("</Document>");
out.println("</kml>");
out.close();
// RailwayGraph graph = _railwayShapeService.getGraph();
// Set<RailwayGraph.Node> remaining = new HashSet<RailwayGraph.Node>(
// graph.getNodes());
// while (!remaining.isEmpty()) {
// Node node = remaining.iterator().next();
// while (true) {
// if (!remaining.remove(node)) {
// break;
// }
// }
// List<Node> path = exploreRailwayPath(node, remaining);
// }
}
private Location computeCentroid(Set<Location> locations) {
double xTotal = 0;
double yTotal = 0;
for (Location location : locations) {
xTotal += location.x;
yTotal += location.y;
}
Location location = new Location();
location.x = xTotal / locations.size();
location.y = yTotal / locations.size();
return location;
}
private static class OrderedNode implements Comparable<OrderedNode> {
public final RawBerthNode node;
public final int value;
public OrderedNode(RawBerthNode node, int value) {
this.node = node;
this.value = value;
}
@Override
public int compareTo(OrderedNode o) {
return Double.compare(this.value, o.value);
}
}
}