public GraphIndex(Graph graph) { LOG.info("Indexing graph..."); for (String feedId : graph.getFeedIds()) { for (Agency agency : graph.getAgencies(feedId)) { Map<String, Agency> agencyForId = agenciesForFeedId.getOrDefault(feedId, new HashMap<>()); agencyForId.put(agency.getId(), agency); this.agenciesForFeedId.put(feedId, agencyForId); } } Collection<Edge> edges = graph.getEdges(); /* We will keep a separate set of all vertices in case some have the same label. * Maybe we should just guarantee unique labels. */ Set<Vertex> vertices = Sets.newHashSet(); for (Edge edge : edges) { vertices.add(edge.getFromVertex()); vertices.add(edge.getToVertex()); if (edge instanceof TablePatternEdge) { TablePatternEdge patternEdge = (TablePatternEdge) edge; TripPattern pattern = patternEdge.getPattern(); patternForId.put(pattern.code, pattern); } } for (Vertex vertex : vertices) { vertexForId.put(vertex.getLabel(), vertex); if (vertex instanceof TransitStop) { TransitStop transitStop = (TransitStop) vertex; Stop stop = transitStop.getStop(); stopForId.put(stop.getId(), stop); stopVertexForStop.put(stop, transitStop); stopsForParentStation.put(stop.getParentStation(), stop); } } for (TransitStop stopVertex : stopVertexForStop.values()) { Envelope envelope = new Envelope(stopVertex.getCoordinate()); stopSpatialIndex.insert(envelope, stopVertex); } for (TripPattern pattern : patternForId.values()) { patternsForFeedId.put(pattern.getFeedId(), pattern); patternsForRoute.put(pattern.route, pattern); for (Trip trip : pattern.getTrips()) { patternForTrip.put(trip, pattern); tripForId.put(trip.getId(), trip); } for (Stop stop : pattern.getStops()) { patternsForStop.put(stop, pattern); } } for (Route route : patternsForRoute.asMap().keySet()) { routeForId.put(route.getId(), route); } // Copy these two service indexes from the graph until we have better ones. calendarService = graph.getCalendarService(); serviceCodes = graph.serviceCodes; this.graph = graph; LOG.info("Done indexing graph."); }
/** * Stop clustering is slow to perform and only used in profile routing for the moment. Therefore * it is not done automatically, and any method requiring stop clusters should call this method to * ensure that the necessary indexes are lazy-initialized. */ public synchronized void clusterStopsAsNeeded() { if (stopClusterSpatialIndex == null) { clusterStops(); LOG.info("Creating a spatial index for stop clusters."); stopClusterSpatialIndex = new HashGridSpatialIndex<StopCluster>(); for (StopCluster cluster : stopClusterForId.values()) { Envelope envelope = new Envelope(new Coordinate(cluster.lon, cluster.lat)); stopClusterSpatialIndex.insert(envelope, cluster); } } }
/** * Find transfer candidates for profile routing. TODO replace with an on-street search using the * existing profile router functions. */ public Map<StopCluster, Double> findNearbyStopClusters(StopCluster sc, double radius) { Map<StopCluster, Double> ret = Maps.newHashMap(); Envelope env = new Envelope(new Coordinate(sc.lon, sc.lat)); env.expandBy( SphericalDistanceLibrary.metersToLonDegrees(radius, sc.lat), SphericalDistanceLibrary.metersToDegrees(radius)); for (StopCluster cluster : stopClusterSpatialIndex.query(env)) { // TODO this should account for area-like nature of clusters. Use size of bounding boxes. double distance = SphericalDistanceLibrary.distance(sc.lat, sc.lon, cluster.lat, cluster.lon); if (distance < radius) ret.put(cluster, distance); } return ret; }
/** * FIXME OBA parentStation field is a string, not an AgencyAndId, so it has no agency/feed scope * But the DC regional graph has no parent stations pre-defined, so no use dealing with them for * now. However Trimet stops have "landmark" or Transit Center parent stations, so we don't use * the parent stop field. * * <p>Ideally in the future stop clusters will replicate and/or share implementation with GTFS * parent stations. * * <p>We can't use a similarity comparison, we need exact matches. This is because many street * names differ by only one letter or number, e.g. 34th and 35th or Avenue A and Avenue B. * Therefore normalizing the names before the comparison is essential. The agency must provide * either parent station information or a well thought out stop naming scheme to cluster stops -- * no guessing is reasonable without that information. */ public void clusterStops() { int psIdx = 0; // unique index for next parent stop LOG.info("Clustering stops by geographic proximity and name..."); // Each stop without a cluster will greedily claim other stops without clusters. for (Stop s0 : stopForId.values()) { if (stopClusterForStop.containsKey(s0)) continue; // skip stops that have already been claimed by a cluster String s0normalizedName = StopNameNormalizer.normalize(s0.getName()); StopCluster cluster = new StopCluster(String.format("C%03d", psIdx++), s0normalizedName); // LOG.info("stop {}", s0normalizedName); // No need to explicitly add s0 to the cluster. It will be found in the spatial index query // below. Envelope env = new Envelope(new Coordinate(s0.getLon(), s0.getLat())); env.expandBy( SphericalDistanceLibrary.metersToLonDegrees(CLUSTER_RADIUS, s0.getLat()), SphericalDistanceLibrary.metersToDegrees(CLUSTER_RADIUS)); for (TransitStop ts1 : stopSpatialIndex.query(env)) { Stop s1 = ts1.getStop(); double geoDistance = SphericalDistanceLibrary.fastDistance( s0.getLat(), s0.getLon(), s1.getLat(), s1.getLon()); if (geoDistance < CLUSTER_RADIUS) { String s1normalizedName = StopNameNormalizer.normalize(s1.getName()); // LOG.info(" --> {}", s1normalizedName); // LOG.info(" geodist {} stringdist {}", geoDistance, stringDistance); if (s1normalizedName.equals(s0normalizedName)) { // Create a bidirectional relationship between the stop and its cluster cluster.children.add(s1); stopClusterForStop.put(s1, cluster); } } } cluster.computeCenter(); stopClusterForId.put(cluster.id, cluster); } // LOG.info("Done clustering stops."); // for (StopCluster cluster : stopClusterForId.values()) { // LOG.info("{} at {} {}", cluster.name, cluster.lat, cluster.lon); // for (Stop stop : cluster.children) { // LOG.info(" {}", stop.getName()); // } // } }