package org.opentripplanner.graph_builder.module; import com.google.common.collect.Iterables; import com.vividsolutions.jts.geom.Envelope; import org.opentripplanner.common.geometry.SphericalDistanceLibrary; import org.opentripplanner.graph_builder.services.GraphBuilderModule; import org.opentripplanner.routing.edgetype.StreetTransitLink; import org.opentripplanner.routing.graph.Edge; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.routing.impl.StreetVertexIndexServiceImpl; import org.opentripplanner.routing.vertextype.TransitStop; import org.opentripplanner.routing.vertextype.TransitStopStreetVertex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; /** * This module takes advantage of the fact that in some cities, an authoritative linking location for GTFS stops is * provided by tags in the OSM data. * * When OSM data is being loaded, certain OSM nodes that represent transit stops are made into TransitStopStreetVertex * instances. In some cities, these nodes have a ref=* tag which gives the corresponding GFTS stop ID for the stop. * See http://wiki.openstreetmap.org/wiki/Tag:highway%3Dbus_stop * * This module will attempt to link all transit stops to such nodes in the OSM data, based on the stop ID and ref tag. * It is run before the main transit stop linker, and if no linkage was created here, the main linker should create * one based on distance or other heuristics. */ public class TransitToTaggedStopsModule implements GraphBuilderModule { private static final Logger LOG = LoggerFactory.getLogger(TransitToTaggedStopsModule.class); StreetVertexIndexServiceImpl index; private double searchRadiusM = 250; private double searchRadiusLat = SphericalDistanceLibrary.metersToDegrees(searchRadiusM); public List<String> provides() { return Arrays.asList("street to transit", "linking"); } public List<String> getPrerequisites() { return Arrays.asList("streets"); // why not "transit" ? } @Override public void buildGraph(Graph graph, HashMap<Class<?>, Object> extra) { LOG.info("Linking transit stops to tagged bus stops..."); index = new StreetVertexIndexServiceImpl(graph); // iterate over a copy of vertex list because it will be modified ArrayList<Vertex> vertices = new ArrayList<>(); vertices.addAll(graph.getVertices()); for (TransitStop ts : Iterables.filter(vertices, TransitStop.class)) { // if the street is already linked there is no need to linked it again, // could happened if using the prune isolated island boolean alreadyLinked = false; for(Edge e:ts.getOutgoing()){ if(e instanceof StreetTransitLink) { alreadyLinked = true; break; } } if(alreadyLinked) continue; // only connect transit stops that (a) are entrances, or (b) have no associated // entrances if (ts.isEntrance() || !ts.hasEntrances()) { boolean wheelchairAccessible = ts.hasWheelchairEntrance(); if (!connectVertexToStop(ts, wheelchairAccessible)) { LOG.debug("Could not connect " + ts.getStopCode() + " at " + ts.getCoordinate().toString()); //LOG.warn(graph.addBuilderAnnotation(new StopUnlinked(ts))); } } } } private boolean connectVertexToStop(TransitStop ts, boolean wheelchairAccessible) { String stopCode = ts.getStopCode(); if (stopCode == null){ return false; } Envelope envelope = new Envelope(ts.getCoordinate()); double xscale = Math.cos(ts.getCoordinate().y * Math.PI / 180); envelope.expandBy(searchRadiusLat / xscale, searchRadiusLat); Collection<Vertex> vertices = index.getVerticesForEnvelope(envelope); // Iterate over all nearby vertices representing transit stops in OSM, linking to them if they have a stop code // in their ref= tag that matches the GTFS stop code of this TransitStop. for (Vertex v : vertices){ if (!(v instanceof TransitStopStreetVertex)){ continue; } TransitStopStreetVertex tsv = (TransitStopStreetVertex) v; // Only use stop codes for linking TODO: find better method to connect stops without stop code if (tsv.stopCode != null && tsv.stopCode.equals(stopCode)) { new StreetTransitLink(ts, tsv, wheelchairAccessible); new StreetTransitLink(tsv, ts, wheelchairAccessible); LOG.debug("Connected " + ts.toString() + " to " + tsv.getLabel()); return true; } } return false; } @Override public void checkInputs() { //no inputs } }