diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..273fff0a
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+ - package-ecosystem: "maven" # See documentation for possible values
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml
index be71f3ee..7af50c61 100644
--- a/.github/workflows/maven-release.yml
+++ b/.github/workflows/maven-release.yml
@@ -2,8 +2,7 @@ name: Maven Deploy
on:
release:
- types:
- - created
+ types: [created]
jobs:
deploy-release:
@@ -24,7 +23,7 @@ jobs:
- name: Setup Java
uses: actions/setup-java@v2
with:
- java-version: 11
+ java-version: 17
distribution: 'adopt'
server-id: pt2matsim-releases
server-username: MAVEN_USERNAME
diff --git a/.github/workflows/maven-test.yml b/.github/workflows/maven-test.yml
index e71cf1d1..7468c447 100644
--- a/.github/workflows/maven-test.yml
+++ b/.github/workflows/maven-test.yml
@@ -13,9 +13,9 @@ jobs:
steps:
- uses: actions/checkout@v2
- - name: Set up JDK 11
+ - name: Set up JDK 17
uses: actions/setup-java@v1
with:
- java-version: 1.11
+ java-version: 1.17
- name: Build with Maven
run: mvn test -B -Dmatsim.preferLocalDtds=true
diff --git a/README.md b/README.md
index 6c7f573b..4a7462c5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# PT2MATSim
-[![Build Status](https://travis-ci.org/matsim-org/pt2matsim.svg?branch=master)](https://travis-ci.org/matsim-org/pt2matsim)
+[![Status Maven Test](https://github.com/matsim-org/pt2matsim/actions/workflows/maven-test.yml/badge.svg)](https://github.com/matsim-org/pt2matsim/actions/workflows/maven-test.yml)
PT2MATSim is a package to convert public transit data from GTFS, HAFAS or OSM to a completely mapped MATSim schedule.
@@ -45,8 +45,8 @@ To include pt2matsim in your own maven project, add this snippet to your pom.xml
org.matsim
pt2matsim
- 22.3
+ 24.4
-The master branch contains the snapshot version with the latest changes. Clone the git repository to use it.
\ No newline at end of file
+The master branch contains the snapshot version with the latest changes. Clone the git repository to use it.
diff --git a/doc/defaultOsmConfig.xml b/doc/defaultOsmConfig.xml
index 84af330d..bf37dc9a 100644
--- a/doc/defaultOsmConfig.xml
+++ b/doc/defaultOsmConfig.xml
@@ -24,6 +24,8 @@
This file can be used for visualization purposes in Simunto Via or GIS software. -->
+
+
diff --git a/pom.xml b/pom.xml
index f5df9da7..609e71d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,17 +4,17 @@
4.0.0
org.matsim
pt2matsim
- 22.3
+ 24.5-SNAPSHOT
PT2MATSim
Public Transport to MATSim
- pt2matsim-releases
- pt2matsim Releases Maven Repository
- https://repo.matsim.org/repository/matsim-releases/
-
+ pt2matsim-releases
+ pt2matsim Releases Maven Repository
+ https://repo.matsim.org/repository/matsim-releases/
+
@@ -23,70 +23,118 @@
https://repo.matsim.org/repository/matsim
- osgeo
- https://repo.osgeo.org/repository/release
+ osgeo
+ https://repo.osgeo.org/repository/release
-
- 24.2
-
+
+ 29.6
+ 2.18.2
+ 1.19.0
+ 2.23.1
+ 5.10.3
+
org.matsim
matsim
- 13.0
+ 2024.0
- junit
- junit
- 4.13.1
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
test
com.opencsv
opencsv
- 3.7
+ 5.9
- commons-io
- commons-io
- 2.7
-
+ commons-io
+ commons-io
+ 2.16.1
+
de.grundid.opendatalab
geojson-jackson
- 1.5
+ 1.14
net.lingala.zip4j
zip4j
- 2.9.1
+ 2.11.5
- org.geotools
- gt-referencing
- ${geotools.version}
+ com.google.guava
+ guava
+ 33.3.1-jre
+
+
+ org.apache.logging.log4j
+ log4j-api
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ org.locationtech.jts
+ jts-core
+ ${jts.version}
org.geotools
- gt-epsg-hsql
+ gt-opengis
${geotools.version}
+
+
+
+ com.fasterxml.jackson
+ jackson-bom
+ ${jackson.version}
+ import
+ pom
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.8.0
+
+
+
org.apache.maven.plugins
maven-compiler-plugin
- 2.3.2
+ 3.13.0
- 1.8
- 1.8
- false
- false
+ 17
+ true
+ true
UTF-8
true
@@ -101,7 +149,7 @@
org.apache.maven.plugins
maven-shade-plugin
- 3.1.0
+ 3.6.0
@@ -109,6 +157,7 @@
true
+ false
@@ -117,16 +166,16 @@
-
-
- *:*
-
- META-INF/*.SF
- META-INF/*.DSA
- META-INF/*.RSA
-
-
-
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
@@ -134,7 +183,7 @@
org.apache.maven.plugins
maven-source-plugin
- 3.0.1
+ 3.3.1
attach-sources
diff --git a/src/main/java/org/matsim/pt2matsim/config/OsmConverterConfigGroup.java b/src/main/java/org/matsim/pt2matsim/config/OsmConverterConfigGroup.java
index 6ef16f54..c4f50732 100644
--- a/src/main/java/org/matsim/pt2matsim/config/OsmConverterConfigGroup.java
+++ b/src/main/java/org/matsim/pt2matsim/config/OsmConverterConfigGroup.java
@@ -21,6 +21,8 @@
import org.matsim.core.api.internal.MatsimParameters;
import org.matsim.core.config.*;
+import org.matsim.core.config.ReflectiveConfigGroup.Comment;
+import org.matsim.core.config.ReflectiveConfigGroup.Parameter;
import org.matsim.core.utils.collections.CollectionUtils;
import org.matsim.pt2matsim.osm.lib.Osm;
@@ -65,6 +67,9 @@ public class OsmConverterConfigGroup extends ReflectiveConfigGroup {
private boolean keepTagsAsAttributes = true;
private boolean keepWaysWithPublicTransit = true;
+ @Parameter
+ @Comment("If true: OSM turn restrictions are parsed and written as disallowedNextLinks attribute to the first link.")
+ public boolean parseTurnRestrictions = false;
public OsmConverterConfigGroup() {
super(GROUP_NAME);
@@ -254,7 +259,7 @@ public ConfigGroup createParameterSet(final String type) {
*/
public static class OsmWayParams extends ReflectiveConfigGroup implements MatsimParameters {
- public final static String SET_NAME = "wayDefaultParams";
+ public static final String SET_NAME = "wayDefaultParams";
private String osmKey;
private String osmValue;
@@ -385,7 +390,7 @@ private void setAllowedTransportModesString(String allowedTransportModesString)
*/
public static class RoutableSubnetworkParams extends ReflectiveConfigGroup implements MatsimParameters {
- public final static String SET_NAME = "routableSubnetwork";
+ public static final String SET_NAME = "routableSubnetwork";
/** Network mode, for which a consistent routable network is created **/
private String subnetworkMode;
diff --git a/src/main/java/org/matsim/pt2matsim/config/PublicTransitMappingConfigGroup.java b/src/main/java/org/matsim/pt2matsim/config/PublicTransitMappingConfigGroup.java
index 2eb638ce..fc41e3d4 100644
--- a/src/main/java/org/matsim/pt2matsim/config/PublicTransitMappingConfigGroup.java
+++ b/src/main/java/org/matsim/pt2matsim/config/PublicTransitMappingConfigGroup.java
@@ -479,7 +479,7 @@ public void setCandidateDistanceMultiplier(double multiplier) {
*/
public static class TransportModeAssignment extends ReflectiveConfigGroup implements MatsimParameters {
- public final static String SET_NAME = "transportModeAssignment";
+ public static final String SET_NAME = "transportModeAssignment";
private static final String SCHEDULE_MODE = "scheduleMode";
private static final String NETWORK_MODES = "networkModes";
diff --git a/src/main/java/org/matsim/pt2matsim/editor/BasicScheduleEditor.java b/src/main/java/org/matsim/pt2matsim/editor/BasicScheduleEditor.java
index adcc6805..3cb9fc0f 100644
--- a/src/main/java/org/matsim/pt2matsim/editor/BasicScheduleEditor.java
+++ b/src/main/java/org/matsim/pt2matsim/editor/BasicScheduleEditor.java
@@ -18,8 +18,13 @@
package org.matsim.pt2matsim.editor;
+import com.opencsv.CSVParserBuilder;
import com.opencsv.CSVReader;
-import org.apache.log4j.Logger;
+import com.opencsv.CSVReaderBuilder;
+import com.opencsv.exceptions.CsvValidationException;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
@@ -50,7 +55,7 @@
*/
public class BasicScheduleEditor implements ScheduleEditor {
- protected static Logger log = Logger.getLogger(RunScheduleEditor.class);
+ protected static Logger log = LogManager.getLogger(RunScheduleEditor.class);
// fields
private final Network network;
private final TransitSchedule schedule;
@@ -93,15 +98,20 @@ public TransitSchedule getSchedule() {
*/
@Override
public void parseCommandCsv(String filePath) throws IOException {
- CSVReader reader = new CSVReader(new FileReader(filePath), ';');
-
- String[] line = reader.readNext();
- while(line != null) {
- log.info(CollectionUtils.arrayToString(line));
- executeCmdLine(line);
- line = reader.readNext();
+ try (CSVReader reader = new CSVReaderBuilder(new FileReader(filePath))
+ .withCSVParser(new CSVParserBuilder()
+ .withSeparator(';')
+ .build())
+ .build()) {
+ String[] line = reader.readNext();
+ while (line != null) {
+ log.info(CollectionUtils.arrayToString(line));
+ executeCmdLine(line);
+ line = reader.readNext();
+ }
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
- reader.close();
}
/**
diff --git a/src/main/java/org/matsim/pt2matsim/editor/RunScheduleEditor.java b/src/main/java/org/matsim/pt2matsim/editor/RunScheduleEditor.java
index 057f7553..ead6e9d3 100644
--- a/src/main/java/org/matsim/pt2matsim/editor/RunScheduleEditor.java
+++ b/src/main/java/org/matsim/pt2matsim/editor/RunScheduleEditor.java
@@ -18,8 +18,10 @@
package org.matsim.pt2matsim.editor;
-import org.apache.log4j.Level;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.network.Network;
import org.matsim.core.network.io.NetworkWriter;
import org.matsim.pt.transitSchedule.api.TransitSchedule;
@@ -37,7 +39,7 @@
*/
public class RunScheduleEditor {
- protected static Logger log = Logger.getLogger(RunScheduleEditor.class);
+ protected static Logger log = LogManager.getLogger(RunScheduleEditor.class);
/**
* Loads the schedule and network, then executes all commands in the commands csv file.
@@ -99,17 +101,17 @@ public static void run(String scheduleFile, String networkFile, String commandFi
}
private static void setLogLevels() {
- Logger.getLogger(org.matsim.core.router.Dijkstra.class).setLevel(Level.ERROR); // suppress no route found warnings
- Logger.getLogger(Network.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.api.core.v01.network.Node.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.api.core.v01.network.Link.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.utils.io.MatsimXmlParser.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.utils.io.MatsimFileTypeGuesser.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.network.filter.NetworkFilterManager.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.router.util.PreProcessDijkstra.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.router.util.PreProcessDijkstra.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.router.util.PreProcessEuclidean.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.router.util.PreProcessLandmarks.class).setLevel(Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.router.Dijkstra.class).getName(), Level.ERROR); // suppress no route found warnings
+ Configurator.setLevel(LogManager.getLogger(Network.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.api.core.v01.network.Node.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.api.core.v01.network.Link.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.utils.io.MatsimXmlParser.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.utils.io.MatsimFileTypeGuesser.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.network.filter.NetworkFilterManager.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.router.util.PreProcessDijkstra.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.router.util.PreProcessDijkstra.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.router.util.PreProcessEuclidean.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.router.util.PreProcessLandmarks.class).getName(), Level.WARN);
}
}
\ No newline at end of file
diff --git a/src/main/java/org/matsim/pt2matsim/gtfs/GtfsConverter.java b/src/main/java/org/matsim/pt2matsim/gtfs/GtfsConverter.java
index 5affd810..815153da 100644
--- a/src/main/java/org/matsim/pt2matsim/gtfs/GtfsConverter.java
+++ b/src/main/java/org/matsim/pt2matsim/gtfs/GtfsConverter.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.gtfs;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.core.utils.misc.Time;
import org.matsim.pt.transitSchedule.api.*;
@@ -50,7 +51,7 @@ public class GtfsConverter {
public static final String DAY_WITH_MOST_TRIPS = "dayWithMostTrips";
public static final String DAY_WITH_MOST_SERVICES = "dayWithMostServices";
- protected static Logger log = Logger.getLogger(GtfsConverter.class);
+ protected static Logger log = LogManager.getLogger(GtfsConverter.class);
protected final GtfsFeed feed;
protected final TransitScheduleFactory scheduleFactory = ScheduleTools.createSchedule().getFactory();
diff --git a/src/main/java/org/matsim/pt2matsim/gtfs/GtfsFeedImpl.java b/src/main/java/org/matsim/pt2matsim/gtfs/GtfsFeedImpl.java
index 206394da..b93eacfd 100755
--- a/src/main/java/org/matsim/pt2matsim/gtfs/GtfsFeedImpl.java
+++ b/src/main/java/org/matsim/pt2matsim/gtfs/GtfsFeedImpl.java
@@ -19,10 +19,13 @@
package org.matsim.pt2matsim.gtfs;
import com.opencsv.CSVReader;
+import com.opencsv.exceptions.CsvValidationException;
+
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import org.apache.commons.io.input.BOMInputStream;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.core.utils.geometry.CoordinateTransformation;
@@ -47,7 +50,7 @@
*/
public class GtfsFeedImpl implements GtfsFeed {
- protected static final Logger log = Logger.getLogger(GtfsFeedImpl.class);
+ protected static final Logger log = LogManager.getLogger(GtfsFeedImpl.class);
/**
* Path to the folder where the gtfs files are located
@@ -217,6 +220,8 @@ protected void loadAgencies() throws IOException {
reader.close();
} catch (ArrayIndexOutOfBoundsException e) {
throw new RuntimeException("Line " + l + " in agency.txt is empty or malformed.");
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
log.info("... agency.txt loaded");
}
@@ -267,6 +272,8 @@ protected void loadStops() throws IOException {
reader.close();
} catch (ArrayIndexOutOfBoundsException e) {
throw new RuntimeException("Line " + l + " in stops.txt is empty or malformed.");
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
log.info("... stops.txt loaded");
}
@@ -313,6 +320,8 @@ protected boolean loadCalendar() {
return false;
} catch (ArrayIndexOutOfBoundsException i) {
throw new RuntimeException("Line " + l + " in calendar.txt is empty or malformed.");
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
log.info("... calendar.txt loaded");
return true;
@@ -366,6 +375,8 @@ protected boolean loadCalendarDates() {
return false;
} catch (ArrayIndexOutOfBoundsException i) {
throw new RuntimeException("Line " + l + " in calendar_dates.txt is empty or malformed.");
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
return true;
}
@@ -409,6 +420,8 @@ protected void loadShapes() {
log.info("... no shapes file found.");
} catch (ArrayIndexOutOfBoundsException i) {
throw new RuntimeException("Line " + l + " in shapes.txt is empty or malformed.");
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
}
@@ -457,6 +470,8 @@ protected void loadRoutes() throws IOException {
reader.close();
} catch (ArrayIndexOutOfBoundsException i) {
throw new RuntimeException("Line " + l + " in routes.txt is empty or malformed.");
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
log.info("... routes.txt loaded");
}
@@ -517,6 +532,8 @@ protected void loadTrips() throws IOException {
reader.close();
} catch (ArrayIndexOutOfBoundsException i) {
throw new RuntimeException("Line " + l + " in trips.txt is empty or malformed.");
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
log.info("... trips.txt loaded");
}
@@ -602,6 +619,8 @@ protected void loadStopTimes() throws IOException {
reader.close();
} catch (ArrayIndexOutOfBoundsException i) {
throw new RuntimeException("Line " + l + " in stop_times.txt is empty or malformed.");
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
log.info("... stop_times.txt loaded");
}
@@ -656,6 +675,8 @@ protected void loadFrequencies() {
throw new RuntimeException("Line " + l + " in frequencies.txt is empty or malformed.");
} catch (IOException e) {
e.printStackTrace();
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
}
@@ -699,6 +720,8 @@ protected void loadTransfers() {
transfersFileFound = false;
} catch (IOException e) {
e.printStackTrace();
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
if(transfersFileFound) {
log.info("... transfers.txt loaded");
@@ -709,14 +732,15 @@ protected String unzip(String compressedZip) {
String unzippedFolder = compressedZip.substring(0, compressedZip.length() - 4) + "/";
log.info("Unzipping " + compressedZip + " to " + unzippedFolder);
- try {
- ZipFile zipFile = new ZipFile(compressedZip);
+ try (ZipFile zipFile = new ZipFile(compressedZip)) {
if(zipFile.isEncrypted()) {
throw new RuntimeException("Zip file is encrypted");
}
zipFile.extractAll(unzippedFolder);
} catch (ZipException e) {
e.printStackTrace();
+ } catch(IOException e) {
+ throw new RuntimeException(e);
}
return unzippedFolder;
}
diff --git a/src/main/java/org/matsim/pt2matsim/hafas/HafasConverter.java b/src/main/java/org/matsim/pt2matsim/hafas/HafasConverter.java
index 9a821197..962e7b9d 100644
--- a/src/main/java/org/matsim/pt2matsim/hafas/HafasConverter.java
+++ b/src/main/java/org/matsim/pt2matsim/hafas/HafasConverter.java
@@ -21,7 +21,8 @@
package org.matsim.pt2matsim.hafas;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.core.utils.collections.MapUtils;
import org.matsim.core.utils.geometry.CoordinateTransformation;
@@ -32,6 +33,7 @@
import org.matsim.pt2matsim.tools.debug.ScheduleCleaner;
import org.matsim.vehicles.VehicleCapacity;
import org.matsim.vehicles.VehicleType;
+import org.matsim.vehicles.VehicleUtils;
import org.matsim.vehicles.Vehicles;
import org.matsim.vehicles.VehiclesFactory;
@@ -51,7 +53,7 @@
*/
public class HafasConverter {
- protected static Logger log = Logger.getLogger(HafasConverter.class);
+ protected static Logger log = LogManager.getLogger(HafasConverter.class);
public static void run(String hafasFolder, TransitSchedule schedule, CoordinateTransformation transformation, Vehicles vehicles) throws IOException {
run(hafasFolder, schedule, transformation, vehicles, -1);
@@ -89,7 +91,7 @@ public static void run(String hafasFolder, TransitSchedule schedule, CoordinateT
// 1. Read and create stop facilities
log.info(" Read transit stops...");
- StopReader.run(schedule, transformation, hafasFolder + "BFKOORD_GEO");
+ StopReader.run(schedule, transformation, hafasFolder + "BFKOORD_WGS");
log.info(" Read transit stops... done.");
// 1.a Read minimal transfer times
@@ -181,9 +183,9 @@ private static void createTransitRoutesFromFPLAN(List routes, Transi
// using default values for vehicle type
vehicleType.setLength(defaultVehicleType.length);
vehicleType.setWidth(defaultVehicleType.width);
- vehicleType.setAccessTime(defaultVehicleType.accessTime);
- vehicleType.setEgressTime(defaultVehicleType.egressTime);
- vehicleType.setDoorOperationMode(defaultVehicleType.doorOperation);
+ VehicleUtils.setAccessTime(vehicleType, defaultVehicleType.accessTime);
+ VehicleUtils.setEgressTime(vehicleType, defaultVehicleType.egressTime);
+ VehicleUtils.setDoorOperationMode(vehicleType, defaultVehicleType.doorOperation);
vehicleType.setPcuEquivalents(defaultVehicleType.pcuEquivalents);
VehicleCapacity vehicleCapacity = vehicleType.getCapacity();
diff --git a/src/main/java/org/matsim/pt2matsim/hafas/lib/BitfeldAnalyzer.java b/src/main/java/org/matsim/pt2matsim/hafas/lib/BitfeldAnalyzer.java
index 391eff0f..e2a731cf 100644
--- a/src/main/java/org/matsim/pt2matsim/hafas/lib/BitfeldAnalyzer.java
+++ b/src/main/java/org/matsim/pt2matsim/hafas/lib/BitfeldAnalyzer.java
@@ -21,7 +21,8 @@
package org.matsim.pt2matsim.hafas.lib;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import java.io.BufferedReader;
import java.io.FileInputStream;
@@ -40,14 +41,17 @@
* @author boescpa
*/
public class BitfeldAnalyzer {
- protected static Logger log = Logger.getLogger(BitfeldAnalyzer.class);
+ protected static Logger log = LogManager.getLogger(BitfeldAnalyzer.class);
public static Set findBitfeldnumbersOfBusiestDay(String FPLAN, String BITFELD) throws IOException {
final Set bitfeldNummern = new HashSet<>();
final int posMaxFVals = find4DayBlockWithMostFVals(FPLAN, BITFELD);
BufferedReader readsLines = new BufferedReader(new InputStreamReader(new FileInputStream(BITFELD), "latin1"));
- String newLine = readsLines.readLine();
- while (newLine != null) {
+ String newLine;
+ while ((newLine = readsLines.readLine()) != null) {
+ if (newLine.startsWith("*")) {
+ continue;
+ }
/*Spalte Typ Bedeutung
1−6 INT32 Bitfeldnummer
8−103 CHAR Bitfeld (Binärkodierung der Tage, an welchen Fahrt, in Hexadezimalzahlen notiert.)*/
@@ -68,7 +72,6 @@ public static Set findBitfeldnumbersOfBusiestDay(String FPLAN, String B
if (matches >= 1) {
bitfeldNummern.add(bitfeldnummer);
}
- newLine = readsLines.readLine();
}
readsLines.close();
bitfeldNummern.add(0);
@@ -116,8 +119,11 @@ private static int find4DayBlockWithMostFVals(String FPLAN, String BITFELD) {
int[] bitfeldStats = new int[96];
try {
BufferedReader readsLines = new BufferedReader(new InputStreamReader(new FileInputStream(BITFELD), "latin1"));
- String newLine = readsLines.readLine();
- while (newLine != null) {
+ String newLine;
+ while ((newLine = readsLines.readLine()) != null) {
+ if (newLine.startsWith("*")) {
+ continue;
+ }
/*Spalte Typ Bedeutung
1−6 INT32 Bitfeldnummer
8−103 CHAR Bitfeld (Binärkodierung der Tage, an welchen Fahrt, in Hexadezimalzahlen notiert.)*/
@@ -131,7 +137,6 @@ private static int find4DayBlockWithMostFVals(String FPLAN, String BITFELD) {
bitfeldStats[i] += bitFeldValue;
}
}
- newLine = readsLines.readLine();
}
readsLines.close();
} catch (IOException e) {
@@ -162,12 +167,14 @@ public static Set getBitfieldsAtValidDay(final int dayNr, final String
Set validBitfields = new HashSet<>();
BufferedReader readsLines = new BufferedReader(new InputStreamReader(new FileInputStream(pathFile), "utf-8"));
- String newLine = readsLines.readLine();
- while (newLine != null) {
+ String newLine;
+ while ((newLine = readsLines.readLine()) != null) {
+ if (newLine.startsWith("*")) {
+ continue;
+ }
int id = Integer.parseInt(newLine.substring(0, 6));
String bitfield = new BigInteger(newLine.substring(7), 16).toString(2).substring(offset_bitstring);
if (bitfield.charAt(dayNr)== '1') validBitfields.add(id);
- newLine = readsLines.readLine();
}
readsLines.close();
// TODO this error-prone and should be removed by a stable solution
diff --git a/src/main/java/org/matsim/pt2matsim/hafas/lib/ECKDATENReader.java b/src/main/java/org/matsim/pt2matsim/hafas/lib/ECKDATENReader.java
index 9d866fcc..bcf20865 100644
--- a/src/main/java/org/matsim/pt2matsim/hafas/lib/ECKDATENReader.java
+++ b/src/main/java/org/matsim/pt2matsim/hafas/lib/ECKDATENReader.java
@@ -1,6 +1,7 @@
package org.matsim.pt2matsim.hafas.lib;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import java.io.*;
import java.time.LocalDate;
@@ -9,7 +10,7 @@
public class ECKDATENReader {
- protected static Logger log = Logger.getLogger(ECKDATENReader.class);
+ protected static Logger log = LogManager.getLogger(ECKDATENReader.class);
private static final String ECKDATEN = "ECKDATEN";
/*
1 1−10 CHAR Fahrplanstart im Format TT.MM.JJJJ
@@ -19,7 +20,17 @@ public class ECKDATENReader {
public static LocalDate getFahrPlanStart(String pathToHafasFolder) throws IOException {
if (new File(pathToHafasFolder, ECKDATEN).exists()) {
BufferedReader readsLines = new BufferedReader(new InputStreamReader(new FileInputStream(pathToHafasFolder + ECKDATEN), "utf-8"));
- LocalDate startDate = getDate(readsLines.readLine());
+ String line;
+ String firstLineAfterComments = null;
+
+ while ((line = readsLines.readLine()) != null) {
+ if (line.startsWith("*")) {
+ continue;
+ }
+ firstLineAfterComments = line;
+ break;
+ }
+ LocalDate startDate = getDate(firstLineAfterComments);
readsLines.close();
return startDate;
} else {
@@ -31,8 +42,17 @@ public static LocalDate getFahrPlanStart(String pathToHafasFolder) throws IOExce
public static LocalDate getFahrPlanEnd(String pathToHafasFolder) throws IOException {
if (new File(pathToHafasFolder, ECKDATEN).exists()) {
BufferedReader readsLines = new BufferedReader(new InputStreamReader(new FileInputStream(pathToHafasFolder + ECKDATEN), "utf-8"));
- readsLines.readLine();
- LocalDate endDate = getDate(readsLines.readLine());
+ String line;
+ String secondLineAfterComments = null;
+
+ while ((line = readsLines.readLine()) != null) {
+ if (line.startsWith("*")) {
+ continue;
+ }
+ secondLineAfterComments = readsLines.readLine();
+ break;
+ }
+ LocalDate endDate = getDate(secondLineAfterComments);
readsLines.close();
return endDate;
diff --git a/src/main/java/org/matsim/pt2matsim/hafas/lib/FPLANReader.java b/src/main/java/org/matsim/pt2matsim/hafas/lib/FPLANReader.java
index 8016ef49..b24c3ca3 100644
--- a/src/main/java/org/matsim/pt2matsim/hafas/lib/FPLANReader.java
+++ b/src/main/java/org/matsim/pt2matsim/hafas/lib/FPLANReader.java
@@ -21,7 +21,8 @@
package org.matsim.pt2matsim.hafas.lib;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.core.utils.misc.Counter;
import org.matsim.vehicles.VehicleType;
@@ -41,7 +42,7 @@
* @author polettif
*/
public class FPLANReader {
- protected static Logger log = Logger.getLogger(FPLANReader.class);
+ protected static Logger log = LogManager.getLogger(FPLANReader.class);
/**
* Only reads the PtRoutes and leaves line/route
@@ -64,24 +65,25 @@ public static List parseFPLAN(Set bitfeldNummern, Map stopId = Id.create(newLine.substring(0, 7), TransitStopFacility.class);
double transferTime = Integer.parseInt(newLine.substring(11, 13)) * 60;
minimalTransferTimes.set(stopId, stopId, transferTime);
- newLine = readsLines.readLine();
}
readsLines.close();
} else {
diff --git a/src/main/java/org/matsim/pt2matsim/hafas/lib/OperatorReader.java b/src/main/java/org/matsim/pt2matsim/hafas/lib/OperatorReader.java
index b0e924ad..df8ec56d 100644
--- a/src/main/java/org/matsim/pt2matsim/hafas/lib/OperatorReader.java
+++ b/src/main/java/org/matsim/pt2matsim/hafas/lib/OperatorReader.java
@@ -37,16 +37,20 @@ public class OperatorReader {
public static Map readOperators(String BETRIEB_DE) throws IOException {
Map operators = new HashMap<>();
- BufferedReader readsLines = new BufferedReader(new InputStreamReader(new FileInputStream(BETRIEB_DE), "utf-8"));
- String newLine = readsLines.readLine();
- while (newLine != null) {
- String abbrevationOperator = newLine.split("\"")[1].replace(" ","");
- newLine = readsLines.readLine();
- if (newLine == null) break;
- String operatorId = newLine.substring(8, 14).trim();
- operators.put(operatorId, abbrevationOperator);
- // read the next operator:
- newLine = readsLines.readLine();
+ try(BufferedReader readsLines = new BufferedReader(new InputStreamReader(new FileInputStream(BETRIEB_DE), "utf-8"))) {
+ String newLine;
+ while ((newLine = readsLines.readLine()) != null) {
+ if (newLine.startsWith("*")) {
+ continue;
+ }
+ String abbrevationOperator = newLine.split("\"")[1].replace(" ","");
+ newLine = readsLines.readLine();
+ if (newLine == null) break;
+ String[] operatorIds = newLine.substring(8).trim().split("\\s+");
+ for (String operatorId : operatorIds) {
+ operators.put(operatorId.trim(), abbrevationOperator);
+ }
+ }
}
return operators;
}
diff --git a/src/main/java/org/matsim/pt2matsim/hafas/lib/StopReader.java b/src/main/java/org/matsim/pt2matsim/hafas/lib/StopReader.java
index 67b291ba..6240b12e 100644
--- a/src/main/java/org/matsim/pt2matsim/hafas/lib/StopReader.java
+++ b/src/main/java/org/matsim/pt2matsim/hafas/lib/StopReader.java
@@ -21,7 +21,8 @@
package org.matsim.pt2matsim.hafas.lib;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.core.utils.geometry.CoordinateTransformation;
@@ -37,53 +38,55 @@
import java.util.Map;
/**
- * Reads all stops from HAFAS-BFKOORD_GEO and adds them as TransitStopFacilities
+ * Reads all stops from HAFAS-BFKOORD_WGS and adds them as TransitStopFacilities
* to the provided TransitSchedule.
*
* @author boescpa
*/
public class StopReader {
- protected static Logger log = Logger.getLogger(StopReader.class);
+ protected static Logger log = LogManager.getLogger(StopReader.class);
private final CoordinateTransformation transformation;
private final TransitSchedule schedule;
private final TransitScheduleFactory scheduleBuilder;
private final Map usedCoordinates = new HashMap<>();
- private final String pathToBFKOORD_GEOFile;
+ private final String pathToBFKOORD_WGSFile;
- public StopReader(TransitSchedule schedule, CoordinateTransformation transformation, String pathToBFKOORD_GEOFile) {
+ public StopReader(TransitSchedule schedule, CoordinateTransformation transformation, String pathToBFKOORD_WGSFile) {
this.schedule = schedule;
this.transformation = transformation;
this.scheduleBuilder = this.schedule.getFactory();
- this.pathToBFKOORD_GEOFile = pathToBFKOORD_GEOFile;
+ this.pathToBFKOORD_WGSFile = pathToBFKOORD_WGSFile;
}
- public static void run(TransitSchedule schedule, CoordinateTransformation transformation, String pathToBFKOORD_GEOFile) throws IOException {
- new StopReader(schedule, transformation, pathToBFKOORD_GEOFile).createStops();
+ public static void run(TransitSchedule schedule, CoordinateTransformation transformation, String pathToBFKOORD_WGSFile) throws IOException {
+ new StopReader(schedule, transformation, pathToBFKOORD_WGSFile).createStops();
}
private void createStops() throws IOException {
log.info(" Read transit stops...");
- BufferedReader readsLines = new BufferedReader(new InputStreamReader(new FileInputStream(pathToBFKOORD_GEOFile), "utf-8"));
- String newLine = readsLines.readLine();
- while (newLine != null) {
+ BufferedReader readsLines = new BufferedReader(new InputStreamReader(new FileInputStream(pathToBFKOORD_WGSFile), "utf-8"));
+ String newLine;
+ while ((newLine = readsLines.readLine()) != null) {
+ if (newLine.startsWith("*")) {
+ continue;
+ }
/*
1−7 INT32 Nummer der Haltestelle
- 9−18 FLOAT X-Koordinate
- 20−29 FLOAT Y-Koordinate
- 31−36 INT16 Z-Koordinate (Tunnel und andere Streckenelemente ohne eigentliche Haltestelle haben keine Z-Koordinate)
- 38ff CHAR Kommentarzeichen "%"gefolgt vom Klartext des Haltestellennamens (optional zur besseren Lesbarkeit)
+ 9−19 FLOAT X-Koordinate
+ 21−31 FLOAT Y-Koordinate
+ 33−38 INT16 Z-Koordinate (Tunnel und andere Streckenelemente ohne eigentliche Haltestelle haben keine Z-Koordinate)
+ 40ff CHAR Kommentarzeichen "%"gefolgt vom Klartext des Haltestellennamens (optional zur besseren Lesbarkeit)
*/
Id stopId = Id.create(newLine.substring(0, 7), TransitStopFacility.class);
- double xCoord = Double.parseDouble(newLine.substring(8, 18));
- double yCoord = Double.parseDouble(newLine.substring(19, 29));
+ double xCoord = Double.parseDouble(newLine.substring(8, 19));
+ double yCoord = Double.parseDouble(newLine.substring(20, 31));
Coord coord = new Coord(xCoord, yCoord);
if (this.transformation != null) {
coord = this.transformation.transform(coord);
}
- String stopName = newLine.substring(39, newLine.length());
+ String stopName = newLine.substring(41);
createStop(stopId, coord, stopName);
- newLine = readsLines.readLine();
}
readsLines.close();
log.info(" Read transit stops... done.");
diff --git a/src/main/java/org/matsim/pt2matsim/mapping/PTMapper.java b/src/main/java/org/matsim/pt2matsim/mapping/PTMapper.java
index 3acdd254..09299f64 100644
--- a/src/main/java/org/matsim/pt2matsim/mapping/PTMapper.java
+++ b/src/main/java/org/matsim/pt2matsim/mapping/PTMapper.java
@@ -21,7 +21,8 @@
package org.matsim.pt2matsim.mapping;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
@@ -66,7 +67,7 @@
*/
public class PTMapper {
- protected static Logger log = Logger.getLogger(PTMapper.class);
+ protected static Logger log = LogManager.getLogger(PTMapper.class);
private final PseudoSchedule pseudoSchedule = new PseudoScheduleImpl();
private Network network;
private TransitSchedule schedule;
diff --git a/src/main/java/org/matsim/pt2matsim/mapping/Progress.java b/src/main/java/org/matsim/pt2matsim/mapping/Progress.java
index ecb3f766..8b4c6396 100644
--- a/src/main/java/org/matsim/pt2matsim/mapping/Progress.java
+++ b/src/main/java/org/matsim/pt2matsim/mapping/Progress.java
@@ -1,9 +1,10 @@
package org.matsim.pt2matsim.mapping;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
public class Progress {
- private Logger logger = Logger.getLogger(Progress.class);
+ private Logger logger = LogManager.getLogger(Progress.class);
private final long total;
private final String description;
diff --git a/src/main/java/org/matsim/pt2matsim/mapping/PseudoRoutingImpl.java b/src/main/java/org/matsim/pt2matsim/mapping/PseudoRoutingImpl.java
index 2a9e1b7f..3d9db86c 100644
--- a/src/main/java/org/matsim/pt2matsim/mapping/PseudoRoutingImpl.java
+++ b/src/main/java/org/matsim/pt2matsim/mapping/PseudoRoutingImpl.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.mapping;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.core.router.util.LeastCostPathCalculator;
@@ -31,10 +32,11 @@
import org.matsim.pt2matsim.mapping.networkRouter.ScheduleRoutersFactory;
import org.matsim.pt2matsim.mapping.pseudoRouter.*;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Queue;
import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Generates and calculates the pseudoRoutes for all the queued
@@ -46,14 +48,14 @@
*/
public class PseudoRoutingImpl implements PseudoRouting {
- protected static Logger log = Logger.getLogger(PseudoRoutingImpl.class);
+ protected static Logger log = LogManager.getLogger(PseudoRoutingImpl.class);
private final Progress progress;
private static boolean warnMinTravelCost = true;
+ private static Queue queue = new ConcurrentLinkedQueue<>();
private final LinkCandidateCreator linkCandidates;
- private final ScheduleRoutersFactory scheduleRoutersFactory;
- private final List queue = new ArrayList<>();
+ private final ScheduleRouters scheduleRouters;
private final Set necessaryArtificialLinks = new HashSet<>();
@@ -62,7 +64,7 @@ public class PseudoRoutingImpl implements PseudoRouting {
public PseudoRoutingImpl(ScheduleRoutersFactory scheduleRoutersFactory, LinkCandidateCreator linkCandidates, double maxTravelCostFactor, Progress progress) {
this.maxTravelCostFactor = maxTravelCostFactor;
- this.scheduleRoutersFactory = scheduleRoutersFactory;
+ this.scheduleRouters = scheduleRoutersFactory.createInstance();
this.linkCandidates = linkCandidates;
this.progress = progress;
}
@@ -74,9 +76,9 @@ public void addTransitLineToQueue(TransitLine transitLine) {
@Override
public void run() {
- ScheduleRouters scheduleRouters = scheduleRoutersFactory.createInstance();
- for(TransitLine transitLine : queue) {
+ TransitLine transitLine;
+ while ((transitLine = queue.poll()) != null) {
for(TransitRoute transitRoute : transitLine.getRoutes().values()) {
/* [1]
Initiate pseudoGraph and Dijkstra algorithm for the current transitRoute.
diff --git a/src/main/java/org/matsim/pt2matsim/mapping/linkCandidateCreation/LinkCandidateCreatorStandard.java b/src/main/java/org/matsim/pt2matsim/mapping/linkCandidateCreation/LinkCandidateCreatorStandard.java
index 26c20c2e..0153d825 100644
--- a/src/main/java/org/matsim/pt2matsim/mapping/linkCandidateCreation/LinkCandidateCreatorStandard.java
+++ b/src/main/java/org/matsim/pt2matsim/mapping/linkCandidateCreation/LinkCandidateCreatorStandard.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.mapping.linkCandidateCreation;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
@@ -45,7 +46,7 @@
public class LinkCandidateCreatorStandard implements LinkCandidateCreator {
private static final Set loopLinkModes = CollectionUtils.stringToSet(PublicTransitMappingStrings.ARTIFICIAL_LINK_MODE + "," + PublicTransitMappingStrings.STOP_FACILITY_LOOP_LINK);
- protected static Logger log = Logger.getLogger(LinkCandidateCreatorStandard.class);
+ protected static Logger log = LogManager.getLogger(LinkCandidateCreatorStandard.class);
private final TransitSchedule schedule;
private final Network network;
diff --git a/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersGtfsShapes.java b/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersGtfsShapes.java
index b95c8780..4a3403c4 100755
--- a/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersGtfsShapes.java
+++ b/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersGtfsShapes.java
@@ -1,12 +1,13 @@
package org.matsim.pt2matsim.mapping.networkRouter;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.network.Node;
import org.matsim.api.core.v01.population.Person;
-import org.matsim.core.router.FastAStarEuclideanFactory;
+import org.matsim.core.router.speedy.SpeedyALTFactory;
import org.matsim.core.router.util.LeastCostPathCalculator;
import org.matsim.core.router.util.TravelDisutility;
import org.matsim.core.router.util.TravelTime;
@@ -38,7 +39,7 @@
*/
public class ScheduleRoutersGtfsShapes implements ScheduleRouters {
- protected static Logger log = Logger.getLogger(ScheduleRoutersGtfsShapes.class);
+ protected static Logger log = LogManager.getLogger(ScheduleRoutersGtfsShapes.class);
// standard fields
private final TransitSchedule schedule;
@@ -105,7 +106,8 @@ private void load() {
NetworkTools.cutNetwork(cutNetwork, nodesWithinBuffer);
ShapeRouter r = new ShapeRouter(shape);
- pathCalculator = new PathCalculator(new FastAStarEuclideanFactory().createPathCalculator(cutNetwork, r, r));
+ pathCalculator = new PathCalculator(
+ new SpeedyALTFactory().createPathCalculator(cutNetwork, r, r));
pathCalculatorsByShape.put(shapeId, pathCalculator);
networksByShape.put(shapeId, cutNetwork);
diff --git a/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersOsmAttributes.java b/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersOsmAttributes.java
index 64fd698f..45e5102d 100755
--- a/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersOsmAttributes.java
+++ b/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersOsmAttributes.java
@@ -18,13 +18,14 @@
package org.matsim.pt2matsim.mapping.networkRouter;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.network.Node;
import org.matsim.api.core.v01.population.Person;
-import org.matsim.core.router.FastAStarLandmarksFactory;
+import org.matsim.core.router.speedy.SpeedyALTFactory;
import org.matsim.core.router.util.LeastCostPathCalculator;
import org.matsim.core.router.util.LeastCostPathCalculatorFactory;
import org.matsim.core.router.util.TravelDisutility;
@@ -52,7 +53,7 @@
public class ScheduleRoutersOsmAttributes implements ScheduleRouters {
- protected static Logger log = Logger.getLogger(ScheduleRoutersGtfsShapes.class);
+ protected static Logger log = LogManager.getLogger(ScheduleRoutersGtfsShapes.class);
/**
* If a link has a route with the same transport mode as the transit route,
* the link's travel cost is multiplied by this factor.
@@ -99,7 +100,7 @@ public ScheduleRoutersOsmAttributes(TransitSchedule schedule, Network network, M
*/
private void load() {
log.info("Initiating network and router for transit routes...");
- LeastCostPathCalculatorFactory factory = new FastAStarLandmarksFactory(nThreads);
+ LeastCostPathCalculatorFactory factory = new SpeedyALTFactory();
for (TransitLine transitLine : schedule.getTransitLines().values()) {
for (TransitRoute transitRoute : transitLine.getRoutes().values()) {
String scheduleMode = transitRoute.getTransportMode();
diff --git a/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersStandard.java b/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersStandard.java
index 0cc38a72..0352c45e 100755
--- a/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersStandard.java
+++ b/src/main/java/org/matsim/pt2matsim/mapping/networkRouter/ScheduleRoutersStandard.java
@@ -1,12 +1,13 @@
package org.matsim.pt2matsim.mapping.networkRouter;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.network.Node;
import org.matsim.api.core.v01.population.Person;
-import org.matsim.core.router.FastAStarLandmarksFactory;
+import org.matsim.core.router.speedy.SpeedyALTFactory;
import org.matsim.core.router.util.LeastCostPathCalculator;
import org.matsim.core.router.util.LeastCostPathCalculatorFactory;
import org.matsim.core.router.util.TravelDisutility;
@@ -35,7 +36,7 @@
*/
public class ScheduleRoutersStandard implements ScheduleRouters {
- protected static Logger log = Logger.getLogger(ScheduleRoutersStandard.class);
+ protected static Logger log = LogManager.getLogger(ScheduleRoutersStandard.class);
// standard fields
private final TransitSchedule schedule;
@@ -78,7 +79,7 @@ private void load() {
log.info("==============================================");
log.info("Creating network routers for transit routes...");
log.info("Initiating network and router for transit routes...");
- LeastCostPathCalculatorFactory factory = new FastAStarLandmarksFactory(nThreads);
+ LeastCostPathCalculatorFactory factory = new SpeedyALTFactory();
for(TransitLine transitLine : schedule.getTransitLines().values()) {
for(TransitRoute transitRoute : transitLine.getRoutes().values()) {
String scheduleMode = transitRoute.getTransportMode();
diff --git a/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/ArtificialLinkImpl.java b/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/ArtificialLinkImpl.java
index 257a9594..cc557c10 100644
--- a/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/ArtificialLinkImpl.java
+++ b/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/ArtificialLinkImpl.java
@@ -26,6 +26,7 @@
import org.matsim.pt2matsim.mapping.linkCandidateCreation.LinkCandidate;
import org.matsim.pt2matsim.tools.PTMapperTools;
import org.matsim.utils.objectattributes.attributable.Attributes;
+import org.matsim.utils.objectattributes.attributable.AttributesImpl;
import java.util.Set;
@@ -46,7 +47,7 @@ public class ArtificialLinkImpl implements ArtificialLink {
private double capacity = 9999;
private Set transportModes = PublicTransitMappingStrings.ARTIFICIAL_LINK_MODE_AS_SET;
- private Attributes attributes = new Attributes();
+ private Attributes attributes = new AttributesImpl();
public ArtificialLinkImpl(LinkCandidate fromLinkCandidate, LinkCandidate toLinkCandidate, double freespeed, double linkLength) {
this.id = PTMapperTools.createArtificialLinkId(fromLinkCandidate, toLinkCandidate);
diff --git a/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/PseudoGraphImpl.java b/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/PseudoGraphImpl.java
index 431a562e..680c4d22 100644
--- a/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/PseudoGraphImpl.java
+++ b/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/PseudoGraphImpl.java
@@ -19,7 +19,8 @@
package org.matsim.pt2matsim.mapping.pseudoRouter;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.core.utils.geometry.CoordUtils;
@@ -35,7 +36,7 @@ public class PseudoGraphImpl implements PseudoGraph {
/*package*/ static final String SOURCE = "SOURCE";
/*package*/ static final String DESTINATION = "DESTINATION";
- protected static Logger log = Logger.getLogger(PseudoGraphImpl.class);
+ protected static Logger log = LogManager.getLogger(PseudoGraphImpl.class);
private final Id SOURCE_ID = Id.create(SOURCE, PseudoRouteStop.class);
private final PseudoRouteStop SOURCE_PSEUDO_STOP = new PseudoRouteStopImpl(SOURCE);
private final Id DESTINATION_ID = Id.create(DESTINATION, PseudoRouteStop.class);
@@ -120,8 +121,8 @@ private void runDijkstra() {
for(Link l : stopPairLinks.get(getKey(stopA, stopB))) {
networkLinkIds.add(l.getId());
- if(l instanceof ArtificialLink) {
- artificialNetworkLinks.add((ArtificialLink) l);
+ if (l instanceof ArtificialLink al) {
+ artificialNetworkLinks.add(al);
}
}
networkLinkIds.add(stopB.getLinkId());
diff --git a/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/PseudoScheduleImpl.java b/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/PseudoScheduleImpl.java
index a6fe6550..64e079d0 100644
--- a/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/PseudoScheduleImpl.java
+++ b/src/main/java/org/matsim/pt2matsim/mapping/pseudoRouter/PseudoScheduleImpl.java
@@ -25,7 +25,8 @@
import java.util.Map;
import java.util.Set;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.pt.transitSchedule.api.MinimalTransferTimes.MinimalTransferTimesIterator;
@@ -66,7 +67,7 @@ public void createFacilitiesAndLinkSequences(TransitSchedule schedule) {
Map, Set>> parentsToChildren = new HashMap<>();
- Logger logger = Logger.getLogger(PseudoScheduleImpl.class);
+ Logger logger = LogManager.getLogger(PseudoScheduleImpl.class);
int totalNumber = pseudoSchedule.size();
int currentNumber = 0;
diff --git a/src/main/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverter.java b/src/main/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverter.java
index c82d1c88..ac5d16b6 100644
--- a/src/main/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverter.java
+++ b/src/main/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverter.java
@@ -21,13 +21,33 @@
package org.matsim.pt2matsim.osm;
-import org.apache.log4j.Logger;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.IdMap;
import org.matsim.api.core.v01.TransportMode;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.network.Node;
import org.matsim.core.config.ConfigGroup;
+import org.matsim.core.network.DisallowedNextLinks;
import org.matsim.core.network.NetworkUtils;
import org.matsim.core.network.algorithms.NetworkCleaner;
import org.matsim.core.utils.collections.CollectionUtils;
@@ -42,10 +62,6 @@
import org.matsim.pt2matsim.osm.lib.OsmData;
import org.matsim.pt2matsim.tools.NetworkTools;
-import java.io.IOException;
-import java.nio.file.Paths;
-import java.util.*;
-
/**
* Converts {@link OsmData} to a MATSim network, uses a config file
* ({@link OsmConverterConfigGroup}) to store conversion parameters and default
@@ -64,8 +80,32 @@
*/
public class OsmMultimodalNetworkConverter {
- private final static Logger log = Logger.getLogger(OsmMultimodalNetworkConverter.class);
-
+ private static final Logger log = LogManager.getLogger(OsmMultimodalNetworkConverter.class);
+
+ /**
+ * mode == null means "all modes"
+ */
+ static record OsmTurnRestriction(Set modes, List> nextWayIds, RestrictionType restrictionType) {
+
+ enum RestrictionType {
+ PROHIBITIVE, // no_*
+ MANDATORY; // only_*
+ }
+
+ }
+
+ private static final Map OSM_2_MATSIM_MODE_MAP = Map.of(
+ Osm.Key.BUS, "bus",
+ Osm.Key.BICYCLE, TransportMode.bike,
+ Osm.Key.MOTORCYCLE, TransportMode.motorcycle,
+ Osm.Key.MOTORCAR, TransportMode.car);
+
+ private static final List TURN_RESTRICTION_KEY_SUFFIXES = List.of(
+ "", // for all modes
+ ":" + Osm.Key.BUS,
+ ":" + Osm.Key.BICYCLE,
+ ":" + Osm.Key.MOTORCAR);
+
static final int SPEED_LIMIT_WALK_KPH = 10;
// // no speed limit (Germany) .. assume 200kph
static final int SPEED_LIMIT_NONE_KPH = 200;
@@ -84,6 +124,8 @@ public class OsmMultimodalNetworkConverter {
* connects osm way ids and link ids of the generated network
**/
protected final Map, Id> osmIds = new HashMap<>();
+ protected final Map, List>> wayLinkMap = new HashMap<>(); // reverse of osmIds
+ protected final Map, DisallowedNextLinks> disallowedNextLinks = new IdMap<>(Link.class);
protected OsmConverterConfigGroup config;
protected Network network;
protected long id = 0;
@@ -110,6 +152,9 @@ public void convert(OsmConverterConfigGroup config) {
readWayParams();
convertToNetwork(transformation);
cleanNetwork();
+ if (config.parseTurnRestrictions) {
+ addDisallowedNextLinksAttributes();
+ }
if(config.getKeepTagsAsAttributes()) addAttributes();
if (this.config.getOutputDetailedLinkGeometryFile() != null) {
@@ -270,26 +315,33 @@ protected void convertToNetwork(CoordinateTransformation transformation) {
}
}
+ // create reverse lookup map for link ids
+ wayLinkMap.putAll(osmIds.entrySet().stream().collect(
+ Collectors.groupingBy(Entry::getValue, Collectors.mapping(Entry::getKey, Collectors.toList()))));
+
+ // parse turn restriction relations into disallowed links
+ this.attachTurnRestrictionsAsDisallowedNextLinks();
+
log.info("= conversion statistics: ==========================");
- log.info("MATSim: # nodes created: " + this.network.getNodes().size());
- log.info("MATSim: # links created: " + this.network.getLinks().size());
+ log.info("MATSim: # nodes created: {}", this.network.getNodes().size());
+ log.info("MATSim: # links created: {}", this.network.getLinks().size());
- if(this.unknownHighways.size() > 0) {
+ if (!this.unknownHighways.isEmpty()) {
log.info("The following highway-types had no defaults set and were thus NOT converted:");
for(String highwayType : this.unknownHighways) {
- log.info("- \"" + highwayType + "\"");
+ log.info("- \"{}\"", highwayType);
}
}
- if(this.unknownRailways.size() > 0) {
+ if (!this.unknownRailways.isEmpty()) {
log.info("The following railway-types had no defaults set and were thus NOT converted:");
for(String railwayType : this.unknownRailways) {
- log.info("- \"" + railwayType + "\"");
+ log.info("- \"{}\"", railwayType);
}
}
- if(this.unknownWays.size() > 0) {
+ if (!this.unknownWays.isEmpty()) {
log.info("The way-types with the following tags had no defaults set and were thus NOT converted:");
for(String wayType : this.unknownWays) {
- log.info("- \"" + wayType + "\"");
+ log.info("- \"{}\"", wayType);
}
}
log.info("= end of conversion statistics ====================");
@@ -367,7 +419,10 @@ protected void createLink(final Osm.Way way, final Osm.Node fromNode, final Osm.
modes.add(TransportMode.pt);
}
}
-
+
+ // TURN RESTRICTIONS
+ List osmTurnRestrictions = this.parseTurnRestrictions(way, modes);
+
// LENGTH
if (length == 0.0) {
log.warn("Attempting to create a link of length 0.0, which will mess up the routing. Fixing to 1.0!");
@@ -388,6 +443,9 @@ protected void createLink(final Osm.Way way, final Osm.Node fromNode, final Osm.
l.setCapacity(laneCountForward * laneCapacity);
l.setNumberOfLanes(laneCountForward);
l.setAllowedModes(modes);
+ if (config.parseTurnRestrictions) {
+ l.getAttributes().putAttribute(OsmTurnRestriction.class.getSimpleName(), osmTurnRestrictions);
+ }
network.addLink(l);
osmIds.put(l.getId(), way.getId());
@@ -403,6 +461,9 @@ protected void createLink(final Osm.Way way, final Osm.Node fromNode, final Osm.
l.setCapacity(laneCountBackward * laneCapacity);
l.setNumberOfLanes(laneCountBackward);
l.setAllowedModes(modes);
+ if (config.parseTurnRestrictions) {
+ l.getAttributes().putAttribute(OsmTurnRestriction.class.getSimpleName(), osmTurnRestrictions);
+ }
network.addLink(l);
osmIds.put(l.getId(), way.getId());
@@ -458,7 +519,7 @@ else if(Osm.Value.WALK.equals(value)) {
} catch (NumberFormatException e) {
if(!unknownMaxspeedTags.contains(value)) {
unknownMaxspeedTags.add(value);
- log.warn("Could not parse '" + key + "': " + e.getMessage() + " (way " + way.getId() + ")");
+ log.warn("Could not parse '{}': {} (way {})", key, e.getMessage(), way.getId());
}
return Optional.empty();
}
@@ -505,7 +566,7 @@ private Optional parseLanesValue(final Osm.Way way, String key) {
} catch (NumberFormatException e) {
if(!unknownLanesTags.contains(value)) {
unknownLanesTags.add(value);
- log.warn("Could not parse '" + key + "': " + e.getMessage() + " (way " + way.getId() + ")");
+ log.warn("Could not parse '{}': {} (way {})", key, e.getMessage(), way.getId());
}
return Optional.empty();
}
@@ -585,6 +646,19 @@ protected OsmConverterConfigGroup.OsmWayParams getWayDefaultParams(Osm.Way way)
return wayDefaults;
}
+ /**
+ * Adds DisallowedNextLinks attributes to links. See {@link #addAttributes()}
+ * documentation as to why this cannot be done directly when creating the link.
+ */
+ private void addDisallowedNextLinksAttributes() {
+ network.getLinks().values().forEach(link -> {
+ DisallowedNextLinks dnl = disallowedNextLinks.get(link.getId());
+ if (dnl != null) {
+ NetworkUtils.setDisallowedNextLinks(link, dnl);
+ }
+ });
+ }
+
/**
* Adds attributes to the network link. Cannot be added directly upon link creation since we need to
* clean the road network and attributes are not copied while filtering
@@ -681,5 +755,187 @@ public Network getNetwork() {
return this.network;
}
+ // Turn Restrictions
+
+ @Nullable
+ private List parseTurnRestrictions(final Osm.Way way, Set modes) {
+
+ if (!config.parseTurnRestrictions) {
+ return null;
+ }
+
+ List osmTurnRestrictions = new ArrayList<>();
+ for (Osm.Relation relation : way.getRelations().values()) {
+
+ Map relationTags = relation.getTags();
+
+ // we only consider this relation, if
+ // - it is a turn restriction relation and
+ // - this way is the "from" link
+ if (!(Osm.Key.RESTRICTION.equals(relationTags.get(Osm.Key.TYPE))
+ && Osm.Value.FROM.equals(relation.getMemberRole(way)))) {
+ continue;
+ }
+
+ // identify modes
+ Set restrictionModes = new HashSet<>(modes);
+ // remove except modes
+ String exceptModesString = relationTags.get(Osm.Key.EXCEPT);
+ if (exceptModesString != null) {
+ for (String exceptMode : exceptModesString.split(";")) {
+ String matsimExceptMode = OSM_2_MATSIM_MODE_MAP.getOrDefault(exceptMode, exceptMode);
+ modes.remove(matsimExceptMode);
+ }
+ }
+
+ // identify restriction type and eventually add modes
+ OsmTurnRestriction.RestrictionType restrictionType = null;
+ for (String suffix : TURN_RESTRICTION_KEY_SUFFIXES) {
+ String restrictionTypeString = relationTags.get(Osm.Key.RESTRICTION + suffix);
+ if (restrictionTypeString != null) {
+
+ // add restriction type
+ if (restrictionTypeString.startsWith(Osm.Key.PROHIBITORY_RESTRICTION_PREFIX)) {
+ restrictionType = OsmTurnRestriction.RestrictionType.PROHIBITIVE;
+ } else if (restrictionTypeString.startsWith(Osm.Key.MANDATORY_RESTRICTION_PREFIX)) {
+ restrictionType = OsmTurnRestriction.RestrictionType.MANDATORY;
+ }
+
+ // add explicit modes, if
+ // - suffix specified it and
+ // - it is a MATSim mode
+ if (suffix.length() > 1) {
+ String mode = suffix.substring(1);
+ String matsimMode = OSM_2_MATSIM_MODE_MAP.get(mode);
+ if (matsimMode == null) {
+ // skip this, if not one of MATSim modes
+ restrictionType = null;
+ continue;
+ }
+ restrictionModes.add(matsimMode);
+ }
+
+ break; // take first one
+ }
+ }
+ if (restrictionType == null) {
+ log.warn("Could not identify turn restriction relation: https://www.openstreetmap.org/relation/{}",
+ relation.getId());
+ continue;
+ }
+
+ // create intermediate turn restriction record
+ List> nextWayIds = new ArrayList<>();
+ Id toWayId = null;
+ for (Osm.Element element : relation.getMembers()) {
+ if (element instanceof Osm.Way wayElement) {
+ if (Osm.Value.TO.equals(relation.getMemberRole(wayElement))) {
+ toWayId = wayElement.getId();
+ } else if (Osm.Value.VIA.equals(relation.getMemberRole(wayElement))) {
+ nextWayIds.add(wayElement.getId());
+ }
+ }
+ }
+ nextWayIds.add(toWayId);
+ osmTurnRestrictions.add(new OsmTurnRestriction(restrictionModes, nextWayIds, restrictionType));
+ }
+
+ return osmTurnRestrictions;
+ }
+
+ private void attachTurnRestrictionsAsDisallowedNextLinks() {
+
+ if (!config.parseTurnRestrictions) {
+ return;
+ }
+
+ for (Link link : network.getLinks().values()) {
+
+ // get turn restrictions
+ List osmTurnRestrictions = (List) link.getAttributes()
+ .getAttribute(OsmTurnRestriction.class.getSimpleName());
+ if (osmTurnRestrictions == null) {
+ break;
+ }
+
+ // create DisallowedNextLink
+ for (OsmTurnRestriction tr : osmTurnRestrictions) {
+
+ // find next link ids from next way ids
+ List> nextLinkIds = findLinkIds(wayLinkMap, network, link.getToNode(), tr.nextWayIds);
+ if (nextLinkIds.size() == tr.nextWayIds.size()) { // found next link ids from this link's toNode
+
+ // find link id lists to disallow
+ List>> disallowedNextLinkIdLists = new ArrayList<>();
+ if (tr.restrictionType.equals(OsmTurnRestriction.RestrictionType.PROHIBITIVE)) {
+ disallowedNextLinkIdLists.add(nextLinkIds);
+ } else if (tr.restrictionType.equals(OsmTurnRestriction.RestrictionType.MANDATORY)) {
+ // we need to exclude all other links originating from fromWay's toNode
+ link.getToNode().getOutLinks().values().stream()
+ .map(Link::getId)
+ .filter(lId -> !lId.equals(nextLinkIds.get(0)))
+ .forEach(lId -> disallowedNextLinkIdLists.add(List.of(lId)));
+ }
+
+ // attach DisallowedNextLinks objects
+ DisallowedNextLinks dnl = new DisallowedNextLinks();
+ for (List> disallowedNextLinkIds : disallowedNextLinkIdLists) {
+ for (String mode : tr.modes) {
+ dnl.addDisallowedLinkSequence(mode, disallowedNextLinkIds);
+ }
+ }
+ disallowedNextLinks.put(link.getId(), dnl);
+ }
+
+ }
+
+ // remove attribute
+ link.getAttributes().removeAttribute(OsmTurnRestriction.class.getSimpleName());
+ }
+ }
+
+ // Statics
+
+ /**
+ * Finds list of link ids starting from {@code lastNode} from list of OSM way
+ * ids.
+ *
+ * @param wayLinkMap
+ * @param network
+ * @param lastNode
+ * @param wayIds
+ * @return
+ */
+ protected static List> findLinkIds(Map, List>> wayLinkMap, Network network,
+ Node lastNode, List> wayIds) {
+
+ List> linkIds = new ArrayList<>();
+
+ int i = 0;
+ do {
+ Id wayId = wayIds.get(i);
+ // for every link id, that could stem from this way
+ List> linkIdCandidates = wayLinkMap.get(wayId);
+ if (linkIdCandidates == null) {
+ // requested way id has no link ids -> turn restriction is incomplete
+ return Collections.emptyList();
+ }
+ for (Id linkIdCandidate : linkIdCandidates) {
+ if (lastNode.getId().equals(network.getLinks().get(linkIdCandidate).getFromNode().getId())) {
+ linkIds.add(linkIdCandidate);
+ i += 1;
+ lastNode = network.getLinks().get(linkIds.get(linkIds.size() - 1)).getToNode();
+ break;
+ }
+ // try next link candidate
+ }
+ if (i == 0) { // no linkCandidate was fitting -> lastNode is not attached to way ids
+ return Collections.emptyList();
+ }
+ } while (i < wayIds.size());
+
+ return linkIds;
+ }
+
}
diff --git a/src/main/java/org/matsim/pt2matsim/osm/OsmTransitScheduleConverter.java b/src/main/java/org/matsim/pt2matsim/osm/OsmTransitScheduleConverter.java
index e1f0b624..7302ab46 100644
--- a/src/main/java/org/matsim/pt2matsim/osm/OsmTransitScheduleConverter.java
+++ b/src/main/java/org/matsim/pt2matsim/osm/OsmTransitScheduleConverter.java
@@ -19,7 +19,8 @@
package org.matsim.pt2matsim.osm;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
@@ -44,7 +45,7 @@
*/
public class OsmTransitScheduleConverter {
- protected static final Logger log = Logger.getLogger(OsmTransitScheduleConverter.class);
+ protected static final Logger log = LogManager.getLogger(OsmTransitScheduleConverter.class);
protected final OsmData osmData;
@@ -235,7 +236,7 @@ protected TransitRoute createTransitRoute(Osm.Relation relation) {
}
}
- NetworkRoute networkRoute = (linkSequenceForward.size() == 0 ? null : RouteUtils.createNetworkRoute(linkSequenceForward, null));
+ NetworkRoute networkRoute = (linkSequenceForward.size() == 0 ? null : RouteUtils.createNetworkRoute(linkSequenceForward));
if(stopSequenceForward.size() == 0) {
return null;
diff --git a/src/main/java/org/matsim/pt2matsim/osm/lib/Osm.java b/src/main/java/org/matsim/pt2matsim/osm/lib/Osm.java
index 43afe9cb..9b8ec62b 100644
--- a/src/main/java/org/matsim/pt2matsim/osm/lib/Osm.java
+++ b/src/main/java/org/matsim/pt2matsim/osm/lib/Osm.java
@@ -95,7 +95,7 @@ public interface Way extends Element, Identifiable {
List getNodes();
/**
- * @return the relations of which this node is a member
+ * @return the relations of which this way is a member
*/
Map, Relation> getRelations();
}
@@ -113,7 +113,7 @@ public interface Relation extends Element, Identifiable {
String getMemberRole(Element member);
/**
- * @return the relations of which this node is a member
+ * @return the relations of which this relation is a member
*/
Map, Relation> getRelations();
}
@@ -133,17 +133,24 @@ public static final class Key {
public static final String HIGHWAY = "highway";
public static final String SERVICE = "service";
- public final static String LANES = "lanes";
- public final static String MAXSPEED = "maxspeed";
- public final static String JUNCTION = "junction";
- public final static String ONEWAY = "oneway";
- public final static String ACCESS = "access";
+ public static final String LANES = "lanes";
+ public static final String MAXSPEED = "maxspeed";
+ public static final String JUNCTION = "junction";
+ public static final String ONEWAY = "oneway";
+ public static final String ACCESS = "access";
public static final String PSV = "psv";
public static final String BUS = "bus";
public static final String TAXI = "taxi";
- public final static String FORWARD = "forward";
- public final static String BACKWARD = "backward";
+ public static final String FORWARD = "forward";
+ public static final String BACKWARD = "backward";
+
+ public static final String RESTRICTION = "restriction";
+ public static final String PROHIBITORY_RESTRICTION_PREFIX = "no_";
+ public static final String MANDATORY_RESTRICTION_PREFIX = "only_";
+ public static final String EXCEPT = "except";
+ public static final String MOTORCAR = "motorcar";
+ public static final String BICYCLE = "bicycle";
// rarely used
public static final String TYPE = "type";
@@ -195,9 +202,9 @@ public static final class Value {
public static final String RESIDENTIAL = "residential";
public static final String LIVING_STREET = "living_street";
public static final String SERVICE = "service";
- public final static String STOP_POSITION = "stop_position";
- public final static String BUS = "bus";
- public final static String TROLLEYBUS = "trolleybus";
+ public static final String STOP_POSITION = "stop_position";
+ public static final String BUS = "bus";
+ public static final String TROLLEYBUS = "trolleybus";
public static final String FERRY = "ferry";
@@ -217,6 +224,12 @@ public static final class Value {
// values for maxspeed=*
public static final String WALK = "walk";
public static final String NONE = "none";
+
+ // turn restriction roles
+ public static final String FROM = "from";
+ public static final String VIA = "via";
+ public static final String TO = "to";
+
}
diff --git a/src/main/java/org/matsim/pt2matsim/osm/lib/OsmDataImpl.java b/src/main/java/org/matsim/pt2matsim/osm/lib/OsmDataImpl.java
index 71762efd..d587c2d1 100644
--- a/src/main/java/org/matsim/pt2matsim/osm/lib/OsmDataImpl.java
+++ b/src/main/java/org/matsim/pt2matsim/osm/lib/OsmDataImpl.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.osm.lib;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.core.utils.misc.Counter;
@@ -29,7 +30,7 @@
*/
public class OsmDataImpl implements OsmData {
- private final static Logger log = Logger.getLogger(OsmData.class);
+ private static final Logger log = LogManager.getLogger(OsmData.class);
protected final Map, Osm.Node> nodes = new HashMap<>();
protected final Map, Osm.Way> ways = new HashMap<>();
@@ -130,14 +131,14 @@ public void buildMap() {
// add relations to nodes/ways/relations
for(Osm.Element e : memberList) {
- if(e instanceof OsmElement.Node) {
- ((OsmElement.Node) e).addRelation(currentRel);
+ if (e instanceof OsmElement.Node n) {
+ n.addRelation(currentRel);
}
- if(e instanceof OsmElement.Way) {
- ((OsmElement.Way) e).addRelation(currentRel);
+ if (e instanceof OsmElement.Way w) {
+ w.addRelation(currentRel);
}
- if(e instanceof OsmElement.Relation) {
- ((OsmElement.Relation) e).addRelation(currentRel);
+ if (e instanceof OsmElement.Relation r) {
+ r.addRelation(currentRel);
}
}
}
@@ -186,14 +187,14 @@ public void removeRelation(Id id) {
Osm.Relation rel = relations.get(id);
for(Osm.Element e : rel.getMembers()) {
- if(e instanceof OsmElement.Node) {
- ((OsmElement.Node) e).getRelations().remove(rel.getId());
+ if (e instanceof OsmElement.Node n) {
+ n.getRelations().remove(rel.getId());
}
- if(e instanceof OsmElement.Way) {
- ((OsmElement.Way) e).getRelations().remove(rel.getId());
+ if (e instanceof OsmElement.Way w) {
+ w.getRelations().remove(rel.getId());
}
- if(e instanceof OsmElement.Relation) {
- ((OsmElement.Relation) e).getRelations().remove(rel.getId());
+ if (e instanceof OsmElement.Relation r) {
+ r.getRelations().remove(rel.getId());
}
}
removeMemberFromRelations(rel);
@@ -202,14 +203,14 @@ public void removeRelation(Id id) {
private void removeMemberFromRelations(Osm.Element e) {
Collection memberOfRelations = null;
- if(e instanceof Osm.Node) {
- memberOfRelations = ((Osm.Node) e).getRelations().values();
+ if (e instanceof Osm.Node n) {
+ memberOfRelations = n.getRelations().values();
}
- else if(e instanceof Osm.Way) {
- memberOfRelations = ((Osm.Way) e).getRelations().values();
+ else if (e instanceof Osm.Way w) {
+ memberOfRelations = w.getRelations().values();
}
- else if(e instanceof Osm.Relation) {
- memberOfRelations = ((Osm.Relation) e).getRelations().values();
+ else if (e instanceof Osm.Relation r) {
+ memberOfRelations = r.getRelations().values();
}
assert memberOfRelations != null;
diff --git a/src/main/java/org/matsim/pt2matsim/osm/lib/OsmFileReader.java b/src/main/java/org/matsim/pt2matsim/osm/lib/OsmFileReader.java
index 58103754..b2d84363 100644
--- a/src/main/java/org/matsim/pt2matsim/osm/lib/OsmFileReader.java
+++ b/src/main/java/org/matsim/pt2matsim/osm/lib/OsmFileReader.java
@@ -40,7 +40,7 @@ public class OsmFileReader extends MatsimXmlParser {
private ParsedRelation currentRelation = null;
public OsmFileReader(OsmData osmData) {
- super();
+ super(ValidationType.DTD_OR_XSD);
this.osmData = osmData;
this.setValidating(false);
}
diff --git a/src/main/java/org/matsim/pt2matsim/plausibility/MappingAnalysis.java b/src/main/java/org/matsim/pt2matsim/plausibility/MappingAnalysis.java
index bfe54b03..a1eaaadc 100644
--- a/src/main/java/org/matsim/pt2matsim/plausibility/MappingAnalysis.java
+++ b/src/main/java/org/matsim/pt2matsim/plausibility/MappingAnalysis.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.plausibility;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
@@ -49,7 +50,7 @@ public class MappingAnalysis {
private static final double MEASURE_INTERVAL = 1;
- private static final Logger log = Logger.getLogger(MappingAnalysis.class);
+ private static final Logger log = LogManager.getLogger(MappingAnalysis.class);
private final TransitSchedule schedule;
private final Map, RouteShape> shapes;
private final Network network;
diff --git a/src/main/java/org/matsim/pt2matsim/plausibility/PlausibilityCheck.java b/src/main/java/org/matsim/pt2matsim/plausibility/PlausibilityCheck.java
index 2a14de30..e5196324 100644
--- a/src/main/java/org/matsim/pt2matsim/plausibility/PlausibilityCheck.java
+++ b/src/main/java/org/matsim/pt2matsim/plausibility/PlausibilityCheck.java
@@ -18,8 +18,10 @@
package org.matsim.pt2matsim.plausibility;
-import org.apache.log4j.Level;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.LogManager;
import org.geojson.Feature;
import org.geojson.FeatureCollection;
import org.matsim.api.core.v01.Id;
@@ -63,7 +65,7 @@
*/
public class PlausibilityCheck {
- protected static final Logger log = Logger.getLogger(PlausibilityCheck.class);
+ protected static final Logger log = LogManager.getLogger(PlausibilityCheck.class);
public static final String CsvSeparator = ",";
@@ -509,12 +511,12 @@ private void addWarningToContainers(PlausibilityWarning warning) {
}
public static void setLogLevels() {
- Logger.getLogger(MGC.class).setLevel(Level.ERROR);
- Logger.getLogger(MatsimFileTypeGuesser.class).setLevel(Level.ERROR);
- Logger.getLogger(Network.class).setLevel(Level.ERROR);
- Logger.getLogger(Node.class).setLevel(Level.ERROR);
- Logger.getLogger(Link.class).setLevel(Level.ERROR);
- Logger.getLogger(MatsimXmlParser.class).setLevel(Level.ERROR);
+ Configurator.setLevel(LogManager.getLogger(MGC.class).getName(), Level.ERROR);
+ Configurator.setLevel(LogManager.getLogger(MatsimFileTypeGuesser.class).getName(), Level.ERROR);
+ Configurator.setLevel(LogManager.getLogger(Network.class).getName(), Level.ERROR);
+ Configurator.setLevel(LogManager.getLogger(Node.class).getName(), Level.ERROR);
+ Configurator.setLevel(LogManager.getLogger(Link.class).getName(), Level.ERROR);
+ Configurator.setLevel(LogManager.getLogger(MatsimXmlParser.class).getName(), Level.ERROR);
}
public Map> getWarnings() {
diff --git a/src/main/java/org/matsim/pt2matsim/run/CheckMappedSchedulePlausibility.java b/src/main/java/org/matsim/pt2matsim/run/CheckMappedSchedulePlausibility.java
index 0412bb98..fc932086 100644
--- a/src/main/java/org/matsim/pt2matsim/run/CheckMappedSchedulePlausibility.java
+++ b/src/main/java/org/matsim/pt2matsim/run/CheckMappedSchedulePlausibility.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.run;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.network.Network;
import org.matsim.core.utils.geometry.geotools.MGC;
import org.matsim.pt.transitSchedule.api.TransitSchedule;
@@ -37,7 +38,7 @@
*/
public final class CheckMappedSchedulePlausibility {
- protected static final Logger log = Logger.getLogger(PlausibilityCheck.class);
+ protected static final Logger log = LogManager.getLogger(PlausibilityCheck.class);
/**
* Performs a plausibility check on the given schedule and network files
diff --git a/src/main/java/org/matsim/pt2matsim/run/Gtfs2TransitSchedule.java b/src/main/java/org/matsim/pt2matsim/run/Gtfs2TransitSchedule.java
index 802c6541..9d01c9d4 100644
--- a/src/main/java/org/matsim/pt2matsim/run/Gtfs2TransitSchedule.java
+++ b/src/main/java/org/matsim/pt2matsim/run/Gtfs2TransitSchedule.java
@@ -18,8 +18,10 @@
package org.matsim.pt2matsim.run;
-import org.apache.log4j.Level;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.core.utils.geometry.geotools.MGC;
import org.matsim.core.utils.io.IOUtils;
@@ -47,7 +49,7 @@
*/
public final class Gtfs2TransitSchedule {
- protected static Logger log = Logger.getLogger(Gtfs2TransitSchedule.class);
+ protected static Logger log = LogManager.getLogger(Gtfs2TransitSchedule.class);
private static final String INFO_OUTPUT_OPTION_SCHEDULE = "schedule";
@@ -112,7 +114,7 @@ public static void main(final String[] args) {
*
*/
public static void run(String gtfsFolder, String sampleDayParam, String outputCoordinateSystem, String scheduleFile, String vehicleFile, String additionalLineInfoFile) {
- Logger.getLogger(MGC.class).setLevel(Level.ERROR);
+ Configurator.setLevel(LogManager.getLogger(MGC.class).getName(), Level.ERROR);
// check sample day parameter
if(!isValidSampleDayParam(sampleDayParam)) {
diff --git a/src/main/java/org/matsim/pt2matsim/run/PublicTransitMapper.java b/src/main/java/org/matsim/pt2matsim/run/PublicTransitMapper.java
index 2e744a6b..c4a830a2 100644
--- a/src/main/java/org/matsim/pt2matsim/run/PublicTransitMapper.java
+++ b/src/main/java/org/matsim/pt2matsim/run/PublicTransitMapper.java
@@ -21,7 +21,8 @@
package org.matsim.pt2matsim.run;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.TransportMode;
import org.matsim.api.core.v01.network.Network;
import org.matsim.pt.transitSchedule.api.TransitSchedule;
@@ -43,7 +44,7 @@
*/
public final class PublicTransitMapper {
- protected static Logger log = Logger.getLogger(PublicTransitMapper.class);
+ protected static Logger log = LogManager.getLogger(PublicTransitMapper.class);
private PublicTransitMapper() {
}
diff --git a/src/main/java/org/matsim/pt2matsim/run/gis/Network2Geojson.java b/src/main/java/org/matsim/pt2matsim/run/gis/Network2Geojson.java
index 4237d1d2..e57c68e7 100644
--- a/src/main/java/org/matsim/pt2matsim/run/gis/Network2Geojson.java
+++ b/src/main/java/org/matsim/pt2matsim/run/gis/Network2Geojson.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.run.gis;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.geojson.Feature;
import org.geojson.FeatureCollection;
import org.matsim.api.core.v01.Coord;
@@ -80,7 +81,7 @@ public static void run(String networkCoordSys, Network network, String networkOu
}
- protected static Logger log = Logger.getLogger(Network2Geojson.class);
+ protected static Logger log = LogManager.getLogger(Network2Geojson.class);
private final CoordinateTransformation ct;
private Network network;
private FeatureCollection linkFeatures = new FeatureCollection();
diff --git a/src/main/java/org/matsim/pt2matsim/run/gis/Network2ShapeFile.java b/src/main/java/org/matsim/pt2matsim/run/gis/Network2ShapeFile.java
index 8d9bd412..9acf3cb1 100644
--- a/src/main/java/org/matsim/pt2matsim/run/gis/Network2ShapeFile.java
+++ b/src/main/java/org/matsim/pt2matsim/run/gis/Network2ShapeFile.java
@@ -24,9 +24,9 @@
import org.matsim.api.core.v01.network.Node;
import org.matsim.core.utils.collections.CollectionUtils;
import org.matsim.core.utils.geometry.geotools.MGC;
+import org.matsim.core.utils.gis.GeoFileWriter;
import org.matsim.core.utils.gis.PointFeatureFactory;
import org.matsim.core.utils.gis.PolylineFeatureFactory;
-import org.matsim.core.utils.gis.ShapeFileWriter;
import org.matsim.pt2matsim.tools.NetworkTools;
import org.opengis.feature.simple.SimpleFeature;
@@ -94,7 +94,7 @@ public void convertNodes(String nodesOutputFile) {
nodeFeatures.add(f);
}
- ShapeFileWriter.writeGeometries(nodeFeatures, nodesOutputFile);
+ GeoFileWriter.writeGeometries(nodeFeatures, nodesOutputFile);
}
public void convertLinks(String linksOutputFile) {
@@ -125,7 +125,7 @@ public void convertLinks(String linksOutputFile) {
linkFeatures.add(f);
}
- ShapeFileWriter.writeGeometries(linkFeatures, linksOutputFile);
+ GeoFileWriter.writeGeometries(linkFeatures, linksOutputFile);
}
private Coordinate[] getCoordinates(Link link) {
diff --git a/src/main/java/org/matsim/pt2matsim/run/gis/Schedule2Geojson.java b/src/main/java/org/matsim/pt2matsim/run/gis/Schedule2Geojson.java
index 1d6ca188..1518cd2b 100644
--- a/src/main/java/org/matsim/pt2matsim/run/gis/Schedule2Geojson.java
+++ b/src/main/java/org/matsim/pt2matsim/run/gis/Schedule2Geojson.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.run.gis;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.geojson.Feature;
import org.geojson.FeatureCollection;
import org.matsim.api.core.v01.Coord;
@@ -92,7 +93,7 @@ public static void run(String crs, TransitSchedule schedule, Network network, St
s2s.writeSchedule(outputFile);
}
- private static final Logger log = Logger.getLogger(Schedule2Geojson.class);
+ private static final Logger log = LogManager.getLogger(Schedule2Geojson.class);
private final TransitSchedule schedule;
private final Network network;
private final CoordinateTransformation ct;
diff --git a/src/main/java/org/matsim/pt2matsim/run/gis/Schedule2ShapeFile.java b/src/main/java/org/matsim/pt2matsim/run/gis/Schedule2ShapeFile.java
index 1aa27910..8a1e5981 100644
--- a/src/main/java/org/matsim/pt2matsim/run/gis/Schedule2ShapeFile.java
+++ b/src/main/java/org/matsim/pt2matsim/run/gis/Schedule2ShapeFile.java
@@ -19,16 +19,17 @@
package org.matsim.pt2matsim.run.gis;
import org.locationtech.jts.geom.Coordinate;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.core.utils.collections.CollectionUtils;
import org.matsim.core.utils.collections.MapUtils;
import org.matsim.core.utils.geometry.geotools.MGC;
+import org.matsim.core.utils.gis.GeoFileWriter;
import org.matsim.core.utils.gis.PointFeatureFactory;
import org.matsim.core.utils.gis.PolylineFeatureFactory;
-import org.matsim.core.utils.gis.ShapeFileWriter;
import org.matsim.pt.transitSchedule.api.*;
import org.matsim.pt2matsim.tools.NetworkTools;
import org.matsim.pt2matsim.tools.ScheduleTools;
@@ -43,7 +44,7 @@
*/
public class Schedule2ShapeFile {
- private static final Logger log = Logger.getLogger(Schedule2ShapeFile.class);
+ private static final Logger log = LogManager.getLogger(Schedule2ShapeFile.class);
private final TransitSchedule schedule;
private final Network network;
private final String crs;
@@ -143,7 +144,7 @@ public void stopRefLinks2Polylines(String outputFile) {
}
}
- ShapeFileWriter.writeGeometries(lineFeatures, outputFile);
+ GeoFileWriter.writeGeometries(lineFeatures, outputFile);
}
@@ -180,7 +181,7 @@ public void stopFacilities2Points(String pointOutputFile) {
pointFeatures.add(pf);
}
- ShapeFileWriter.writeGeometries(pointFeatures, pointOutputFile);
+ GeoFileWriter.writeGeometries(pointFeatures, pointOutputFile);
}
/**
@@ -228,7 +229,7 @@ public void routes2Polylines(String outputFile, boolean useNetworkLinks) {
}
}
- ShapeFileWriter.writeGeometries(features, outputFile);
+ GeoFileWriter.writeGeometries(features, outputFile);
}
/**
diff --git a/src/main/java/org/matsim/pt2matsim/tools/CoordTools.java b/src/main/java/org/matsim/pt2matsim/tools/CoordTools.java
index 9a7d62ac..f2b14071 100644
--- a/src/main/java/org/matsim/pt2matsim/tools/CoordTools.java
+++ b/src/main/java/org/matsim/pt2matsim/tools/CoordTools.java
@@ -269,6 +269,7 @@ public static Map getStopsInAreaBool(TransitSchedu
*
* @deprecated not used anywhere
*/
+ @Deprecated
public static int getBorderCrossType(Coord SWcut, Coord NEcut, Coord fromCoord, Coord toCoord) {
int fromSector = getAreaOfInterestSector(SWcut, NEcut, fromCoord);
int toSector = getAreaOfInterestSector(SWcut, NEcut, toCoord);
diff --git a/src/main/java/org/matsim/pt2matsim/tools/CsvTools.java b/src/main/java/org/matsim/pt2matsim/tools/CsvTools.java
index 60b03708..29258185 100644
--- a/src/main/java/org/matsim/pt2matsim/tools/CsvTools.java
+++ b/src/main/java/org/matsim/pt2matsim/tools/CsvTools.java
@@ -20,6 +20,8 @@
package org.matsim.pt2matsim.tools;
import com.opencsv.CSVReader;
+import com.opencsv.exceptions.CsvValidationException;
+
import org.matsim.core.utils.collections.MapUtils;
import org.matsim.core.utils.collections.Tuple;
@@ -142,15 +144,16 @@ public static void writeNestedMapToFile(Object[] header, Map> readNestedMapFromFile(String fileName, boolean ignoreFirstLine) throws IOException {
Map> map = new HashMap<>();
-
- CSVReader reader = new CSVReader(new FileReader(fileName));
- if(ignoreFirstLine) reader.readNext();
- String[] line = reader.readNext();
- while(line != null) {
- MapUtils.getMap(line[0], map).put(line[1], line[2]);
- line = reader.readNext();
+ try(CSVReader reader = new CSVReader(new FileReader(fileName))) {
+ if(ignoreFirstLine) reader.readNext();
+ String[] line = reader.readNext();
+ while(line != null) {
+ MapUtils.getMap(line[0], map).put(line[1], line[2]);
+ line = reader.readNext();
+ }
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
- reader.close();
return map;
}
diff --git a/src/main/java/org/matsim/pt2matsim/tools/GtfsTools.java b/src/main/java/org/matsim/pt2matsim/tools/GtfsTools.java
index 0ea38f21..409873f2 100644
--- a/src/main/java/org/matsim/pt2matsim/tools/GtfsTools.java
+++ b/src/main/java/org/matsim/pt2matsim/tools/GtfsTools.java
@@ -137,7 +137,7 @@ public static Map> getTripsOndates(GtfsFeed feed) {
* usually the largest file.
*/
public static void writeStopTimes(Collection trips, String folder) throws IOException {
- CSVWriter stopTimesWriter = new CSVWriter(new FileWriter(folder + GtfsDefinitions.Files.STOP_TIMES.fileName), ',');
+ CSVWriter stopTimesWriter = new CSVWriter(new FileWriter(folder + GtfsDefinitions.Files.STOP_TIMES.fileName));
String[] header = GtfsDefinitions.Files.STOP_TIMES.columns;
stopTimesWriter.writeNext(header, true);
@@ -161,7 +161,7 @@ public static void writeStopTimes(Collection trips, String folder) throws
* Experimental class to write stops.txt (i.e. after filtering for one date)
*/
public static void writeStops(Collection stops, String path) throws IOException {
- CSVWriter stopsWriter = new CSVWriter(new FileWriter(path + GtfsDefinitions.Files.STOPS.fileName), ',');
+ CSVWriter stopsWriter = new CSVWriter(new FileWriter(path + GtfsDefinitions.Files.STOPS.fileName));
String[] header = GtfsDefinitions.Files.STOPS.columns;
stopsWriter.writeNext(header, true);
for(Stop stop : stops) {
@@ -180,7 +180,7 @@ public static void writeStops(Collection stops, String path) throws IOExce
* Experimental class to write trips.txt (i.e. after filtering for one date)
*/
public static void writeTrips(Collection trips, String path) throws IOException {
- CSVWriter tripsWriter = new CSVWriter(new FileWriter(path + GtfsDefinitions.Files.TRIPS.fileName), ',');
+ CSVWriter tripsWriter = new CSVWriter(new FileWriter(path + GtfsDefinitions.Files.TRIPS.fileName));
String[] header = GtfsDefinitions.Files.TRIPS.columns;
tripsWriter.writeNext(header, true);
for(Trip trip : trips) {
@@ -198,7 +198,7 @@ public static void writeTrips(Collection trips, String path) throws IOExce
* Experimental class to write transfers.txt (i.e. after creating additional walk transfer)
*/
public static void writeTransfers(Collection transfers, String path) throws IOException {
- CSVWriter transfersWiter = new CSVWriter(new FileWriter(path + GtfsDefinitions.Files.TRANSFERS.fileName), ',');
+ CSVWriter transfersWiter = new CSVWriter(new FileWriter(path + GtfsDefinitions.Files.TRANSFERS.fileName));
String[] columns = GtfsDefinitions.Files.TRANSFERS.columns;
String[] optionalColumns = GtfsDefinitions.Files.TRANSFERS.optionalColumns;
String[] header = Stream.concat(Arrays.stream(columns), Arrays.stream(optionalColumns)).toArray(String[]::new);
diff --git a/src/main/java/org/matsim/pt2matsim/tools/NetworkTools.java b/src/main/java/org/matsim/pt2matsim/tools/NetworkTools.java
index c590cf6a..a4c1a7df 100644
--- a/src/main/java/org/matsim/pt2matsim/tools/NetworkTools.java
+++ b/src/main/java/org/matsim/pt2matsim/tools/NetworkTools.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.tools;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.TransportMode;
@@ -26,6 +27,7 @@
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.network.NetworkFactory;
import org.matsim.api.core.v01.network.Node;
+import org.matsim.core.config.groups.NetworkConfigGroup;
import org.matsim.core.network.NetworkUtils;
import org.matsim.core.network.algorithms.NetworkTransform;
import org.matsim.core.network.filter.NetworkFilterManager;
@@ -53,7 +55,7 @@
*/
public final class NetworkTools {
- protected static Logger log = Logger.getLogger(NetworkTools.class);
+ protected static Logger log = LogManager.getLogger(NetworkTools.class);
private NetworkTools() {}
@@ -180,7 +182,10 @@ public static Map> findClosestLinks(Network network, Coord coo
* For opposite links, the link which has the coordinate on its right side is sorted "closer" to the coordinate.
* If more than two links have the exact same distance, links are sorted by distance to their respective closest node.
* After that, behaviour is undefined.
+ *
+ * @deprecated See https://github.com/matsim-org/pt2matsim/issues/199
*/
+ @Deprecated(since = "23.10-SNAPSHOT")
public static List findClosestLinksSorted(Network network, Coord coord, double nodeSearchRadius, Set allowedTransportModes) {
List links = new ArrayList<>();
Map> sortedLinks = findClosestLinks(network, coord, nodeSearchRadius, allowedTransportModes);
@@ -201,7 +206,7 @@ public static List findClosestLinksSorted(Network network, Coord coord, do
Map tmp = new HashMap<>();
for(Link l : list) {
double fromNodeDist = CoordUtils.calcEuclideanDistance(l.getFromNode().getCoord(), coord);
- double toNodeDist = CoordUtils.calcEuclideanDistance(l.getFromNode().getCoord(), coord);
+ double toNodeDist = CoordUtils.calcEuclideanDistance(l.getToNode().getCoord(), coord);
double nodeDist = fromNodeDist < toNodeDist ? fromNodeDist : toNodeDist;
double d = nodeDist + (coordIsOnRightSideOfLink(coord, l) ? 1 : 100);
@@ -225,7 +230,7 @@ public static List findClosestLinksSorted(Network network, Coord coord, do
* @return the filtered new network
*/
public static Network createFilteredNetworkByLinkMode(Network network, Set transportModes) {
- NetworkFilterManager filterManager = new NetworkFilterManager(network);
+ NetworkFilterManager filterManager = new NetworkFilterManager(network, new NetworkConfigGroup());
filterManager.addLinkFilter(new LinkFilter(transportModes));
Network newNetwork = filterManager.applyFilters();
removeNotUsedNodes(newNetwork);
@@ -233,7 +238,7 @@ public static Network createFilteredNetworkByLinkMode(Network network, Set transportModes) {
- NetworkFilterManager filterManager = new NetworkFilterManager(network);
+ NetworkFilterManager filterManager = new NetworkFilterManager(network, new NetworkConfigGroup());
filterManager.addLinkFilter(new InverseLinkFilter(transportModes));
return filterManager.applyFilters();
}
diff --git a/src/main/java/org/matsim/pt2matsim/tools/PTMapperTools.java b/src/main/java/org/matsim/pt2matsim/tools/PTMapperTools.java
index 91e309f4..9f889024 100644
--- a/src/main/java/org/matsim/pt2matsim/tools/PTMapperTools.java
+++ b/src/main/java/org/matsim/pt2matsim/tools/PTMapperTools.java
@@ -18,8 +18,10 @@
package org.matsim.pt2matsim.tools;
-import org.apache.log4j.Level;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
@@ -45,7 +47,7 @@
*/
public final class PTMapperTools {
- protected static Logger log = Logger.getLogger(PTMapperTools.class);
+ protected static Logger log = LogManager.getLogger(PTMapperTools.class);
private PTMapperTools() {
}
@@ -208,13 +210,13 @@ private static Id useCloserRefLinkForChildStopFacility(TransitSchedule sch
}
public static void setLogLevels() {
- Logger.getLogger(org.matsim.core.router.Dijkstra.class).setLevel(Level.ERROR); // suppress no route found warnings
- Logger.getLogger(Network.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.network.filter.NetworkFilterManager.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.router.util.PreProcessDijkstra.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.router.util.PreProcessDijkstra.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.router.util.PreProcessEuclidean.class).setLevel(Level.WARN);
- Logger.getLogger(org.matsim.core.router.util.PreProcessLandmarks.class).setLevel(Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.router.Dijkstra.class).getName(), Level.ERROR); // suppress no route found warnings
+ Configurator.setLevel(LogManager.getLogger(Network.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.network.filter.NetworkFilterManager.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.router.util.PreProcessDijkstra.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.router.util.PreProcessDijkstra.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.router.util.PreProcessEuclidean.class).getName(), Level.WARN);
+ Configurator.setLevel(LogManager.getLogger(org.matsim.core.router.util.PreProcessLandmarks.class).getName(), Level.WARN);
}
/**
diff --git a/src/main/java/org/matsim/pt2matsim/tools/ScheduleTools.java b/src/main/java/org/matsim/pt2matsim/tools/ScheduleTools.java
index ce561f3d..88f55eb8 100644
--- a/src/main/java/org/matsim/pt2matsim/tools/ScheduleTools.java
+++ b/src/main/java/org/matsim/pt2matsim/tools/ScheduleTools.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.tools;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.Scenario;
import org.matsim.api.core.v01.TransportMode;
@@ -49,7 +50,7 @@
*/
public final class ScheduleTools {
- protected static Logger log = Logger.getLogger(ScheduleTools.class);
+ protected static Logger log = LogManager.getLogger(ScheduleTools.class);
private ScheduleTools() {}
@@ -204,10 +205,11 @@ public static VehicleType createDefaultVehicleType(String id, String defaultVehi
VehicleType vehicleType = vf.createVehicleType(vTypeId);
vehicleType.setLength(defaultValues.length);
vehicleType.setWidth(defaultValues.width);
- vehicleType.setAccessTime(defaultValues.accessTime);
- vehicleType.setEgressTime(defaultValues.egressTime);
- vehicleType.setDoorOperationMode(defaultValues.doorOperation);
+ VehicleUtils.setAccessTime(vehicleType, defaultValues.accessTime);
+ VehicleUtils.setEgressTime(vehicleType, defaultValues.egressTime);
+ VehicleUtils.setDoorOperationMode(vehicleType, defaultValues.doorOperation);
vehicleType.setPcuEquivalents(defaultValues.pcuEquivalents);
+ vehicleType.setNetworkMode(defaultValues.transportMode.name);
VehicleCapacity capacity = vehicleType.getCapacity();
capacity.setSeats(defaultValues.capacitySeats);
@@ -315,7 +317,7 @@ public static void routeSchedule(TransitSchedule schedule, Network network, Sche
// add link sequence to schedule
if(linkIdSequence != null) {
- transitRoute.setRoute(RouteUtils.createNetworkRoute(linkIdSequence, network));
+ transitRoute.setRoute(RouteUtils.createNetworkRoute(linkIdSequence));
}
} else {
log.warn("Route " + transitRoute.getId() + " on line " + transitLine.getId() + " has no stop sequence");
diff --git a/src/main/java/org/matsim/pt2matsim/tools/ShapeTools.java b/src/main/java/org/matsim/pt2matsim/tools/ShapeTools.java
index c30aec90..00e1b416 100644
--- a/src/main/java/org/matsim/pt2matsim/tools/ShapeTools.java
+++ b/src/main/java/org/matsim/pt2matsim/tools/ShapeTools.java
@@ -19,6 +19,8 @@
package org.matsim.pt2matsim.tools;
import com.opencsv.CSVReader;
+import com.opencsv.exceptions.CsvValidationException;
+
import org.locationtech.jts.geom.Coordinate;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
@@ -30,8 +32,8 @@
import org.matsim.core.utils.geometry.CoordinateTransformation;
import org.matsim.core.utils.geometry.geotools.MGC;
import org.matsim.core.utils.geometry.transformations.TransformationFactory;
+import org.matsim.core.utils.gis.GeoFileWriter;
import org.matsim.core.utils.gis.PolylineFeatureFactory;
-import org.matsim.core.utils.gis.ShapeFileWriter;
import org.matsim.pt2matsim.gtfs.GtfsFeed;
import org.matsim.pt2matsim.gtfs.GtfsFeedImpl;
import org.matsim.pt2matsim.gtfs.lib.GtfsDefinitions;
@@ -226,7 +228,7 @@ public static void writeGtfsTripsToFile(GtfsFeed gtfsFeed, Set serviceId
}
}
}
- ShapeFileWriter.writeGeometries(features, outFile);
+ GeoFileWriter.writeGeometries(features, outFile);
}
/**
@@ -255,7 +257,7 @@ public static void writeESRIShapeFile(Collection extends RouteShape> shapes, S
features.add(f);
}
}
- ShapeFileWriter.writeGeometries(features, filename);
+ GeoFileWriter.writeGeometries(features, filename);
}
@@ -263,9 +265,7 @@ public static Map, RouteShape> readShapesFile(String shapeFile, S
Map, RouteShape> shapes = new HashMap<>();
CoordinateTransformation ct = TransformationFactory.getCoordinateTransformation("WGS84", outputCoordinateSystem);
- CSVReader reader;
- try {
- reader = new CSVReader(new FileReader(shapeFile));
+ try (CSVReader reader = new CSVReader(new FileReader(shapeFile))) {
String[] header = reader.readNext();
Map col = getIndices(header, GtfsDefinitions.Files.SHAPES.columns);
String[] line = reader.readNext();
@@ -280,7 +280,6 @@ public static Map, RouteShape> readShapesFile(String shapeFile, S
currentShape.addPoint(ct.transform(point), Integer.parseInt(line[col.get(GtfsDefinitions.SHAPE_PT_SEQUENCE)]));
line = reader.readNext();
}
- reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("File not found!");
@@ -288,6 +287,8 @@ public static Map, RouteShape> readShapesFile(String shapeFile, S
throw new RuntimeException("Emtpy line found file!");
} catch (IOException e) {
e.printStackTrace();
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
}
return shapes;
}
diff --git a/src/main/java/org/matsim/pt2matsim/tools/debug/ScheduleCleaner.java b/src/main/java/org/matsim/pt2matsim/tools/debug/ScheduleCleaner.java
index 23193a0a..c1521937 100644
--- a/src/main/java/org/matsim/pt2matsim/tools/debug/ScheduleCleaner.java
+++ b/src/main/java/org/matsim/pt2matsim/tools/debug/ScheduleCleaner.java
@@ -18,7 +18,8 @@
package org.matsim.pt2matsim.tools.debug;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
@@ -47,7 +48,7 @@
*/
public final class ScheduleCleaner {
- protected static Logger log = Logger.getLogger(ScheduleTools.class);
+ protected static Logger log = LogManager.getLogger(ScheduleTools.class);
private ScheduleCleaner() {}
diff --git a/src/test/java/org/matsim/pt2matsim/editor/BasicScheduleEditorTest.java b/src/test/java/org/matsim/pt2matsim/editor/BasicScheduleEditorTest.java
index 52a3dcb4..89c8cf87 100644
--- a/src/test/java/org/matsim/pt2matsim/editor/BasicScheduleEditorTest.java
+++ b/src/test/java/org/matsim/pt2matsim/editor/BasicScheduleEditorTest.java
@@ -1,7 +1,7 @@
package org.matsim.pt2matsim.editor;
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
@@ -17,7 +17,7 @@
/**
* @author polettif
*/
-public class BasicScheduleEditorTest {
+class BasicScheduleEditorTest {
/**
* Possible Commands:
@@ -25,7 +25,7 @@ public class BasicScheduleEditorTest {
* ["rerouteViaLink"] [TransitLineId] [TransitRouteId] [oldLinkId] [newLinkId]
*/
@Test
- public void rerouteViaLink() {
+ void rerouteViaLink() {
TransitSchedule schedule = initSchedule();
Network network = NetworkToolsTest.initNetwork();
@@ -47,7 +47,7 @@ public void rerouteViaLink() {
linkIdsExpected.add(Id.createLinkId("ZI"));
linkIdsExpected.add(Id.createLinkId("IB"));
- Assert.assertEquals(linkIdsExpected, linkIds);
+ Assertions.assertEquals(linkIdsExpected, linkIds);
}
/**
@@ -57,7 +57,7 @@ public void rerouteViaLink() {
* ["changeRefLink"] ["allTransitRoutesOnLink"] [linkId] [ParentId] [newlinkId]
*/
@Test
- public void changeRefLink() {
+ void changeRefLink() {
TransitSchedule schedule = initSchedule();
Network network = NetworkToolsTest.initNetwork();
@@ -79,7 +79,7 @@ public void changeRefLink() {
linkIdsExpected.add(Id.createLinkId("ZI"));
linkIdsExpected.add(Id.createLinkId("IB"));
- Assert.assertEquals(linkIdsExpected, linkIds);
+ Assertions.assertEquals(linkIdsExpected, linkIds);
}
}
\ No newline at end of file
diff --git a/src/test/java/org/matsim/pt2matsim/gtfs/GtfsConverterTest.java b/src/test/java/org/matsim/pt2matsim/gtfs/GtfsConverterTest.java
index 33a5b2be..6a0bd492 100644
--- a/src/test/java/org/matsim/pt2matsim/gtfs/GtfsConverterTest.java
+++ b/src/test/java/org/matsim/pt2matsim/gtfs/GtfsConverterTest.java
@@ -1,8 +1,8 @@
package org.matsim.pt2matsim.gtfs;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.core.utils.geometry.transformations.TransformationFactory;
@@ -16,14 +16,14 @@
/**
* @author polettif
*/
-public class GtfsConverterTest {
+class GtfsConverterTest {
private GtfsFeed gtfsFeed;
private GtfsConverter gtfsConverter;
private String coordSystem = TransformationFactory.CH1903_LV03_Plus;
private TransitSchedule convertedSchedule;
- @Before
+ @BeforeEach
public void convert() {
gtfsFeed = new GtfsFeedImpl("test/gtfs-feed/");
@@ -32,25 +32,25 @@ public void convert() {
}
@Test
- public void convertAll() {
+ void convertAll() {
TransitSchedule schedule = gtfsConverter.convert(GtfsConverter.ALL_SERVICE_IDS, coordSystem);
- Assert.assertTrue(TransitScheduleValidator.validateAllStopsExist(schedule).isValid());
- Assert.assertTrue(TransitScheduleValidator.validateOffsets(schedule).isValid());
+ Assertions.assertTrue(TransitScheduleValidator.validateAllStopsExist(schedule).isValid());
+ Assertions.assertTrue(TransitScheduleValidator.validateOffsets(schedule).isValid());
}
@Test
- public void numberOfStopsAndRoutes() {
+ void numberOfStopsAndRoutes() {
int nTransitRoutes = 0;
for(TransitLine transitLine : convertedSchedule.getTransitLines().values()) {
nTransitRoutes += transitLine.getRoutes().size();
}
- Assert.assertEquals(6, convertedSchedule.getFacilities().size());
- Assert.assertEquals(3, convertedSchedule.getTransitLines().size());
- Assert.assertEquals(3, nTransitRoutes);
+ Assertions.assertEquals(6, convertedSchedule.getFacilities().size());
+ Assertions.assertEquals(3, convertedSchedule.getTransitLines().size());
+ Assertions.assertEquals(3, nTransitRoutes);
}
@Test
- public void departuresFromFrequencies() {
+ void departuresFromFrequencies() {
TransitSchedule baseSchedule = ScheduleToolsTest.initSchedule();
for(TransitLine transitLine : convertedSchedule.getTransitLines().values()) {
for(TransitRoute transitRoute : transitLine.getRoutes().values()) {
@@ -67,25 +67,25 @@ public void departuresFromFrequencies() {
actualDepTimes.add(departure.getDepartureTime());
}
- Assert.assertEquals(expectedDepTimes, actualDepTimes);
+ Assertions.assertEquals(expectedDepTimes, actualDepTimes);
}
}
}
@Test
- public void offsets() {
+ void offsets() {
TransitSchedule baseSchedule = ScheduleToolsTest.initSchedule();
for(TransitLine transitLine : convertedSchedule.getTransitLines().values()) {
for(TransitRoute transitRoute : transitLine.getRoutes().values()) {
TransitRoute expectedRoute = baseSchedule.getTransitLines().get(transitLine.getId()).getRoutes().get(transitRoute.getId());
- Assert.assertEquals(expectedRoute.getStops().size(), transitRoute.getStops().size());
+ Assertions.assertEquals(expectedRoute.getStops().size(), transitRoute.getStops().size());
for(int i = 0; i < transitRoute.getStops().size() - 1; i++) {
if(i > 0) {
- Assert.assertEquals(expectedRoute.getStops().get(i).getArrivalOffset().seconds(),
+ Assertions.assertEquals(expectedRoute.getStops().get(i).getArrivalOffset().seconds(),
transitRoute.getStops().get(i).getArrivalOffset().seconds(), 0.1);
}
if(i < transitRoute.getStops().size() - 2) {
- Assert.assertEquals(expectedRoute.getStops().get(i).getDepartureOffset().seconds(),
+ Assertions.assertEquals(expectedRoute.getStops().get(i).getDepartureOffset().seconds(),
transitRoute.getStops().get(i).getDepartureOffset().seconds(), 0.1);
}
}
@@ -94,17 +94,17 @@ public void offsets() {
}
@Test
- public void stops() {
+ void stops() {
TransitSchedule baseSchedule = ScheduleToolsTest.initUnmappedSchedule();
for(TransitStopFacility actualStopFac : convertedSchedule.getFacilities().values()) {
TransitStopFacility expectedStopFac = baseSchedule.getFacilities().get(actualStopFac.getId());
- Assert.assertEquals(expectedStopFac.getCoord(), actualStopFac.getCoord());
- Assert.assertEquals(expectedStopFac.getName(), actualStopFac.getName());
+ Assertions.assertEquals(expectedStopFac.getCoord(), actualStopFac.getCoord());
+ Assertions.assertEquals(expectedStopFac.getName(), actualStopFac.getName());
}
}
@Test
- public void combineRoutes() {
+ void combineRoutes() {
TransitSchedule test = ScheduleTools.createSchedule();
TransitScheduleFactory f = test.getFactory();
Id lineId = Id.create("L", TransitLine.class);
@@ -137,14 +137,14 @@ public void combineRoutes() {
line.addRoute(route2);
line.addRoute(route3);
- Assert.assertEquals(3, line.getRoutes().size());
+ Assertions.assertEquals(3, line.getRoutes().size());
// only routes with identical stop sequence (1, 2, 3) and departure sequence (2, 3) are combined.
gtfsConverter.combineTransitRoutes(test);
- Assert.assertEquals(2, line.getRoutes().size());
+ Assertions.assertEquals(2, line.getRoutes().size());
}
@Test
- public void testTransfers() {
+ void testTransfers() {
Set expectedTransferTimes = new TreeSet<>();
MinimalTransferTimes.MinimalTransferTimesIterator iter1 = ScheduleToolsTest.initUnmappedSchedule().getMinimalTransferTimes().iterator();
while(iter1.hasNext()) {
@@ -159,7 +159,7 @@ public void testTransfers() {
actualTransferTime.add(getTransferTimeTestString(iter2));
}
- Assert.assertEquals(expectedTransferTimes, actualTransferTime);
+ Assertions.assertEquals(expectedTransferTimes, actualTransferTime);
}
private String getTransferTimeTestString(MinimalTransferTimes.MinimalTransferTimesIterator iterator) {
diff --git a/src/test/java/org/matsim/pt2matsim/gtfs/GtfsFeedImplTest.java b/src/test/java/org/matsim/pt2matsim/gtfs/GtfsFeedImplTest.java
index 5d093d3c..ddd1781c 100644
--- a/src/test/java/org/matsim/pt2matsim/gtfs/GtfsFeedImplTest.java
+++ b/src/test/java/org/matsim/pt2matsim/gtfs/GtfsFeedImplTest.java
@@ -1,8 +1,8 @@
package org.matsim.pt2matsim.gtfs;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.core.utils.geometry.transformations.TransformationFactory;
@@ -23,39 +23,39 @@
/**
* @author polettif
*/
-public class GtfsFeedImplTest {
+class GtfsFeedImplTest {
private GtfsFeed feed;
- @Before
+ @BeforeEach
public void prepare() {
feed = new GtfsFeedImpl("test/gtfs-feed/");
- Assert.assertEquals(TransformationFactory.WGS84, feed.getCurrentCoordSystem());
+ Assertions.assertEquals(TransformationFactory.WGS84, feed.getCurrentCoordSystem());
}
@Test
- public void compareShapes() {
+ void compareShapes() {
feed.transform(TransformationFactory.CH1903_LV03_Plus);
for(Map.Entry, RouteShape> entry : ShapeToolsTest.initShapes().entrySet()) {
RouteShape feedShape = feed.getShapes().get(entry.getKey());
for(Map.Entry coordEntry : entry.getValue().getCoordsSorted().entrySet()) {
- Assert.assertEquals(coordEntry.getValue(), feedShape.getCoordsSorted().get(coordEntry.getKey()));
+ Assertions.assertEquals(coordEntry.getValue(), feedShape.getCoordsSorted().get(coordEntry.getKey()));
}
}
}
@Test
- public void statistics() {
- Assert.assertEquals(6, feed.getStops().size());
- Assert.assertEquals(3, feed.getRoutes().size());
- Assert.assertEquals(4, feed.getServices().size());
- Assert.assertEquals(3, feed.getShapes().size());
- Assert.assertEquals(6, feed.getTrips().size());
- Assert.assertEquals(6, feed.getTransfers().size());
+ void statistics() {
+ Assertions.assertEquals(6, feed.getStops().size());
+ Assertions.assertEquals(3, feed.getRoutes().size());
+ Assertions.assertEquals(4, feed.getServices().size());
+ Assertions.assertEquals(3, feed.getShapes().size());
+ Assertions.assertEquals(6, feed.getTrips().size());
+ Assertions.assertEquals(6, feed.getTransfers().size());
}
@Test
- public void stopsAndCoordsEqualAfterRetransform() {
+ void stopsAndCoordsEqualAfterRetransform() {
Map stopCoords1 = cloneFeedStopCoords(feed);
// transform
@@ -69,9 +69,9 @@ public void stopsAndCoordsEqualAfterRetransform() {
Stop testStop = feed.getStops().values().stream().findFirst().
map(s -> new StopImpl(s.getId(), s.getName(), s.getLon(), s.getLat(), s.getLocationType(), s.getParentStationId())).
orElse(null);
- Assert.assertNotNull(testStop);
- Assert.assertEquals(testStop, feed.getStops().get(testStop.getId()));
- Assert.assertEquals(TransformationFactory.CH1903_LV03_Plus, feed.getCurrentCoordSystem());
+ Assertions.assertNotNull(testStop);
+ Assertions.assertEquals(testStop, feed.getStops().get(testStop.getId()));
+ Assertions.assertEquals(TransformationFactory.CH1903_LV03_Plus, feed.getCurrentCoordSystem());
// retransform
feed.transform(TransformationFactory.WGS84);
@@ -95,40 +95,40 @@ private void testCoordsEqual(Map stopCoordsExpected, Map covered = new TreeSet<>();
covered.add(LocalDate.of(2018, 10, 2));
covered.add(LocalDate.of(2018, 10, 3));
covered.add(LocalDate.of(2018, 10, 4));
covered.add(LocalDate.of(2018, 10, 6));
- Assert.assertEquals(covered, emptService.getCoveredDays());
+ Assertions.assertEquals(covered, emptService.getCoveredDays());
}
@Test
- public void gtfsShapesGeojson() {
+ void gtfsShapesGeojson() {
GtfsTools.writeShapesToGeojson(feed, "test/shapes.geojson");
new File("test/shapes.geojson").delete();
}
@Test
- public void missingCalendar() {
+ void missingCalendar() {
new GtfsFeedImpl("test/gtfs-feed-cal/");
}
diff --git a/src/test/java/org/matsim/pt2matsim/gtfs/GtfsRealFeedTest.java b/src/test/java/org/matsim/pt2matsim/gtfs/GtfsRealFeedTest.java
index 1920e4db..d7d7c5fb 100644
--- a/src/test/java/org/matsim/pt2matsim/gtfs/GtfsRealFeedTest.java
+++ b/src/test/java/org/matsim/pt2matsim/gtfs/GtfsRealFeedTest.java
@@ -1,33 +1,33 @@
package org.matsim.pt2matsim.gtfs;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
/**
* @author polettif
*/
-public class GtfsRealFeedTest {
+class GtfsRealFeedTest {
private GtfsFeed feed;
private GtfsConverter converter;
- @Before
+ @BeforeEach
public void loadAndConvert() {
feed = new GtfsFeedImpl("test/stib-mivb-gtfs.zip");
converter = new GtfsConverter(feed);
}
@Test
- public void statistics() {
- Assert.assertEquals(2514, feed.getStops().size());
- Assert.assertEquals(92, feed.getRoutes().size());
- Assert.assertEquals(139, feed.getServices().size());
- Assert.assertEquals(806, feed.getShapes().size());
+ void statistics() {
+ Assertions.assertEquals(2514, feed.getStops().size());
+ Assertions.assertEquals(92, feed.getRoutes().size());
+ Assertions.assertEquals(139, feed.getServices().size());
+ Assertions.assertEquals(806, feed.getShapes().size());
}
@Test
- public void convert() {
+ void convert() {
converter.convert(GtfsConverter.DAY_WITH_MOST_TRIPS, "EPSG:32631");
}
diff --git a/src/test/java/org/matsim/pt2matsim/gtfs/SpaceIdTest.java b/src/test/java/org/matsim/pt2matsim/gtfs/SpaceIdTest.java
index 9422ff4a..099ac24a 100644
--- a/src/test/java/org/matsim/pt2matsim/gtfs/SpaceIdTest.java
+++ b/src/test/java/org/matsim/pt2matsim/gtfs/SpaceIdTest.java
@@ -4,8 +4,8 @@
import java.util.Arrays;
import java.util.HashSet;
-import org.junit.After;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.Scenario;
@@ -25,8 +25,8 @@
import org.matsim.vehicles.VehicleUtils;
import org.matsim.vehicles.Vehicles;
-public class SpaceIdTest {
- @After
+class SpaceIdTest {
+ @AfterEach
public void cleanup() {
new File("test_output_schedule.xml").delete();
}
@@ -41,7 +41,7 @@ public void cleanup() {
* link IDs that do not have spaces included.
*/
@Test
- public void testFeedWithSpacesInId() {
+ void testFeedWithSpacesInId() {
GtfsFeed feed = new GtfsFeedImpl("test/space-feed/");
GtfsConverter covnerter = new GtfsConverter(feed);
diff --git a/src/test/java/org/matsim/pt2matsim/hafas/HafasConverterTest.java b/src/test/java/org/matsim/pt2matsim/hafas/HafasConverterTest.java
index 5573bea9..f9a63c22 100644
--- a/src/test/java/org/matsim/pt2matsim/hafas/HafasConverterTest.java
+++ b/src/test/java/org/matsim/pt2matsim/hafas/HafasConverterTest.java
@@ -1,8 +1,8 @@
package org.matsim.pt2matsim.hafas;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Id;
import org.matsim.core.utils.geometry.CoordinateTransformation;
import org.matsim.core.utils.geometry.transformations.TransformationFactory;
@@ -16,12 +16,12 @@
/**
* @author polettif
*/
-public class HafasConverterTest {
+class HafasConverterTest {
private TransitSchedule schedule;
private Vehicles vehicles;
- @Before
+ @BeforeEach
public void convert() throws IOException {
this.schedule = ScheduleTools.createSchedule();
this.vehicles = VehicleUtils.createVehiclesContainer();
@@ -33,21 +33,21 @@ public void convert() throws IOException {
}
@Test
- public void transitRoutes() {
- Assert.assertEquals(1, schedule.getTransitLines().size());
+ void transitRoutes() {
+ Assertions.assertEquals(1, schedule.getTransitLines().size());
int nRoutes = 0;
for(TransitLine tl : schedule.getTransitLines().values()) {
- Assert.assertEquals("BRB", tl.getId().toString());
+ Assertions.assertEquals("BRB", tl.getId().toString());
for(TransitRoute tr : tl.getRoutes().values()) {
nRoutes++;
}
}
- Assert.assertEquals(2, nRoutes);
+ Assertions.assertEquals(2, nRoutes);
}
@Test
- public void minimalTransferTimes() {
+ void minimalTransferTimes() {
int nbMinimalTransferTimes = 0;
MinimalTransferTimes transferTimes = schedule.getMinimalTransferTimes();
MinimalTransferTimes.MinimalTransferTimesIterator iterator = transferTimes.iterator();
@@ -55,24 +55,24 @@ public void minimalTransferTimes() {
iterator.next();
nbMinimalTransferTimes += 1;
}
- Assert.assertEquals(3, nbMinimalTransferTimes);
+ Assertions.assertEquals(3, nbMinimalTransferTimes);
- Assert.assertEquals(5*60.0, transferTimes.get(
+ Assertions.assertEquals(5 * 60.0, transferTimes.get(
Id.create("8508350", TransitStopFacility.class),
Id.create("8508350", TransitStopFacility.class)), 0.00001);
- Assert.assertEquals(6*60.0, transferTimes.get(
+ Assertions.assertEquals(6 * 60.0, transferTimes.get(
Id.create("8508351", TransitStopFacility.class),
Id.create("8508351", TransitStopFacility.class)), 0.00001);
- Assert.assertEquals(60*60.0, transferTimes.get(
+ Assertions.assertEquals(60 * 60.0, transferTimes.get(
Id.create("8508350", TransitStopFacility.class),
Id.create("8508351", TransitStopFacility.class)), 0.00001);
}
@Test
- public void nStops() {
- Assert.assertEquals(3, schedule.getFacilities().size());
+ void nStops() {
+ Assertions.assertEquals(3, schedule.getFacilities().size());
}
}
\ No newline at end of file
diff --git a/src/test/java/org/matsim/pt2matsim/hafas/HafasFplanTest.java b/src/test/java/org/matsim/pt2matsim/hafas/HafasFplanTest.java
index 4b96cfa7..999022b4 100644
--- a/src/test/java/org/matsim/pt2matsim/hafas/HafasFplanTest.java
+++ b/src/test/java/org/matsim/pt2matsim/hafas/HafasFplanTest.java
@@ -1,7 +1,7 @@
package org.matsim.pt2matsim.hafas;
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
import org.matsim.pt.transitSchedule.api.TransitSchedule;
import org.matsim.pt2matsim.tools.ScheduleTools;
import org.matsim.vehicles.VehicleUtils;
@@ -10,9 +10,9 @@
import java.io.IOException;
import java.util.stream.Collectors;
-public class HafasFplanTest {
+class HafasFplanTest {
@Test
- public void simpleFplanTest() throws IOException {
+ void simpleFplanTest() throws IOException {
String hafasFolder = "test/FPLAN_HAFAS/";
@@ -21,10 +21,10 @@ public void simpleFplanTest() throws IOException {
HafasConverter.run(hafasFolder, schedule, null, vehicles);
int nbRoutes = schedule.getTransitLines().values().stream().flatMap(l -> l.getRoutes().values().stream()).collect(Collectors.toList()).size();
- Assert.assertEquals(2, nbRoutes);
+ Assertions.assertEquals(2, nbRoutes);
int nbDeps = schedule.getTransitLines().values().stream().
flatMap(l -> l.getRoutes().values().stream().flatMap(r -> r.getDepartures().values().stream())).collect(Collectors.toList()).size();
- Assert.assertEquals(3, nbDeps);
+ Assertions.assertEquals(3, nbDeps);
}
}
diff --git a/src/test/java/org/matsim/pt2matsim/mapping/PTMapperShapesTest.java b/src/test/java/org/matsim/pt2matsim/mapping/PTMapperShapesTest.java
index 3a2d080a..cc4b41ee 100644
--- a/src/test/java/org/matsim/pt2matsim/mapping/PTMapperShapesTest.java
+++ b/src/test/java/org/matsim/pt2matsim/mapping/PTMapperShapesTest.java
@@ -1,8 +1,8 @@
package org.matsim.pt2matsim.mapping;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
@@ -29,13 +29,13 @@
/**
* @author polettif
*/
-public class PTMapperShapesTest {
+class PTMapperShapesTest {
public Network network;
public TransitSchedule schedule;
public PublicTransitMappingConfigGroup ptmConfig;
- @Before
+ @BeforeEach
public void prepare() {
ptmConfig = initPTMConfig();
network = NetworkToolsTest.initNetwork();
@@ -50,20 +50,20 @@ public void prepare() {
}
@Test
- public void validateMappedSchedule() {
- Assert.assertTrue(TransitScheduleValidator.validateAll(schedule, network).isValid());
+ void validateMappedSchedule() {
+ Assertions.assertTrue(TransitScheduleValidator.validateAll(schedule, network).isValid());
}
@Test
- public void allowedModes() {
+ void allowedModes() {
for(Link l : network.getLinks().values()) {
- Assert.assertFalse(l.getAllowedModes().contains(PublicTransitMappingStrings.ARTIFICIAL_LINK_MODE));
+ Assertions.assertFalse(l.getAllowedModes().contains(PublicTransitMappingStrings.ARTIFICIAL_LINK_MODE));
}
}
@Test
- public void linkSequences() {
+ void linkSequences() {
TransitSchedule initSchedule = ScheduleToolsTest.initSchedule();
for(TransitLine l : schedule.getTransitLines().values()) {
@@ -71,7 +71,7 @@ public void linkSequences() {
TransitRoute initRoute = initSchedule.getTransitLines().get(l.getId()).getRoutes().get(r.getId());
List> initLinkIds = ScheduleTools.getTransitRouteLinkIds(initRoute);
List> linkIds = ScheduleTools.getTransitRouteLinkIds(r);
- Assert.assertEquals(initLinkIds, linkIds);
+ Assertions.assertEquals(initLinkIds, linkIds);
}
}
}
diff --git a/src/test/java/org/matsim/pt2matsim/mapping/PTMapperTest.java b/src/test/java/org/matsim/pt2matsim/mapping/PTMapperTest.java
index 78004810..2b07cc09 100644
--- a/src/test/java/org/matsim/pt2matsim/mapping/PTMapperTest.java
+++ b/src/test/java/org/matsim/pt2matsim/mapping/PTMapperTest.java
@@ -1,8 +1,8 @@
package org.matsim.pt2matsim.mapping;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
@@ -11,7 +11,6 @@
import org.matsim.pt.transitSchedule.api.TransitSchedule;
import org.matsim.pt.transitSchedule.api.TransitStopFacility;
import org.matsim.pt.utils.TransitScheduleValidator;
-import org.matsim.pt2matsim.config.OsmConverterConfigGroup;
import org.matsim.pt2matsim.config.PublicTransitMappingConfigGroup;
import org.matsim.pt2matsim.config.PublicTransitMappingStrings;
import org.matsim.pt2matsim.run.CreateDefaultPTMapperConfig;
@@ -47,7 +46,7 @@ public static PublicTransitMappingConfigGroup initPTMConfig() {
return config;
}
- @Before
+ @BeforeEach
public void prepare() {
ptmConfig = initPTMConfig();
network = NetworkToolsTest.initNetwork();
@@ -57,25 +56,25 @@ public void prepare() {
}
@Test
- public void validateMappedSchedule() {
- Assert.assertTrue(TransitScheduleValidator.validateAll(schedule, network).isValid());
+ void validateMappedSchedule() {
+ Assertions.assertTrue(TransitScheduleValidator.validateAll(schedule, network).isValid());
}
@Test
- public void allowedModes() {
+ void allowedModes() {
for(Link l : network.getLinks().values()) {
- Assert.assertFalse(l.getAllowedModes().contains(PublicTransitMappingStrings.ARTIFICIAL_LINK_MODE));
+ Assertions.assertFalse(l.getAllowedModes().contains(PublicTransitMappingStrings.ARTIFICIAL_LINK_MODE));
}
}
@Test
- public void numberOfStopFacilities() {
- Assert.assertEquals(10, schedule.getFacilities().size());
+ void numberOfStopFacilities() {
+ Assertions.assertEquals(10, schedule.getFacilities().size());
}
@Test
- public void linkSequences() {
+ void linkSequences() {
TransitSchedule initSchedule = ScheduleToolsTest.initSchedule();
for(TransitLine l : schedule.getTransitLines().values()) {
@@ -84,14 +83,14 @@ public void linkSequences() {
List> initLinkIds = ScheduleTools.getTransitRouteLinkIds(initRoute);
List> linkIds = ScheduleTools.getTransitRouteLinkIds(r);
if(!r.getId().equals(ROUTE_B)) { // route B cantt be guessed by the mapper because there's not enough information
- Assert.assertEquals(initLinkIds, linkIds);
+ Assertions.assertEquals(initLinkIds, linkIds);
}
}
}
}
@Test
- public void artificialLinks() {
+ void artificialLinks() {
PublicTransitMappingConfigGroup ptmConfig2 = initPTMConfig();
ptmConfig2.setMaxLinkCandidateDistance(3);
@@ -100,11 +99,11 @@ public void artificialLinks() {
new PTMapper(schedule2, network2).run(ptmConfig2);
// 1 loop link, 3 artificial links
- Assert.assertEquals(NetworkToolsTest.initNetwork().getLinks().size()+4, network2.getLinks().size());
- Assert.assertEquals(9, schedule2.getFacilities().size());
+ Assertions.assertEquals(NetworkToolsTest.initNetwork().getLinks().size() + 4, network2.getLinks().size());
+ Assertions.assertEquals(9, schedule2.getFacilities().size());
}
@Test
- public void noTransportModeAssignment() {
+ void noTransportModeAssignment() {
PublicTransitMappingConfigGroup noTMAConfig = new PublicTransitMappingConfigGroup();
noTMAConfig.getModesToKeepOnCleanUp().add("car");
noTMAConfig.setNumOfThreads(2);
@@ -122,18 +121,18 @@ public void noTransportModeAssignment() {
for(TransitRoute transitRoute : transitLine.getRoutes().values()) {
List> linkIds = ScheduleTools.getTransitRouteLinkIds(transitRoute);
for(Id linkId : linkIds) {
- Assert.assertTrue(linkId.toString().contains("pt_"));
+ Assertions.assertTrue(linkId.toString().contains("pt_"));
}
}
}
// only artificial stop links
for(TransitStopFacility transitStopFacility : schedule2.getFacilities().values()) {
- Assert.assertTrue(transitStopFacility.getLinkId().toString().contains("pt_"));
+ Assertions.assertTrue(transitStopFacility.getLinkId().toString().contains("pt_"));
}
}
@Test
- public void defaultConfig() {
+ void defaultConfig() {
CreateDefaultPTMapperConfig.main(new String[]{"doc/defaultPTMapperConfig.xml"});
}
diff --git a/src/test/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverterTest.java b/src/test/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverterTest.java
index d26bb33b..bc05adc6 100644
--- a/src/test/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverterTest.java
+++ b/src/test/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverterTest.java
@@ -1,16 +1,14 @@
package org.matsim.pt2matsim.osm;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
-import org.junit.BeforeClass;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.pt2matsim.config.OsmConverterConfigGroup;
@@ -18,7 +16,6 @@
import org.matsim.pt2matsim.osm.lib.OsmDataImpl;
import org.matsim.pt2matsim.osm.lib.OsmFileReader;
import org.matsim.pt2matsim.run.CreateDefaultOsmConfig;
-import org.matsim.pt2matsim.run.CreateDefaultPTMapperConfig;
/**
* @author polettif
@@ -29,7 +26,7 @@ public class OsmMultimodalNetworkConverterTest {
private static Map> osmid2link;
private static final double DELTA = 0.001;
- @BeforeClass
+ @BeforeAll
public static void convertGerasdorfArtificialLanesAndMaxspeed() {
// setup config
OsmConverterConfigGroup osmConfig = OsmConverterConfigGroup.createDefaultConfig();
@@ -37,6 +34,7 @@ public static void convertGerasdorfArtificialLanesAndMaxspeed() {
osmConfig.setOsmFile("test/osm/GerasdorfArtificialLanesAndMaxspeed.osm");
osmConfig.setOutputNetworkFile("test/osm/GerasdorfArtificialLanesAndMaxspeed.xml.gz");
osmConfig.setMaxLinkLength(1000);
+ osmConfig.parseTurnRestrictions = true; // turn restrictions not explicitly tested in this class
// read OSM file
OsmData osm = new OsmDataImpl();
@@ -72,41 +70,41 @@ private static Set getLinksTowardsNode(Set links, long osmNodeId) {
}
@Test
- public void testDefaultResidential() {
+ void testDefaultResidential() {
Set links = osmid2link.get(7994891L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
assertLanes(links, 1);
assertMaxspeed("taken from OsmConverterConfigGroup.createDefaultConfig", links, 15);
}
@Test
- public void testDefaultPrimary() {
+ void testDefaultPrimary() {
Set links = osmid2link.get(7994890L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
assertLanes("taken from OsmConverterConfigGroup.createDefaultConfig", links, 1);
assertMaxspeed("taken from OsmConverterConfigGroup.createDefaultConfig", links, 80);
}
@Test
- public void testPrimaryWithLanesAndMaxspeed() {
+ void testPrimaryWithLanesAndMaxspeed() {
Set links = osmid2link.get(7994889L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
assertLanes(links, 3);
assertMaxspeed(links, 70);
}
@Test
- public void testPrimaryWithOddLanesAndMaxspeed() {
+ void testPrimaryWithOddLanesAndMaxspeed() {
Set links = osmid2link.get(7994888L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
assertLanes(links, 3.5);
assertMaxspeed(links, 70);
}
@Test
- public void testPrimaryWithForwardAndBackwardLanesAndMaxspeed() {
+ void testPrimaryWithForwardAndBackwardLanesAndMaxspeed() {
Set links = osmid2link.get(7994887L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
Set linksToNorth = getLinksTowardsNode(links, 59836731L);
assertLanes(linksToNorth, 3);
@@ -118,9 +116,9 @@ public void testPrimaryWithForwardAndBackwardLanesAndMaxspeed() {
}
@Test
- public void testPrimaryWithForwardAndBackwardSpecialLanesAndMaxspeed() {
+ void testPrimaryWithForwardAndBackwardSpecialLanesAndMaxspeed() {
Set links = osmid2link.get(7994886L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
Set linksToNorth = getLinksTowardsNode(links, 57443579L);
assertLanes("4 minus one bus lane", linksToNorth, 3);
@@ -132,128 +130,129 @@ public void testPrimaryWithForwardAndBackwardSpecialLanesAndMaxspeed() {
}
@Test
- public void testPrimaryWithSpecialLanes() {
+ void testPrimaryWithSpecialLanes() {
Set links = osmid2link.get(7994912L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
assertLanes("4 per direction minus one taxi lane", links, 3);
assertMaxspeed(links, 70);
}
@Test
- public void testDefaultResidentialOneway() {
+ void testDefaultResidentialOneway() {
Set links = osmid2link.get(7994914L);
- assertEquals("oneway", 1, links.size());
- assertEquals("oneway up north", 1, getLinksTowardsNode(links, 59836794L).size());
+ Assertions.assertEquals(1, links.size(), "oneway");
+ Assertions.assertEquals(1, getLinksTowardsNode(links, 59836794L).size(), "oneway up north");
assertLanes(links, 1);
assertMaxspeed("taken from OsmConverterConfigGroup.createDefaultConfig", links, 15);
}
@Test
- public void testResidentialInvalidLanesAndMaxspeed() {
+ void testResidentialInvalidLanesAndMaxspeed() {
Set links = osmid2link.get(7994891L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
assertLanes("taken from OsmConverterConfigGroup.createDefaultConfig", links, 1);
assertMaxspeed("taken from OsmConverterConfigGroup.createDefaultConfig", links, 15);
}
@Test
- public void testDefaultPrimaryOneway() {
+ void testDefaultPrimaryOneway() {
Set links = osmid2link.get(7994919L);
- assertEquals("oneway", 1, links.size());
- assertEquals("oneway up north", 1, getLinksTowardsNode(links, 59836804L).size());
+ Assertions.assertEquals(1, links.size(), "oneway");
+ Assertions.assertEquals(1, getLinksTowardsNode(links, 59836804L).size(), "oneway up north");
assertLanes("taken from OsmConverterConfigGroup.createDefaultConfig", links, 1);
assertMaxspeed("taken from OsmConverterConfigGroup.createDefaultConfig", links, 80);
}
@Test
- public void testPrimaryOnewayWithLanesAndMaxspeed() {
+ void testPrimaryOnewayWithLanesAndMaxspeed() {
Set links = osmid2link.get(240536138L);
- assertEquals("oneway", 1, links.size());
- assertEquals("oneway up north", 1, getLinksTowardsNode(links, 2482638327L).size());
+ Assertions.assertEquals(1, links.size(), "oneway");
+ Assertions.assertEquals(1, getLinksTowardsNode(links, 2482638327L).size(), "oneway up north");
assertLanes(links, 3);
assertMaxspeed(links, 70);
}
@Test
- public void testPrimaryOnewayWithForwardLanesAndMaxspeed() {
+ void testPrimaryOnewayWithForwardLanesAndMaxspeed() {
Set links = osmid2link.get(7994920L);
- assertEquals("oneway", 1, links.size());
- assertEquals("oneway up north", 1, getLinksTowardsNode(links, 59836807L).size());
+ Assertions.assertEquals(1, links.size(), "oneway");
+ Assertions.assertEquals(1, getLinksTowardsNode(links, 59836807L).size(), "oneway up north");
assertLanes(links, 3);
assertMaxspeed(links, 70);
}
@Test
- public void testPrimaryOnewayWithForwardSpecialLanesAndMaxspeed() {
+ void testPrimaryOnewayWithForwardSpecialLanesAndMaxspeed() {
Set links = osmid2link.get(7994925L);
- assertEquals("oneway", 1, links.size());
- assertEquals("oneway up north", 1, getLinksTowardsNode(links, 59836816L).size());
+ Assertions.assertEquals(1, links.size(), "oneway");
+ Assertions.assertEquals(1, getLinksTowardsNode(links, 59836816L).size(), "oneway up north");
assertLanes("4 minus one bus lane", links, 3);
assertMaxspeed(links, 70);
}
@Test
- public void testPrimaryOnewayWithSpecialLane() {
+ void testPrimaryOnewayWithSpecialLane() {
Set links = osmid2link.get(7994927L);
- assertEquals("oneway", 1, links.size());
- assertEquals("oneway up north", 1, getLinksTowardsNode(links, 59836820L).size());
+ Assertions.assertEquals(1, links.size(), "oneway");
+ Assertions.assertEquals(1, getLinksTowardsNode(links, 59836820L).size(), "oneway up north");
assertLanes("4 minus one bus lane", links, 3);
assertMaxspeed(links, 70);
}
@Test
- public void testPrimaryDefaultReversedOneway() {
+ void testPrimaryDefaultReversedOneway() {
Set links = osmid2link.get(7994930L);
- assertEquals("oneway", 1, links.size());
- assertEquals("oneway down south", 1, getLinksTowardsNode(links, 59836834L).size());
+ Assertions.assertEquals(1, links.size(), "oneway");
+ Assertions.assertEquals(1, getLinksTowardsNode(links, 59836834L).size(), "oneway down south");
assertLanes(links, 3);
assertMaxspeed(links, 70);
}
@Test
- public void testMotorwayWithoutMaxspeedAndOneway() {
+ void testMotorwayWithoutMaxspeedAndOneway() {
Set links = osmid2link.get(7994932L);
- assertEquals("oneway by default - taken from OsmConverterConfigGroup.createDefaultConfig", 1, links.size());
- assertEquals("oneway up north", 1, getLinksTowardsNode(links, 59836844L).size());
+ Assertions.assertEquals(1, links.size(),
+ "oneway by default - taken from OsmConverterConfigGroup.createDefaultConfig");
+ Assertions.assertEquals(1, getLinksTowardsNode(links, 59836844L).size(), "oneway up north");
assertLanes("taken from OsmConverterConfigGroup.createDefaultConfig", links, 2);
assertMaxspeed(links, OsmMultimodalNetworkConverter.SPEED_LIMIT_NONE_KPH);
}
@Test
- public void testResidentialWithMaxspeedWalk() {
+ void testResidentialWithMaxspeedWalk() {
Set links = osmid2link.get(7994934L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
assertMaxspeed(links, OsmMultimodalNetworkConverter.SPEED_LIMIT_WALK_KPH);
}
@Test
- public void testResidentialWithMaxspeedMiles() {
+ void testResidentialWithMaxspeedMiles() {
Set links = osmid2link.get(7994935L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
assertMaxspeed(links, 20 * 1.609344);
}
@Test
- public void testResidentialWithMaxspeedKnots() {
+ void testResidentialWithMaxspeedKnots() {
Set links = osmid2link.get(7994935L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
assertMaxspeed(links, 20 * 1.609344);
}
@Test
- public void testResidentialMultipleSpeedLimits() {
+ void testResidentialMultipleSpeedLimits() {
Set links = osmid2link.get(7999581L);
- assertEquals("bidirectional", 2, links.size());
+ Assertions.assertEquals(2, links.size(), "bidirectional");
assertMaxspeed("second speed limit is ignored", links, 40);
}
@Test
- public void testDeadEndStreetsAreContainedInNetwork() {
- assertEquals(2, osmid2link.get(22971704L).size());
- assertEquals(2, osmid2link.get(153227314L).size());
- assertEquals(2, osmid2link.get(95142433L).size());
- assertEquals(2, osmid2link.get(95142441L).size());
+ void testDeadEndStreetsAreContainedInNetwork() {
+ Assertions.assertEquals(2, osmid2link.get(22971704L).size());
+ Assertions.assertEquals(2, osmid2link.get(153227314L).size());
+ Assertions.assertEquals(2, osmid2link.get(95142433L).size());
+ Assertions.assertEquals(2, osmid2link.get(95142441L).size());
}
private static void assertLanes(Set links, double expectedLanes) {
@@ -261,9 +260,10 @@ private static void assertLanes(Set links, double expectedLanes) {
}
private static void assertLanes(String message, Set links, double expectedLanes) {
- assertFalse("at least one link expected", links.isEmpty());
+ Assertions.assertFalse(links.isEmpty(), "at least one link expected");
for (Link link : links) {
- assertEquals("lanes (in one direction): " + message, expectedLanes, link.getNumberOfLanes(), DELTA);
+ Assertions.assertEquals(expectedLanes, link.getNumberOfLanes(), DELTA,
+ "lanes (in one direction): " + message);
}
}
@@ -272,14 +272,15 @@ private static void assertMaxspeed(Set links, double expectedFreespeedKph)
}
private static void assertMaxspeed(String message, Set links, double expectedFreespeedKph) {
- assertFalse("at least one link expected", links.isEmpty());
+ Assertions.assertFalse(links.isEmpty(), "at least one link expected");
for (Link link : links) {
- assertEquals("freespeed m/s: message", expectedFreespeedKph / 3.6, link.getFreespeed(), DELTA);
+ Assertions.assertEquals(expectedFreespeedKph / 3.6, link.getFreespeed(), DELTA,
+ "freespeed m/s: " + message);
}
}
@Test
- public void convertWaterlooCityCentre() {
+ void convertWaterlooCityCentre() {
// setup config
OsmConverterConfigGroup osmConfig = OsmConverterConfigGroup.createDefaultConfig();
osmConfig.setOutputCoordinateSystem("WGS84");
@@ -300,7 +301,7 @@ public void convertWaterlooCityCentre() {
}
@Test
- public void convertEPSG() {
+ void convertEPSG() {
OsmConverterConfigGroup osmConfig = OsmConverterConfigGroup.createDefaultConfig();
osmConfig.setOutputCoordinateSystem("EPSG:8682");
osmConfig.setOsmFile("test/osm/Belgrade.osm");
@@ -313,7 +314,7 @@ public void convertEPSG() {
}
@Test
- public void defaultConfig() {
+ void defaultConfig() {
CreateDefaultOsmConfig.main(new String[]{"doc/defaultOsmConfig.xml"});
}
diff --git a/src/test/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverterTurnRestrictionsTest.java b/src/test/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverterTurnRestrictionsTest.java
new file mode 100644
index 00000000..0575db68
--- /dev/null
+++ b/src/test/java/org/matsim/pt2matsim/osm/OsmMultimodalNetworkConverterTurnRestrictionsTest.java
@@ -0,0 +1,70 @@
+package org.matsim.pt2matsim.osm;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.TransportMode;
+import org.matsim.api.core.v01.network.Link;
+import org.matsim.api.core.v01.network.Network;
+import org.matsim.core.network.DisallowedNextLinks;
+import org.matsim.core.network.DisallowedNextLinksUtils;
+import org.matsim.core.network.NetworkUtils;
+import org.matsim.pt2matsim.config.OsmConverterConfigGroup;
+import org.matsim.pt2matsim.osm.lib.OsmData;
+import org.matsim.pt2matsim.osm.lib.OsmDataImpl;
+import org.matsim.pt2matsim.osm.lib.OsmFileReader;
+
+class OsmMultimodalNetworkConverterTurnRestrictionsTest {
+
+ private static Network network;
+
+ @BeforeAll
+ static void convertRudolfplatz() {
+ // setup config
+ OsmConverterConfigGroup osmConfig = OsmConverterConfigGroup.createDefaultConfig();
+ osmConfig.setOutputCoordinateSystem("EPSG:25832");
+ osmConfig.setOsmFile("test/osm/Rudolfplatz.osm");
+ osmConfig.setOutputNetworkFile("test/osm/Rudolfplatz.xml.gz");
+ osmConfig.setMaxLinkLength(1000);
+ osmConfig.parseTurnRestrictions = true;
+
+ // read OSM file
+ OsmData osm = new OsmDataImpl();
+ new OsmFileReader(osm).readFile(osmConfig.getOsmFile());
+
+ // convert
+ OsmMultimodalNetworkConverter converter = new OsmMultimodalNetworkConverter(osm);
+ converter.convert(osmConfig);
+
+ network = converter.getNetwork();
+
+ // write file
+ // NetworkTools.writeNetwork(network, osmConfig.getOutputNetworkFile());
+ }
+
+ @Test
+ void testisValid() {
+
+ Assertions.assertTrue(DisallowedNextLinksUtils.isValid(network));
+
+ }
+
+ @Test
+ void testDisallowedNextLinks() {
+
+ Id lId124 = Id.createLinkId("124");
+ Link l124 = network.getLinks().get(lId124);
+ DisallowedNextLinks dnl = NetworkUtils.getDisallowedNextLinks(l124);
+
+ Assertions.assertEquals(Map.of(
+ "bus", List.of(List.of(Id.createLinkId("68"), Id.createLinkId("414"))),
+ TransportMode.car, List.of(List.of(Id.createLinkId("68"), Id.createLinkId("414"))),
+ TransportMode.pt, List.of(List.of(Id.createLinkId("68"), Id.createLinkId("414")))), dnl.getAsMap());
+
+ }
+
+}
diff --git a/src/test/java/org/matsim/pt2matsim/osm/OsmTurnRestrictionTest.java b/src/test/java/org/matsim/pt2matsim/osm/OsmTurnRestrictionTest.java
new file mode 100644
index 00000000..efab6660
--- /dev/null
+++ b/src/test/java/org/matsim/pt2matsim/osm/OsmTurnRestrictionTest.java
@@ -0,0 +1,227 @@
+package org.matsim.pt2matsim.osm;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.network.Link;
+import org.matsim.api.core.v01.network.Network;
+import org.matsim.api.core.v01.network.Node;
+import org.matsim.core.network.NetworkUtils;
+import org.matsim.pt2matsim.osm.lib.Osm;
+
+/**
+ * Test finding a sequence of link ids from OSM way ids.
+ */
+class OsmTurnRestrictionTest {
+
+ private static final Logger LOG = LogManager.getLogger(OsmTurnRestrictionTest.class);
+
+ protected static final Map, Id> osmIds = new HashMap<>();
+ protected static final Map, List>> wayLinkMap = new HashMap<>(); // reverse of osmIds
+ protected static final Network network = NetworkUtils.createNetwork();
+
+ static {
+
+ Node n0 = NetworkUtils.createNode(Id.createNodeId("0"));
+ Node n1 = NetworkUtils.createNode(Id.createNodeId("1"));
+ Node n2 = NetworkUtils.createNode(Id.createNodeId("2"));
+ Node n3 = NetworkUtils.createNode(Id.createNodeId("3"));
+ Node n4 = NetworkUtils.createNode(Id.createNodeId("4"));
+ Node n5 = NetworkUtils.createNode(Id.createNodeId("5"));
+
+ network.addNode(n0);
+ network.addNode(n1);
+ network.addNode(n2);
+ network.addNode(n3);
+ network.addNode(n4);
+ network.addNode(n5);
+
+ // * n0
+ // l01 l10 (0110)
+ // * n1 - l15 l51 (1551) - n5
+ // l12 l21 (1221) \
+ // * n2
+ // l23 l32 (2332) | l14 l41 (1441)
+ // * n3
+ // l34 l43 (3443) /
+ // * n4
+
+ Id w0110 = Id.create("0110", Osm.Way.class);
+ Link l01 = NetworkUtils.createLink(Id.createLinkId("01"), n0, n1, network, 1, 1, 300, 1);
+ Link l10 = NetworkUtils.createLink(Id.createLinkId("10"), n1, n0, network, 1, 1, 300, 1);
+ osmIds.put(l01.getId(), w0110);
+ osmIds.put(l10.getId(), w0110);
+
+ Id w1221 = Id.create("1221", Osm.Way.class);
+ Link l12 = NetworkUtils.createLink(Id.createLinkId("12"), n1, n2, network, 1, 1, 300, 1);
+ Link l21 = NetworkUtils.createLink(Id.createLinkId("21"), n2, n1, network, 1, 1, 300, 1);
+ osmIds.put(l12.getId(), w1221);
+ osmIds.put(l21.getId(), w1221);
+
+ Id w1441 = Id.create("1441", Osm.Way.class);
+ Link l14 = NetworkUtils.createLink(Id.createLinkId("14"), n1, n4, network, 1, 1, 300, 1);
+ Link l41 = NetworkUtils.createLink(Id.createLinkId("41"), n4, n1, network, 1, 1, 300, 1);
+ osmIds.put(l14.getId(), w1441);
+ osmIds.put(l41.getId(), w1441);
+
+ Id w2332 = Id.create("2332", Osm.Way.class);
+ Link l23 = NetworkUtils.createLink(Id.createLinkId("23"), n2, n3, network, 1, 1, 300, 1);
+ Link l32 = NetworkUtils.createLink(Id.createLinkId("32"), n3, n2, network, 1, 1, 300, 1);
+ osmIds.put(l23.getId(), w2332);
+ osmIds.put(l32.getId(), w2332);
+
+ Id w3443 = Id.create("3443", Osm.Way.class);
+ Link l34 = NetworkUtils.createLink(Id.createLinkId("34"), n3, n4, network, 1, 1, 300, 1);
+ Link l43 = NetworkUtils.createLink(Id.createLinkId("43"), n4, n3, network, 1, 1, 300, 1);
+ osmIds.put(l34.getId(), w3443);
+ osmIds.put(l43.getId(), w3443);
+
+ Id w1551 = Id.create("1551", Osm.Way.class);
+ Link l15 = NetworkUtils.createLink(Id.createLinkId("15"), n1, n5, network, 1, 1, 300, 1);
+ Link l51 = NetworkUtils.createLink(Id.createLinkId("51"), n5, n1, network, 1, 1, 300, 1);
+ osmIds.put(l15.getId(), w1551);
+ osmIds.put(l51.getId(), w1551);
+
+ network.addLink(l01);
+ network.addLink(l10);
+ network.addLink(l12);
+ network.addLink(l21);
+ network.addLink(l14);
+ network.addLink(l41);
+ network.addLink(l23);
+ network.addLink(l32);
+ network.addLink(l34);
+ network.addLink(l43);
+ network.addLink(l15);
+ network.addLink(l51);
+
+ wayLinkMap.putAll(osmIds.entrySet().stream().collect(
+ Collectors.groupingBy(Entry::getValue, Collectors.mapping(Entry::getKey, Collectors.toList()))));
+ }
+
+ @Test
+ void testFindLinks0() {
+
+ OsmMultimodalNetworkConverter.OsmTurnRestriction tr = new OsmMultimodalNetworkConverter.OsmTurnRestriction(null,
+ List.of(Id.create("1221", Osm.Way.class),
+ Id.create("2332", Osm.Way.class)),
+ OsmMultimodalNetworkConverter.OsmTurnRestriction.RestrictionType.PROHIBITIVE);
+ LOG.info(tr.nextWayIds());
+
+ Id nodeId = Id.createNodeId("0"); // wrong!
+ LOG.info(nodeId);
+ Node node = network.getNodes().get(nodeId);
+ List> linkIds = OsmMultimodalNetworkConverter.findLinkIds(wayLinkMap, network, node,
+ tr.nextWayIds());
+ LOG.info(linkIds);
+
+ // NodeId does not fit to nextWayIds -> list of links should be empty
+ Assertions.assertEquals(Collections.emptyList(), linkIds);
+ }
+
+ @Test
+ void testFindLinks1() {
+
+ OsmMultimodalNetworkConverter.OsmTurnRestriction tr = new OsmMultimodalNetworkConverter.OsmTurnRestriction(null,
+ List.of(Id.create("1221", Osm.Way.class),
+ Id.create("2332", Osm.Way.class)),
+ OsmMultimodalNetworkConverter.OsmTurnRestriction.RestrictionType.PROHIBITIVE);
+ LOG.info(tr.nextWayIds());
+
+ Id nodeId = Id.createNodeId("1");
+ LOG.info(nodeId);
+ Node node = network.getNodes().get(nodeId);
+ List> linkIds = OsmMultimodalNetworkConverter.findLinkIds(wayLinkMap, network, node,
+ tr.nextWayIds());
+ LOG.info(linkIds);
+
+ Assertions.assertEquals(List.of(Id.createLinkId("12"), Id.createLinkId("23")), linkIds);
+ }
+
+ @Test
+ void testFindLinks2() {
+
+ OsmMultimodalNetworkConverter.OsmTurnRestriction tr = new OsmMultimodalNetworkConverter.OsmTurnRestriction(null,
+ List.of(Id.create("1221", Osm.Way.class),
+ Id.create("2332", Osm.Way.class),
+ Id.create("3443", Osm.Way.class)),
+ OsmMultimodalNetworkConverter.OsmTurnRestriction.RestrictionType.PROHIBITIVE);
+ LOG.info(tr.nextWayIds());
+
+ Id nodeId = Id.createNodeId("1");
+ LOG.info(nodeId);
+ Node node = network.getNodes().get(nodeId);
+ List> linkIds = OsmMultimodalNetworkConverter.findLinkIds(wayLinkMap, network, node,
+ tr.nextWayIds());
+ LOG.info(linkIds);
+
+ Assertions.assertEquals(List.of(Id.createLinkId("12"), Id.createLinkId("23"), Id.createLinkId("34")), linkIds);
+ }
+
+ @Test
+ void testFindLinks3() {
+
+ OsmMultimodalNetworkConverter.OsmTurnRestriction tr = new OsmMultimodalNetworkConverter.OsmTurnRestriction(null,
+ List.of(Id.create("1221", Osm.Way.class),
+ Id.create("2332", Osm.Way.class),
+ Id.create("3443", Osm.Way.class),
+ Id.create("3443", Osm.Way.class),
+ Id.create("2332", Osm.Way.class)),
+ OsmMultimodalNetworkConverter.OsmTurnRestriction.RestrictionType.PROHIBITIVE);
+ LOG.info(tr.nextWayIds());
+
+ Id nodeId = Id.createNodeId("1");
+ LOG.info(nodeId);
+ Node node = network.getNodes().get(nodeId);
+ List> linkIds = OsmMultimodalNetworkConverter.findLinkIds(wayLinkMap, network, node,
+ tr.nextWayIds());
+ LOG.info(linkIds);
+
+ Assertions.assertEquals(List.of(
+ Id.createLinkId("12"),
+ Id.createLinkId("23"),
+ Id.createLinkId("34"),
+ Id.createLinkId("43"),
+ Id.createLinkId("32")), linkIds);
+ }
+
+ @Test
+ void testFindLinks4() {
+
+ OsmMultimodalNetworkConverter.OsmTurnRestriction tr = new OsmMultimodalNetworkConverter.OsmTurnRestriction(null,
+ List.of(Id.create("1221", Osm.Way.class),
+ Id.create("2332", Osm.Way.class),
+ Id.create("3443", Osm.Way.class),
+ Id.create("3443", Osm.Way.class),
+ Id.create("2332", Osm.Way.class),
+ Id.create("1221", Osm.Way.class),
+ Id.create("1551", Osm.Way.class)),
+ OsmMultimodalNetworkConverter.OsmTurnRestriction.RestrictionType.PROHIBITIVE);
+ LOG.info(tr.nextWayIds());
+
+ Id nodeId = Id.createNodeId("1");
+ LOG.info(nodeId);
+ Node node = network.getNodes().get(nodeId);
+ List> linkIds = OsmMultimodalNetworkConverter.findLinkIds(wayLinkMap, network, node,
+ tr.nextWayIds());
+ LOG.info(linkIds);
+
+ Assertions.assertEquals(List.of(
+ Id.createLinkId("12"),
+ Id.createLinkId("23"),
+ Id.createLinkId("34"),
+ Id.createLinkId("43"),
+ Id.createLinkId("32"),
+ Id.createLinkId("21"),
+ Id.createLinkId("15")), linkIds);
+ }
+}
diff --git a/src/test/java/org/matsim/pt2matsim/osm/RoutableSubnetworkTest.java b/src/test/java/org/matsim/pt2matsim/osm/RoutableSubnetworkTest.java
index f28530b4..fd2fc4f3 100644
--- a/src/test/java/org/matsim/pt2matsim/osm/RoutableSubnetworkTest.java
+++ b/src/test/java/org/matsim/pt2matsim/osm/RoutableSubnetworkTest.java
@@ -1,6 +1,7 @@
package org.matsim.pt2matsim.osm;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.core.config.ConfigGroup;
@@ -13,15 +14,13 @@
import java.util.HashSet;
import java.util.Set;
-import static org.junit.Assert.assertEquals;
-
/**
* @author shoerl
*/
-public class RoutableSubnetworkTest {
+class RoutableSubnetworkTest {
@Test
- public void customSubnetworks() {
+ void customSubnetworks() {
// setup config
OsmConverterConfigGroup osmConfig = OsmConverterConfigGroup.createDefaultConfig();
osmConfig.setOutputCoordinateSystem("WGS84");
@@ -52,7 +51,7 @@ public void customSubnetworks() {
int carPassengerLinks = countModeLinks(converter.getNetwork(), "car_passenger");
// Since some car links are not connected, we expect that there are more (uncleaned) passenger links now
- assertEquals(carLinks + 8, carPassengerLinks);
+ Assertions.assertEquals(carLinks + 8, carPassengerLinks);
// II) Convert with a network layer for car_passenger
osmConfig.addParameterSet(new OsmConverterConfigGroup.RoutableSubnetworkParams("car_passenger", Collections.singleton("car")));
@@ -64,7 +63,7 @@ public void customSubnetworks() {
int carPassengerLinks2 = countModeLinks(converter2.getNetwork(), "car_passenger");
// Now car_passenger should be cleaned just as car... Thus it should be the same number.
- assertEquals(carLinks2, carPassengerLinks2);
+ Assertions.assertEquals(carLinks2, carPassengerLinks2);
}
private static int countModeLinks(Network network, String mode) {
diff --git a/src/test/java/org/matsim/pt2matsim/osm/lib/AllowedTagsFilterTest.java b/src/test/java/org/matsim/pt2matsim/osm/lib/AllowedTagsFilterTest.java
index 083c086d..6e054211 100644
--- a/src/test/java/org/matsim/pt2matsim/osm/lib/AllowedTagsFilterTest.java
+++ b/src/test/java/org/matsim/pt2matsim/osm/lib/AllowedTagsFilterTest.java
@@ -1,8 +1,8 @@
package org.matsim.pt2matsim.osm.lib;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
@@ -10,7 +10,7 @@
/**
* @author polettif
*/
-public class AllowedTagsFilterTest {
+class AllowedTagsFilterTest {
private AllowedTagsFilter filter1;
private AllowedTagsFilter filter2;
@@ -21,7 +21,7 @@ public class AllowedTagsFilterTest {
private Osm.Element way22;
private Osm.Element node;
- @Before
+ @BeforeEach
public void prepare() {
filter1 = new AllowedTagsFilter();
filter1.add(Osm.ElementType.WAY, "key", "value1");
@@ -63,43 +63,43 @@ public void prepare() {
}
@Test
- public void matches() {
- Assert.assertTrue(filter1.matches(way11));
- Assert.assertTrue(filter1.matches(way12));
- Assert.assertTrue(filter1.matches(way21));
- Assert.assertFalse(filter1.matches(way22));
- Assert.assertFalse(filter1.matches(node));
-
- Assert.assertFalse(filter2.matches(way11));
- Assert.assertTrue(filter2.matches(way12));
- Assert.assertTrue(filter2.matches(way21));
- Assert.assertTrue(filter2.matches(way22));
- Assert.assertTrue(filter2.matches(node));
-
- Assert.assertTrue(filter3.matches(way11));
- Assert.assertTrue(filter3.matches(way12));
- Assert.assertFalse(filter3.matches(way21));
- Assert.assertFalse(filter3.matches(way22));
+ void matches() {
+ Assertions.assertTrue(filter1.matches(way11));
+ Assertions.assertTrue(filter1.matches(way12));
+ Assertions.assertTrue(filter1.matches(way21));
+ Assertions.assertFalse(filter1.matches(way22));
+ Assertions.assertFalse(filter1.matches(node));
+
+ Assertions.assertFalse(filter2.matches(way11));
+ Assertions.assertTrue(filter2.matches(way12));
+ Assertions.assertTrue(filter2.matches(way21));
+ Assertions.assertTrue(filter2.matches(way22));
+ Assertions.assertTrue(filter2.matches(node));
+
+ Assertions.assertTrue(filter3.matches(way11));
+ Assertions.assertTrue(filter3.matches(way12));
+ Assertions.assertFalse(filter3.matches(way21));
+ Assertions.assertFalse(filter3.matches(way22));
}
@Test
- public void mergeFilter() {
+ void mergeFilter() {
AllowedTagsFilter merged12 = new AllowedTagsFilter();
merged12.mergeFilter(filter1);
merged12.mergeFilter(filter2);
- Assert.assertTrue(merged12.matches(way11));
- Assert.assertTrue(merged12.matches(way12));
- Assert.assertTrue(merged12.matches(way21));
- Assert.assertTrue(merged12.matches(way22));
+ Assertions.assertTrue(merged12.matches(way11));
+ Assertions.assertTrue(merged12.matches(way12));
+ Assertions.assertTrue(merged12.matches(way21));
+ Assertions.assertTrue(merged12.matches(way22));
AllowedTagsFilter merged123 = new AllowedTagsFilter();
merged123.mergeFilter(filter1);
merged123.mergeFilter(filter2);
merged123.mergeFilter(filter3);
- Assert.assertTrue(merged123.matches(way11));
- Assert.assertTrue(merged123.matches(way12));
- Assert.assertFalse(merged123.matches(way21));
- Assert.assertFalse(merged123.matches(way22));
+ Assertions.assertTrue(merged123.matches(way11));
+ Assertions.assertTrue(merged123.matches(way12));
+ Assertions.assertFalse(merged123.matches(way21));
+ Assertions.assertFalse(merged123.matches(way22));
}
}
\ No newline at end of file
diff --git a/src/test/java/org/matsim/pt2matsim/plausibility/MappingAnalysisTest.java b/src/test/java/org/matsim/pt2matsim/plausibility/MappingAnalysisTest.java
index 3ea022f7..3fae35fa 100644
--- a/src/test/java/org/matsim/pt2matsim/plausibility/MappingAnalysisTest.java
+++ b/src/test/java/org/matsim/pt2matsim/plausibility/MappingAnalysisTest.java
@@ -1,8 +1,8 @@
package org.matsim.pt2matsim.plausibility;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Network;
import org.matsim.pt.transitSchedule.api.TransitLine;
@@ -22,7 +22,7 @@
/**
* @author polettif
*/
-public class MappingAnalysisTest {
+class MappingAnalysisTest {
private MappingAnalysis analysis;
private Id lineA = Id.create("lineA", TransitLine.class);
@@ -31,7 +31,7 @@ public class MappingAnalysisTest {
private Id routeA2 = Id.create("routeA2", TransitRoute.class);
private Id routeB = Id.create("routeB", TransitRoute.class);
- @Before
+ @BeforeEach
public void prepare() {
PublicTransitMappingConfigGroup ptmConfig = PTMapperTest.initPTMConfig();
Network network = NetworkToolsTest.initNetwork();
@@ -45,21 +45,21 @@ public void prepare() {
}
@Test
- public void quantiles() {
- Assert.assertEquals(7, analysis.getQ8585(), 0.001);
+ void quantiles() {
+ Assertions.assertEquals(7, analysis.getQ8585(), 0.001);
TreeMap quantilesA1 = analysis.getQuantiles(lineA, routeA1);
- Assert.assertEquals(0.0, quantilesA1.get(0), 0.001);
+ Assertions.assertEquals(0.0, quantilesA1.get(0), 0.001);
TreeMap quantilesA2 = analysis.getQuantiles(lineA, routeA2);
- Assert.assertEquals(ShapeToolsTest.offset, quantilesA2.get(50), 0.001);
+ Assertions.assertEquals(ShapeToolsTest.offset, quantilesA2.get(50), 0.001);
TreeMap quantilesB = analysis.getQuantiles(lineB, routeB);
- Assert.assertEquals(0.0, quantilesB.get(0), 0.001);
+ Assertions.assertEquals(0.0, quantilesB.get(0), 0.001);
}
@Test
- public void lengthRatios() {
+ void lengthRatios() {
analysis.getLengthRatio(lineA, routeA1);
}
}
\ No newline at end of file
diff --git a/src/test/java/org/matsim/pt2matsim/plausibility/PlausibilityCheckTest.java b/src/test/java/org/matsim/pt2matsim/plausibility/PlausibilityCheckTest.java
index fb0d44f2..d30ec248 100644
--- a/src/test/java/org/matsim/pt2matsim/plausibility/PlausibilityCheckTest.java
+++ b/src/test/java/org/matsim/pt2matsim/plausibility/PlausibilityCheckTest.java
@@ -1,10 +1,9 @@
package org.matsim.pt2matsim.plausibility;
-import org.locationtech.jts.util.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import org.matsim.api.core.v01.network.Network;
import org.matsim.pt.transitSchedule.api.TransitSchedule;
import org.matsim.pt2matsim.plausibility.log.PlausibilityWarning;
@@ -15,23 +14,24 @@
import org.matsim.pt2matsim.tools.ScheduleToolsTest;
import java.io.File;
+import java.nio.file.Path;
import java.util.Map;
import java.util.Set;
/**
* @author polettif
*/
-public class PlausibilityCheckTest {
+class PlausibilityCheckTest {
- @Rule
- public TemporaryFolder temporaryFolder = new TemporaryFolder();
+ @TempDir
+ public Path temporaryFolder;
private String testDir;
private PlausibilityCheck check;
- @Before
+ @BeforeEach
public void prepare() {
- testDir = temporaryFolder.getRoot().toString();
+ testDir = temporaryFolder.toString();
TransitSchedule s = ScheduleToolsTest.initSchedule();
Network n = NetworkToolsTest.initNetwork();
@@ -43,22 +43,22 @@ public void prepare() {
}
@Test
- public void checks() {
+ void checks() {
Map> warnings = check.getWarnings();
- Assert.equals(0, warnings.get(PlausibilityWarning.Type.ArtificialLinkWarning).size());
- Assert.equals(0, warnings.get(PlausibilityWarning.Type.LoopWarning).size());
- Assert.equals(4, warnings.get(PlausibilityWarning.Type.DirectionChangeWarning).size());
- Assert.equals(0, warnings.get(PlausibilityWarning.Type.TravelTimeWarning).size());
+ Assertions.assertEquals(0, warnings.get(PlausibilityWarning.Type.ArtificialLinkWarning).size());
+ Assertions.assertEquals(0, warnings.get(PlausibilityWarning.Type.LoopWarning).size());
+ Assertions.assertEquals(4, warnings.get(PlausibilityWarning.Type.DirectionChangeWarning).size());
+ Assertions.assertEquals(0, warnings.get(PlausibilityWarning.Type.TravelTimeWarning).size());
}
@Test
- public void writeGeojson() {
+ void writeGeojson() {
check.writeResultsGeojson(testDir + "plausibility.geojson");
new File(testDir + "plausibility.geojson").delete();
}
@Test
- public void staticRun() {
+ void staticRun() {
String scheduleFile = testDir + "testSchedule.xml";
String networkFile = testDir + "testNetwork.xml";
String outputfolder = testDir + "plausibility/";
diff --git a/src/test/java/org/matsim/pt2matsim/run/gis/Network2GeojsonTest.java b/src/test/java/org/matsim/pt2matsim/run/gis/Network2GeojsonTest.java
index 3cabd575..dbb41065 100644
--- a/src/test/java/org/matsim/pt2matsim/run/gis/Network2GeojsonTest.java
+++ b/src/test/java/org/matsim/pt2matsim/run/gis/Network2GeojsonTest.java
@@ -1,7 +1,7 @@
package org.matsim.pt2matsim.run.gis;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.network.Network;
import org.matsim.core.utils.geometry.transformations.TransformationFactory;
import org.matsim.pt2matsim.tools.NetworkToolsTest;
@@ -11,17 +11,17 @@
/**
* @author polettif
*/
-public class Network2GeojsonTest {
+class Network2GeojsonTest {
private Network network;
- @Before
+ @BeforeEach
public void prepare() {
this.network = NetworkToolsTest.initNetwork();
}
@Test
- public void run() {
+ void run() {
Network2Geojson.run(TransformationFactory.CH1903_LV03_Plus, network, "test/nodes.geojson", "test/links.geojson");
new File("test/nodes.geojson").delete();
new File("test/links.geojson").delete();
diff --git a/src/test/java/org/matsim/pt2matsim/run/gis/Schedule2GeojsonTest.java b/src/test/java/org/matsim/pt2matsim/run/gis/Schedule2GeojsonTest.java
index 7f9e1bde..f936982a 100644
--- a/src/test/java/org/matsim/pt2matsim/run/gis/Schedule2GeojsonTest.java
+++ b/src/test/java/org/matsim/pt2matsim/run/gis/Schedule2GeojsonTest.java
@@ -1,7 +1,7 @@
package org.matsim.pt2matsim.run.gis;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.network.Network;
import org.matsim.core.utils.geometry.transformations.TransformationFactory;
import org.matsim.pt.transitSchedule.api.TransitSchedule;
@@ -13,19 +13,19 @@
/**
* @author polettif
*/
-public class Schedule2GeojsonTest {
+class Schedule2GeojsonTest {
private TransitSchedule schedule;
private Network network;
- @Before
+ @BeforeEach
public void prepare() {
this.schedule = ScheduleToolsTest.initSchedule();
this.network = NetworkToolsTest.initNetwork();
}
@Test
- public void run() {
+ void run() {
Schedule2Geojson.run(TransformationFactory.CH1903_LV03_Plus, this.schedule, this.network, "test/schedule.geojson");
new File("test/schedule.geojson").delete();
}
diff --git a/src/test/java/org/matsim/pt2matsim/tools/CoordToolsTest.java b/src/test/java/org/matsim/pt2matsim/tools/CoordToolsTest.java
index d2f2b423..3b52489a 100644
--- a/src/test/java/org/matsim/pt2matsim/tools/CoordToolsTest.java
+++ b/src/test/java/org/matsim/pt2matsim/tools/CoordToolsTest.java
@@ -1,15 +1,13 @@
package org.matsim.pt2matsim.tools;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Coord;
import org.matsim.core.utils.geometry.CoordUtils;
import java.util.HashMap;
import java.util.Map;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
/**
* @author polettif
*/
@@ -57,29 +55,29 @@ public class CoordToolsTest {
*/
@Test
- public void getAzimuth() {
- assertEquals(0, 200* CoordTools.getAzimuth(coordA,coordD)/Math.PI, testDelta);
- assertEquals(50, 200* CoordTools.getAzimuth(coordA,coordC)/Math.PI, testDelta);
- assertEquals(100, 200* CoordTools.getAzimuth(coordA,coordB)/Math.PI, testDelta);
- assertEquals(150, 200* CoordTools.getAzimuth(coordA,coordI)/Math.PI, testDelta);
- assertEquals(200, 200* CoordTools.getAzimuth(coordA,coordH)/Math.PI, testDelta);
- assertEquals(250, 200* CoordTools.getAzimuth(coordA,coordG)/Math.PI, testDelta);
- assertEquals(300, 200* CoordTools.getAzimuth(coordA,coordF)/Math.PI, testDelta);
- assertEquals(350, 200* CoordTools.getAzimuth(coordA,coordE)/Math.PI, testDelta);
+ void getAzimuth() {
+ Assertions.assertEquals(0, 200 * CoordTools.getAzimuth(coordA, coordD) / Math.PI, testDelta);
+ Assertions.assertEquals(50, 200 * CoordTools.getAzimuth(coordA, coordC) / Math.PI, testDelta);
+ Assertions.assertEquals(100, 200 * CoordTools.getAzimuth(coordA, coordB) / Math.PI, testDelta);
+ Assertions.assertEquals(150, 200 * CoordTools.getAzimuth(coordA, coordI) / Math.PI, testDelta);
+ Assertions.assertEquals(200, 200 * CoordTools.getAzimuth(coordA, coordH) / Math.PI, testDelta);
+ Assertions.assertEquals(250, 200 * CoordTools.getAzimuth(coordA, coordG) / Math.PI, testDelta);
+ Assertions.assertEquals(300, 200 * CoordTools.getAzimuth(coordA, coordF) / Math.PI, testDelta);
+ Assertions.assertEquals(350, 200 * CoordTools.getAzimuth(coordA, coordE) / Math.PI, testDelta);
}
@Test
- public void getAzimuthDiff() {
- assertEquals(100, 200*CoordTools.getAngleDiff(coordA, coordD, coordC)/Math.PI, testDelta);
- assertEquals(150, 200*CoordTools.getAngleDiff(coordA, coordD, coordB)/Math.PI, testDelta);
- assertEquals(-100, 200*CoordTools.getAngleDiff(coordA, coordB, coordC)/Math.PI, testDelta);
- assertEquals(-150, 200*CoordTools.getAngleDiff(coordA, coordB, coordD)/Math.PI, testDelta);
+ void getAzimuthDiff() {
+ Assertions.assertEquals(100, 200 * CoordTools.getAngleDiff(coordA, coordD, coordC) / Math.PI, testDelta);
+ Assertions.assertEquals(150, 200 * CoordTools.getAngleDiff(coordA, coordD, coordB) / Math.PI, testDelta);
+ Assertions.assertEquals(-100, 200 * CoordTools.getAngleDiff(coordA, coordB, coordC) / Math.PI, testDelta);
+ Assertions.assertEquals(-150, 200 * CoordTools.getAngleDiff(coordA, coordB, coordD) / Math.PI, testDelta);
- assertEquals(50, 200*CoordTools.getAngleDiff(coordH, coordA, coordC)/Math.PI, testDelta);
- assertEquals(-50, 200*CoordTools.getAngleDiff(coordH, coordA, coordE)/Math.PI, testDelta);
+ Assertions.assertEquals(50, 200 * CoordTools.getAngleDiff(coordH, coordA, coordC) / Math.PI, testDelta);
+ Assertions.assertEquals(-50, 200 * CoordTools.getAngleDiff(coordH, coordA, coordE) / Math.PI, testDelta);
- assertEquals(0, 200*CoordTools.getAngleDiff(coordF, coordA, coordB)/Math.PI, testDelta);
- assertEquals(200, 200*CoordTools.getAngleDiff(coordA, coordF, coordB)/Math.PI, testDelta);
+ Assertions.assertEquals(0, 200 * CoordTools.getAngleDiff(coordF, coordA, coordB) / Math.PI, testDelta);
+ Assertions.assertEquals(200, 200 * CoordTools.getAngleDiff(coordA, coordF, coordB) / Math.PI, testDelta);
Map coords = new HashMap<>();
coords.put("A", coordA); coords.put("B", coordB); coords.put("C", coordC); coords.put("D", coordD);
@@ -91,8 +89,8 @@ public void getAzimuthDiff() {
for(Map.Entry c2 : coords.entrySet()) {
if(!c1.equals(c0) && !c2.equals(c0) && !c1.equals(c2)) {
double diff = CoordTools.getAngleDiff(c0.getValue(), c1.getValue(), c2.getValue());
- assertTrue(diff <= Math.PI);
- assertTrue(diff >= -Math.PI);
+ Assertions.assertTrue(diff <= Math.PI);
+ Assertions.assertTrue(diff >= -Math.PI);
}
}
}
@@ -101,26 +99,26 @@ public void getAzimuthDiff() {
}
@Test
- public void calcNewPoint() {
+ void calcNewPoint() {
Coord newPointD = CoordTools.calcNewPoint(coordA, 0.00 * Math.PI, CoordUtils.calcEuclideanDistance(coordA, coordD));
- assertEquals(newPointD.getX(), coordD.getX(), testDelta);
- assertEquals(newPointD.getY(), coordD.getY(), testDelta);
+ Assertions.assertEquals(newPointD.getX(), coordD.getX(), testDelta);
+ Assertions.assertEquals(newPointD.getY(), coordD.getY(), testDelta);
Coord newPointX = CoordTools.calcNewPoint(coordA, 0.25 * Math.PI, CoordUtils.calcEuclideanDistance(coordA, coordC));
- assertEquals(newPointX.getX(), coordC.getX(), testDelta);
- assertEquals(newPointX.getY(), coordC.getY(), testDelta);
+ Assertions.assertEquals(newPointX.getX(), coordC.getX(), testDelta);
+ Assertions.assertEquals(newPointX.getY(), coordC.getY(), testDelta);
Coord duplicateCoordZ = CoordTools.calcNewPoint(coordA, CoordTools.getAzimuth(coordA, coordZ), CoordUtils.calcEuclideanDistance(coordA, coordZ));
- assertEquals(duplicateCoordZ.getX(), coordZ.getX(), testDelta);
- assertEquals(duplicateCoordZ.getY(), coordZ.getY(), testDelta);
+ Assertions.assertEquals(duplicateCoordZ.getX(), coordZ.getX(), testDelta);
+ Assertions.assertEquals(duplicateCoordZ.getY(), coordZ.getY(), testDelta);
Coord newPointB = CoordTools.calcNewPoint(coordA, 0.50 * Math.PI, 20);
- assertEquals(newPointB.getX(), coordB.getX(), testDelta);
- assertEquals(newPointB.getY(), coordB.getY(), testDelta);
+ Assertions.assertEquals(newPointB.getX(), coordB.getX(), testDelta);
+ Assertions.assertEquals(newPointB.getY(), coordB.getY(), testDelta);
Coord newPointG = CoordTools.calcNewPoint(coordA, 1.25*Math.PI, CoordUtils.calcEuclideanDistance(coordA, coordG));
- assertEquals(newPointG.getX(), coordG.getX(), testDelta);
- assertEquals(newPointG.getY(), coordG.getY(), testDelta);
+ Assertions.assertEquals(newPointG.getX(), coordG.getX(), testDelta);
+ Assertions.assertEquals(newPointG.getY(), coordG.getY(), testDelta);
}
}
\ No newline at end of file
diff --git a/src/test/java/org/matsim/pt2matsim/tools/CsvToolsTest.java b/src/test/java/org/matsim/pt2matsim/tools/CsvToolsTest.java
index 730f5ea0..38236c6f 100644
--- a/src/test/java/org/matsim/pt2matsim/tools/CsvToolsTest.java
+++ b/src/test/java/org/matsim/pt2matsim/tools/CsvToolsTest.java
@@ -1,7 +1,7 @@
package org.matsim.pt2matsim.tools;
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
import org.matsim.core.utils.collections.MapUtils;
import java.io.File;
@@ -12,10 +12,10 @@
/**
* @author polettif
*/
-public class CsvToolsTest {
+class CsvToolsTest {
@Test
- public void mapCsvTest() throws IOException {
+ void mapCsvTest() throws IOException {
String file = "test/testfile.csv";
Map> testMap = new HashMap<>();
@@ -30,7 +30,7 @@ public void mapCsvTest() throws IOException {
CsvTools.writeNestedMapToFile(testMap, file);
Map> readMap = CsvTools.readNestedMapFromFile(file, false);
- Assert.assertEquals(testMap, readMap);
+ Assertions.assertEquals(testMap, readMap);
File f = new File(file);
boolean del = f.delete();
diff --git a/src/test/java/org/matsim/pt2matsim/tools/GtfsToolsTest.java b/src/test/java/org/matsim/pt2matsim/tools/GtfsToolsTest.java
index c9c53ce2..a248d5a2 100644
--- a/src/test/java/org/matsim/pt2matsim/tools/GtfsToolsTest.java
+++ b/src/test/java/org/matsim/pt2matsim/tools/GtfsToolsTest.java
@@ -1,9 +1,9 @@
package org.matsim.pt2matsim.tools;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.pt2matsim.gtfs.GtfsFeed;
import org.matsim.pt2matsim.gtfs.GtfsFeedImpl;
@@ -14,19 +14,19 @@
/**
* @author polettif
*/
-public class GtfsToolsTest {
+class GtfsToolsTest {
private GtfsFeed gtfsFeed;
private String output = "test/gtfs-feed-rewrite/";
- @Before
+ @BeforeEach
public void prepare() {
gtfsFeed = new GtfsFeedImpl("test/gtfs-feed/");
new File(output).mkdir();
}
@Test
- public void writeFiles() throws IOException {
+ void writeFiles() throws IOException {
GtfsTools.writeStopTimes(gtfsFeed.getTrips().values(), output);
GtfsTools.writeStops(gtfsFeed.getStops().values(), output);
GtfsTools.writeTrips(gtfsFeed.getTrips().values(), output);
@@ -34,16 +34,16 @@ public void writeFiles() throws IOException {
}
@Test
- public void getDayWithMostServices() {
- Assert.assertEquals(LocalDate.of(2018, 10, 2), GtfsTools.getDayWithMostServices(gtfsFeed));
+ void getDayWithMostServices() {
+ Assertions.assertEquals(LocalDate.of(2018, 10, 2), GtfsTools.getDayWithMostServices(gtfsFeed));
}
@Test
- public void getDayWithMostTrips() {
- Assert.assertEquals(LocalDate.of(2018, 10, 5), GtfsTools.getDayWithMostTrips(gtfsFeed));
+ void getDayWithMostTrips() {
+ Assertions.assertEquals(LocalDate.of(2018, 10, 5), GtfsTools.getDayWithMostTrips(gtfsFeed));
}
- @After
+ @AfterEach
public void clean() {
new File(output).delete();
}
diff --git a/src/test/java/org/matsim/pt2matsim/tools/NetworkToolsTest.java b/src/test/java/org/matsim/pt2matsim/tools/NetworkToolsTest.java
index bbe727ff..574392e7 100644
--- a/src/test/java/org/matsim/pt2matsim/tools/NetworkToolsTest.java
+++ b/src/test/java/org/matsim/pt2matsim/tools/NetworkToolsTest.java
@@ -1,8 +1,8 @@
package org.matsim.pt2matsim.tools;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
@@ -14,7 +14,6 @@
import java.util.*;
-import static org.junit.Assert.assertTrue;
import static org.matsim.pt2matsim.tools.CoordToolsTest.*;
/**
@@ -148,7 +147,7 @@ public static Network initNetwork() {
return net;
}
- @Before
+ @BeforeEach
public void prepare() {
network = initNetwork();
}
@@ -158,39 +157,40 @@ private Link getLink(String id) {
}
@Test
- public void getNearestLink() {
+ void getNearestLink() {
Coord testR = new Coord(2600041.0, 1200050.0);
Coord testL = new Coord(2600039.0, 1200050.0);
Node nearestNode = NetworkUtils.getNearestNode(network, testR);
- Assert.assertEquals("A", nearestNode.getId().toString());
+ Assertions.assertEquals("A", nearestNode.getId().toString());
- Assert.assertEquals("AD", NetworkTools.getNearestLink(network, testR, 4).getId().toString());
- Assert.assertEquals("DA", NetworkTools.getNearestLink(network, testL, 4).getId().toString());
+ Assertions.assertEquals("AD", NetworkTools.getNearestLink(network, testR, 4).getId().toString());
+ Assertions.assertEquals("DA", NetworkTools.getNearestLink(network, testL, 4).getId().toString());
}
@Test
- public void getOppositeLink() {
+ void getOppositeLink() {
Network network = initNetwork();
- Assert.assertEquals("AD", NetworkTools.getOppositeLink(network.getLinks().get(Id.createLinkId("DA"))).getId().toString());
+ Assertions.assertEquals("AD",
+ NetworkTools.getOppositeLink(network.getLinks().get(Id.createLinkId("DA"))).getId().toString());
}
@Test
- public void coordIsOnRightSideOfLink() {
+ void coordIsOnRightSideOfLink() {
Coord c = new Coord(2600039.0, 1200041.0);
- Assert.assertTrue(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("IG"))));
- Assert.assertTrue(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("FE"))));
- Assert.assertTrue(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("ED"))));
- Assert.assertTrue(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("DA"))));
+ Assertions.assertTrue(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("IG"))));
+ Assertions.assertTrue(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("FE"))));
+ Assertions.assertTrue(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("ED"))));
+ Assertions.assertTrue(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("DA"))));
- Assert.assertFalse(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("AD"))));
- Assert.assertFalse(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("AX"))));
+ Assertions.assertFalse(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("AD"))));
+ Assertions.assertFalse(NetworkTools.coordIsOnRightSideOfLink(c, network.getLinks().get(Id.createLinkId("AX"))));
}
@Test
- public void linkSequenceHasDuplicateLink() {
+ void linkSequenceHasDuplicateLink() {
List seq = new ArrayList<>();
seq.add(getLink("XA"));
seq.add(getLink("AB"));
@@ -201,64 +201,64 @@ public void linkSequenceHasDuplicateLink() {
seq.add(getLink("BI"));
seq.add(getLink("IH"));
- assertTrue(NetworkTools.linkSequenceHasDuplicateLink(seq));
+ Assertions.assertTrue(NetworkTools.linkSequenceHasDuplicateLink(seq));
}
@Test
- public void linkSequenceHasUTurns() {
+ void linkSequenceHasUTurns() {
List seq = new ArrayList<>();
seq.add(getLink("AB"));
seq.add(getLink("BC"));
seq.add(getLink("CB"));
seq.add(getLink("BI"));
- assertTrue(NetworkTools.linkSequenceHasUTurns(seq));
+ Assertions.assertTrue(NetworkTools.linkSequenceHasUTurns(seq));
}
@Test
- public void getSingleFilePrecedingLink() {
- Assert.assertEquals("AH", NetworkTools.getSingleFilePrecedingLink(getLink("HZ")).getId().toString());
- Assert.assertEquals("ZI", NetworkTools.getSingleFileSucceedingLink(getLink("HZ")).getId().toString());
+ void getSingleFilePrecedingLink() {
+ Assertions.assertEquals("AH", NetworkTools.getSingleFilePrecedingLink(getLink("HZ")).getId().toString());
+ Assertions.assertEquals("ZI", NetworkTools.getSingleFileSucceedingLink(getLink("HZ")).getId().toString());
}
@Test
- public void reduceSequencedLinks() {
+ void reduceSequencedLinks() {
List seq = new ArrayList<>();
seq.add(getLink("AH"));
seq.add(getLink("HZ"));
seq.add(getLink("ZI"));
NetworkTools.reduceSequencedLinks(seq, new Coord(2600050.0, 1200035.0));
- Assert.assertEquals(1, seq.size());
- Assert.assertEquals("AH", seq.get(0).getId().toString());
+ Assertions.assertEquals(1, seq.size());
+ Assertions.assertEquals("AH", seq.get(0).getId().toString());
Collection extends Link> seqAll = new HashSet<>(network.getLinks().values());
NetworkTools.reduceSequencedLinks(seqAll, new Coord(2600041.0, 1200042.0));
- Assert.assertEquals(10, network.getLinks().size()-seqAll.size());
+ Assertions.assertEquals(10, network.getLinks().size() - seqAll.size());
}
@Test
- public void calcRouteLength() {
+ void calcRouteLength() {
List seq = new ArrayList<>();
seq.add(getLink("AB"));
seq.add(getLink("BC"));
seq.add(getLink("CD"));
seq.add(getLink("DA"));
- Assert.assertEquals(80.0, NetworkTools.calcRouteLength(seq, true), 0.0001);
+ Assertions.assertEquals(80.0, NetworkTools.calcRouteLength(seq, true), 0.0001);
}
@Test
- public void findClosestLinks() {
+ void findClosestLinks() {
Node node = network.getNodes().get(Id.createNodeId("G"));
Coord coordToLookFrom = new Coord(node.getCoord().getX() + 2, node.getCoord().getY() + 2);
Map> cars = NetworkTools.findClosestLinks(network, coordToLookFrom, 3, Collections.singleton("car"));
- Assert.assertEquals(1, cars.keySet().size());
- Assert.assertEquals(4, cars.get(2.0).size());
+ Assertions.assertEquals(1, cars.keySet().size());
+ Assertions.assertEquals(4, cars.get(2.0).size());
Map> nullTransportModes = NetworkTools.findClosestLinks(network, coordToLookFrom, 3, null);
- Assert.assertEquals(1, nullTransportModes.keySet().size());
- Assert.assertEquals(4, nullTransportModes.get(2.0).size());
+ Assertions.assertEquals(1, nullTransportModes.keySet().size());
+ Assertions.assertEquals(4, nullTransportModes.get(2.0).size());
}
diff --git a/src/test/java/org/matsim/pt2matsim/tools/ScheduleToolsTest.java b/src/test/java/org/matsim/pt2matsim/tools/ScheduleToolsTest.java
index 06dc5395..34b4bc35 100644
--- a/src/test/java/org/matsim/pt2matsim/tools/ScheduleToolsTest.java
+++ b/src/test/java/org/matsim/pt2matsim/tools/ScheduleToolsTest.java
@@ -1,8 +1,8 @@
package org.matsim.pt2matsim.tools;
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
@@ -179,16 +179,16 @@ public static TransitSchedule initUnmappedSchedule() {
}
@Test
- public void validateTestSchedule() {
+ void validateTestSchedule() {
ValidationResult result = TransitScheduleValidator.validateAll(ScheduleToolsTest.initSchedule(), NetworkToolsTest.initNetwork());
for(ValidationIssue> issue : result.getIssues()) {
System.err.println(issue.getSeverity() + ": " + issue.getMessage());
}
- Assert.assertTrue(result.isValid());
+ Assertions.assertTrue(result.isValid());
}
@Test
- public void mergeSchedules() {
+ void mergeSchedules() {
TransitSchedule testSchedule = initSchedule();
ScheduleTools.mergeSchedules(testSchedule, initSchedule());
@@ -201,13 +201,13 @@ public void mergeSchedules() {
nRoutesInit += l.getRoutes().size();
}
- Assert.assertEquals(testSchedule.getTransitLines().size(), initSchedule().getTransitLines().size());
- Assert.assertEquals(nRoutesInit, nRoutesTest);
- Assert.assertEquals(testSchedule.getFacilities().size(), initSchedule().getFacilities().size());
+ Assertions.assertEquals(testSchedule.getTransitLines().size(), initSchedule().getTransitLines().size());
+ Assertions.assertEquals(nRoutesInit, nRoutesTest);
+ Assertions.assertEquals(testSchedule.getFacilities().size(), initSchedule().getFacilities().size());
}
@Test
- public void mergeSchedulesOffset() {
+ void mergeSchedulesOffset() {
TransitSchedule baseSchedule = initSchedule();
TransitSchedule testSchedule = initSchedule();
ScheduleTools.mergeSchedules(testSchedule, baseSchedule, 24 * 3600, 60 * 3600);
@@ -226,14 +226,14 @@ public void mergeSchedulesOffset() {
}
}
- Assert.assertEquals(testSchedule.getTransitLines().size(), initSchedule().getTransitLines().size());
- Assert.assertEquals(nRoutesInit, nRoutesTest);
- Assert.assertEquals(testSchedule.getFacilities().size(), initSchedule().getFacilities().size());
- Assert.assertEquals(nDeparturesInit * 2, nDeparturesTest);
+ Assertions.assertEquals(testSchedule.getTransitLines().size(), initSchedule().getTransitLines().size());
+ Assertions.assertEquals(nRoutesInit, nRoutesTest);
+ Assertions.assertEquals(testSchedule.getFacilities().size(), initSchedule().getFacilities().size());
+ Assertions.assertEquals(nDeparturesInit * 2, nDeparturesTest);
}
@Test
- public void mergeSchedulesOffsetTimeLimit() {
+ void mergeSchedulesOffsetTimeLimit() {
TransitSchedule baseSchedule = initSchedule();
TransitSchedule testSchedule = initSchedule();
ScheduleTools.mergeSchedules(testSchedule, baseSchedule, 24 * 3600, 24 * 3600 + 12.5 * 3600);
@@ -252,14 +252,14 @@ public void mergeSchedulesOffsetTimeLimit() {
}
}
- Assert.assertEquals(testSchedule.getTransitLines().size(), initSchedule().getTransitLines().size());
- Assert.assertEquals(nRoutesInit, nRoutesTest);
- Assert.assertEquals(testSchedule.getFacilities().size(), initSchedule().getFacilities().size());
- Assert.assertEquals(nDeparturesInit + 6, nDeparturesTest);
+ Assertions.assertEquals(testSchedule.getTransitLines().size(), initSchedule().getTransitLines().size());
+ Assertions.assertEquals(nRoutesInit, nRoutesTest);
+ Assertions.assertEquals(testSchedule.getFacilities().size(), initSchedule().getFacilities().size());
+ Assertions.assertEquals(nDeparturesInit + 6, nDeparturesTest);
}
@Test
- public void freespeedBasedOnSchedule() {
+ void freespeedBasedOnSchedule() {
TransitSchedule schedule = initSchedule();
Network network = NetworkToolsTest.initNetwork();
Network baseNetwork = NetworkToolsTest.initNetwork();
@@ -286,6 +286,6 @@ public void freespeedBasedOnSchedule() {
linksChanged.add(link.getId());
}
}
- Assert.assertEquals(linksUsedBySchedule, linksChanged);
+ Assertions.assertEquals(linksUsedBySchedule, linksChanged);
}
}
\ No newline at end of file
diff --git a/src/test/java/org/matsim/pt2matsim/tools/ShapeToolsTest.java b/src/test/java/org/matsim/pt2matsim/tools/ShapeToolsTest.java
index 5e3675ab..22839364 100644
--- a/src/test/java/org/matsim/pt2matsim/tools/ShapeToolsTest.java
+++ b/src/test/java/org/matsim/pt2matsim/tools/ShapeToolsTest.java
@@ -1,8 +1,8 @@
package org.matsim.pt2matsim.tools;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Node;
@@ -67,7 +67,7 @@ private static Coord offset(Coord c) {
return new Coord(c.getX() + offset, c.getY() + offset);
}
- @Before
+ @BeforeEach
public void prepare() {
Map, RouteShape> shapes = initShapes();
this.shapeA1 = shapes.get(Id.create("A1", RouteShape.class));
@@ -76,26 +76,26 @@ public void prepare() {
}
@Test
- public void calcMinDistanceToShape() {
- Assert.assertEquals(0, ShapeTools.calcMinDistanceToShape(coordX, shapeB), d);
- Assert.assertEquals(5, ShapeTools.calcMinDistanceToShape(new Coord(2600035, 1200035), shapeB), d);
+ void calcMinDistanceToShape() {
+ Assertions.assertEquals(0, ShapeTools.calcMinDistanceToShape(coordX, shapeB), d);
+ Assertions.assertEquals(5, ShapeTools.calcMinDistanceToShape(new Coord(2600035, 1200035), shapeB), d);
Coord bx = CoordTools.calcNewPoint(coordX, CoordTools.getAzimuth(coordX, coordB), CoordUtils.calcEuclideanDistance(coordX, coordB) / 2);
- Assert.assertEquals(0, ShapeTools.calcMinDistanceToShape(bx, shapeA1), d);
+ Assertions.assertEquals(0, ShapeTools.calcMinDistanceToShape(bx, shapeA1), d);
}
@Test
- public void getNodesWithinBuffer() {
+ void getNodesWithinBuffer() {
Collection nodes = ShapeTools.getNodesWithinBuffer(NetworkToolsTest.initNetwork(), shapeB, 1.0);
- Assert.assertEquals(9, nodes.size());
+ Assertions.assertEquals(9, nodes.size());
}
@Test
- public void getShapeLength() {
+ void getShapeLength() {
double lengthA1 = ShapeTools.getShapeLength(shapeA1);
double lengthA2 = ShapeTools.getShapeLength(shapeA2);
- Assert.assertEquals(lengthA1, lengthA2, d);
+ Assertions.assertEquals(lengthA1, lengthA2, d);
}
}
\ No newline at end of file
diff --git a/test/BrienzRothornBahn-HAFAS/BETRIEB_DE b/test/BrienzRothornBahn-HAFAS/BETRIEB_DE
index b3dc2045..df8c96d3 100644
--- a/test/BrienzRothornBahn-HAFAS/BETRIEB_DE
+++ b/test/BrienzRothornBahn-HAFAS/BETRIEB_DE
@@ -1,2 +1,3 @@
+* Kommentarzeile
00075 K "BRB" L "BRB" V "Brienz Rothorn Bahn AG"
00075 : 000104
diff --git a/test/BrienzRothornBahn-HAFAS/BFKOORD_GEO b/test/BrienzRothornBahn-HAFAS/BFKOORD_GEO
deleted file mode 100644
index c1bfa2b5..00000000
--- a/test/BrienzRothornBahn-HAFAS/BFKOORD_GEO
+++ /dev/null
@@ -1,3 +0,0 @@
-8508350 8.038089 46.755214 566 % Brienz BRB
-8508351 8.019845 46.772373 1346 % Planalp
-8508352 8.038447 46.787399 2252 % Brienzer Rothorn
diff --git a/test/BrienzRothornBahn-HAFAS/BFKOORD_WGS b/test/BrienzRothornBahn-HAFAS/BFKOORD_WGS
new file mode 100644
index 00000000..a251e5e7
--- /dev/null
+++ b/test/BrienzRothornBahn-HAFAS/BFKOORD_WGS
@@ -0,0 +1,4 @@
+* Kommentarzeile
+8508350 8.0380890 46.7552140 566 % Brienz BRB
+8508351 8.0198450 46.7723730 1346 % Planalp
+8508352 8.0384470 46.7873990 2252 % Brienzer Rothorn
diff --git a/test/BrienzRothornBahn-HAFAS/BITFELD b/test/BrienzRothornBahn-HAFAS/BITFELD
index 36de90af..7de27864 100644
--- a/test/BrienzRothornBahn-HAFAS/BITFELD
+++ b/test/BrienzRothornBahn-HAFAS/BITFELD
@@ -1 +1,2 @@
+* Kommentarzeile
003499 F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
diff --git a/test/BrienzRothornBahn-HAFAS/FPLAN b/test/BrienzRothornBahn-HAFAS/FPLAN
index 9cf79fa6..0fcf29bf 100644
--- a/test/BrienzRothornBahn-HAFAS/FPLAN
+++ b/test/BrienzRothornBahn-HAFAS/FPLAN
@@ -1,4 +1,4 @@
-*Z 00001 000104 001 % 00001 000104 001 (001)
+*Z 000001 000104 001 % 00001 000104 001 (001)
*G R 8508350 8508352 00730 00825 % 00001 000104 001 (002)
*A VE 8508350 8508352 003499 00730 00825 % 00001 000104 001 (003)
*A 2 8508350 8508352 00730 00825 % 00001 000104 001 (004)
@@ -7,7 +7,7 @@
8508350 Brienz BRB 00730 % 00001 000104 001 (007)
8508351 Planalp 00756 00756 % 00001 000104 001 (008)
8508352 Brienzer Rothorn 00825 % 00001 000104 001 (009)
-*Z 00002 000104 001 % 00002 000104 001 (001)
+*Z 000002 000104 001 % 00002 000104 001 (001)
*G R 8508352 8508350 00830 00930 % 00002 000104 001 (002)
*A VE 8508352 8508350 003499 00830 00930 % 00002 000104 001 (003)
*A 2 8508352 8508350 00830 00930 % 00002 000104 001 (004)
diff --git a/test/BrienzRothornBahn-HAFAS/readme.txt b/test/BrienzRothornBahn-HAFAS/readme.txt
index 8881e9e8..6b40bea4 100644
--- a/test/BrienzRothornBahn-HAFAS/readme.txt
+++ b/test/BrienzRothornBahn-HAFAS/readme.txt
@@ -1,12 +1,13 @@
Extract of http://www.fahrplanfelder.ch/de/fahrplandaten.html => testdaten.zip (accessed 02.11.2016), which is publicly available.
+Note 2024: test files are no longer available in the link above and the HAFAS-Format has changed since. The test files have been manually edited to reflect the changes.
Extracted the files
- BETRIEB_DE
- - BFKOORD_GEO
+ - BFKOORD_WGS
- BITFELD
- FPLAN
Modifications:
- FPLAN is reduced to the first two public transport lines (Brienz BRB to Rothorn via Planalp and back).
- - BETRIEB_DE and BFKOORD_GEO are reduced to only the values required by the reduced FPLAN.
+ - BETRIEB_DE and BFKOORD_WGS are reduced to only the values required by the reduced FPLAN.
- BITFELD is also reduced to the line required by the reduced FPLAN, but additionally the BITFELD-Stream of this line was changed and simplified for testing purposes.
diff --git a/test/FPLAN_HAFAS/BETRIEB_DE b/test/FPLAN_HAFAS/BETRIEB_DE
index 3ffed341..d8d16d68 100644
--- a/test/FPLAN_HAFAS/BETRIEB_DE
+++ b/test/FPLAN_HAFAS/BETRIEB_DE
@@ -1,2 +1,3 @@
+* Kommentarzeile
00343 K "RhB" L "RhB" V "Rhätische Bahn"
00343 : 000072
\ No newline at end of file
diff --git a/test/FPLAN_HAFAS/BFKOORD_GEO b/test/FPLAN_HAFAS/BFKOORD_GEO
deleted file mode 100644
index 1be721b1..00000000
--- a/test/FPLAN_HAFAS/BFKOORD_GEO
+++ /dev/null
@@ -1,21 +0,0 @@
-8509002 9.554028 46.967439 523 % Landquart
-8509056 9.560866 46.957199 523 % Landquart Ried
-8509055 9.564754 46.949829 523 % Igis
-8509054 9.559589 46.934843 531 % Zizers
-8509053 9.559173 46.918987 537 % Untervaz-Trimmis
-8509051 9.533122 46.876984 562 % Haldenstein
-8509006 9.531445 46.861898 577 % Chur Wiesental
-8509000 9.528925 46.853080 585 % Chur
-8509183 9.412551 46.823664 604 % Reichenau-Tamins
-8509167 9.359521 46.816578 609 % Trin
-8509168 9.310299 46.807380 635 % Versam-Safien
-8509169 9.275459 46.791794 669 % Valendas-Sagogn
-8509170 9.233080 46.778494 655 % Castrisch
-8509171 9.207437 46.775359 698 % Ilanz
-8509173 9.145614 46.774698 733 % Rueun
-8509174 9.120152 46.769560 744 % Waltensburg/Vuorz
-8509175 9.062430 46.754768 788 % Tavanasa-Breil/Brigels
-8509176 8.990444 46.742543 852 % Trun
-8509177 8.957496 46.732577 928 % Rabius-Surrein
-8509178 8.931326 46.724078 982 % Sumvitg-Cumpadials
-8509179 8.855021 46.704979 1130 % Disentis/Mustér
\ No newline at end of file
diff --git a/test/FPLAN_HAFAS/BFKOORD_WGS b/test/FPLAN_HAFAS/BFKOORD_WGS
new file mode 100644
index 00000000..4536a07a
--- /dev/null
+++ b/test/FPLAN_HAFAS/BFKOORD_WGS
@@ -0,0 +1,22 @@
+* Kommentarzeile
+8509002 9.5540280 46.9674390 523 % Landquart
+8509056 9.5608660 46.9571990 523 % Landquart Ried
+8509055 9.5647540 46.9498290 523 % Igis
+8509054 9.5595890 46.9348430 531 % Zizers
+8509053 9.5591730 46.9189870 537 % Untervaz-Trimmis
+8509051 9.5331220 46.8769840 562 % Haldenstein
+8509006 9.5314450 46.8618980 577 % Chur Wiesental
+8509000 9.5289250 46.8530800 585 % Chur
+8509183 9.4125510 46.8236640 604 % Reichenau-Tamins
+8509167 9.3595210 46.8165780 609 % Trin
+8509168 9.3102990 46.8073800 635 % Versam-Safien
+8509169 9.2754590 46.7917940 669 % Valendas-Sagogn
+8509170 9.2330800 46.7784940 655 % Castrisch
+8509171 9.2074370 46.7753590 698 % Ilanz
+8509173 9.1456140 46.7746980 733 % Rueun
+8509174 9.1201520 46.7695600 744 % Waltensburg/Vuorz
+8509175 9.0624300 46.7547680 788 % Tavanasa-Breil/Brigels
+8509176 8.9904440 46.7425430 852 % Trun
+8509177 8.9574960 46.7325770 928 % Rabius-Surrein
+8509178 8.9313260 46.7240780 982 % Sumvitg-Cumpadials
+8509179 8.8550210 46.7049790 1130 % Disentis/Mustér
\ No newline at end of file
diff --git a/test/FPLAN_HAFAS/FPLAN b/test/FPLAN_HAFAS/FPLAN
index 0996916b..f7b21c1d 100644
--- a/test/FPLAN_HAFAS/FPLAN
+++ b/test/FPLAN_HAFAS/FPLAN
@@ -1,4 +1,4 @@
-*Z 01729 000072 001 % -- 6100720007 --
+*Z 001729 000072 001 % -- 6100720007 --
*G RE 8509002 8509179 %
*A VE 8509002 8509179 %
*A X 8509056 8509056 %
@@ -35,7 +35,7 @@
8509177 Rabius-Surrein 01057 01057 %
8509178 Sumvitg-Cumpadials 01100 01100 %
8509179 Disentis/Mustér 01111 %
-*Z 99999 000072 001 % -- 6100720007 --
+*Z 099999 000072 001 % -- 6100720007 --
*G RE 8509002 8509179 %
*A VE 8509002 8509179 %
*A X 8509056 8509056 %
@@ -72,7 +72,7 @@
8509177 Rabius-Surrein 01057 01057 %
8509178 Sumvitg-Cumpadials 01100 01100 %
8509179 Disentis/Mustér 01111 %
-*Z 01729 000072 001 % -- 6100720007 --
+*Z 001729 000072 001 % -- 6100720007 --
*G RE 8509002 8509179 %
*A VE 8509002 8509179 %
*A X 8509056 8509056 %
diff --git a/test/osm/Rudolfplatz.osm b/test/osm/Rudolfplatz.osm
new file mode 100644
index 00000000..9d6b1381
--- /dev/null
+++ b/test/osm/Rudolfplatz.osm
@@ -0,0 +1,72474 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+