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

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

There is a newer version: 2.6.0
Show newest version
package org.opentripplanner.routing.graph;

import static org.opentripplanner.model.projectinfo.OtpProjectInfo.projectInfo;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
import org.opentripplanner.common.geometry.CompactElevationProfile;
import org.opentripplanner.datastore.api.DataSource;
import org.opentripplanner.model.projectinfo.GraphFileHeader;
import org.opentripplanner.model.projectinfo.OtpProjectInfo;
import org.opentripplanner.routing.graph.kryosupport.KryoBuilder;
import org.opentripplanner.standalone.config.BuildConfig;
import org.opentripplanner.standalone.config.RouterConfig;
import org.opentripplanner.transit.model.basic.SubMode;
import org.opentripplanner.transit.model.network.RoutingTripPattern;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.service.TransitModel;
import org.opentripplanner.util.OtpAppException;
import org.opentripplanner.util.lang.OtpNumberFormat;
import org.opentripplanner.util.logging.ProgressTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This is the class that get serialized/deserialized into/from the file graph.obj.
 * 

* The Graph object does not contain a collection of edges. The set of edges is generated on demand * from the vertices. However, when serializing, we intentionally do not serialize the vertices' * edge lists to prevent excessive recursion. So we need to save the edges along with the graph. We * used to make two serialization calls, one for the graph and one for the edges. But we need the * serializer to know that vertices referenced by the edges are the same vertices stored in the * graph itself. The easiest way to do this is to make only one serialization call, serializing a * single object that contains both the graph and the edge collection. */ public class SerializedGraphObject implements Serializable { private static final Logger LOG = LoggerFactory.getLogger(SerializedGraphObject.class); public final Graph graph; public final TransitModel transitModel; private final Collection edges; /** * The config JSON used to build this graph. Allows checking whether the configuration has * changed. */ public final BuildConfig buildConfig; /** Embed a router configuration inside the graph, for starting up with a single file. */ public final RouterConfig routerConfig; /** * All submodes are cached in a static collection inside SubMode, * hence we need to serialize that as well */ private final List allTransitSubModes; private final int stopLocationCounter; private final int routingTripPatternCounter; public SerializedGraphObject( Graph graph, TransitModel transitModel, BuildConfig buildConfig, RouterConfig routerConfig ) { this.graph = graph; this.edges = graph.getEdges(); this.transitModel = transitModel; this.buildConfig = buildConfig; this.routerConfig = routerConfig; this.allTransitSubModes = SubMode.listAllCachedSubModes(); this.stopLocationCounter = StopLocation.indexCounter(); this.routingTripPatternCounter = RoutingTripPattern.indexCounter(); } public static void verifyTheOutputGraphIsWritableIfDataSourceExist(DataSource graphOutput) { if (graphOutput != null) { // Abort building a graph if the file can not be saved if (graphOutput.exists()) { LOG.info( "Graph already exists and will be overwritten at the end of the " + "build process. Graph: {}", graphOutput.path() ); } if (!graphOutput.isWritable()) { throw new RuntimeException("Cannot create or write to graph at: " + graphOutput.path()); } } } public static SerializedGraphObject load(DataSource source) { return load(source.asInputStream(), source.path()); } public static SerializedGraphObject load(File file) { try { return load(new FileInputStream(file), file.getAbsolutePath()); } catch (FileNotFoundException e) { LOG.error("Graph file not found: " + file, e); throw new OtpAppException(e.getMessage()); } } /** * After deserialization, the vertices will all have null outgoing and incoming edge lists because * those edge lists are marked transient, to prevent excessive recursion depth while serializing. * This method will reconstruct all those edge lists after deserialization. */ public void reconstructEdgeLists() { for (Vertex v : graph.getVertices()) { v.initEdgeLists(); } for (Edge e : edges) { Vertex fromVertex = e.getFromVertex(); Vertex toVertex = e.getToVertex(); fromVertex.addOutgoing(e); toVertex.addIncoming(e); } } /** * Save this object to the target it the target data source is not {@code null}. */ public void save(@Nullable DataSource target) { if (target != null) { save(target.asOutputStream(), target.name(), target.size()); } else { LOG.info("Not saving graph to disk, as requested."); } } /* private methods */ private static SerializedGraphObject load(InputStream inputStream, String sourceDescription) { // TODO store version information, halt load if versions mismatch try (inputStream) { LOG.info("Reading graph from '{}'", sourceDescription); Input input = new Input(inputStream); validateGraphSerializationId( input.readBytes(GraphFileHeader.headerLength()), sourceDescription ); Kryo kryo = KryoBuilder.create(); SerializedGraphObject serObj = (SerializedGraphObject) kryo.readClassAndObject(input); SubMode.deserializeSubModeCache(serObj.allTransitSubModes); StopLocation.initIndexCounter(serObj.stopLocationCounter); RoutingTripPattern.initIndexCounter(serObj.routingTripPatternCounter); CompactElevationProfile.setDistanceBetweenSamplesM( serObj.graph.getDistanceBetweenElevationSamples() ); LOG.debug("Graph read."); serObj.reconstructEdgeLists(); serObj.transitModel.getStopModel().reindexAfterDeserialization(); serObj.transitModel.index(); logSerializationCompleteStatus(serObj.graph, serObj.transitModel); return serObj; } catch (IOException e) { LOG.error("Exception while loading graph: {}", e.getLocalizedMessage(), e); return null; } catch (KryoException ke) { LOG.warn( "Exception while loading graph: {}\n{}", sourceDescription, ke.getLocalizedMessage() ); throw new OtpAppException( "Unable to load graph. The deserialization failed. Is the " + "loaded graph build with the same OTP version as you are using to load it? " + "Graph: " + sourceDescription ); } } @SuppressWarnings("Convert2MethodRef") private static OutputStream wrapOutputStreamWithProgressTracker( OutputStream outputStream, long size ) { return ProgressTracker.track( "Save graph", 500_000, size, outputStream, // Keep this to get correct logging info for class and line number msg -> LOG.info(msg) ); } private static void validateGraphSerializationId(byte[] header, String sourceName) { var expFileHeader = projectInfo().graphFileHeaderInfo; var graphFileHeader = GraphFileHeader.parse(header); if (!expFileHeader.equals(graphFileHeader)) { if (!expFileHeader.equals(graphFileHeader)) { throw new OtpAppException( "The graph file is incompatible with this version of OTP. " + "The OTP serialization version id '%s' do not match the id " + "'%s' in '%s' file-header.", expFileHeader.otpSerializationVersionId(), graphFileHeader.otpSerializationVersionId(), sourceName ); } } } private void save(OutputStream outputStream, String graphName, long size) { LOG.info("Writing graph {} ...", graphName); outputStream = wrapOutputStreamWithProgressTracker(outputStream, size); Kryo kryo = KryoBuilder.create(); Output output = new Output(outputStream); output.write(OtpProjectInfo.projectInfo().graphFileHeaderInfo.header()); kryo.writeClassAndObject(output, this); output.close(); LOG.info("Graph written: {}", graphName); // Summarize serialized classes and associated serializers to stdout: // ((InstanceCountingClassResolver) kryo.getClassResolver()).summarize(); } private static void logSerializationCompleteStatus(Graph graph, TransitModel transitModel) { var f = new OtpNumberFormat(); var nStops = f.formatNumber(transitModel.getStopModel().stopIndexSize()); var nTransfers = f.formatNumber(transitModel.getTransferService().listAll().size()); var nPatterns = f.formatNumber(transitModel.getAllTripPatterns().size()); var nVertices = f.formatNumber(graph.countVertices()); var nEdges = f.formatNumber(graph.countEdges()); LOG.info("Graph loaded. |V|={} |E|={}", nVertices, nEdges); LOG.info( "Transit loaded. |Stops|={} |Patterns|={} |ConstrainedTransfers|={}", nStops, nPatterns, nTransfers ); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy