All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.routing.graph.Graph Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.routing.graph;

import com.google.common.annotations.VisibleForTesting;
import jakarta.inject.Inject;
import java.io.Serializable;
import java.time.Instant;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.locationtech.jts.geom.Geometry;
import org.opentripplanner.ext.dataoverlay.configuration.DataOverlayParameterBindings;
import org.opentripplanner.framework.geometry.CompactElevationProfile;
import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.model.calendar.openinghours.OpeningHoursCalendarService;
import org.opentripplanner.routing.fares.FareService;
import org.opentripplanner.routing.graph.index.StreetIndex;
import org.opentripplanner.routing.linking.VertexLinker;
import org.opentripplanner.routing.services.notes.StreetNotesService;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.StreetEdge;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.model.vertex.VertexLabel;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.service.SiteRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This is one of the main data structures in OpenTripPlanner. It represents a mathematical object
 * called a graph (https://en.wikipedia.org/wiki/Graph_theory) relative to which many routing
 * algorithms are defined. A graph is made up of vertices and edges. These are also referred to as
 * nodes and arcs or links, but in OTP we always use the vertices and edges terminology.
 * 

* In OTP1, the Graph contained vertices and edges representing the entire transportation network, * including edges representing both street segments and public transit lines connecting stops. In * OTP2, the Graph edges now represent only the street network. Transit routing is performed on * other data structures suited to the Raptor algorithm (the TimetableRepository). Some transit-related * vertices are still present in the Graph, specifically those representing transit stops, * entrances, and elevators. Their presence in the street graph creates a connection between the two * routable data structures (identifying where stops in the TimetableRepository are located relative to * roads). *

* Other data structures related to street routing, such as elevation data and vehicle parking * information, are also collected here as fields of the Graph. For historical reasons the Graph * sometimes serves as a catch-all, as it used to be the root of the object tree representing the * whole transportation network. This use of the Graph object is being phased out and discouraged. *

* In some sense the Graph is just some indexes into a set of vertices. The Graph used to hold lists * of edges for each vertex, but those lists are now attached to the vertices themselves. *

* TODO RT_AB: I favor renaming to StreetGraph to emphasize what it represents. TG agreed in review. */ public class Graph implements Serializable { private static final Logger LOG = LoggerFactory.getLogger(Graph.class); /** Attaches text notes to street edges, which do not affect routing. */ public final StreetNotesService streetNotesService = new StreetNotesService(); // Ideally we could just get rid of vertex labels, but they're used in tests and graph building. private final Map vertices = new ConcurrentHashMap<>(); /** Conserve memory by reusing immutable instances of Strings, integer arrays, etc. */ public final transient Deduplicator deduplicator; public final Instant buildTime = Instant.now(); @Nullable private final OpeningHoursCalendarService openingHoursCalendarService; private transient StreetIndex streetIndex; /** The convex hull of all the graph vertices. Generated at the time the Graph is built. */ private Geometry convexHull = null; /** The preferences that were used for building this Graph instance. */ public Preferences preferences = null; /** True if OSM data was loaded into this Graph. */ public boolean hasStreets = false; /** * The difference in meters between the WGS84 ellipsoid height and geoid height at the graph's * center */ public Double ellipsoidToGeoidDifference = 0.0; /** True if this graph contains elevation data. */ public boolean hasElevation = false; /** If this graph contains elevation data, the minimum elevation value. Otherwise null. */ public Double minElevation = null; /** If this graph contains elevation data, the maximum elevation value. Otherwise null. */ public Double maxElevation = null; /** * The horizontal distance across the ground between successive elevation samples in * CompactElevationProfile. */ // TODO refactoring transit model: remove and instead always serialize directly from and to the // static variable in CompactElevationProfile in SerializedGraphObject private double distanceBetweenElevationSamples; private FareService fareService; /** * Hack. I've tried three different ways of generating unique labels. Previously we were just * tolerating edge label collisions. For some reason we're repeatedly generating splits on the * same edge objects, despite a comment that said it was guaranteed there would only ever be one * split per edge. This is going to fail as soon as we load a base OSM graph and build transit on * top of it. */ public long nextSplitNumber = 0; /** * DataOverlay Sandbox module parameter bindings configured in the build-config, and needed when * creating the data overlay context when routing. */ public DataOverlayParameterBindings dataOverlayParameterBindings; @Inject public Graph( Deduplicator deduplicator, @Nullable OpeningHoursCalendarService openingHoursCalendarService ) { this.deduplicator = deduplicator; this.openingHoursCalendarService = openingHoursCalendarService; } public Graph(Deduplicator deduplicator) { this(deduplicator, null); } /** Constructor for deserialization. */ public Graph() { this(new Deduplicator(), null); } /** Add the given vertex to the graph. */ public void addVertex(Vertex v) { Vertex old = vertices.put(v.getLabel(), v); if (old != null) { if (old == v) { LOG.error("repeatedly added the same vertex: {}", v); } else { LOG.error( "duplicate vertex label in graph (added vertex to graph anyway): {}", v.getLabel() ); } } } /** * Removes an edge from the graph. This method is not thread-safe. * * @param e The edge to be removed */ public void removeEdge(Edge e) { if (e != null) { streetNotesService.removeStaticNotes(e); e.remove(); } } /** * Fetching a vertex by its by label. This is convenient in tests and such, but avoid using in general. * * @see VertexLabel */ @VisibleForTesting @Nullable public Vertex getVertex(VertexLabel label) { return vertices.get(label); } /** * Converts the input to a string-based label and looks it up in the graph. Remember that there * are other, non-string vertex labels for which this method will not work. * @see VertexLabel */ @VisibleForTesting @Nullable public Vertex getVertex(String label) { return vertices.get(VertexLabel.string(label)); } /** * Get all the vertices in the graph. */ public Collection getVertices() { return this.vertices.values(); } public List getVerticesOfType(Class cls) { return this.getVertices() .stream() .filter(cls::isInstance) .map(cls::cast) .collect(Collectors.toList()); } @Nullable public TransitStopVertex getStopVertexForStopId(FeedScopedId id) { return streetIndex.findTransitStopVertices(id); } /** * Return all the edges in the graph. Derived from vertices on demand. */ public Collection getEdges() { Set edges = new HashSet<>(); for (Vertex v : this.getVertices()) { edges.addAll(v.getOutgoing()); } return edges; } public List getEdgesOfType(Class cls) { return this.getEdges() .stream() .filter(cls::isInstance) .map(cls::cast) .collect(Collectors.toList()); } /** * Return only the StreetEdges in the graph. */ public Collection getStreetEdges() { return getEdgesOfType(StreetEdge.class); } public boolean containsVertex(Vertex v) { return (v != null) && vertices.get(v.getLabel()) == v; } public void remove(Vertex vertex) { vertices.remove(vertex.getLabel()); } public void removeIfUnconnected(Vertex v) { if (v.getDegreeIn() == 0 && v.getDegreeOut() == 0) { remove(v); } } public int countVertices() { return vertices.size(); } /** * Find the total number of edges in this Graph. There are assumed to be no Edges in an incoming * edge list that are not in an outgoing edge list. * * @return number of outgoing edges in the graph */ public int countEdges() { int ne = 0; for (Vertex v : getVertices()) { ne += v.getDegreeOut(); } return ne; } /** * Perform indexing on vertices, edges and create transient data structures. This used to be done * in readObject methods upon deserialization, but stand-alone mode now allows passing graphs from * graphbuilder to server in memory, without a round trip through serialization. *

* TODO OTP2 - Indexing the streetIndex is not something that should be delegated outside the * - graph. This allows a module to index the streetIndex BEFORE another module add * - something that should go into the index; Hence, inconsistent data. */ public void index(SiteRepository siteRepository) { LOG.info("Index street model..."); streetIndex = new StreetIndex(this, siteRepository); LOG.info("Index street model complete."); } @Nullable public OpeningHoursCalendarService getOpeningHoursCalendarService() { return this.openingHoursCalendarService; } /** * Get streetIndex, safe to use while routing, but do not use during graph build. * @see #getStreetIndexSafe(SiteRepository) */ public StreetIndex getStreetIndex() { return this.streetIndex; } /** * Get streetIndex during graph build, both OSM street data and transit data must be loaded * before calling this. */ public StreetIndex getStreetIndexSafe(SiteRepository siteRepository) { indexIfNotIndexed(siteRepository); return this.streetIndex; } /** * Get VertexLinker, safe to use while routing, but do not use during graph build. * @see #getLinkerSafe(SiteRepository) */ public VertexLinker getLinker() { return streetIndex.getVertexLinker(); } /** * Get VertexLinker during graph build, both OSM street data and transit data must be loaded * before calling this. */ public VertexLinker getLinkerSafe(SiteRepository siteRepository) { indexIfNotIndexed(siteRepository); return streetIndex.getVertexLinker(); } /** * Calculates convexHull of all the vertices during build time */ public void calculateConvexHull() { convexHull = GeometryUtils.makeConvexHull(getVertices(), Vertex::getCoordinate); } /** * @return calculated convexHull; */ public Geometry getConvexHull() { return convexHull; } public void initEllipsoidToGeoidDifference(double value, double lat, double lon) { this.ellipsoidToGeoidDifference = value; LOG.info( "Computed ellipsoid/geoid offset at ({}, {}) as {}", lat, lon, this.ellipsoidToGeoidDifference ); } public double getDistanceBetweenElevationSamples() { return distanceBetweenElevationSamples; } public void setDistanceBetweenElevationSamples(double distanceBetweenElevationSamples) { this.distanceBetweenElevationSamples = distanceBetweenElevationSamples; CompactElevationProfile.setDistanceBetweenSamplesM(distanceBetweenElevationSamples); } public FareService getFareService() { return fareService; } public void setFareService(FareService fareService) { this.fareService = fareService; } private void indexIfNotIndexed(SiteRepository siteRepository) { if (streetIndex == null) { index(siteRepository); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy