I created my own transfer information site (European Subcontinent Railway) using OpenTripPlanner (hereinafter, OTP) as a route search engine, but only with OTP as a route search engine. It seems that there is not much Japanese information on how to use it (although there is not much information on English ...), so I summarized it as an article. Also, since only the timetable data is registered without registering the map data, please use it as a reference for such usage.
OpenTripPlanner is an open source integrated route search application. It is possible to take in OpenStreetMap and GTFS (described later) data and perform route search including walking and vehicles, so-called transfer guidance.
GTFS is a format for public transportation routes and operation information created by Google. In short, it is timetable data, and in recent years, transportation data around the world has been released as open data.
Before the war (around 1939), when there were no jet planes, it seems that one ticket could be used to travel by rail and ship from Japan to Europe. We are creating a hobby site that is particularly useless and can provide information on train transfers in such times. The timetables of railways from all over the world are collected, and the data of the railway trunk lines from Tokyo to London and Paris (Japan → Korea → former Manchuria → former Soviet Union → Poland → Germany → Belgium → UK / France) are registered.
For the method of launching OTP as a normal web application, refer to other articles, but this time I will explain specifically for the following.
--Use OTP as a Java library instead of an application --Register and use only GTFS data without registering map data in OTP
The following version of the environment is assumed.
--JDK 1.8 or above
Create a Maven project and add the following information to the pom.xml file. I'm adding the repository because OTP references another OSS called OneBusAway and GeoTools.
pom.xml
<repositories>
<repository>
<id>onebusaway-releases</id>
<name>Onebusaway Releases Repo</name>
<url>http://nexus.onebusaway.org/content/repositories/releases/</url>
</repository>
<repository>
<id>osgeo</id>
<name>OSGeo Release Repository</name>
<url>https://repo.osgeo.org/repository/release/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.opentripplanner</groupId>
<artifactId>otp</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
OTP stores road and transportation routes and timetable data in an object called Graph. However, before creating a Graph object, you need to read the zipped GTFS data and create a GtfsBundle object. Since OTP can read multiple GTFS files, it is necessary to give each file a unique ID. (The part where feedId is set)
python
GtfsBundle gtfsBundle = new GtfsBundle(file);
gtfsBundle.setTransfersTxtDefinesStationPaths(true);
GtfsFeedId feedId = new GtfsFeedId.Builder().id("id").build();
gtfsBundle.setFeedId(feedId);
After merging the created GtfsBundle into one List (gtfsBundles), register it in the Graph object.
python
GtfsModule gtfsModule = new GtfsModule(gtfsBundles);
Graph graph = new Graph();
gtfsModule.buildGraph(graph, null);
graph.summarizeBuilderAnnotations();
graph.index(new DefaultStreetVertexIndexFactory());
First, create a RoutingRequest object that sets search conditions such as departure place, arrival place, and departure (or arrival) time. Since it is originally a route search application that uses map data, it is necessary to specify the coordinates even at the station.
python
RoutingRequest routingRequest = new RoutingRequest();
routingRequest.setNumItineraries(3); //Number of candidates for search results
//Set the reference time to search
LocalDateTime ldt = LocalDateTime.parse("1939-08-01T13:00");
routingRequest.dateTime = ldt.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
routingRequest.setArriveBy(false); //The default is to specify the departure time, but if True, you can specify the arrival time.
//Latitude of departure and arrival/Specified by longitude
routingRequest.from = new GenericLocation(35.3369, 139.44699); //Tsujido Station
routingRequest.to = new GenericLocation(35.17096, 136.88232); //Nagoya station
routingRequest.ignoreRealtimeUpdates = true; //Ignore GTFS Realtime information
routingRequest.reverseOptimizing = true; //Search in the opposite direction from the search results and search for the latest departure time
//Setting to search only with GTFS data
routingRequest.setModes(new TraverseModeSet(TraverseMode.TRANSIT));
routingRequest.onlyTransitTrips = true;
//Set Graph object
routingRequest.setRoutingContext(graph);
Search using RoutingReques object and Graph object.
python
Router router = new Router("OTP", graph);
List<GraphPath> paths = new GraphPathFinder(router).getPaths(routingRequest);
TripPlan tripPlan = GraphPathToTripPlanConverter.generatePlan(paths, routingRequest);
The search result is stored in the TripPlan object. The candidate Itinerary object of the search result is stored in the List obtained by the itinerary property of TripPlan. The Leg object of the route information is stored in the list obtained by the leg property of Itinerary in the order of transfer.
August 1939 using GTFS data (timetable data of the Tokaido Line in December 1939, about 80 years ago) on my site European Subcontinent Railway Let's explore the route from Tsujido Station to Nagoya Station at 1:00 pm on the 1st. The result is as follows. If you miss the train at 13:08 (by the way, the regular train bound for Maibara), it seems that it is faster to return to Yokohama and Ofuna and catch the limited express Sakura Shimonoseki and express Osaka. You can check the timetable of each station in 1939 by clicking the station name on each route map in European Subcontinent Railway. (Tsujido Station, Yokohama Station, Ofuna Station)
13:08 Departure- 18:28 arrival(320 minutes)
>Tsujido 13:08 departure-Numazu 14:46 arrivals
>Numazu 15:04 departures-Nagoya 18:28 arrivals
13:18 Departure- 19:00 arrival(342 minutes)
>Tsujido 13:18 shots-Yokohama 13:50 arrivals
>Yokohama 13:58 departures-Nagoya 19:00 arrival
14:08 Departure- 19:38 arrival(330 minutes)
>Tsujido 14:08 departure-Ofuna 14:20 arrivals
>Ofuna 14:26 shots-Nagoya 19:38 arrivals
The full text of this source looks like this.
OtpTest.java
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.opentripplanner.api.model.TripPlan;
import org.opentripplanner.api.resource.GraphPathToTripPlanConverter;
import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.graph_builder.model.GtfsBundle;
import org.opentripplanner.graph_builder.module.GtfsFeedId;
import org.opentripplanner.graph_builder.module.GtfsModule;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.Route;
import org.opentripplanner.model.Stop;
import org.opentripplanner.model.Trip;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.edgetype.TripPattern;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.GraphIndex;
import org.opentripplanner.routing.impl.DefaultStreetVertexIndexFactory;
import org.opentripplanner.routing.impl.GraphPathFinder;
import org.opentripplanner.routing.spt.GraphPath;
import org.opentripplanner.standalone.Router;
public class OtpTest {
public static void main(String[] args) {
//Path to the GTFS file storage folder
String in = "";
//Read all ZIP files in the folder as GTFS files
List<GtfsBundle> gtfsBundles = null;
try (final Stream<Path> pathStream = Files.list(Paths.get(in))) {
gtfsBundles = pathStream.map(Path::toFile).filter(file -> file.getName().toLowerCase().endsWith(".zip"))
.map(file -> {
GtfsBundle gtfsBundle = new GtfsBundle(file);
gtfsBundle.setTransfersTxtDefinesStationPaths(true);
//Since it is necessary to give each GTFS file a unique ID, use the file name as the ID.
String id = file.getName().substring(0, file.getName().length() - 4);
gtfsBundle.setFeedId(new GtfsFeedId.Builder().id(id).build());
return gtfsBundle;
}).collect(Collectors.toList());
} catch (final IOException e) {
throw new RuntimeException(e);
}
//Processing to register the read GTFS file in the Graph object
GtfsModule gtfsModule = new GtfsModule(gtfsBundles);
Graph graph = new Graph();
gtfsModule.buildGraph(graph, null);
graph.summarizeBuilderAnnotations();
graph.index(new DefaultStreetVertexIndexFactory());
//Graph can also be serialized and saved
// graph.save(file);
//Setting search conditions
RoutingRequest routingRequest = new RoutingRequest();
routingRequest.setNumItineraries(3); //Number of candidates for search results
//Set the reference time to search
LocalDateTime ldt = LocalDateTime.parse("1939-08-01T13:00");
routingRequest.dateTime = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()).getTime() / 1000;
routingRequest.setArriveBy(false); //The default is to specify the departure time, but if True, you can specify the arrival time.
//Latitude of departure and arrival/Specified as mild
routingRequest.from = new GenericLocation(35.3369, 139.44699); //Tsujido Station
routingRequest.to = new GenericLocation(35.17096, 136.88232); //Nagoya station
routingRequest.ignoreRealtimeUpdates = true; //Ignore GTFS Realtime information
routingRequest.reverseOptimizing = true; //Search in the opposite direction from the search results and search for the latest departure time
//Setting to search only with GTFS data
routingRequest.setModes(new TraverseModeSet(TraverseMode.TRANSIT));
routingRequest.onlyTransitTrips = true;
//Set Graph object
routingRequest.setRoutingContext(graph);
//Search processing
Router router = new Router("OTP", graph);
List<GraphPath> paths = new GraphPathFinder(router).getPaths(routingRequest);
TripPlan tripPlan = GraphPathToTripPlanConverter.generatePlan(paths, routingRequest);
//Display search results
tripPlan.itinerary.forEach(p -> {
//Summary information for each route candidate
System.out.println(String.format("%d:%02d Departure- %d:%02d arrival(%d minutes)",
p.startTime.get(Calendar.HOUR_OF_DAY), p.startTime.get(Calendar.MINUTE),
p.endTime.get(Calendar.HOUR_OF_DAY), p.endTime.get(Calendar.MINUTE),
(p.duration / 60)));
p.legs.forEach(l -> {
//Information on each train in the route
System.out.println(String.format("> %s %d:%02d departure- %s %d:%02d arrival",
l.from.name, l.startTime.get(Calendar.HOUR_OF_DAY), l.startTime.get(Calendar.MINUTE),
l.to.name, l.endTime.get(Calendar.HOUR_OF_DAY), l.endTime.get(Calendar.MINUTE)));
});
});
}
}
Also, from the Leg object, if the route drawing information is stored in GTFS shapes.txt, it can be obtained as a GEO-encoded character string with l.legGeometry.getPoints ()
.
Recommended Posts