/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.graph_builder;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.geotools.referencing.GeodeticCalculator;
import org.onebusaway.gtfs.model.Trip;
import org.opentripplanner.common.IterableLibrary;
import org.opentripplanner.routing.edgetype.PatternHop;
import org.opentripplanner.routing.edgetype.TableTripPattern;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.vertextype.StreetVertex;
import org.opentripplanner.routing.vertextype.TransitStop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.csvreader.CsvWriter;
import com.google.common.collect.Multiset;
import com.google.common.collect.TreeMultiset;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.linearref.LinearLocation;
import com.vividsolutions.jts.linearref.LocationIndexedLine;
public class GraphStats {
private static final Logger LOG = LoggerFactory.getLogger(GraphStats.class);
@Parameter(names = { "-v", "--verbose" }, description = "Verbose output")
private boolean verbose = false;
@Parameter(names = { "-d", "--debug"}, description = "Debug mode")
private boolean debug = false;
@Parameter(names = { "-h", "--help"}, description = "Print this help message and exit", help = true)
private boolean help;
@Parameter(names = { "-g", "--graph"}, description = "path to the graph file", required = true)
private String graphPath;
@Parameter(names = { "-o", "--out"}, description = "output file")
private String outPath;
private CommandEndpoints commandEndpoints = new CommandEndpoints();
private CommandSpeedStats commandSpeedStats = new CommandSpeedStats();
private CommandPatternStats commandPatternStats = new CommandPatternStats();
private JCommander jc;
private Graph graph;
private CsvWriter writer;
public static void main(String[] args) {
GraphStats graphStats = new GraphStats(args);
graphStats.run();
}
private GraphStats(String[] args) {
jc = new JCommander(this);
jc.addCommand(commandEndpoints);
jc.addCommand(commandSpeedStats);
jc.addCommand(commandPatternStats);
try {
jc.parse(args);
} catch (Exception e) {
System.out.println(e.getMessage());
jc.usage();
System.exit(1);
}
if (help || jc.getParsedCommand() == null) {
jc.usage();
System.exit(0);
}
}
private void run() {
/* open input graph (same for all commands) */
File graphFile = new File(graphPath);
try {
graph = Graph.load(graphFile, Graph.LoadLevel.FULL);
} catch (Exception e) {
LOG.error("Exception while loading graph from " + graphFile);
return;
}
/* open output stream (same for all commands) */
if (outPath != null) {
try {
writer = new CsvWriter(outPath, ',', Charset.forName("UTF8"));
} catch (Exception e) {
LOG.error("Exception while opening output file " + outPath);
return;
}
} else {
writer = new CsvWriter(System.out, ',', Charset.forName("UTF8"));
}
LOG.info("done loading graph.");
String command = jc.getParsedCommand();
if (command.equals("endpoints")) {
commandEndpoints.run();
} else if (command.equals("speedstats")) {
commandSpeedStats.run();
} else if (command.equals("patternstats")) {
commandPatternStats.run();
}
writer.close();
}
@Parameters(commandNames = "endpoints", commandDescription = "Generate random endpoints for performance testing")
class CommandEndpoints {
@Parameter(names = { "-r", "--radius"}, description = "perturbation radius in meters")
private double radius = 100;
@Parameter(names = { "-n", "--number"}, description = "number of endpoints to generate")
private int n = 20;
@Parameter(names = { "-s", "--stops"}, description = "choose endpoints near stops not street vertices")
private boolean useStops = false;
@Parameter(names = { "-rs", "--seed"}, description = "random seed, allows reproducible results")
private Long seed = null;
// go along road then random
public void run() {
LOG.info(String.format("Producing %d random endpoints within radius %2.2fm around %s.",
n, radius, useStops ? "stops" : "streets"));
List<Vertex> vertices = new ArrayList<Vertex>();
GeodeticCalculator gc = new GeodeticCalculator();
Class<?> klasse = useStops ? TransitStop.class : StreetVertex.class;
for (Vertex v : graph.getVertices())
if (klasse.isInstance(v))
vertices.add(v);
Random random = new Random();
if (seed != null)
random.setSeed(seed);
Collections.shuffle(vertices, random);
vertices = vertices.subList(0, n);
try {
writer.writeRecord( new String[] {"n", "name", "lon", "lat"} );
int i = 0;
for (Vertex v : vertices) {
Coordinate c;
if (v instanceof StreetVertex) {
LineString ls = ((StreetVertex)v).getOutgoing().iterator().next().getGeometry();
int numPoints = ls.getNumPoints();
LocationIndexedLine lil = new LocationIndexedLine(ls);
int seg = random.nextInt(numPoints);
double frac = random.nextDouble();
LinearLocation ll = new LinearLocation(seg, frac);
c = lil.extractPoint(ll);
} else {
c = v.getCoordinate();
}
// perturb
double distance = random.nextDouble() * radius;
double azimuth = random.nextDouble() * 360 - 180;
// double x = c.x + r * Math.cos(theta);
// double y = c.y + r * Math.sin(theta);
gc.setStartingGeographicPoint(c.x, c.y);
gc.setDirection(azimuth, distance);
Point2D dest = gc.getDestinationGeographicPoint();
String name = v.getName();
String[] entries = new String[] {
Integer.toString(i), name,
Double.toString(dest.getX()), Double.toString(dest.getY())
};
writer.writeRecord(entries);
i += 1;
}
} catch (IOException ioe) {
LOG.error("Excpetion while writing CSV: {}", ioe.getMessage());
}
LOG.info("done.");
}
}
@Parameters(commandNames = "speedstats", commandDescription = "speed stats")
class CommandSpeedStats {
public void run() {
LOG.info("dumping hop info...");
try {
writer.writeRecord( new String[] {"route", "distance", "time", "speed"} );
for (Vertex v : graph.getVertices()) {
for (PatternHop ph : IterableLibrary.filter(v.getOutgoing(), PatternHop.class)) {
// Vertex fromv = ph.getFromVertex();
// Vertex tov = ph.getToVertex();
double distance = ph.getDistance();
if (distance < 3)
continue;
TableTripPattern ttp = ph.getPattern();
List<Trip> trips = ttp.getTrips();
int hop = ph.stopIndex;
String route = ttp.getExemplar().getRoute().getId().toString();
for (int trip = 0; trip < trips.size(); trip++){
int time = ttp.getRunningTime(hop, trip);
double speed = distance / time;
if (Double.isInfinite(speed) || Double.isNaN(speed))
continue;
String[] entries = new String[] {
route, Double.toString(distance), Integer.toString(time),
Double.toString(speed)
};
writer.writeRecord(entries);
}
}
}
} catch (IOException e) {
LOG.error("Exception writing CSV: {}", e.getMessage());
return;
}
LOG.info("done.");
}
}
@Parameters(commandNames = "patternstats", commandDescription = "trip pattern stats")
class CommandPatternStats {
public void run() {
LOG.info("counting number of trips per pattern...");
try {
writer.writeRecord( new String[] {
"nTripsInPattern", "frequency",
"cumulativePatterns", "empiricalDistPatterns",
"cumulativeTrips", "empiricalDistTrips" } );
Set<TableTripPattern> patterns = new HashSet<TableTripPattern>();
for (Vertex v : graph.getVertices()) {
for (PatternHop ph : IterableLibrary.filter(v.getOutgoing(), PatternHop.class)) {
TableTripPattern ttp = ph.getPattern();
patterns.add(ttp);
}
}
Multiset<Integer> counts = TreeMultiset.create();
int nPatterns = patterns.size();
LOG.info("total number of patterns is: {}", nPatterns);
int nTrips = 0;
for (TableTripPattern ttp : patterns) {
List<Trip> trips = ttp.getTrips();
counts.add(trips.size());
nTrips += trips.size();
}
LOG.info("total number of trips is: {}", nTrips);
LOG.info("average number of trips per pattern is: {}", nTrips/nPatterns);
int cPatterns = 0;
int cTrips = 0;
for (Multiset.Entry<Integer> count : counts.entrySet()) {
cPatterns += count.getCount();
cTrips += count.getCount() * count.getElement();
writer.writeRecord( new String[] {
count.getElement().toString(),
Integer.toString(count.getCount()),
Integer.toString(cPatterns),
Double.toString(cPatterns / (double) nPatterns),
Integer.toString(cTrips),
Double.toString(cTrips / (double) nTrips)
} );
}
} catch (IOException e) {
LOG.error("Exception writing CSV: {}", e.getMessage());
return;
}
LOG.info("done.");
}
}
}