Skip to content

Commit

Permalink
adding a railway osm reader, similar to the existing highway approach
Browse files Browse the repository at this point in the history
  • Loading branch information
ikaddoura committed Mar 25, 2024
1 parent c49d991 commit 3630417
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package org.matsim.contrib.osm.networkReader;

import de.topobyte.osm4j.core.model.iface.OsmNode;
import de.topobyte.osm4j.core.model.iface.OsmWay;
import de.topobyte.osm4j.core.model.util.OsmModelUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Coord;
import org.matsim.core.utils.geometry.CoordinateTransformation;

import java.nio.file.Path;
import java.text.NumberFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;

class OsmRailwayParser extends OsmNetworkParser {

private static final Logger log = LogManager.getLogger(OsmRailwayParser.class);
private static final NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.UK);

private final CoordinateTransformation transformation;
private final Map<String, LinkProperties> linkProperties;
private final BiPredicate<Coord, Integer> linkFilter;
final ExecutorService executor;
Map<Long, ProcessedOsmWay> ways;
Map<Long, ProcessedOsmNode> nodes;
Map<Long, List<ProcessedOsmWay>> nodeReferences;

OsmRailwayParser(CoordinateTransformation transformation, Map<String, LinkProperties> linkProperties, BiPredicate<Coord, Integer> linkFilter, ExecutorService executor) {
super(transformation, linkProperties, linkFilter, executor);

this.transformation = transformation;
this.linkProperties = linkProperties;
this.linkFilter = linkFilter;
this.executor = executor;
}

public Map<Long, ProcessedOsmWay> getWays() {
return ways;
}

public Map<Long, ProcessedOsmNode> getNodes() {
return nodes;
}

void parse(Path inputFile) {

// make sure we have empty collections
ways = new ConcurrentHashMap<>();
nodes = new ConcurrentHashMap<>();
nodeReferences = new ConcurrentHashMap<>();

new PbfParser.Builder()
.setWaysHandler(this::handleWay)
.setExecutor(executor)
.build()
.parse(inputFile);

log.info("Finished reading ways");
log.info("Starting to read nodes");

new PbfParser.Builder()
.setNodeHandler(this::handleNode)
.setExecutor(executor)
.build()
.parse(inputFile);

log.info("finished reading nodes");
}

void handleNode(OsmNode osmNode) {

if (nodeReferences.containsKey(osmNode.getId())) {

List<ProcessedOsmWay> waysThatReferenceNode = nodeReferences.get(osmNode.getId());
Coord transformedCoord = transformation.transform(new Coord(osmNode.getLongitude(), osmNode.getLatitude()));

// 'testWhetherReferencingLinksAreInFilter' may be expensive because it might include a test whether the
// supplied coordinate is within a shape. Therefore we want to test as few cases as possible. Two cases are tested anyway:
// 1. if more than one way references this node, this node is an intersection and a possible end node for a
// matsim link.
// 2. if this node is either the start- or end node of a referencing way which makes it a candidate for a node
// in a matsim link as well
//
// if a way has both ends outside the filter and no intersections within the filter it will not be included
// in the final network. I think this is unlikely in real world scenarios, so I think we can live with this
// to achieve faster execution
List<ProcessedOsmWay> filteredReferencingLinks;
if (waysThatReferenceNode.size() > 1 || isEndNodeOfReferencingLink(osmNode, waysThatReferenceNode.get(0)))
filteredReferencingLinks = testWhetherReferencingLinksAreInFilter(transformedCoord, waysThatReferenceNode);
else
filteredReferencingLinks = Collections.emptyList();

ProcessedOsmNode result = new ProcessedOsmNode(osmNode.getId(), filteredReferencingLinks, transformedCoord);
this.nodes.put(result.getId(), result);

if (nodes.size() % 100000 == 0) {
log.info("Added " + numberFormat.format(nodes.size()) + " nodes");
}
}
}

void handleWay(OsmWay osmWay) {

Map<String, String> tags = OsmModelUtil.getTagsAsMap(osmWay);

if (isStreetOfInterest(tags)) {
LinkProperties linkProperty = linkProperties.get(tags.get(OsmTags.RAILWAY));
ProcessedOsmWay processedWay = ProcessedOsmWay.create(osmWay, tags, linkProperty);
ways.put(osmWay.getId(), processedWay);

// keep track of which node is referenced by which way
for (int i = 0; i < osmWay.getNumberOfNodes(); i++) {

long nodeId = osmWay.getNodeId(i);
nodeReferences.computeIfAbsent(nodeId, id -> Collections.synchronizedList(new ArrayList<>()))
.add(processedWay);
}

if (ways.size() % 10000 == 0) {
log.info("Added " + numberFormat.format(ways.size()) + " ways");
}
}
}

private boolean isStreetOfInterest(Map<String, String> tags) {
return tags.containsKey(OsmTags.RAILWAY) && linkProperties.containsKey(tags.get(OsmTags.RAILWAY));
}

private boolean isEndNodeOfReferencingLink(OsmNode node, ProcessedOsmWay processedOsmWay) {
return processedOsmWay.getEndNodeId() == node.getId() || processedOsmWay.getStartNode() == node.getId();
}

private List<ProcessedOsmWay> testWhetherReferencingLinksAreInFilter(Coord coord, List<ProcessedOsmWay> waysThatReferenceNode) {

return waysThatReferenceNode.stream()
.filter(way -> linkFilter.test(coord, way.getLinkProperties().hierarchyLevel))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.matsim.contrib.osm.networkReader;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.function.BiPredicate;
import java.util.function.Predicate;

import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.network.Link;
import org.matsim.core.network.NetworkUtils;

public final class OsmRailwayReader extends SupersonicOsmNetworkReader {

public OsmRailwayReader(OsmNetworkParser parser,
Predicate<Long> preserveNodeWithId,
BiPredicate<Coord, Integer> includeLinkAtCoordWithHierarchy,
AfterLinkCreated afterLinkCreated, double freeSpeedFactor, double adjustCapacityLength, boolean storeOriginalGeometry) {

super(parser, preserveNodeWithId, includeLinkAtCoordWithHierarchy, (link, tags, direction) -> handleLink(link, tags, direction, afterLinkCreated), freeSpeedFactor, adjustCapacityLength, storeOriginalGeometry);
}

private static void handleLink(Link link, Map<String, String> tags, SupersonicOsmNetworkReader.Direction direction, AfterLinkCreated outfacingCallback) {

String railwayType = tags.get(OsmTags.RAILWAY);
link.getAttributes().putAttribute("osm_way_type", "railway");
link.getAttributes().putAttribute(NetworkUtils.TYPE, railwayType);

for (String tag : tags.keySet()) {
link.getAttributes().putAttribute(tag, tags.get(tag));
}

setAllowedModes(link, tags);

outfacingCallback.accept(link, tags, direction);
}

private static void setAllowedModes(Link link, Map<String, String> tags) {
if (tags.containsKey(OsmTags.SERVICE) && tags.get(OsmTags.SERVICE).equals("yard")) {
Set<String> allowedModes = new HashSet<>();
link.setAllowedModes(allowedModes);
} else {
// everything else should be allowed for rail
Set<String> allowedModes = new HashSet<>();
allowedModes.add("rail");
link.setAllowedModes(allowedModes);
}
}

@Override
Collection<Link> createLinks(WaySegment segment) {
Collection<Link> links = super.createLinks(segment);
return links;
}

public static class Builder extends AbstractBuilder<OsmRailwayReader> {

@Override
OsmRailwayReader createInstance() {
OsmRailwayParser parser = new OsmRailwayParser(coordinateTransformation, linkProperties, includeLinkAtCoordWithHierarchy, Executors.newWorkStealingPool());
return new OsmRailwayReader(parser, preserveNodeWithId, includeLinkAtCoordWithHierarchy, afterLinkCreated, freeSpeedFactor, adjustCapacityLength, storeOriginalGeometry);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,14 @@ public class OsmTags {
public static final String CROSSING = "crossing";
public static final String TYPE = "type";
public static final String RESTRICTION = "restriction";

public static final String RAILWAY = "railway";
public static final String RAIL = "rail";
public static final String NARROW_GAUGE = "narrow_gauge";
public static final String TRAM = "tram";
public static final String FUNICULAR = "funicular";
public static final String SUBWAY = "subway";
public static final String LIGHT_RAIL = "light_rail";
public static final String MONORAIL = "monorail";
public static final String USAGE = "usage";
}
7 changes: 7 additions & 0 deletions contribs/railsim/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.matsim.contrib</groupId>
<artifactId>osm</artifactId>
<version>16.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@

package ch.sbb.matsim.contrib.railsim;

import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule;
import org.matsim.api.core.v01.Scenario;
import org.matsim.core.config.Config;
import org.matsim.core.config.ConfigUtils;
import org.matsim.core.controler.Controler;
import org.matsim.core.controler.OutputDirectoryHierarchy;
import org.matsim.core.scenario.ScenarioUtils;

import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule;

/**
* Example script that shows how to use railsim included in this contrib.
*/
Expand All @@ -37,12 +38,13 @@ private RunRailsimExample() {

public static void main(String[] args) {

if (args.length == 0) {
System.err.println("Path to config is required as first argument.");
System.exit(2);
String configFilename;
if (args.length != 0) {
configFilename = args[0];
} else {
configFilename = "test/input/ch/sbb/matsim/contrib/railsim/integration/microOlten/config.xml";
}

String configFilename = args[0];
Config config = ConfigUtils.loadConfig(configFilename);
config.controller().setOverwriteFileSetting(OutputDirectoryHierarchy.OverwriteFileSetting.deleteDirectoryIfExists);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ch.sbb.matsim.contrib.railsim.prepare;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.network.NetworkWriter;
import org.matsim.contrib.osm.networkReader.LinkProperties;
import org.matsim.contrib.osm.networkReader.OsmRailwayReader;
import org.matsim.contrib.osm.networkReader.OsmTags;
import org.matsim.core.network.algorithms.NetworkCleaner;
import org.matsim.core.utils.geometry.CoordinateTransformation;
import org.matsim.core.utils.geometry.transformations.TransformationFactory;

public class RunRailOsmNetworkReader {

private static final String inputFile = "path/to/file.osm.pbf";
private static final String outputFile = "path/to/network.xml.gz";

private static final CoordinateTransformation coordinateTransformation = TransformationFactory.getCoordinateTransformation(TransformationFactory.WGS84, "EPSG:2056");

public static void main(String[] args) {

ConcurrentMap<String, LinkProperties> linkProperties = new ConcurrentHashMap<>();
linkProperties.put(OsmTags.RAIL, new LinkProperties(1, 1, 30., 1000., false));
linkProperties.put(OsmTags.NARROW_GAUGE, new LinkProperties(2, 1, 30., 1000., false));
linkProperties.put(OsmTags.LIGHT_RAIL, new LinkProperties(3, 1, 30., 1000., false));
linkProperties.put(OsmTags.SUBWAY, new LinkProperties(4, 1, 30., 1000., false));
linkProperties.put(OsmTags.MONORAIL, new LinkProperties(5, 1, 30., 1000., false));

Network network = new OsmRailwayReader.Builder()
.setCoordinateTransformation(coordinateTransformation)
.setLinkProperties(linkProperties)
.setPreserveNodeWithId(id -> true) // this filter keeps the detailed geometries
.build()
.read(inputFile);

network.getAttributes().putAttribute("data_origin", "OSM");

new NetworkCleaner().run(network);
new NetworkWriter(network).write(outputFile);
}
}

0 comments on commit 3630417

Please sign in to comment.