diff --git a/contribs/application/src/main/java/org/matsim/application/options/CountsOption.java b/contribs/application/src/main/java/org/matsim/application/options/CountsOption.java deleted file mode 100644 index bea5678c997..00000000000 --- a/contribs/application/src/main/java/org/matsim/application/options/CountsOption.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.matsim.application.options; - -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVRecord; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.network.Link; -import picocli.CommandLine; - -import javax.annotation.Nullable; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.stream.Collectors; - -/** - * Common options when working with counts data. - */ -public final class CountsOption { - - @CommandLine.Option(names = "--ignored-counts", description = "path to csv with count station ids to ignore") - private Path ignored; - - @CommandLine.Option(names = "--manual-matched-counts", description = "path to csv with manual matched count stations and link ids") - private Path manual; - - private Set ignoredCounts = null; - - private Map> manualMatchedCounts = null; - - public CountsOption() { - } - - public CountsOption(@Nullable Path ignored, @Nullable Path manual) { - this.ignored = ignored; - this.manual = manual; - } - - /** - * Get list of ignored count ids. - */ - public Set getIgnored() { - readIgnored(); - return ignoredCounts; - } - - /** - * Return mapping of count id to specified link id. - */ - public Map> getManualMatched() { - readManualMatched(); - return manualMatchedCounts; - } - - private void readManualMatched() { - - // Already read - if (manualMatchedCounts != null) - return; - - if (manual == null) { - manualMatchedCounts = new HashMap<>(); - return; - } - - try (var reader = Files.newBufferedReader(manual)) { - List records = CSVFormat.Builder.create() - .setAllowMissingColumnNames(true) - .setDelimiter(';') - .build() - .parse(reader) - .getRecords(); - - manualMatchedCounts = records.stream().collect( - Collectors.toMap( - r -> r.get(0), - r -> Id.createLinkId(r.get(1)) - ) - ); - - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - } - - private void readIgnored() { - // Already read the counts - if (ignoredCounts != null) - return; - - if (ignored == null) { - ignoredCounts = new HashSet<>(); - return; - } - - try { - ignoredCounts = new HashSet<>(Files.readAllLines(ignored)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - /** - * Check whether station id should be ignored. - */ - public boolean isIgnored(String stationId) { - readIgnored(); - return ignoredCounts.contains(stationId); - } - - /** - * Return manually matched link id. - * - * @return null if not matched - */ - public Id isManuallyMatched(String stationId) { - readManualMatched(); - - if (manualMatchedCounts.isEmpty() || !manualMatchedCounts.containsKey(stationId)) - return null; - - return manualMatchedCounts.get(stationId); - } -} diff --git a/contribs/application/src/main/java/org/matsim/application/options/CountsOptions.java b/contribs/application/src/main/java/org/matsim/application/options/CountsOptions.java new file mode 100644 index 00000000000..3056bfd1a3e --- /dev/null +++ b/contribs/application/src/main/java/org/matsim/application/options/CountsOptions.java @@ -0,0 +1,155 @@ +package org.matsim.application.options; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +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.network.Link; +import org.matsim.core.utils.io.IOUtils; +import picocli.CommandLine; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.*; + +/** + * Common options when working with counts data. + */ +public final class CountsOptions { + + private static final Logger log = LogManager.getLogger(CountsOptions.class); + + @CommandLine.Option(names = "--counts-mapping", description = "Path to csv with count station ids to ignore") + private String input; + + private Map> manualMatchedCounts = null; + private Set ignoredCounts = null; + + public CountsOptions() { + } + + public CountsOptions(@Nullable String input) { + this.input = input; + } + + /** + * Get list of ignored count ids. + */ + public Set getIgnored() { + readMapping(); + return ignoredCounts; + } + + /** + * Return mapping of count id to specified link id. + */ + public Map> getManualMatched() { + readMapping(); + return manualMatchedCounts; + } + + private synchronized void readMapping() { + + // Already read + if (manualMatchedCounts != null) + return; + + manualMatchedCounts = new HashMap<>(); + ignoredCounts = new HashSet<>(); + + // No input file + if (input == null) + return; + + try (var reader = IOUtils.getBufferedReader(input)) { + CSVFormat format = CSVFormat.Builder.create() + .setAllowMissingColumnNames(true) + .setDelimiter(CsvOptions.detectDelimiter(input)) + .setHeader() + .setSkipHeaderRecord(true) + .build(); + + try (CSVParser csv = new CSVParser(reader, format)) { + Schema schema = parseSchema(csv.getHeaderNames()); + + log.info("Using schema for counts mapping: {}", schema); + + for (CSVRecord row : csv) { + + String stationId = row.get(schema.stationColumn); + manualMatchedCounts.put(stationId, Id.createLinkId(row.get(schema.linkColumn))); + + if (schema.usingColumn != null) { + + String value = row.get(schema.usingColumn).strip().toLowerCase(); + boolean val = value.equals("y") || value.equals("x") || value.equals("true"); + + if (schema.isWhiteList && !val) + ignoredCounts.add(stationId); + else if (!schema.isWhiteList && val) + ignoredCounts.add(stationId); + } + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + } + + /** + * Check whether station id should be ignored. + */ + public boolean isIgnored(String stationId) { + readMapping(); + return ignoredCounts.contains(stationId); + } + + /** + * Return manually matched link id. + * + * @return null if not matched + */ + public Id isManuallyMatched(String stationId) { + readMapping(); + + if (manualMatchedCounts.isEmpty() || !manualMatchedCounts.containsKey(stationId)) + return null; + + return manualMatchedCounts.get(stationId); + } + + private Schema parseSchema(List header) { + + List names = header.stream() + .map(String::toLowerCase) + .map(String::strip) + .map(s -> s.replace("_", "")) + .toList(); + + int linkId = names.indexOf("linkid"); + + if (linkId < 0) + throw new IllegalArgumentException("Link id column not found in csv: " + header); + + int using = names.indexOf("using"); + int ignore = names.indexOf("ignore"); + + // first or second column for station id + int station = linkId == 0 ? 1 : 0; + + if (using > 0) + return new Schema(header.get(station), header.get(linkId), header.get(using), true); + + if (ignore > 0) + return new Schema(header.get(station), header.get(linkId), header.get(ignore), false); + + return new Schema(header.get(station), header.get(linkId), null, false); + } + + private record Schema(String stationColumn, String linkColumn, String usingColumn, boolean isWhiteList) { + } +} diff --git a/contribs/application/src/main/java/org/matsim/application/options/CsvOptions.java b/contribs/application/src/main/java/org/matsim/application/options/CsvOptions.java index a022a6f5771..1b2d6575450 100644 --- a/contribs/application/src/main/java/org/matsim/application/options/CsvOptions.java +++ b/contribs/application/src/main/java/org/matsim/application/options/CsvOptions.java @@ -80,7 +80,7 @@ public static Character detectDelimiter(String path) throws IOException { * Get the CSV format defined by the options. */ public CSVFormat getFormat() { - CSVFormat.Builder format = this.csvFormat.getFormat().builder().setSkipHeaderRecord(true); + CSVFormat.Builder format = this.csvFormat.getFormat().builder().setHeader().setSkipHeaderRecord(true); if (csvDelimiter != null) format = format.setDelimiter(csvDelimiter); diff --git a/contribs/application/src/main/java/org/matsim/application/options/ShpOptions.java b/contribs/application/src/main/java/org/matsim/application/options/ShpOptions.java index 79069264296..7962c3d8d80 100644 --- a/contribs/application/src/main/java/org/matsim/application/options/ShpOptions.java +++ b/contribs/application/src/main/java/org/matsim/application/options/ShpOptions.java @@ -369,8 +369,15 @@ public SimpleFeature queryFeature(Coord coord) { List result = index.query(new Envelope(p)); for (SimpleFeature ft : result) { Geometry geom = (Geometry) ft.getDefaultGeometry(); - if (geom.contains(MGC.coordinate2Point(p))) - return ft; + + // Catch Exception for invalid, too complex geometries + try { + if (geom.contains(MGC.coordinate2Point(p))) + return ft; + } catch (TopologyException e) { + if (geom.convexHull().contains(MGC.coordinate2Point(p))) + return ft; + } } return null; @@ -381,17 +388,7 @@ public SimpleFeature queryFeature(Coord coord) { */ @SuppressWarnings("unchecked") public boolean contains(Coord coord) { - - Coordinate p = MGC.coord2Coordinate(ct.transform(coord)); - - List result = index.query(new Envelope(p)); - for (SimpleFeature ft : result) { - Geometry geom = (Geometry) ft.getDefaultGeometry(); - if (geom.contains(MGC.coordinate2Point(p))) - return true; - } - - return false; + return queryFeature(coord) != null; } /** diff --git a/contribs/application/src/main/java/org/matsim/application/prepare/counts/CreateCountsFromBAStData.java b/contribs/application/src/main/java/org/matsim/application/prepare/counts/CreateCountsFromBAStData.java index dd02f6572ee..f4db223e183 100644 --- a/contribs/application/src/main/java/org/matsim/application/prepare/counts/CreateCountsFromBAStData.java +++ b/contribs/application/src/main/java/org/matsim/application/prepare/counts/CreateCountsFromBAStData.java @@ -11,7 +11,7 @@ import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.application.MATSimAppCommand; -import org.matsim.application.options.CountsOption; +import org.matsim.application.options.CountsOptions; import org.matsim.application.options.CrsOptions; import org.matsim.application.options.ShpOptions; import org.matsim.core.config.groups.NetworkConfigGroup; @@ -63,7 +63,7 @@ public class CreateCountsFromBAStData implements MATSimAppCommand { @CommandLine.Mixin private final ShpOptions shp = new ShpOptions(); @CommandLine.Mixin - private final CountsOption counts = new CountsOption(); + private final CountsOptions counts = new CountsOptions(); @CommandLine.Mixin private final CrsOptions crs = new CrsOptions("EPSG:25832"); @CommandLine.Option(names = "--network", description = "path to MATSim network", required = true) @@ -284,7 +284,7 @@ private void readHourlyTrafficVolume(Path pathToDisaggregatedData, Map index, BAStCountStation station, CountsOption counts) { + private void match(Network network, NetworkIndex index, BAStCountStation station, CountsOptions counts) { Id manuallyMatched = counts.isManuallyMatched(station.getId()); Link matched; @@ -330,7 +330,7 @@ private List> createRoadTypeFilter(List types) { return filter; } - private void matchBAStWithNetwork(String pathToNetwork, Map stations, CountsOption countsOption, CrsOptions crs) { + private void matchBAStWithNetwork(String pathToNetwork, Map stations, CountsOptions countsOption, CrsOptions crs) { if (crs.getTargetCRS() != null) throw new RuntimeException("Please don't specify --target-crs. Only use --input-crs to determinate the network crs!"); @@ -366,7 +366,7 @@ private void matchBAStWithNetwork(String pathToNetwork, Map readBAStCountStations(Path pathToAggregatedData, ShpOptions shp, CountsOption counts) { + private Map readBAStCountStations(Path pathToAggregatedData, ShpOptions shp, CountsOptions counts) { List stations = new ArrayList<>(); diff --git a/contribs/application/src/main/java/org/matsim/application/prepare/network/CreateNetworkFromSumo.java b/contribs/application/src/main/java/org/matsim/application/prepare/network/CreateNetworkFromSumo.java index 1a7272bf277..5542ed01d6a 100644 --- a/contribs/application/src/main/java/org/matsim/application/prepare/network/CreateNetworkFromSumo.java +++ b/contribs/application/src/main/java/org/matsim/application/prepare/network/CreateNetworkFromSumo.java @@ -55,9 +55,6 @@ public final class CreateNetworkFromSumo implements MATSimAppCommand { @CommandLine.Option(names = "--output", description = "Output xml file", required = true) private Path output; - @CommandLine.Mixin - private final ShpOptions shp = new ShpOptions(); - @CommandLine.Mixin private final CrsOptions crs = new CrsOptions(); @@ -67,6 +64,9 @@ public final class CreateNetworkFromSumo implements MATSimAppCommand { @CommandLine.Option(names = "--free-speed-factor", description = "Free-speed reduction for urban links") private double freeSpeedFactor = LinkProperties.DEFAULT_FREESPEED_FACTOR; + @CommandLine.Option(names = "--lane-restrictions", description = "Define how restricted lanes are handled: ${COMPLETION-CANDIDATES}", defaultValue = "IGNORE") + private SumoNetworkConverter.LaneRestriction laneRestriction = SumoNetworkConverter.LaneRestriction.IGNORE; + public static void main(String[] args) { System.exit(new CommandLine(new CreateNetworkFromSumo()).execute(args)); } @@ -74,29 +74,11 @@ public static void main(String[] args) { @Override public Integer call() throws Exception { -// since ShpOptions.getShapeFile() no longer is a path but a string, we have to check if it is defined before creating SumoNetworkConverter to -// preserve the possibility to run the converter without a shp file, otherwise, when calling Path.of(shp.getShapeFile) a NullPointerException is caused -sme0324 - Path path = null; - - if (shp.isDefined()) { - path = Path.of(shp.getShapeFile()); - } - - SumoNetworkConverter converter = SumoNetworkConverter.newInstance(input, output, path, crs.getInputCRS(), crs.getTargetCRS(), freeSpeedFactor); + SumoNetworkConverter converter = SumoNetworkConverter.newInstance(input, output, crs.getInputCRS(), crs.getTargetCRS(), freeSpeedFactor, laneRestriction); Network network = NetworkUtils.createNetwork(); - Lanes lanes = LanesUtils.createLanesContainer(); - - SumoNetworkHandler handler = converter.convert(network, lanes); - - converter.calculateLaneCapacities(network, lanes); - // This needs to run without errors, otherwise network is broken - network.getLinks().values().forEach(link -> { - LanesToLinkAssignment l2l = lanes.getLanesToLinkAssignments().get(link.getId()); - if (l2l != null) - LanesUtils.createLanes(link, l2l); - }); + SumoNetworkHandler handler = converter.convert(network); if (capacities != null) { @@ -105,10 +87,8 @@ public Integer call() throws Exception { log.info("Read lane capacities from {}, containing {} lanes", capacities, map.size()); int n = setLinkCapacities(network, map); - int n2 = setLaneCapacities(lanes, map); - - log.info("Unmatched links: {}, lanes: {}", n, n2); + log.info("Unmatched links: {}", n); } if (crs.getTargetCRS() != null) @@ -116,10 +96,8 @@ public Integer call() throws Exception { NetworkUtils.writeNetwork(network, output.toAbsolutePath().toString()); new NetworkWriter(network).write(output.toAbsolutePath().toString()); - new LanesWriter(lanes).write(output.toAbsolutePath().toString().replace(".xml", "-lanes.xml")); converter.writeGeometry(handler, output.toAbsolutePath().toString().replace(".xml", "-linkGeometries.csv").replace(".gz", "")); - converter.writeFeatures(handler, output.toAbsolutePath().toString().replace(".xml", "-ft.csv")); return 0; diff --git a/contribs/application/src/test/java/org/matsim/application/prepare/counts/CreateCountsFromBAStDataTest.java b/contribs/application/src/test/java/org/matsim/application/prepare/counts/CreateCountsFromBAStDataTest.java index c74d59b7801..1545ef4395d 100644 --- a/contribs/application/src/test/java/org/matsim/application/prepare/counts/CreateCountsFromBAStDataTest.java +++ b/contribs/application/src/test/java/org/matsim/application/prepare/counts/CreateCountsFromBAStDataTest.java @@ -22,8 +22,7 @@ public class CreateCountsFromBAStDataTest { String countsOutput = "test-counts.xml.gz"; - String ignoredCounts = "ignored.csv"; - String manualMatchedCounts = "manual.csv"; + String mapping = "mapping.csv"; String wrongManualMatchedCounts = "wrong_manual.csv"; String network = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("berlin"), "network.xml.gz").toString(); @@ -61,9 +60,6 @@ void testCreateCountsFromBAStData() { assertThat(counts.getMeasureLocations()) .hasSize(24); - assertThat(counts.getCounts()) - .hasSize(24); - for (Map.Entry, MeasurementLocation> e : counts.getMeasureLocations().entrySet()) { assertThat(e.getValue().hasMeasurableForMode(Measurable.VOLUMES, TransportMode.car)) .isTrue(); @@ -105,7 +101,7 @@ void testWithIgnoredStations() { "--shp-crs=" + shpCrs, "--year=2021", "--output=" + out2, - "--ignored-counts=" + utils.getPackageInputDirectory() + ignoredCounts, + "--counts-mapping=" + utils.getPackageInputDirectory() + mapping, }; new CreateCountsFromBAStData().execute(args2); @@ -140,7 +136,7 @@ void testManualMatchedCounts() { "--shp-crs=" + shpCrs, "--year=2021", "--output=" + out, - "--manual-matched-counts=" + utils.getPackageInputDirectory() + manualMatchedCounts, + "--counts-mapping=" + utils.getPackageInputDirectory() + mapping, }; new CreateCountsFromBAStData().execute(args); @@ -178,7 +174,7 @@ void testManualMatchingWithWrongInput() { "--shp-crs=" + shpCrs, "--year=2021", "--output=" + out, - "--manual-matched-counts=" + utils.getPackageInputDirectory() + wrongManualMatchedCounts, + "--counts-mapping=" + utils.getPackageInputDirectory() + wrongManualMatchedCounts, }; Assertions.assertThrows(RuntimeException.class, () -> new CreateCountsFromBAStData().execute(args)); diff --git a/contribs/application/test/input/org/matsim/application/prepare/counts/ignored.csv b/contribs/application/test/input/org/matsim/application/prepare/counts/ignored.csv deleted file mode 100644 index a43b7bf6621..00000000000 --- a/contribs/application/test/input/org/matsim/application/prepare/counts/ignored.csv +++ /dev/null @@ -1 +0,0 @@ -2012 diff --git a/contribs/application/test/input/org/matsim/application/prepare/counts/manual.csv b/contribs/application/test/input/org/matsim/application/prepare/counts/manual.csv deleted file mode 100644 index 58488f11a34..00000000000 --- a/contribs/application/test/input/org/matsim/application/prepare/counts/manual.csv +++ /dev/null @@ -1,2 +0,0 @@ -2012_1;4205 -2012_2;4219 diff --git a/contribs/application/test/input/org/matsim/application/prepare/counts/mapping.csv b/contribs/application/test/input/org/matsim/application/prepare/counts/mapping.csv new file mode 100644 index 00000000000..c6503661752 --- /dev/null +++ b/contribs/application/test/input/org/matsim/application/prepare/counts/mapping.csv @@ -0,0 +1,4 @@ +station,linkId,Using +2012_1,4205,x +2012_2,4219,x +2014,xxx, \ No newline at end of file diff --git a/contribs/sumo/src/main/java/org/matsim/contrib/sumo/SumoNetworkConverter.java b/contribs/sumo/src/main/java/org/matsim/contrib/sumo/SumoNetworkConverter.java index 1f311fc663a..592243b1310 100644 --- a/contribs/sumo/src/main/java/org/matsim/contrib/sumo/SumoNetworkConverter.java +++ b/contribs/sumo/src/main/java/org/matsim/contrib/sumo/SumoNetworkConverter.java @@ -15,15 +15,14 @@ import org.matsim.api.core.v01.network.Node; import org.matsim.contrib.osm.networkReader.LinkProperties; import org.matsim.contrib.osm.networkReader.OsmTags; +import org.matsim.core.network.DisallowedNextLinks; import org.matsim.core.network.NetworkUtils; import org.matsim.core.network.algorithms.NetworkCleaner; import org.matsim.core.scenario.ProjectionUtils; 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.GeoFileReader; import org.matsim.core.utils.io.IOUtils; -import org.matsim.lanes.*; import org.xml.sax.SAXException; import picocli.CommandLine; @@ -37,8 +36,6 @@ import java.util.concurrent.Callable; import java.util.stream.Collectors; -import static org.matsim.lanes.LanesUtils.calculateAndSetCapacity; - /** * Converter for sumo networks * @@ -54,9 +51,6 @@ public class SumoNetworkConverter implements Callable { @CommandLine.Option(names = "--output", description = "Output xml file", required = true) private Path output; - @CommandLine.Option(names = "--shp", description = "Optional shape file used for filtering") - private Path shapeFile; - @CommandLine.Option(names = "--from-crs", description = "Coordinate system of input data", required = true) private String fromCRS; @@ -66,14 +60,18 @@ public class SumoNetworkConverter implements Callable { @CommandLine.Option(names = "--free-speed-factor", description = "Free-speed reduction for urban links", defaultValue = "0.9") private double freeSpeedFactor = LinkProperties.DEFAULT_FREESPEED_FACTOR; - private SumoNetworkConverter(List input, Path output, Path shapeFile, String fromCRS, String toCRS, double freeSpeedFactor) { + @CommandLine.Option(names = "--lane-restrictions", description = "Define how restricted lanes are handled: ${COMPLETION-CANDIDATES}", defaultValue = "IGNORE") + private LaneRestriction laneRestriction = LaneRestriction.IGNORE; + + private SumoNetworkConverter(List input, Path output, String fromCRS, String toCRS, double freeSpeedFactor, + LaneRestriction laneRestriction) { this.input = input; this.output = output; - this.shapeFile = shapeFile; this.fromCRS = fromCRS; this.toCRS = toCRS; this.freeSpeedFactor = freeSpeedFactor; - } + this.laneRestriction = laneRestriction; + } private SumoNetworkConverter() { } @@ -87,26 +85,27 @@ private SumoNetworkConverter() { * @param toCRS desired coordinate system of network */ public static SumoNetworkConverter newInstance(List input, Path output, String fromCRS, String toCRS) { - return new SumoNetworkConverter(input, output, null, fromCRS, toCRS, LinkProperties.DEFAULT_FREESPEED_FACTOR); + return new SumoNetworkConverter(input, output, fromCRS, toCRS, LinkProperties.DEFAULT_FREESPEED_FACTOR, LaneRestriction.IGNORE); } + /** - * Creates a new converter instance, with a shape file for filtering. + * Creates a new instance. * - * @param shapeFile only include links in this shape file. - * @see #newInstance(List, Path, String, String) + * @see #newInstance(List, Path, String, String, double) */ - public static SumoNetworkConverter newInstance(List input, Path output, Path shapeFile, String fromCRS, String toCRS) { - return new SumoNetworkConverter(input, output, shapeFile, fromCRS, toCRS, LinkProperties.DEFAULT_FREESPEED_FACTOR); + public static SumoNetworkConverter newInstance(List input, Path output, String inputCRS, String targetCRS, double freeSpeedFactor) { + return new SumoNetworkConverter(input, output, inputCRS, targetCRS, freeSpeedFactor, LaneRestriction.IGNORE); } /** * Creates a new instance. * - * @see #newInstance(List, Path, Path, String, String, double) + * @see #newInstance(List, Path, String, String, double) */ - public static SumoNetworkConverter newInstance(List input, Path output, Path shapeFile, String inputCRS, String targetCRS, double freeSpeedFactor) { - return new SumoNetworkConverter(input, output, shapeFile, inputCRS, targetCRS, freeSpeedFactor); + public static SumoNetworkConverter newInstance(List input, Path output, String inputCRS, String targetCRS, + double freeSpeedFactor, LaneRestriction laneRestriction) { + return new SumoNetworkConverter(input, output, inputCRS, targetCRS, freeSpeedFactor, laneRestriction); } /** @@ -147,32 +146,20 @@ private static boolean isModeAllowed(String mode, SumoNetworkHandler.Edge edge, /** * Execute the converter, which includes conversion and writing the files * - * @see #convert(Network, Lanes) . + * @see #convert(Network). */ @Override public Integer call() throws Exception { Network network = NetworkUtils.createNetwork(); - Lanes lanes = LanesUtils.createLanesContainer(); - SumoNetworkHandler handler = convert(network, lanes); - - calculateLaneCapacities(network, lanes); - - // This needs to run without errors, otherwise network is broken - network.getLinks().values().forEach(link -> { - LanesToLinkAssignment l2l = lanes.getLanesToLinkAssignments().get(link.getId()); - if (l2l != null) - LanesUtils.createLanes(link, l2l); - }); + SumoNetworkHandler handler = convert(network); if (toCRS != null) ProjectionUtils.putCRS(network, toCRS); NetworkUtils.writeNetwork(network, output.toAbsolutePath().toString()); - new LanesWriter(lanes).write(output.toAbsolutePath().toString().replace(".xml", "-lanes.xml")); - writeGeometry(handler, output.toAbsolutePath().toString().replace(".xml", "-linkGeometries.csv")); writeFeatures(handler, output.toAbsolutePath().toString().replace(".xml", "-ft.csv")); @@ -196,19 +183,6 @@ public void writeFeatures(SumoNetworkHandler handler, String output) { } } - /** - * Calculates lane capacities, according to {@link LanesUtils}. - */ - public void calculateLaneCapacities(Network network, Lanes lanes) { - for (LanesToLinkAssignment l2l : lanes.getLanesToLinkAssignments().values()) { - Link link = network.getLinks().get(l2l.getLinkId()); - for (Lane lane : l2l.getLanes().values()) { - calculateAndSetCapacity(lane, - lane.getToLaneIds() == null || lane.getToLaneIds().isEmpty(), link, network); - } - } - } - /** * Writes link geometries. */ @@ -252,10 +226,9 @@ public void writeGeometry(SumoNetworkHandler handler, String path) { * Perform the actual conversion on given input data. * * @param network results will be added into this network. - * @param lanes resulting lanes are added into this object. * @return internal handler used for conversion */ - public SumoNetworkHandler convert(Network network, Lanes lanes) throws ParserConfigurationException, SAXException, IOException { + public SumoNetworkHandler convert(Network network) throws ParserConfigurationException, SAXException, IOException { log.info("Parsing SUMO network"); @@ -276,7 +249,6 @@ public SumoNetworkHandler convert(Network network, Lanes lanes) throws ParserCon } NetworkFactory f = network.getFactory(); - LanesFactory lf = lanes.getFactory(); Map linkProperties = LinkProperties.createLinkProperties(); @@ -299,7 +271,6 @@ public SumoNetworkHandler convert(Network network, Lanes lanes) throws ParserCon link.getAttributes().putAttribute(NetworkUtils.TYPE, edge.type); - link.setNumberOfLanes(edge.lanes.size()); Set modes = Sets.newHashSet(TransportMode.car, TransportMode.ride); SumoNetworkHandler.Type type = sumoHandler.types.get(edge.type); @@ -318,28 +289,33 @@ public SumoNetworkHandler convert(Network network, Lanes lanes) throws ParserCon link.setAllowedModes(modes); link.setLength(edge.getLength()); - LanesToLinkAssignment l2l = lf.createLanesToLinkAssignment(link.getId()); - for (SumoNetworkHandler.Lane lane : edge.lanes) { - Lane mLane = lf.createLane(Id.create(lane.id, Lane.class)); - mLane.setAlignment(lane.index); - mLane.setStartsAtMeterFromLinkEnd(lane.length); - l2l.addLane(mLane); + if (laneRestriction == LaneRestriction.REDUCE_CAR_LANES) { + + int size = edge.lanes.size(); + + SumoNetworkHandler.Lane lane = edge.lanes.get(0); + edge.lanes.removeIf(l -> l.allow != null && !l.allow.contains("passenger")); + + // Keep at least one lane + if (edge.lanes.isEmpty()) + edge.lanes.add(lane); + + int restricted = size - edge.lanes.size(); + if (restricted > 0) { + link.getAttributes().putAttribute("restricted_lanes", restricted); + } } + link.setNumberOfLanes(edge.lanes.size()); + // set link prop based on MATSim defaults LinkProperties prop = linkProperties.get(type.highway); double speed = type.speed; // incoming lane connected to the others // this is needed by matsim for lanes to work properly - if (edge.lanes.size() >= 1) { - Lane inLane = lf.createLane(Id.create(link.getId() + "_in", Lane.class)); - inLane.setStartsAtMeterFromLinkEnd(link.getLength()); - inLane.setAlignment(0); - l2l.getLanes().keySet().forEach(inLane::addToLaneId); - l2l.addLane(inLane); - + if (!edge.lanes.isEmpty()) { double laneSpeed = edge.lanes.get(0).speed; if (!Double.isNaN(laneSpeed) && laneSpeed > 0) { // use speed info of first lane @@ -358,81 +334,44 @@ public SumoNetworkHandler convert(Network network, Lanes lanes) throws ParserCon link.setFreespeed(LinkProperties.calculateSpeedIfSpeedTag(speed, freeSpeedFactor)); link.setCapacity(LinkProperties.getLaneCapacity(link.getLength(), prop) * link.getNumberOfLanes()); - lanes.addLanesToLinkAssignment(l2l); network.addLink(link); } - if (shapeFile != null) { - Geometry shp = calculateNetworkArea(shapeFile); - - // remove lanes outside survey area - for (Node node : network.getNodes().values()) { - if (!shp.contains(MGC.coord2Point(node.getCoord()))) { - node.getOutLinks().keySet().forEach(l -> lanes.getLanesToLinkAssignments().remove(l)); - node.getInLinks().keySet().forEach(l -> lanes.getLanesToLinkAssignments().remove(l)); - } - } - } - // clean up network new NetworkCleaner().run(network); - // also clean lanes - lanes.getLanesToLinkAssignments().keySet().removeIf(l2l -> !network.getLinks().containsKey(l2l)); + Set> ignored = new HashSet<>(); - for (List connections : sumoHandler.connections.values()) { - for (SumoNetworkHandler.Connection conn : connections) { + for (Map.Entry> kv : sumoHandler.connections.entrySet()) { - Id fromLink = Id.createLinkId(conn.from); - Id toLink = Id.createLinkId(conn.to); + Link link = network.getLinks().get(Id.createLinkId(kv.getKey())); - LanesToLinkAssignment l2l = lanes.getLanesToLinkAssignments().get(fromLink); + if (link != null) { + Set> outLinks = link.getToNode().getOutLinks().keySet(); + Set> allowed = kv.getValue().stream().map(c -> Id.createLinkId(c.to)).collect(Collectors.toSet()); - // link was removed - if (l2l == null) - continue; + Sets.SetView> diff = Sets.difference(outLinks, allowed); - Lane lane = l2l.getLanes().values().stream().filter(l -> l.getAlignment() == conn.fromLane).findFirst().orElse(null); - if (lane == null) { - log.warn("Could not find from lane in network for {}", conn); + if (outLinks.size() == diff.size()) { + ignored.add(link.getId()); continue; } - lane.addToLinkId(toLink); - } - } - - int removed = 0; - - Iterator it = lanes.getLanesToLinkAssignments().values().iterator(); - - // lanes needs to have a target, if missing we need to chose one - while (it.hasNext()) { - LanesToLinkAssignment l2l = it.next(); + if (!diff.isEmpty()) { + DisallowedNextLinks disallowed = new DisallowedNextLinks(); + for (Id id : diff) { + disallowed.addDisallowedLinkSequence(TransportMode.car, List.of(id)); + } - for (Lane lane : l2l.getLanes().values()) { - if (lane.getToLinkIds() == null && lane.getToLaneIds() == null) { - // chose first reachable link from this lane - Collection out = network.getLinks().get(l2l.getLinkId()).getToNode().getOutLinks().values(); - out.forEach(l -> lane.addToLinkId(l.getId())); - - log.warn("No target for lane {}, chosen {}", lane.getId(), out); + NetworkUtils.setDisallowedNextLinks(link, disallowed); } } + } - Set> targets = l2l.getLanes().values().stream() - .filter(l -> l.getToLinkIds() != null) - .map(Lane::getToLinkIds).flatMap(List::stream) - .collect(Collectors.toSet()); - - // remove superfluous lanes (both pointing to same link with not alternative) - if (targets.size() == 1 && network.getLinks().get(l2l.getLinkId()).getToNode().getOutLinks().size() <= 1) { - it.remove(); - removed++; - } + if (!ignored.isEmpty()) { + log.warn("Ignored turn restrictions for {} links with no connections: {}", ignored.size(), ignored); } - log.info("Removed {} superfluous lanes, total={}", removed, lanes.getLanesToLinkAssignments().size()); return sumoHandler; } @@ -480,4 +419,11 @@ private Node createNode(Network network, SumoNetworkHandler sumoHandler, String return node; } + /** + * How restricted lanes should be handled. + */ + public enum LaneRestriction { + IGNORE, REDUCE_CAR_LANES + } + } diff --git a/contribs/sumo/src/test/java/org/matsim/contrib/sumo/SumoNetworkConverterTest.java b/contribs/sumo/src/test/java/org/matsim/contrib/sumo/SumoNetworkConverterTest.java index d66ce733c45..2bb89ec4caa 100644 --- a/contribs/sumo/src/test/java/org/matsim/contrib/sumo/SumoNetworkConverterTest.java +++ b/contribs/sumo/src/test/java/org/matsim/contrib/sumo/SumoNetworkConverterTest.java @@ -3,21 +3,15 @@ import com.google.common.io.Resources; import org.junit.jupiter.api.Test; import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; +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.config.Config; -import org.matsim.core.config.ConfigUtils; import org.matsim.core.network.NetworkUtils; -import org.matsim.core.scenario.ScenarioUtils; -import org.matsim.lanes.LanesReader; -import org.matsim.lanes.LanesToLinkAssignment; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.List; -import java.util.SortedMap; public class SumoNetworkConverterTest { @@ -39,19 +33,10 @@ void convert() throws Exception { assert network.getNodes().size() == 21 : "Must contain 21 nodes"; assert network.getNodes().containsKey(Id.createNodeId("251106770")) : "Must contain specific id"; - Path lanes = Path.of(output.toString().replace(".xml", "-lanes.xml")); + Link link = network.getLinks().get(Id.createLinkId("-461905066#1")); - Config config = ConfigUtils.createConfig(); - Scenario scenario = ScenarioUtils.createScenario(config); - - LanesReader reader = new LanesReader(scenario); - reader.readFile(lanes.toString()); - - SortedMap, LanesToLinkAssignment> l2l = scenario.getLanes().getLanesToLinkAssignments(); - - System.out.println(l2l); - - assert l2l.containsKey(Id.createLinkId("-160346478#3")) : "Must contain link id"; + List>> disallowed = NetworkUtils.getDisallowedNextLinks(link).getDisallowedLinkSequences(TransportMode.car); + assert disallowed.contains(List.of(Id.createLinkId("461905066#0"))) : "Must contain disallowed link sequence"; Path geometry = Path.of(output.toString().replace(".xml", "-linkGeometries.csv"));