diff --git a/.gitignore b/.gitignore index e915921..ca2ad2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ # Add own ignores on top, don't edit the template original_data/gtfs/*.zip +original_data/gtfs/ original_data/gtfs/* + original_data/osm/switzerland_railways.osm # Common files to ignore when working with MATSim diff --git a/original_data/shp/switzerland/switzerland.cpg b/original_data/shp/switzerland/switzerland.cpg new file mode 100644 index 0000000..3ad133c --- /dev/null +++ b/original_data/shp/switzerland/switzerland.cpg @@ -0,0 +1 @@ +UTF-8 \ No newline at end of file diff --git a/original_data/shp/switzerland/switzerland.ctf b/original_data/shp/switzerland/switzerland.ctf new file mode 100644 index 0000000..014cb42 --- /dev/null +++ b/original_data/shp/switzerland/switzerland.ctf @@ -0,0 +1,65 @@ +Name;Alias +NO;NO +NAME;NAME +AGGLO_ID;AGGLO_ID +AMR_ID;AMR_ID +MUN_ID;MUN_ID +KT_ID;KT_ID +MSR_ID;MSR_ID +SL3_ID;SL3_ID +AGGLO_NAME;AGGLO_NAME +AMR_NAME;AMR_NAME +MUN_NAME;MUN_NAME +KT_NAME;KT_NAME +MSR_NAME;MSR_NAME +SL3_NAME;SL3_NAME +AMGR_ID;AMGR_ID +AMGR_NAME;AMGR_NAME +AREA_LAND;AREA_LAND +MOTORISATION_LEVEL;MOTORISA~1 +ZONE_INDEX;ZONE_INDEX +VISIT_L;VISIT_L +VISIT_S;VISIT_S +VISIT_S_ST;VISIT_S_ST +VISIT_S_LT;VISIT_S_LT +SCHL_ENR_1;SCHL_ENR_1 +SCHL_ENR_2;SCHL_ENR_2 +SCHL_APPR;SCHL_APPR +SCHL_ENR_3;SCHL_ENR_3 +AREA_LAND;AREA_LAND +DENSITY;DENSITY +POP_TOTAL;POP_TOTAL +POP_EMPL;POP_EMPL +POP_PUP_1;POP_PUP_1 +POP_PUP_2;POP_PUP_2 +POP_APPR;POP_APPR +POP_STUD_3;POP_STUD_3 +POP_0017;POP_0017 +POP_1824;POP_1824 +POP_2544;POP_2544 +POP_4564;POP_4564 +POP_6574;POP_6574 +POP_75XX;POP_75XX +POP_DL;POP_DL +POP_CARS;POP_CARS +POP_CARAVL;POP_CARAVL +POP_HT;POP_HT +POP_GA;POP_GA +POP_VA;POP_VA +POP_VA_HT;POP_VA_HT +JOBS_TOTAL;JOBS_TOTAL +JOBS_ENDO;JOBS_ENDO +FTE_TOTAL;FTE_TOTAL +FTE_ENDO;FTE_ENDO +AT_CAR;AT_CAR +AT_RIDE;AT_RIDE +PC_CAR;PC_CAR +PC_RIDE;PC_RIDE +ACCSIB_MUL;ACCSIB_MUL +ACCSIB_CAR;ACCSIB_CAR +ACCSIB_PT;ACCSIB_PT +ACCSIB_JOB_45MIN_PT;ACCSIB_J~2 +CAR_TTIMES;CAR_TTIMES +PT_TTIMES;PT_TTIMES +PT_TRSFERS;PT_TRSFERS +PT_FREQNCY;PT_FREQNCY diff --git a/original_data/shp/switzerland/switzerland.dbf b/original_data/shp/switzerland/switzerland.dbf new file mode 100644 index 0000000..44627d4 Binary files /dev/null and b/original_data/shp/switzerland/switzerland.dbf differ diff --git a/original_data/shp/switzerland/switzerland.prj b/original_data/shp/switzerland/switzerland.prj new file mode 100644 index 0000000..a63c61d --- /dev/null +++ b/original_data/shp/switzerland/switzerland.prj @@ -0,0 +1 @@ +PROJCS["CH1903+_LV95_TOWGS",GEOGCS["GCS_CH1903+_LV95_TOWGS",DATUM["D_CH1903+",SPHEROID["Bessel_1841",6377397.155,299.1528128],TOWGS84[674.4,15.1,405.3,0,0,0,0]],PRIMEM["Greenwich",0],UNIT["Degree",0.0174532925199432955]],PROJECTION["Hotine_Oblique_Mercator_Azimuth_Center"],PARAMETER["False_Easting",2600000],PARAMETER["False_Northing",1200000],PARAMETER["Scale_Factor",1],PARAMETER["Azimuth",90],PARAMETER["Longitude_Of_Center",7.439583333333333],PARAMETER["Latitude_Of_Center",46.95240555555556],UNIT["Meter",1]] \ No newline at end of file diff --git a/original_data/shp/switzerland/switzerland.shp b/original_data/shp/switzerland/switzerland.shp new file mode 100644 index 0000000..f98074a Binary files /dev/null and b/original_data/shp/switzerland/switzerland.shp differ diff --git a/original_data/shp/switzerland/switzerland.shx b/original_data/shp/switzerland/switzerland.shx new file mode 100644 index 0000000..a4efed7 Binary files /dev/null and b/original_data/shp/switzerland/switzerland.shx differ diff --git a/pom.xml b/pom.xml index 638cc64..de50bdf 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,13 @@ assertj-core 3.24.2 - + + + org.matsim + pt2matsim + 24.4 + + ch.sbb.matsim.contrib railsim diff --git a/src/main/java/ch/sbb/prepare/GenerateRailsimInput.java b/src/main/java/ch/sbb/prepare/GenerateRailsimInput.java new file mode 100644 index 0000000..2c5065e --- /dev/null +++ b/src/main/java/ch/sbb/prepare/GenerateRailsimInput.java @@ -0,0 +1,303 @@ +/* *********************************************************************** * + * project: org.matsim.* + * *********************************************************************** * + * * + * copyright : (C) 2016 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.prepare; + +import java.io.File; +import java.net.MalformedURLException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.locationtech.jts.geom.prep.PreparedGeometry; +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.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.ConfigWriter; +import org.matsim.core.utils.collections.CollectionUtils; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitRouteStop; +import org.matsim.pt.transitSchedule.api.TransitSchedule; +import org.matsim.pt2matsim.config.OsmConverterConfigGroup; +import org.matsim.pt2matsim.config.PublicTransitMappingConfigGroup; +import org.matsim.pt2matsim.run.CreateDefaultOsmConfig; +import org.matsim.pt2matsim.run.CreateDefaultPTMapperConfig; +import org.matsim.pt2matsim.run.Gtfs2TransitSchedule; +import org.matsim.pt2matsim.run.Osm2MultimodalNetwork; +import org.matsim.pt2matsim.run.PublicTransitMapper; +import org.matsim.pt2matsim.tools.NetworkTools; +import org.matsim.pt2matsim.tools.ScheduleTools; +import org.matsim.pt2matsim.tools.debug.ScheduleCleaner; +import org.matsim.utils.gis.shp2matsim.ShpGeometryUtils; +import org.matsim.vehicles.Vehicles; + +public final class GenerateRailsimInput { + private static final Logger log = LogManager.getLogger(GenerateRailsimInput.class); + + // input osm and gtfs file + private static final String INPUT_OSM_FILE = "original_data/osm/switzerland_railways.osm"; + private static final String INPUT_GTFS_FILE = "original_data/gtfs/gtfs_fp2024_2024-03-20_04-15.zip"; + + // optional: trim the schedule + // private static final String areaShpFileForTrimming = null; + private static final String areaShpFileForTrimming = "original_data/shp/olten/olten.shp"; + + private static final String removeLinesOutsideThisArea = "original_data/shp/switzerland/switzerland.shp"; + + // optional: filter by line name prefix + private static final Set transitLineNamePrefixesToKeep = CollectionUtils.stringToSet("IC,IR,EC,RE"); + // private static final Set transitLineNamePrefixesToKeep = null; + + private static final String EPSG2056 = "EPSG:2056"; + + // matsim input files to write + private static final String MATSIM_INPUT = "matsim_input/"; + private static final String VEHICLES_FINAL = MATSIM_INPUT + "transitVehicles.xml.gz"; + private static final String NETWORK_FINAL = MATSIM_INPUT + "transitNetwork.xml.gz"; + private static final String SCHEDULE_FINAL = MATSIM_INPUT + "transitSchedule.xml.gz"; + + // intermediate files + + private static final String MATSIM_INPUT_TMP = "matsim_input/tmp/"; + + private static final String PT2MATSIM_OSM_CONVERTER_CONFIG_DEFAULT = MATSIM_INPUT_TMP + "pt2matsim_osm_converter_config_default.xml"; + private static final String PT2MATSIM_OSM_CONVERTER_CONFIG = MATSIM_INPUT_TMP + "pt2matsim_osm_converter_config.xml"; + private static final String PT2MATSIM_MAPPER_CONFIG_DEFAULT = MATSIM_INPUT_TMP + "pt2matsim_mapper_config_default.xml"; + private static final String PT2MATSIM_MAPPER_CONFIG = MATSIM_INPUT_TMP + "pt2matsim_mapper_config_adjusted.xml"; + + private static final String SCHEDULE_GTFS = MATSIM_INPUT_TMP + "schedule_gtfs.xml.gz"; + private static final String SCHEDULE_GTFS_FILTERED = MATSIM_INPUT_TMP + "schedule_gtfs_filtered.xml.gz"; + private static final String SCHEDULE_GTFS_FILTERED_MAPPED = MATSIM_INPUT_TMP + "schedule_gtfs_filtered_mapped.xml.gz"; + private static final String SCHEDULE_GTFS_FILTERED_MAPPED_TRIMMED = MATSIM_INPUT_TMP + "schedule_gtfs_filtered_mapped_trimmed.xml.gz"; + + private static final String NETWORK_OSM = MATSIM_INPUT_TMP + "network_osm.xml.gz"; + private static final String NETWORK_OSM_MAPPED = MATSIM_INPUT_TMP + "network_osm_mapped.xml.gz"; + + private static final String VEHICLES_GTFS = MATSIM_INPUT_TMP + "vehicles_gtfs.xml.gz"; + private static final String VEHICLES_GTFS_TRIMMED = MATSIM_INPUT_TMP + "vehicles_gtfs_trimmed.xml.gz"; + + public static void main(String[] args) throws MalformedURLException { + + new File(MATSIM_INPUT).mkdirs(); + new File(MATSIM_INPUT_TMP).mkdirs(); + + // 1. Convert a gtfs schedule to an unmapped transit schedule + gtfsToSchedule(); + filterSchedule(); + + // 2. Convert an osm map to a MATSim network + createOsmConfigFile( PT2MATSIM_OSM_CONVERTER_CONFIG ); + Osm2MultimodalNetwork.main(new String[]{ PT2MATSIM_OSM_CONVERTER_CONFIG }); + + // 3. Map the schedule onto the network + createMapperConfigFile(PT2MATSIM_MAPPER_CONFIG); + PublicTransitMapper.main(new String[]{PT2MATSIM_MAPPER_CONFIG}); + + trimSchedule(); + writeFinalFiles(); + } + + private static void writeFinalFiles() { + TransitSchedule schedule = ScheduleTools.readTransitSchedule(SCHEDULE_GTFS_FILTERED_MAPPED_TRIMMED); + Network network = NetworkTools.readNetwork(NETWORK_OSM_MAPPED); + Vehicles vehicles = ScheduleTools.readVehicles(VEHICLES_GTFS_TRIMMED); + + ScheduleTools.writeTransitSchedule(schedule, SCHEDULE_FINAL); + NetworkTools.writeNetwork(network, NETWORK_FINAL); + ScheduleTools.writeVehicles(vehicles, VEHICLES_FINAL); + } + + private static void trimSchedule() throws MalformedURLException { + TransitSchedule schedule = ScheduleTools.readTransitSchedule(SCHEDULE_GTFS_FILTERED_MAPPED); + Network network = NetworkTools.readNetwork(NETWORK_OSM_MAPPED); + Vehicles vehicles = ScheduleTools.readVehicles(VEHICLES_GTFS); + + if (areaShpFileForTrimming != null) { + List geometries = ShpGeometryUtils.loadPreparedGeometries(new File(areaShpFileForTrimming).toURI().toURL()); + + for (TransitLine transitLine : new HashSet<>(schedule.getTransitLines().values())) { + for(TransitRoute transitRoute : new HashSet<>(transitLine.getRoutes().values())) { + if (routeHasLinkInArea(transitRoute, network, geometries)) { + // keep + } else { + // remove + transitLine.removeRoute(transitRoute); + log.info("Route " + transitRoute + " removed."); + } + } + + } + } + + // remove transit lines without routes + for (TransitLine transitLine : new HashSet<>(schedule.getTransitLines().values())) { + if (transitLine.getRoutes().size() == 0) { + schedule.removeTransitLine(transitLine); + log.info("Remove line " + transitLine.getId()); + } + } + + ScheduleCleaner.cleanVehicles(schedule, vehicles); + ScheduleCleaner.removeNotUsedStopFacilities(schedule); + ScheduleCleaner.removeNotUsedMinimalTransferTimes(schedule); + ScheduleTools.writeTransitSchedule(schedule, SCHEDULE_GTFS_FILTERED_MAPPED_TRIMMED); + ScheduleTools.writeVehicles(vehicles, VEHICLES_GTFS_TRIMMED); + } + + private static boolean routeHasLinkInArea(TransitRoute route, Network network, List geometries) { + for (Id id : route.getRoute().getLinkIds()) { + Link link = network.getLinks().get(id); + if (ShpGeometryUtils.isCoordInPreparedGeometries(link.getCoord(), geometries)) { + return true; + } + } + return false; + } + + private static boolean routeHasStopInArea(TransitRoute route, List geometries) { + for (TransitRouteStop stop : route.getStops()) { + if (ShpGeometryUtils.isCoordInPreparedGeometries(stop.getStopFacility().getCoord(), geometries)) { + return true; + } + } + return false; + } + + public static void filterSchedule() throws MalformedURLException { + TransitSchedule schedule = ScheduleTools.readTransitSchedule(SCHEDULE_GTFS); + + // remove non-rail transit lines, e.g. buses, light-rail, ... + for(TransitLine transitLine : new HashSet<>(schedule.getTransitLines().values())) { + for(TransitRoute transitRoute : new HashSet<>(transitLine.getRoutes().values())) { + if(!transitRoute.getTransportMode().equals("rail")) { + transitLine.removeRoute(transitRoute); + } + } + } + + // remove lines outside the provided area, e.g. Switzerland + if (removeLinesOutsideThisArea != null) { + List geometries = ShpGeometryUtils.loadPreparedGeometries(new File(removeLinesOutsideThisArea).toURI().toURL()); + + for (TransitLine transitLine : new HashSet<>(schedule.getTransitLines().values())) { + for(TransitRoute transitRoute : new HashSet<>(transitLine.getRoutes().values())) { + if (routeHasStopInArea(transitRoute, geometries)) { + // keep + } else { + // remove + transitLine.removeRoute(transitRoute); + } + } + } + } + + if (transitLineNamePrefixesToKeep == null || transitLineNamePrefixesToKeep.isEmpty()) { + // do not filter by line name prefix + + } else { + for(TransitLine transitLine : new HashSet<>(schedule.getTransitLines().values())) { + boolean lineToKeep = false; + for (String prefix : transitLineNamePrefixesToKeep) { + if (transitLine.getName().startsWith(prefix)) { + lineToKeep = true; + break; + } + } + + if(lineToKeep) { + // keep + } else { + // remove + for(TransitRoute transitRoute : new HashSet<>(transitLine.getRoutes().values())) { + transitLine.removeRoute(transitRoute); + } + } + } + } + + ScheduleCleaner.removeNotUsedStopFacilities(schedule); + ScheduleTools.writeTransitSchedule(schedule, SCHEDULE_GTFS_FILTERED); + } + + /** + * 1. A GTFS or HAFAS Schedule or a OSM map with information on public transport + * has to be converted to an unmapped MATSim Transit Schedule. + * + * Here as a first example, the GTFS-schedule of GrandRiverTransit, Waterloo-Area, Canada, is converted. + */ + public static void gtfsToSchedule() { + String[] gtfsConverterArgs = new String[]{ + // [0] gtfs zip file + INPUT_GTFS_FILE, + // [1] which service ids should be used. One of the following: + // dayWithMostTrips, date in the format yyyymmdd, , dayWithMostServices, all + "dayWithMostTrips", + // [2] the output coordinate system. Use WGS84 for no transformation. + EPSG2056, + // [3] output transit schedule file + SCHEDULE_GTFS, + // [4] output default vehicles file (optional) + VEHICLES_GTFS, + }; + Gtfs2TransitSchedule.main(gtfsConverterArgs); + } + + /** + * 2. A MATSim network of the area is required. If no such network is already available, + * the PT2MATSim package provides the possibility to use OSM-maps as data-input. + * + */ + public static void createOsmConfigFile(String configFile) { + CreateDefaultOsmConfig.main(new String[]{PT2MATSIM_OSM_CONVERTER_CONFIG_DEFAULT}); + Config osmConverterConfig = ConfigUtils.loadConfig( + PT2MATSIM_OSM_CONVERTER_CONFIG_DEFAULT, + new OsmConverterConfigGroup()); + OsmConverterConfigGroup osmConfig = ConfigUtils.addOrGetModule(osmConverterConfig, OsmConverterConfigGroup.class); + osmConfig.setOsmFile(INPUT_OSM_FILE); + osmConfig.setOutputCoordinateSystem(EPSG2056); + osmConfig.setOutputNetworkFile(NETWORK_OSM); + osmConfig.setKeepPaths(true); + new ConfigWriter(osmConverterConfig).write(configFile); + } + + /** + * 3. The core of the PT2MATSim-package is the mapping process of the schedule to the network. + */ + public static void createMapperConfigFile(String configFile) { + CreateDefaultPTMapperConfig.main(new String[]{ PT2MATSIM_MAPPER_CONFIG_DEFAULT}); + Config config = ConfigUtils.loadConfig( + PT2MATSIM_MAPPER_CONFIG_DEFAULT, + PublicTransitMappingConfigGroup.createDefaultConfig()); + PublicTransitMappingConfigGroup ptmConfig = ConfigUtils.addOrGetModule(config, PublicTransitMappingConfigGroup.class); + + ptmConfig.setInputNetworkFile(NETWORK_OSM); + ptmConfig.setOutputNetworkFile(NETWORK_OSM_MAPPED); + ptmConfig.setOutputScheduleFile(SCHEDULE_GTFS_FILTERED_MAPPED); + ptmConfig.setInputScheduleFile(SCHEDULE_GTFS_FILTERED); + ptmConfig.setModesToKeepOnCleanUp(CollectionUtils.stringToSet("rail")); + ptmConfig.setScheduleFreespeedModes(CollectionUtils.stringToSet("rail")); + new ConfigWriter(config).write(configFile); + + } + +} \ No newline at end of file diff --git a/src/main/java/ch/sbb/run/info.md b/src/main/java/ch/sbb/run/info.md new file mode 100644 index 0000000..c7d9dab --- /dev/null +++ b/src/main/java/ch/sbb/run/info.md @@ -0,0 +1 @@ +see 'main' branch \ No newline at end of file