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