diff --git a/NOTICE-THIRD-PARTY.md b/NOTICE-THIRD-PARTY.md index de248d952..ef75a8aac 100644 --- a/NOTICE-THIRD-PARTY.md +++ b/NOTICE-THIRD-PARTY.md @@ -101,6 +101,14 @@ GraphHopper Core (8.0) * Source: https://github.com/graphhopper/graphhopper/graphhopper-core +GraphHopper Reader for Gtfs Data (8.0) + + * License: Apache-2.0 + * Maven artifact: `com.graphhopper:graphhopper-reader-gtfs:8.0` + * Project: https://www.graphhopper.com/graphhopper-reader-gtfs + * Source: https://github.com/graphhopper/graphhopper/graphhopper-reader-gtfs + + GraphHopper Web API (8.0) * License: Apache-2.0 @@ -117,6 +125,14 @@ Gson (2.10.1) * Source: https://github.com/google/gson/gson/ +gtfs-realtime-bindings (0.0.5) + + * License: Apache-2.0 + * Maven artifact: `io.mobilitydata.transit:gtfs-realtime-bindings:0.0.5` + * Project: https://github.com/MobilityData/gtfs-realtime-bindings + * Source: https://github.com/MobilityData/gtfs-realtime-bindings + + Guava: Google Core Libraries for Java (32.1.1-jre) * License: Apache-2.0 @@ -221,6 +237,22 @@ Logback Core Module (1.5.0) * Source: https://github.com/qos-ch/logback/logback-core +mapdb (1.0.8) + + * License: Apache-2.0 + * Maven artifact: `org.mapdb:mapdb:1.0.8` + * Project: http://www.mapdb.org + * Source: https://github.com/jankotek/MapDB + + +opencsv (5.9) + + * License: Apache-2.0 + * Maven artifact: `com.opencsv:opencsv:5.9` + * Project: http://opencsv.sf.net + * Source: https://sourceforge.net/p/opencsv/source/ci/master/tree/ + + org.locationtech.jts:jts-core (1.19.0) * License: BSD-3-Clause (Eclipse Distribution License), Eclipse Public License, Version 2.0 diff --git a/bundle/src/assembly/mosaic-bundle.xml b/bundle/src/assembly/mosaic-bundle.xml index 0fb75cf06..234d2a378 100644 --- a/bundle/src/assembly/mosaic-bundle.xml +++ b/bundle/src/assembly/mosaic-bundle.xml @@ -151,6 +151,13 @@ org.locationtech.jts:jts-core + + com.graphhopper:graphhopper-reader-gtfs + io.mobilitydata.transit:gtfs-realtime-bindings + org.mapdb:mapdb + com.opencsv:opencsv + + com.github.mwiede:jsch com.google.protobuf:protobuf-java org.xerial:sqlite-jdbc diff --git a/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/ApplicationAmbassador.java b/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/ApplicationAmbassador.java index cb8e8d787..03597df11 100644 --- a/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/ApplicationAmbassador.java +++ b/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/ApplicationAmbassador.java @@ -143,7 +143,8 @@ public ApplicationAmbassador(AmbassadorParameter ambassadorParameter) { // set the CNC (central navigation component) CentralNavigationComponent cnc = new CentralNavigationComponent( ambassadorParameter, - ambassadorConfig.navigationConfiguration + Validate.notNull(ambassadorConfig.navigationConfiguration, "Field navigationConfiguration must not be null."), + Validate.notNull(ambassadorConfig.publicTransportConfiguration, "Field publicTransportConfiguration must not be null.") ); SimulationKernel.SimulationKernel.setCentralNavigationComponent(cnc); } @@ -228,6 +229,7 @@ public void initialize(final long startTime, final long endTime) throws Internal private void shutdownSimulationUnits(Event event) { SimulationKernel.SimulationKernel.setCurrentSimulationTime(event.getTime()); + SimulationKernel.SimulationKernel.getCentralNavigationComponent().close(); log.debug("remaining events: {}", eventScheduler.getAllEvents()); UnitSimulator.UnitSimulator.removeAllSimulationUnits(); diff --git a/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponent.java b/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponent.java index 63834b9db..cdf472db5 100644 --- a/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponent.java +++ b/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponent.java @@ -32,14 +32,18 @@ import org.eclipse.mosaic.lib.objects.vehicle.VehicleRoute; import org.eclipse.mosaic.lib.routing.CandidateRoute; import org.eclipse.mosaic.lib.routing.IllegalRouteException; -import org.eclipse.mosaic.lib.routing.Routing; import org.eclipse.mosaic.lib.routing.RoutingCostFunction; import org.eclipse.mosaic.lib.routing.RoutingParameters; import org.eclipse.mosaic.lib.routing.RoutingPosition; import org.eclipse.mosaic.lib.routing.RoutingRequest; import org.eclipse.mosaic.lib.routing.RoutingResponse; +import org.eclipse.mosaic.lib.routing.VehicleRouting; +import org.eclipse.mosaic.lib.routing.config.CPublicTransportRouting; import org.eclipse.mosaic.lib.routing.database.DatabaseRouting; import org.eclipse.mosaic.lib.routing.norouting.NoRouting; +import org.eclipse.mosaic.lib.routing.pt.PtRouting; +import org.eclipse.mosaic.lib.routing.pt.PtRoutingRequest; +import org.eclipse.mosaic.lib.routing.pt.PtRoutingResponse; import org.eclipse.mosaic.rti.api.IllegalValueException; import org.eclipse.mosaic.rti.api.Interaction; import org.eclipse.mosaic.rti.api.InternalFederateException; @@ -55,6 +59,7 @@ import java.util.List; import java.util.Map; import java.util.function.Predicate; +import javax.annotation.Nonnull; /** * The {@link CentralNavigationComponent} unites functionality concerned with @@ -81,13 +86,24 @@ public class CentralNavigationComponent { /** * The IRoutingApi. */ - private Routing routing; + private VehicleRouting vehicleRouting; + + + /** + * Access to Public Transport routing. + */ + private PtRouting ptRouting; /** * The configuration for routingAPI. */ private CApplicationAmbassador.CRoutingByType configuration; + /** + * The configuration for public transport routing. + */ + private CPublicTransportRouting ptConfiguration; + /** * Constructor for the CentralNavigationComponent. * Sets the logger and the configuration for navigation. @@ -99,20 +115,22 @@ public class CentralNavigationComponent { */ public CentralNavigationComponent( final AmbassadorParameter ambassadorParameter, - CApplicationAmbassador.CRoutingByType navigationConfiguration + @Nonnull CApplicationAmbassador.CRoutingByType navigationConfiguration, + @Nonnull CPublicTransportRouting publicTransportConfiguration ) { this.applicationAmbassadorParameter = ambassadorParameter; this.configuration = navigationConfiguration; + this.ptConfiguration = publicTransportConfiguration; } /** * This method initializes the {@link CentralNavigationComponent}. It is called * by the {@link ApplicationAmbassador}. - * The {@link #routing} will be created and initialized and other simulators will be informed + * The {@link #vehicleRouting} will be created and initialized and other simulators will be informed * using a {@link VehicleRoutesInitialization} interaction. * * @param rtiAmbassador the ambassador of the run time infrastructure - * @throws InternalFederateException if the {@link #routing} couldn't be initialized or the + * @throws InternalFederateException if the {@link #vehicleRouting} couldn't be initialized or the * {@link VehicleRoutesInitialization} interaction couldn't be send to the rti */ public void initialize(RtiAmbassador rtiAmbassador) throws InternalFederateException { @@ -121,14 +139,17 @@ public void initialize(RtiAmbassador rtiAmbassador) throws InternalFederateExcep try { this.log.info("Initializing CNC-Navigation"); - routing = createFromType(this.configuration != null ? this.configuration.type : null); - routing.initialize(configuration, applicationAmbassadorParameter.configuration.getParentFile()); + vehicleRouting = createFromType(configuration.type); + vehicleRouting.initialize(configuration, applicationAmbassadorParameter.configuration.getParentFile()); + + ptRouting = new PtRouting(); + ptRouting.initialize(ptConfiguration, applicationAmbassadorParameter.configuration.getParentFile()); this.log.info("CNC - Navigation-System initialized"); try { - final Map routeMap = routing.getRoutesFromDatabaseForMessage(); - for (var routeEntry: routeMap.entrySet()) { + final Map routeMap = vehicleRouting.getRoutesFromDatabaseForMessage(); + for (var routeEntry : routeMap.entrySet()) { SimulationKernel.SimulationKernel.registerRoute(routeEntry.getKey(), routeEntry.getValue()); } @@ -147,6 +168,12 @@ public void initialize(RtiAmbassador rtiAmbassador) throws InternalFederateExcep } } + public void close() { + if (ptRouting != null) { + ptRouting.close(); + } + } + /** * Returns an unmodifiable view of all routes known to the {@link SimulationKernel}. * @@ -156,7 +183,7 @@ public Map getAllRoutes() { return SimulationKernel.SimulationKernel.getRoutes(); } - Routing createFromType(String type) throws InternalFederateException { + VehicleRouting createFromType(String type) throws InternalFederateException { if (type == null || "database".equalsIgnoreCase(type) || "graphhopper".equalsIgnoreCase(type)) { return new DatabaseRouting(); } else if ("no-routing".equalsIgnoreCase(type)) { @@ -164,7 +191,7 @@ Routing createFromType(String type) throws InternalFederateException { } else { try { Class routingImplClass = Class.forName(type); - return (Routing) routingImplClass.getConstructor().newInstance(); + return (VehicleRouting) routingImplClass.getConstructor().newInstance(); } catch (Exception e) { String msg = "Could not create Routing instance from type '" + type + "'."; InternalFederateException ex = new InternalFederateException(msg, e); @@ -183,7 +210,7 @@ Routing createFromType(String type) throws InternalFederateException { * for. */ RoutingResponse findRoutes(RoutingRequest routingRequest) { - return routing.findRoutes(routingRequest); + return vehicleRouting.findRoutes(routingRequest); } /** @@ -220,7 +247,7 @@ public VehicleRoute switchRoute(VehicleData vehicleData, CandidateRoute rawRoute return requestStaticRouteChange(vehicleData, knownRoute, currentRoute, time); } else { // generate a new route - VehicleRoute route = routing.createRouteForRTI(rawRoute); + VehicleRoute route = vehicleRouting.createRouteForRTI(rawRoute); // propagate the new route try { @@ -268,7 +295,7 @@ private VehicleRoute requestStaticRouteChange(VehicleData vehicleData, VehicleRo GeoPoint getTargetPositionOfRoute(String routeId) { if (getAllRoutes().containsKey(routeId)) { String lastConnectionId = Iterables.getLast(getAllRoutes().get(routeId).getConnectionIds(), null); - return routing.getConnection(lastConnectionId).getEndNode().getPosition(); + return vehicleRouting.getConnection(lastConnectionId).getEndNode().getPosition(); } else { return null; } @@ -283,19 +310,30 @@ GeoPoint getTargetPositionOfRoute(String routeId) { public GeoPoint getSourcePositionOfRoute(String routeId) { if (getAllRoutes().containsKey(routeId)) { String firstConnectionId = Iterables.getFirst(getAllRoutes().get(routeId).getConnectionIds(), null); - return routing.getConnection(firstConnectionId).getStartNode().getPosition(); + return vehicleRouting.getConnection(firstConnectionId).getStartNode().getPosition(); } else { return null; } } + /** + * Find a public transport route from a provided position to a provided target position at a specific request time. + * + * @param routingRequest A {@link PtRoutingRequest} that contains + * the origin, the end, the request time, and additional + * routing parameters to calculate the public transport route. + */ + PtRoutingResponse findPtRoute(PtRoutingRequest routingRequest) { + return ptRouting.findPtRoute(routingRequest); + } + /** * Provides the current routing API implementation. * - * @return The {@link Routing}. + * @return The {@link VehicleRouting}. */ - public Routing getRouting() { - return routing; + public VehicleRouting getRouting() { + return vehicleRouting; } /** @@ -305,7 +343,7 @@ public Routing getRouting() { * @return A {@link GeoPoint} representing the position of the node. */ private GeoPoint getPositionOfNode(String nodeId) { - return routing.getNode(nodeId).getPosition(); + return vehicleRouting.getNode(nodeId).getPosition(); } /** @@ -315,7 +353,7 @@ private GeoPoint getPositionOfNode(String nodeId) { * @return the length of the connection in [m] */ double getLengthOfConnection(String connectionId) { - return routing.getConnection(connectionId).getLength(); + return vehicleRouting.getConnection(connectionId).getLength(); } /** @@ -386,7 +424,7 @@ public VehicleDeparture createRouteForOdInfo(long time, OriginDestinationPair od final RoutingRequest request = new RoutingRequest(new RoutingPosition(sourcePoint), new RoutingPosition(targetPoint), params); // find route - final RoutingResponse response = routing.findRoutes(request); + final RoutingResponse response = vehicleRouting.findRoutes(request); // check if best route, matches one of the existing routes and if so choose that existing route if (response.getBestRoute() != null) { VehicleRoute route = null; @@ -398,7 +436,7 @@ public VehicleDeparture createRouteForOdInfo(long time, OriginDestinationPair od } if (route == null) { try { - route = routing.createRouteForRTI(response.getBestRoute()); + route = vehicleRouting.createRouteForRTI(response.getBestRoute()); propagateRoute(route, time); } catch (IllegalRouteException e) { log.error("[CNC.createRouteForODInfo]: Could not create route.", e); @@ -428,7 +466,7 @@ private GeoPoint chooseGeoPointInCircle(GeoCircle origin) { /** * This method refines the road position depending on the - * implementation of the {@link Routing} interface this can have + * implementation of the {@link VehicleRouting} interface this can have * different levels of complexity. * * @param roadPosition the {@link IRoadPosition} to be refined @@ -438,7 +476,7 @@ public IRoadPosition refineRoadPosition(IRoadPosition roadPosition) { if (roadPosition == null) { return null; } - return routing.refineRoadPosition(roadPosition); + return vehicleRouting.refineRoadPosition(roadPosition); } /** @@ -500,7 +538,7 @@ INode getNextNodeOnRoute(String routeId, IRoadPosition roadPosition, Predicate nodeList = currentRoute.getNodeIds().subList(indexOfUpcomingNode, currentRoute.getNodeIds().size()); for (String nodeId : nodeList) { - INode node = routing.getNode(nodeId); + INode node = vehicleRouting.getNode(nodeId); if (nodeCondition.test(node)) { return node; } @@ -520,7 +558,7 @@ INode getNextNodeOnRoute(String routeId, IRoadPosition roadPosition, Predicate approximateCosts(Collection candidateRoutes, String lastNodeId) { List routesWithCosts = new ArrayList<>(); for (CandidateRoute candidateRoute : candidateRoutes) { - routesWithCosts.add(routing.approximateCostsForCandidateRoute(candidateRoute, lastNodeId)); + routesWithCosts.add(vehicleRouting.approximateCostsForCandidateRoute(candidateRoute, lastNodeId)); } return routesWithCosts; } diff --git a/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/simulation/perception/CentralPerceptionComponent.java b/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/simulation/perception/CentralPerceptionComponent.java index 1ae5cc2a2..3b91ba6f1 100644 --- a/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/simulation/perception/CentralPerceptionComponent.java +++ b/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/ambassador/simulation/perception/CentralPerceptionComponent.java @@ -24,7 +24,7 @@ import org.eclipse.mosaic.lib.geo.CartesianRectangle; import org.eclipse.mosaic.lib.objects.trafficlight.TrafficLightGroup; import org.eclipse.mosaic.lib.objects.vehicle.VehicleType; -import org.eclipse.mosaic.lib.routing.Routing; +import org.eclipse.mosaic.lib.routing.VehicleRouting; import org.eclipse.mosaic.lib.routing.database.DatabaseRouting; import org.eclipse.mosaic.rti.api.InternalFederateException; @@ -83,7 +83,7 @@ public CentralPerceptionComponent(CPerception perceptionConfiguration) { */ public void initialize() throws InternalFederateException { try { - Routing routing = SimulationKernel.SimulationKernel.getCentralNavigationComponent().getRouting(); + VehicleRouting routing = SimulationKernel.SimulationKernel.getCentralNavigationComponent().getRouting(); // evaluate bounding box for perception scenarioBounds = configuration.perceptionArea == null ? routing.getScenarioBounds() : configuration.perceptionArea.toCartesian(); diff --git a/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/config/CApplicationAmbassador.java b/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/config/CApplicationAmbassador.java index c03116e4c..660278841 100644 --- a/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/config/CApplicationAmbassador.java +++ b/fed/mosaic-application/src/main/java/org/eclipse/mosaic/fed/application/config/CApplicationAmbassador.java @@ -15,7 +15,8 @@ package org.eclipse.mosaic.fed.application.config; -import org.eclipse.mosaic.lib.routing.config.CRouting; +import org.eclipse.mosaic.lib.routing.config.CPublicTransportRouting; +import org.eclipse.mosaic.lib.routing.config.CVehicleRouting; import org.eclipse.mosaic.lib.util.gson.TimeFieldAdapter; import org.eclipse.mosaic.lib.util.scheduling.MultiThreadedEventScheduler; import org.eclipse.mosaic.rti.TIME; @@ -53,30 +54,37 @@ public class CApplicationAmbassador implements Serializable { */ public int eventSchedulerThreads = 1; + /** + * Configuration options for route calculation via public transport. + * Requires paths to OSM and GTFS files. + */ + public CPublicTransportRouting publicTransportConfiguration = new CPublicTransportRouting(); + /** * Class containing the information for the configuration of the * Routing/Navigation (CentralNavigationComponent). */ - public CRoutingByType navigationConfiguration = null; + public CRoutingByType navigationConfiguration = new CRoutingByType(); + + /** + * Configuration for the perception backend used in the ApplicationSimulator + * to determine surrounding vehicles. + */ + public CPerception perceptionConfiguration = new CPerception(); /** - * Extends the {@link CRouting} configuration with a type parameter - * allowing to define the actual {@link org.eclipse.mosaic.lib.routing.Routing} + * Extends the {@link CVehicleRouting} configuration with a type parameter + * allowing to define the actual {@link org.eclipse.mosaic.lib.routing.VehicleRouting} * implementation to use. */ - public static class CRoutingByType extends CRouting implements Serializable { + public static class CRoutingByType extends CVehicleRouting implements Serializable { /** - * Defines the {@link org.eclipse.mosaic.lib.routing.Routing} implementation + * Defines the {@link org.eclipse.mosaic.lib.routing.VehicleRouting} implementation * to use for navigation. Possible values are {@code "database" or "no-routing"}, * or any full-qualified java class name. */ - public String type = null; + public String type = "database"; } - /** - * Configuration for the perception backend used in the ApplicationSimulator - * to determine surrounding vehicles. - */ - public CPerception perceptionConfiguration = new CPerception(); } diff --git a/fed/mosaic-application/src/main/resources/CApplicationAmbassadorScheme.json b/fed/mosaic-application/src/main/resources/CApplicationAmbassadorScheme.json index 8cabada77..d8ea6ba43 100644 --- a/fed/mosaic-application/src/main/resources/CApplicationAmbassadorScheme.json +++ b/fed/mosaic-application/src/main/resources/CApplicationAmbassadorScheme.json @@ -27,6 +27,10 @@ "description": "Configuration options for the route calculation.", "$ref": "#/definitions/routingByType" }, + "publicTransportConfiguration": { + "description": "Configuration options for the public transport route calculation.", + "$ref": "#/definitions/ptRouting" + }, "perceptionConfiguration": { "description": "Configuration options for perception backend", "$ref": "#/definitions/perceptionConfiguration" @@ -48,6 +52,38 @@ } } }, + "ptRouting": { + "title": "ptRouting", + "description": "Object to define the configuration for the public transport route calculation.", + "type": "object", + "properties": { + "enabled": { + "description": "Defines, if public transport routing is enabled.", + "default": false, + "type": "boolean" + }, + "osmFile": { + "description": "The relative path to the OSM file to load with the GTFS feed.", + "default": "map.osm", + "type": "string" + }, + "gtfsFile": { + "description": "The relative path to the GTFS feed (ZIP archive).", + "default": "gtfs.zip", + "type": "string" + }, + "scheduleDateTime": { + "description": "The real time in ISO format at which the beginning of the simulation should point at. Example format: 2024-11-27T10:15:30", + "default": "2024-12-03T10:15:30", + "type": "string" + }, + "timeZone": { + "description": " The time zone of the location where the PT system is implemented, e.g., \"ECT\".", + "default": "ECT", + "type": "string" + } + } + }, "perceptionConfiguration": { "title": "perceptionConfiguration", "description": "Configuration options for perception backend", diff --git a/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponentTest.java b/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponentTest.java index 77e905f13..334cacc49 100644 --- a/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponentTest.java +++ b/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponentTest.java @@ -24,7 +24,6 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -44,11 +43,12 @@ import org.eclipse.mosaic.lib.objects.vehicle.VehicleRoute; import org.eclipse.mosaic.lib.routing.CandidateRoute; import org.eclipse.mosaic.lib.routing.IllegalRouteException; -import org.eclipse.mosaic.lib.routing.Routing; import org.eclipse.mosaic.lib.routing.RoutingParameters; import org.eclipse.mosaic.lib.routing.RoutingPosition; import org.eclipse.mosaic.lib.routing.RoutingRequest; -import org.eclipse.mosaic.lib.routing.config.CRouting; +import org.eclipse.mosaic.lib.routing.VehicleRouting; +import org.eclipse.mosaic.lib.routing.config.CPublicTransportRouting; +import org.eclipse.mosaic.lib.routing.config.CVehicleRouting; import org.eclipse.mosaic.lib.routing.norouting.NoRouting; import org.eclipse.mosaic.rti.TIME; import org.eclipse.mosaic.rti.api.IllegalValueException; @@ -79,7 +79,7 @@ public class CentralNavigationComponentTest { private CentralNavigationComponent cnc; - private Routing routingMock; + private VehicleRouting routingMock; private RtiAmbassador rtiAmbassadorMock; private static VehicleRoute createExampleRoute0() { @@ -125,7 +125,7 @@ public void initialize_vehicleRoutesInitializationSent() throws InternalFederate cnc.initialize(rtiAmbassadorMock); //ASSERT - verify(routingMock).initialize(isNull(CRouting.class), isA(File.class)); + verify(routingMock).initialize(isA(CVehicleRouting.class), isA(File.class)); verify(rtiAmbassadorMock).triggerInteraction(isA(VehicleRoutesInitialization.class)); } @@ -246,7 +246,7 @@ public void initializeNoRouting() throws InternalFederateException, IOException routingConfig.type = "no-routing"; CentralNavigationComponent centralNavigationComponent - = new CentralNavigationComponent(ambassadorParameter,routingConfig ); + = new CentralNavigationComponent(ambassadorParameter, routingConfig, new CPublicTransportRouting()); centralNavigationComponent.initialize(rtiAmbassadorMock); assertNotNull(centralNavigationComponent.getRouting()); @@ -264,7 +264,7 @@ public void initializeMyTestRouting() throws InternalFederateException, IOExcept routingConfig.type = MyTestRouting.class.getCanonicalName(); CentralNavigationComponent centralNavigationComponent - = new CentralNavigationComponent(ambassadorParameter,routingConfig ); + = new CentralNavigationComponent(ambassadorParameter, routingConfig, new CPublicTransportRouting()); centralNavigationComponent.initialize(rtiAmbassadorMock); assertNotNull(centralNavigationComponent.getRouting()); diff --git a/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponentTestRule.java b/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponentTestRule.java index fb6d220e1..a0d30127d 100644 --- a/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponentTestRule.java +++ b/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/ambassador/simulation/navigation/CentralNavigationComponentTestRule.java @@ -19,7 +19,7 @@ import org.eclipse.mosaic.fed.application.ambassador.SimulationKernel; import org.eclipse.mosaic.fed.application.config.CApplicationAmbassador; -import org.eclipse.mosaic.lib.routing.Routing; +import org.eclipse.mosaic.lib.routing.VehicleRouting; import org.eclipse.mosaic.lib.util.junit.TestUtils; import org.eclipse.mosaic.rti.api.InternalFederateException; import org.eclipse.mosaic.rti.api.RtiAmbassador; @@ -36,7 +36,7 @@ public class CentralNavigationComponentTestRule extends ExternalResource { private final static String configFile = "/application_config.json"; private TemporaryFolder folderRule; - private Routing routingMock = mock(Routing.class); + private VehicleRouting routingMock = mock(VehicleRouting.class); private CentralNavigationComponent centralNavigationComponent; private RtiAmbassador rtiAmbassadorMock; @@ -52,7 +52,7 @@ RtiAmbassador getRtiAmbassadorMock() { return rtiAmbassadorMock; } - public Routing getRoutingMock() { + public VehicleRouting getRoutingMock() { return routingMock; } @@ -63,9 +63,9 @@ protected void before() throws Throwable { CApplicationAmbassador applicationConfig = new CApplicationAmbassador(); AmbassadorParameter ambassadorParameters = new AmbassadorParameter("test", configCopy.getParentFile()); - centralNavigationComponent = new CentralNavigationComponent(ambassadorParameters, applicationConfig.navigationConfiguration) { + centralNavigationComponent = new CentralNavigationComponent(ambassadorParameters, applicationConfig.navigationConfiguration, applicationConfig.publicTransportConfiguration) { @Override - Routing createFromType(String type) throws InternalFederateException { + VehicleRouting createFromType(String type) throws InternalFederateException { return routingMock; } }; diff --git a/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/config/CApplicationAmbassadorTest.java b/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/config/CApplicationAmbassadorTest.java index 3fc41ab5d..729538880 100644 --- a/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/config/CApplicationAmbassadorTest.java +++ b/fed/mosaic-application/src/test/java/org/eclipse/mosaic/fed/application/config/CApplicationAmbassadorTest.java @@ -18,7 +18,6 @@ import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -58,8 +57,8 @@ public void readValidConfig_assertProperties() throws InstantiationException { assertNotNull(applicationAmbassadorConfiguration); // assert that configuration is created assertEquals(40 * TIME.SECOND, applicationAmbassadorConfiguration.messageCacheTime); assertTrue(applicationAmbassadorConfiguration.encodePayloads); - assertNull(applicationAmbassadorConfiguration.navigationConfiguration); - + assertNotNull(applicationAmbassadorConfiguration.navigationConfiguration); + assertEquals("database", applicationAmbassadorConfiguration.navigationConfiguration.type); } /** diff --git a/lib/mosaic-routing/pom.xml b/lib/mosaic-routing/pom.xml index 40b011110..a01a4edaf 100644 --- a/lib/mosaic-routing/pom.xml +++ b/lib/mosaic-routing/pom.xml @@ -34,12 +34,23 @@ com.graphhopper graphhopper-core + + com.graphhopper + graphhopper-reader-gtfs + + + com.opencsv + opencsv + + + io.mobilitydata.transit + gtfs-realtime-bindings + com.google.guava guava - org.eclipse.mosaic mosaic-geomath diff --git a/lib/mosaic-routing/src/main/java/com/csvreader/CsvReader.java b/lib/mosaic-routing/src/main/java/com/csvreader/CsvReader.java new file mode 100644 index 000000000..df66c555a --- /dev/null +++ b/lib/mosaic-routing/src/main/java/com/csvreader/CsvReader.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package com.csvreader; + +import com.opencsv.CSVParserBuilder; +import com.opencsv.CSVReader; +import com.opencsv.CSVReaderBuilder; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +/** + * Replaces com.csvreader.CsvReader from javacsv with an implementation which uses CSVReader from opencsv. + *

+ * By excluding javacsv jar and providing this class with the same full qualified name and same API as a supplement + * we are able to use opencsv instead without touching the code of graphhopper-reader-gtfs. + * Therefore, even though the IDE thinks this class is unused, it is actually used by graphhopper-reader-gtfs. + */ //TODO: remove this once the PR https://github.com/graphhopper/graphhopper/pull/3084 got accepted and we upgrade graphhopper +public class CsvReader { + + private final CSVReader openCsvReader; + private final Map headerIndex = new HashMap<>(); + + private String[] currenRecord; + + public CsvReader(InputStream inputStream, char delimiter, Charset charset) { + this(new InputStreamReader(inputStream, charset), delimiter); + } + + public CsvReader(Reader openCsvReader) { + this(openCsvReader, ','); + } + + public CsvReader(Reader openCsvReader, char delimiter) { + this.openCsvReader = new CSVReaderBuilder(openCsvReader) + .withCSVParser(new CSVParserBuilder().withSeparator(delimiter).build()) + .build(); + } + + public boolean readRecord() { + try { + do { + currenRecord = this.openCsvReader.readNextSilently(); + if (currenRecord == null) { + return false; + } + } while (currenRecord.length == 1 && StringUtils.isEmpty(currenRecord[0])); + return true; + } catch (IOException e) { + currenRecord = null; + return false; + } + } + + public boolean readHeaders() { + Validate.isTrue(currenRecord == null, "Reader has already been used."); + if (readRecord()) { + setHeaders(currenRecord); + return true; + } + return false; + } + + public void setHeaders(String[] strings) { + int index = 0; + for (String entry : strings) { + headerIndex.put(entry, index++); + } + } + + public String get(String column) { + Validate.isTrue(!headerIndex.isEmpty(), "No header defined."); + final Integer index = headerIndex.get(column); + return index != null && index < currenRecord.length + ? currenRecord[index] + : ""; + } +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/Routing.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/VehicleRouting.java similarity index 94% rename from lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/Routing.java rename to lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/VehicleRouting.java index d3be8cef5..1b06b47a1 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/Routing.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/VehicleRouting.java @@ -21,21 +21,21 @@ import org.eclipse.mosaic.lib.objects.road.INode; import org.eclipse.mosaic.lib.objects.road.IRoadPosition; import org.eclipse.mosaic.lib.objects.vehicle.VehicleRoute; -import org.eclipse.mosaic.lib.routing.config.CRouting; +import org.eclipse.mosaic.lib.routing.config.CVehicleRouting; import org.eclipse.mosaic.rti.api.InternalFederateException; import java.io.File; import java.util.Map; /** - * Interface providing a routing API for applications. + * Interface providing a vehicle routing API for applications. */ -public interface Routing { +public interface VehicleRouting { /** * Initializes the connection to the belonging database. */ - void initialize(CRouting routingConfiguration, File configurationLocation) throws InternalFederateException; + void initialize(CVehicleRouting routingConfiguration, File configurationLocation) throws InternalFederateException; /** * Find a route from your actual position to the target position. diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/config/CPublicTransportRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/config/CPublicTransportRouting.java new file mode 100644 index 000000000..6c534b24b --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/config/CPublicTransportRouting.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.config; + +import java.io.Serializable; + +public class CPublicTransportRouting implements Serializable { + + /** + * Declares if PT routing is enabled (default=false). + */ + public boolean enabled = false; + + /** + * The path to the OSM file which is used to calculate walking between PT legs. + * The provided path is expected to be relative to the application directory of the scenario. + */ + public String osmFile = "map.osm"; + + /** + * The path to the GTFS file (ZIP archive) which contains the whole PT schedule. + * The provided path is expected to be relative to the application directory of the scenario. + */ + public String gtfsFile = "gtfs.zip"; + + /** + * The real time in ISO format at which the beginning of the simulation should point at. + * Example format: 2024-11-27T10:15:30 + */ + public String scheduleDateTime = "2024-12-03T10:15:30"; + + /** + * The time zone of the location where the PT system is implemented. + */ + public String timeZone = "ECT"; +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/config/CRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/config/CVehicleRouting.java similarity index 80% rename from lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/config/CRouting.java rename to lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/config/CVehicleRouting.java index 5e94530f5..a44779975 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/config/CRouting.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/config/CVehicleRouting.java @@ -20,10 +20,10 @@ /** * Base Class for the navigation configuration. */ -public class CRouting implements Serializable { +public class CVehicleRouting implements Serializable { /** - * The source for the route calculation, e.g. the path to the database containing the road network. + * The source for the route calculation, e.g., the path to the database containing the road network. */ public String source = null; diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/database/DatabaseRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/database/DatabaseRouting.java index 1186b05b2..8aff3812f 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/database/DatabaseRouting.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/database/DatabaseRouting.java @@ -33,10 +33,10 @@ import org.eclipse.mosaic.lib.objects.vehicle.VehicleRoute; import org.eclipse.mosaic.lib.routing.CandidateRoute; import org.eclipse.mosaic.lib.routing.IllegalRouteException; -import org.eclipse.mosaic.lib.routing.Routing; import org.eclipse.mosaic.lib.routing.RoutingRequest; import org.eclipse.mosaic.lib.routing.RoutingResponse; -import org.eclipse.mosaic.lib.routing.config.CRouting; +import org.eclipse.mosaic.lib.routing.VehicleRouting; +import org.eclipse.mosaic.lib.routing.config.CVehicleRouting; import org.eclipse.mosaic.lib.routing.graphhopper.GraphHopperRouting; import org.eclipse.mosaic.rti.api.InternalFederateException; @@ -50,10 +50,10 @@ import java.util.Map; /** - * An implementation of the {@link Routing} interface which provides access to routing functions + * An implementation of the {@link VehicleRouting} interface which provides access to routing functions * based on data of the scenario-database. */ -public class DatabaseRouting implements Routing { +public class DatabaseRouting implements VehicleRouting { private final static Logger log = LoggerFactory.getLogger(DatabaseRouting.class); @@ -65,7 +65,7 @@ public class DatabaseRouting implements Routing { private GraphHopperRouting routing; @Override - public void initialize(final CRouting configuration, final File baseDirectory) throws InternalFederateException { + public void initialize(final CVehicleRouting configuration, final File baseDirectory) throws InternalFederateException { File dbFile; // try to find the database file diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/norouting/NoRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/norouting/NoRouting.java index 6922e1ef5..b4447c071 100644 --- a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/norouting/NoRouting.java +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/norouting/NoRouting.java @@ -24,10 +24,10 @@ import org.eclipse.mosaic.lib.objects.vehicle.VehicleRoute; import org.eclipse.mosaic.lib.routing.CandidateRoute; import org.eclipse.mosaic.lib.routing.IllegalRouteException; -import org.eclipse.mosaic.lib.routing.Routing; import org.eclipse.mosaic.lib.routing.RoutingRequest; import org.eclipse.mosaic.lib.routing.RoutingResponse; -import org.eclipse.mosaic.lib.routing.config.CRouting; +import org.eclipse.mosaic.lib.routing.VehicleRouting; +import org.eclipse.mosaic.lib.routing.config.CVehicleRouting; import org.eclipse.mosaic.rti.api.InternalFederateException; import com.google.common.collect.Lists; @@ -37,14 +37,14 @@ import java.util.Map; /** - * Implementation of {@link Routing} if no scenario database + * Implementation of {@link VehicleRouting} if no scenario database * or any other road traffic map is present. In that case, online-routing * during the simulation is disabled. */ -public class NoRouting implements Routing { +public class NoRouting implements VehicleRouting { @Override - public void initialize(CRouting configuration, File configurationLocation) throws InternalFederateException { + public void initialize(CVehicleRouting configuration, File configurationLocation) throws InternalFederateException { // nop } diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/MultiModalLeg.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/MultiModalLeg.java new file mode 100644 index 000000000..81c4b75a5 --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/MultiModalLeg.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.pt; + +import org.eclipse.mosaic.lib.objects.vehicle.VehicleDeparture; + +/** + * A leg of a multi-modal-journey consists of a planned departure and planned arrival time, and + * details about the transport mode, such as the walking path or the public transport route. + */ +public class MultiModalLeg { + + public enum Type { + /** + * For legs which uses public transport. + */ + PUBLIC_TRANSPORT, + /** + * For legs which will require foot work. + */ + WALKING, + /** + * For legs which require to spawn a new vehicle. + */ + VEHICLE_PRIVATE, + /** + * For legs which uses a shared vehicle which already exists in the simulation. + */ + VEHICLE_SHARED + } + + private final Type legType; + private final long departureTime; + private final long arrivalTime; + + /** + * For legs which uses public transport. + */ + private PtLeg publicTransportationLeg = null; + + /** + * For legs which will require foot work. + */ + private WalkLeg walkLeg = null; + + /** + * For legs which require to spawn a new vehicle. + */ + private VehicleDeparture vehicleLeg = null; + + /** + * For legs which uses a shared vehicle which already exists in the simulation. + */ + private String sharedVehicleId = null; + + /** + * Creates a new leg in which a new vehicle must be spawned. + */ + public MultiModalLeg(VehicleDeparture vehicleLeg, long departureTime, long arrivalTime) { + this.legType = Type.VEHICLE_PRIVATE; + this.vehicleLeg = vehicleLeg; + + this.departureTime = departureTime; + this.arrivalTime = arrivalTime; + } + + /** + * Creates a new leg which uses public transport. + */ + public MultiModalLeg(PtLeg ptRoute, long departureTime, long arrivalTime) { + this.legType = Type.PUBLIC_TRANSPORT; + this.publicTransportationLeg = ptRoute; + + this.departureTime = departureTime; + this.arrivalTime = arrivalTime; + } + + /** + * Creates a new leg which uses walking mode. + */ + public MultiModalLeg(WalkLeg walkLeg, long departureTime, long arrivalTime) { + this.legType = Type.WALKING; + this.walkLeg = walkLeg; + + this.departureTime = departureTime; + this.arrivalTime = arrivalTime; + } + + /** + * Creates a new leg which uses a shared vehicle by a given vehicle ID. + */ + public MultiModalLeg(String vehicleId, long departureTime, long arrivalTime) { + this.legType = Type.VEHICLE_PRIVATE; + this.sharedVehicleId = vehicleId; + + this.departureTime = departureTime; + this.arrivalTime = arrivalTime; + } + + public long getArrivalTime() { + return arrivalTime; + } + + public long getDepartureTime() { + return departureTime; + } + + public Type getLegType() { + return legType; + } + + /** + * TODO: we should redesign this class somehow that we don't have to return Object here. + */ + public Object getLeg() { + return switch (legType) { + case VEHICLE_PRIVATE -> vehicleLeg; + case VEHICLE_SHARED -> sharedVehicleId; + case PUBLIC_TRANSPORT -> publicTransportationLeg; + case WALKING -> walkLeg; + }; + } +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/MultiModalRoute.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/MultiModalRoute.java new file mode 100644 index 000000000..a696586a5 --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/MultiModalRoute.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.pt; + +import java.util.ArrayList; +import java.util.List; + +/** + * A multi-modal route which consists of multiple legs. + */ +public class MultiModalRoute { + + private final List legs = new ArrayList<>(); + + public MultiModalRoute(List legs) { + this.legs.addAll(legs); + } + + /** + * Returns the individual legs of this multi-modal route. + */ + public List getLegs() { + return legs; + } + +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtLeg.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtLeg.java new file mode 100644 index 000000000..903beaa6c --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtLeg.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.pt; + +import org.eclipse.mosaic.lib.geo.GeoPoint; + +import java.util.ArrayList; +import java.util.List; + +/** + * A public transport leg contains all pt stops of the route. + */ +public class PtLeg { + + /** + * Each public transport stop consists of its geo-location and the planned arrival and departure time at this stop. + * + * @param location The geographic location of the stop. + * @param arrivalTime The time when arriving at this stop. {@code null} for the first stop of the leg. + * @param departureTime The time when leaving this stop. {@code null} for the last stop of the leg. + */ + public record PtStop(GeoPoint location, Long arrivalTime, Long departureTime) {} + + private final List stops = new ArrayList<>(); + + public PtLeg(List stops) { + this.stops.addAll(stops); + } + + /** + * Returns the list of stops along the public transport route. + */ + public List getStops() { + return stops; + } + +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRouting.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRouting.java new file mode 100644 index 000000000..fb86da19c --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRouting.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.pt; + +import org.eclipse.mosaic.lib.geo.GeoPoint; +import org.eclipse.mosaic.lib.math.SpeedUtils; +import org.eclipse.mosaic.lib.routing.config.CPublicTransportRouting; + +import com.google.common.collect.Iterables; +import com.graphhopper.GHResponse; +import com.graphhopper.GraphHopperConfig; +import com.graphhopper.ResponsePath; +import com.graphhopper.Trip; +import com.graphhopper.config.Profile; +import com.graphhopper.gtfs.GraphHopperGtfs; +import com.graphhopper.gtfs.PtRouter; +import com.graphhopper.gtfs.PtRouterImpl; +import com.graphhopper.gtfs.Request; +import com.graphhopper.util.StopWatch; +import com.graphhopper.util.TranslationMap; +import org.apache.commons.lang3.Validate; +import org.locationtech.jts.geom.Coordinate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.file.Path; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Implementation of Public Transport Routing based on GraphHopper GTFS. + * Uses a GTFS file for public transport schedule and an OSM file for walking paths to get access to public transport stations. + * + */ +public class PtRouting { + + private static final Logger LOG = LoggerFactory.getLogger(PtRouting.class); + + private final ExecutorService routingExecution = Executors.newSingleThreadExecutor(); + + private PtRouter ptRouter; + private GraphHopperGtfs graphHopperGtfs; + + private LocalDateTime scheduleDateTime; + private ZoneId timeZone; + + /** + * Initializes the pt routing if it is enabled in the provided {@link CPublicTransportRouting}. + * All paths defined in the provided config are expected to be relative to the provided configuration + * location. + */ + public void initialize(CPublicTransportRouting routingConfiguration, File configurationLocation) { + if (!routingConfiguration.enabled) { + return; + } + + scheduleDateTime = LocalDateTime.parse(routingConfiguration.scheduleDateTime, DateTimeFormatter.ISO_DATE_TIME); + timeZone = ZoneId.of(ZoneId.SHORT_IDS.get(routingConfiguration.timeZone)); + + final Path baseDirectory = configurationLocation.toPath(); + + GraphHopperConfig ghConfig = new GraphHopperConfig() + .putObject("import.osm.ignored_highways", "motorway,trunk,primary") // don't use those roads for walking paths + .putObject("graph.location", baseDirectory.resolve("ptgraph").toAbsolutePath().toString()) // + .putObject("datareader.file", baseDirectory.resolve(routingConfiguration.osmFile).toAbsolutePath().toString()) + .putObject("gtfs.file", baseDirectory.resolve(routingConfiguration.gtfsFile).toAbsolutePath().toString()) + .setProfiles(Collections.singletonList(new Profile("foot").setVehicle("foot"))); + + final StopWatch sw = new StopWatch(); + sw.start(); + graphHopperGtfs = new GraphHopperGtfs(ghConfig); + graphHopperGtfs.init(ghConfig); + graphHopperGtfs.importOrLoad(); + sw.stop(); + + ptRouter = new PtRouterImpl.Factory(ghConfig, + new TranslationMap().doImport(), + graphHopperGtfs.getBaseGraph(), + graphHopperGtfs.getEncodingManager(), + graphHopperGtfs.getLocationIndex(), + graphHopperGtfs.getGtfsStorage() + ).createWithoutRealtimeFeed(); + + LOG.info("Initialized Public Transport Router. Took {} ms.", sw.getMillis()); + } + + /** + * Calculates a public transport route according to the given request. + * The request must contain a valid start and target position, as well as valid request time. + */ + public PtRoutingResponse findPtRoute(PtRoutingRequest request) { + if (ptRouter == null) { + throw new IllegalStateException("PT Routing is not available. Must be enabled in application_config.json."); + } + Validate.notNull(request.getOrigin(), "Starting point must not be null."); + Validate.notNull(request.getDestination(), "Target point must not be null."); + Validate.isTrue(request.getRequestTime() >= 0, "Invalid request time."); + Validate.isTrue(request.getRoutingParameters().getWalkingSpeedMps() > 0, "Walking speed must be greater than 0."); + + Instant departureTime = toScheduleTime(request.getRequestTime()); + + final Request ghRequest = new Request( + request.getOrigin().getLatitude(), + request.getOrigin().getLongitude(), + request.getDestination().getLatitude(), + request.getDestination().getLongitude() + ); + // ghRequest.setBlockedRouteTypes(request.getRoutingParameters().excludedPtModes);//FIXME generalize this + ghRequest.setEarliestDepartureTime(departureTime); + ghRequest.setWalkSpeedKmH(SpeedUtils.ms2kmh(request.getRoutingParameters().getWalkingSpeedMps())); + + final Future responseFuture = routingExecution.submit(() -> ptRouter.route(ghRequest)); + final GHResponse route; + try { + final StopWatch sw = new StopWatch(); + sw.start(); + route = responseFuture.get(30, TimeUnit.SECONDS); + sw.stop(); + LOG.debug("Took {} ms to calculate public transport route.", sw.getMillis()); + } catch (TimeoutException e) { + responseFuture.cancel(true); + throw new RuntimeException("Could not finish route calculation. Exceeded timeout."); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Could not finish route calculation. Exceeded timeout.", e); + } + + final List legs = convertToMultiModalLegs(route.getBest()); + + return new PtRoutingResponse(new MultiModalRoute(legs)); + } + + private List convertToMultiModalLegs(ResponsePath ghBestRoute) { + List legs = new ArrayList<>(); + for (Trip.Leg leg : ghBestRoute.getLegs()) { + if (leg instanceof Trip.PtLeg ptLeg) { + List newStops = new ArrayList<>(); + for (Trip.Stop stop : ptLeg.stops) { + newStops.add(new PtLeg.PtStop( + GeoPoint.lonLat(stop.geometry.getX(), stop.geometry.getY()), + fromScheduleTime(stop.arrivalTime), + fromScheduleTime(stop.departureTime) + )); + } + legs.add(new MultiModalLeg( + new PtLeg(newStops), + newStops.get(0).departureTime(), + Iterables.getLast(newStops).arrivalTime() + )); + } else if (leg instanceof Trip.WalkLeg walkLeg) { + List waypoints = new ArrayList<>(); + for (Coordinate coordinate : walkLeg.geometry.getCoordinates()) { + waypoints.add(GeoPoint.lonLat(coordinate.x, coordinate.y)); + } + legs.add(new MultiModalLeg( + new WalkLeg(waypoints), + fromScheduleTime(leg.getDepartureTime()), + fromScheduleTime(leg.getArrivalTime()) + )); + } + } + return legs; + } + + /** + * Returns a {@link Instant} time object depicting a real timestamp used for requesting the PT schedule. + */ + private Instant toScheduleTime(long simTime) { + return scheduleDateTime.plusNanos(simTime).atZone(timeZone).toInstant(); + } + + /** + * Converts the provided {@link Date} object depicting a real timestamp back to the simulation time. + */ + private Long fromScheduleTime(@Nullable Date date) { + if (date == null) { + return null; + } + return fromScheduleTime(date.toInstant()); + } + + private long fromScheduleTime(@Nonnull Instant instant) { + return scheduleDateTime.until(LocalDateTime.ofInstant(instant, timeZone), ChronoUnit.NANOS); + } + + public void close() { + if (graphHopperGtfs != null) { + graphHopperGtfs.close(); + } + } +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingParameters.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingParameters.java new file mode 100644 index 000000000..08358b365 --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingParameters.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.pt; + +import org.eclipse.mosaic.lib.math.SpeedUtils; + +public class PtRoutingParameters { + + private double walkingSpeedMps = SpeedUtils.kmh2ms(5); + + public PtRoutingParameters walkingSpeedKmh(double kmh) { + walkingSpeedMps = SpeedUtils.kmh2ms(kmh); + return this; + } + + public PtRoutingParameters walkingSpeedMps(double meterPerSecond) { + walkingSpeedMps = meterPerSecond; + return this; + } + + public double getWalkingSpeedMps() { + return walkingSpeedMps; + } +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingRequest.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingRequest.java new file mode 100644 index 000000000..8621bc9c9 --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingRequest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.pt; + +import org.eclipse.mosaic.lib.geo.GeoPoint; + +public class PtRoutingRequest { + + private final long requestTime; + private final GeoPoint origin; + private final GeoPoint destination; + + private final PtRoutingParameters routingParameters; + + /** + * Constructs a request for calculating a public transport route. + * + * @param requestTime The earliest time to start the journey. + * @param origin The geographic location to start the journey. + * @param destination The geographic location to end the journey. + */ + public PtRoutingRequest(long requestTime, GeoPoint origin, GeoPoint destination) { + this(requestTime, origin, destination, new PtRoutingParameters()); + } + + /** + * Constructs a request for calculating a public transport route. + * + * @param requestTime The earliest time to start the journey. + * @param origin The geographic location to start the journey. + * @param destination The geographic location to end the journey. + * @param additionalParameters Additional parameters, such as walking speed. + */ + public PtRoutingRequest(long requestTime, GeoPoint origin, GeoPoint destination, PtRoutingParameters additionalParameters) { + this.requestTime = requestTime; + this.origin = origin; + this.destination = destination; + this.routingParameters = additionalParameters; + } + + public long getRequestTime() { + return requestTime; + } + + public GeoPoint getOrigin() { + return origin; + } + + public GeoPoint getDestination() { + return destination; + } + + public PtRoutingParameters getRoutingParameters() { + return routingParameters; + } +} diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingResponse.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingResponse.java new file mode 100644 index 000000000..b60e57708 --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingResponse.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.pt; + +/** + * The result of the routing request. Contains the multi-modal route which + * matches the routing request at best. + */ +public record PtRoutingResponse(MultiModalRoute bestRoute) { } diff --git a/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/WalkLeg.java b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/WalkLeg.java new file mode 100644 index 000000000..ff7d98931 --- /dev/null +++ b/lib/mosaic-routing/src/main/java/org/eclipse/mosaic/lib/routing/pt/WalkLeg.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.pt; + +import org.eclipse.mosaic.lib.geo.GeoPoint; + +import java.util.ArrayList; +import java.util.List; + +/** + * A walking leg contains a linestring of geographical points which form the walking route. + */ +public class WalkLeg { + + private final List waypoints = new ArrayList<>(); + + public WalkLeg(List waypoints) { + this.waypoints.addAll(waypoints); + } + + /** + * Returns the list of geographical positions which form the walking route. + */ + public final List getWaypoints() { + return waypoints; + } +} diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/database/DatabaseRoutingTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/database/DatabaseRoutingTest.java index 66b283cc9..62416a0cf 100644 --- a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/database/DatabaseRoutingTest.java +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/database/DatabaseRoutingTest.java @@ -32,7 +32,7 @@ import org.eclipse.mosaic.lib.routing.RoutingPosition; import org.eclipse.mosaic.lib.routing.RoutingRequest; import org.eclipse.mosaic.lib.routing.RoutingResponse; -import org.eclipse.mosaic.lib.routing.config.CRouting; +import org.eclipse.mosaic.lib.routing.config.CVehicleRouting; import org.eclipse.mosaic.rti.api.InternalFederateException; import org.apache.commons.io.FileUtils; @@ -66,7 +66,7 @@ public class DatabaseRoutingTest { private final static String dbFile = "/tiergarten.db"; private DatabaseRouting databaseRouting; - private CRouting configuration; + private CVehicleRouting configuration; private File cfgDir; @@ -80,7 +80,7 @@ public void setup() throws IOException { FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(dbFile), dbFileCopy); - configuration = new CRouting(); + configuration = new CVehicleRouting(); databaseRouting = new DatabaseRouting(); } diff --git a/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingTest.java b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingTest.java new file mode 100644 index 000000000..3098d23c4 --- /dev/null +++ b/lib/mosaic-routing/src/test/java/org/eclipse/mosaic/lib/routing/pt/PtRoutingTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024 Fraunhofer FOKUS and others. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contact: mosaic@fokus.fraunhofer.de + */ + +package org.eclipse.mosaic.lib.routing.pt; + +import static org.junit.Assert.assertEquals; + +import org.eclipse.mosaic.lib.geo.GeoPoint; +import org.eclipse.mosaic.lib.junit.GeoProjectionRule; +import org.eclipse.mosaic.lib.routing.config.CPublicTransportRouting; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.time.LocalTime; + +public class PtRoutingTest { + + @Rule + public GeoProjectionRule transformationRule = new GeoProjectionRule(GeoPoint.latLon(36.9, -116.7)); + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private PtRouting ptRouting; + private CPublicTransportRouting routingConfiguration; + private File configDir; + + @Before + public void setup() throws IOException { + routingConfiguration = new CPublicTransportRouting(); + routingConfiguration.enabled = true; + routingConfiguration.scheduleDateTime = "2010-01-01T00:00:00"; + routingConfiguration.timeZone = "PST"; + routingConfiguration.osmFile = "pt.osm"; + routingConfiguration.gtfsFile = "pt-gtfs.zip"; + + configDir = folder.newFolder("pt"); + final File osmFileCopy = folder.newFile("pt/" + routingConfiguration.osmFile); + final File gtfsFileCopy = folder.newFile("pt/" + routingConfiguration.gtfsFile); + + FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/pt/pt.osm"), osmFileCopy); + FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/pt/pt-gtfs.zip"), gtfsFileCopy); + + ptRouting = new PtRouting(); + } + + @Test(expected = IllegalStateException.class) + public void initialize_skipInitialization() { + routingConfiguration.enabled = false; + ptRouting.initialize(routingConfiguration, configDir); + + // ASSERT + ptRouting.findPtRoute(new PtRoutingRequest(0, GeoPoint.ORIGO, GeoPoint.ORIGO)); + } + + @Test + public void findRoute_findPublicTransportRoute() { + ptRouting.initialize(routingConfiguration, configDir); + + PtRoutingResponse response = ptRouting.findPtRoute(new PtRoutingRequest( + LocalTime.of(8, 57).toNanoOfDay(), + GeoPoint.latLon(36.900760, -116.766464), + GeoPoint.latLon(36.907353, -116.761829), + new PtRoutingParameters().walkingSpeedKmh(3) + )); + + // ASSERT + MultiModalRoute route = response.bestRoute(); + assertEquals(3, route.getLegs().size()); + assertEquals(MultiModalLeg.Type.WALKING, route.getLegs().get(0).getLegType()); + assertEquals(MultiModalLeg.Type.PUBLIC_TRANSPORT, route.getLegs().get(1).getLegType()); + assertEquals(MultiModalLeg.Type.WALKING, route.getLegs().get(2).getLegType()); + } + + @Test + public void findRoute_IWalkFasterThanTheBus() { + ptRouting.initialize(routingConfiguration, configDir); + + PtRoutingResponse response = ptRouting.findPtRoute(new PtRoutingRequest( + LocalTime.of(8, 50).toNanoOfDay(), + GeoPoint.latLon(36.900760, -116.766464), + GeoPoint.latLon(36.907353, -116.761829), + new PtRoutingParameters().walkingSpeedKmh(5) + )); + + // ASSERT + MultiModalRoute route = response.bestRoute(); + assertEquals(1, route.getLegs().size()); + assertEquals(MultiModalLeg.Type.WALKING, route.getLegs().get(0).getLegType()); + } + +} diff --git a/lib/mosaic-routing/src/test/resources/pt/pt-gtfs.zip b/lib/mosaic-routing/src/test/resources/pt/pt-gtfs.zip new file mode 100644 index 000000000..2c8238e87 Binary files /dev/null and b/lib/mosaic-routing/src/test/resources/pt/pt-gtfs.zip differ diff --git a/lib/mosaic-routing/src/test/resources/pt/pt.osm b/lib/mosaic-routing/src/test/resources/pt/pt.osm new file mode 100644 index 000000000..c2252f48e --- /dev/null +++ b/lib/mosaic-routing/src/test/resources/pt/pt.osm @@ -0,0 +1,16852 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 710d30706..6b7b7c17f 100644 --- a/pom.xml +++ b/pom.xml @@ -96,11 +96,11 @@ 1.3.9-1 8.0 2.10.1 + 0.0.5 32.1.1-jre 0.8.1 2.16.1 1.3.9 - 2.7.5 1.1.13 0.1.51 1.3.1 @@ -112,6 +112,8 @@ 0.2.16 1.5.0 5.6.0 + 1.0.8 + 5.9 3.23.2 2.0.12 3.42.0.0 @@ -334,10 +336,6 @@ de.westnordost osm-legal-default-speeds-jvm - - org.codehaus.janino - janino - org.apache.xmlgraphics xmlgraphics-commons @@ -352,6 +350,50 @@
+ + + com.graphhopper + graphhopper-reader-gtfs + ${version.graphhopper} + + + javax.inject + javax.inject + + + net.sourceforge.javacsv + javacsv + + + + + + com.opencsv + opencsv + ${version.opencsv} + + + commons-beanutils + commons-beanutils + + + org.apache.commons + commons-collections4 + + + + + + org.mapdb + mapdb + ${version.mapdb} + + + + io.mobilitydata.transit + gtfs-realtime-bindings + ${version.gtfs-bindings} + com.carrotsearch