Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Roadworks-aware routing #5

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@

<properties>
<geotools.version>20.1</geotools.version>
<geotools.wfs.version>16.5</geotools.wfs.version>
<geotools.wfs.version>20.1</geotools.wfs.version>
<!-- Upgrading jackson version might break serialization of null values for GraphQL output fields. -->
<jackson.version>2.8.11</jackson.version>
<jersey.version>2.18</jersey.version>
Expand Down Expand Up @@ -508,7 +508,7 @@
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-wfs</artifactId>
<artifactId>gt-wfs-ng</artifactId>
<version>${geotools.wfs.version}</version>
</dependency>
<!-- provides EPSG database for projections (shapefile loading) -->
Expand Down
24 changes: 9 additions & 15 deletions src/main/java/org/opentripplanner/routing/core/RoutingContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

import com.google.common.collect.Iterables;
import org.locationtech.jts.geom.LineString;
import org.opentripplanner.model.Agency;
import org.opentripplanner.api.resource.DebugOutput;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.model.CalendarService;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.Stop;
import org.opentripplanner.model.calendar.ServiceDate;
import org.opentripplanner.model.CalendarService;
import org.opentripplanner.api.resource.DebugOutput;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.routing.algorithm.strategies.EuclideanRemainingWeightHeuristic;
import org.opentripplanner.routing.algorithm.strategies.RemainingWeightHeuristic;
import org.opentripplanner.routing.algorithm.strategies.TrivialRemainingWeightHeuristic;
Expand All @@ -23,6 +22,7 @@
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.location.StreetLocation;
import org.opentripplanner.routing.location.TemporaryStreetLocation;
import org.opentripplanner.routing.roadworks.RoadworksSource;
import org.opentripplanner.routing.services.OnBoardDepartService;
import org.opentripplanner.routing.vertextype.TemporaryVertex;
import org.opentripplanner.routing.vertextype.TransitStop;
Expand All @@ -31,17 +31,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.*;

/**
* A RoutingContext holds information needed to carry out a search for a particular TraverseOptions, on a specific graph.
Expand Down Expand Up @@ -361,6 +351,10 @@ public void check() {
}
}

public RoadworksSource getRoadworksSource() {
return Optional.ofNullable(graph.roadworksSource).orElse(new RoadworksSource());
}

/**
* Cache ServiceDay objects representing which services are running yesterday, today, and tomorrow relative to the search time. This information
* is very heavily used (at every transit boarding) and Date operations were identified as a performance bottleneck. Must be called after the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,9 @@ private double calculateCarSpeed(RoutingRequest options) {
public double calculateSpeed(RoutingRequest options, TraverseMode traverseMode, long timeMillis) {
if (traverseMode == null) {
return Double.NaN;
} else if (traverseMode.isDriving() && options.getRoutingContext().getRoadworksSource().isBlocked(this)) {
// if the street blocked due to roadworks then the driving speed is set to 0 meters/second, ie. not traversable
return 1;
} else if (traverseMode.isDriving()) {
// NOTE: Automobiles have variable speeds depending on the edge type
return calculateCarSpeed(options);
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/opentripplanner/routing/graph/Graph.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.opentripplanner.routing.edgetype.TripPattern;
import org.opentripplanner.routing.flex.FlexIndex;
import org.opentripplanner.routing.impl.DefaultStreetVertexIndexFactory;
import org.opentripplanner.routing.roadworks.RoadworksSource;
import org.opentripplanner.routing.services.StreetVertexIndexFactory;
import org.opentripplanner.routing.services.StreetVertexIndexService;
import org.opentripplanner.routing.services.notes.StreetNotesService;
Expand Down Expand Up @@ -129,6 +130,8 @@ public class Graph implements Serializable {

public transient TimetableSnapshotSource timetableSnapshotSource = null;

public transient RoadworksSource roadworksSource = new RoadworksSource(30082004L);

private transient List<GraphBuilderAnnotation> graphBuilderAnnotations = new LinkedList<GraphBuilderAnnotation>(); // initialize for tests

private Map<String, Collection<Agency>> agenciesForFeedId = new HashMap<>();
Expand Down Expand Up @@ -1105,4 +1108,6 @@ public void setUseFlexService(boolean useFlexService) {
}
this.useFlexService = useFlexService;
}

public RoadworksSource getRoadworksSource() { return roadworksSource; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.opentripplanner.routing.roadworks;

import com.google.common.collect.Sets;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.graph.Edge;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

public class RoadworksSource {

private Set<Long> blockedWayIds;
private Set<Integer> blockedEdgeIds = new HashSet<>();

public RoadworksSource() {
this.blockedWayIds = Sets.newHashSet();
}

public RoadworksSource(Long ...blockedWayId) {
this.blockedWayIds = Sets.newHashSet(blockedWayId);
}


public boolean isBlocked(StreetEdge edge){
return blockedWayIds.contains(edge.wayId);
}

public void addBlockedEdges(Collection<Edge> edges) {
Set<Integer> ids = edges.stream().map(Edge::getId).collect(Collectors.toSet());
blockedEdgeIds.addAll(ids);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.opentripplanner.updater.car_park.CarParkUpdater;
import org.opentripplanner.updater.example.ExampleGraphUpdater;
import org.opentripplanner.updater.example.ExamplePollingGraphUpdater;
import org.opentripplanner.updater.roadworks.WFSRoadworksPollingGraphUpdater;
import org.opentripplanner.updater.stoptime.MqttGtfsRealtimeUpdater;
import org.opentripplanner.updater.stoptime.PollingStoptimeUpdater;
import org.opentripplanner.updater.stoptime.WebsocketGtfsRealtimeUpdater;
Expand Down Expand Up @@ -99,6 +100,9 @@ else if (type.equals("example-polling-updater")) {
else if (type.equals("winkki-polling-updater")) {
updater = new WinkkiPollingGraphUpdater();
}
else if (type.equals("wfs-roadworks-updater")) {
updater = new WFSRoadworksPollingGraphUpdater();
}
}

if (updater == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package org.opentripplanner.updater.roadworks;

import com.fasterxml.jackson.databind.JsonNode;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.wfs.WFSDataStore;
import org.geotools.data.wfs.WFSDataStoreFactory;
import org.geotools.feature.FeatureIterator;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.FactoryException;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.roadworks.RoadworksSource;
import org.opentripplanner.routing.services.notes.NoteMatcher;
import org.opentripplanner.routing.services.notes.StreetNotesService;
import org.opentripplanner.updater.GraphUpdaterManager;
import org.opentripplanner.updater.GraphWriterRunnable;
import org.opentripplanner.updater.PollingGraphUpdater;
import org.opentripplanner.updater.street_notes.WinkkiPollingGraphUpdater;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

/**
* A graph updater that reads a WFS-interface and updates a RoadworksSource.
* Useful when reading geodata from legacy/external sources, which are not based on OSM
* and where data has to be matched to the street network.
*
* Classes that extend this class should provide getNote which parses the WFS features
* into notes. Also the implementing classes should be added to the GraphUpdaterConfigurator
*
* @see WinkkiPollingGraphUpdater
* <pre>
* type = wfs-roadworks-updater
* frequencySec = 21600
* url = https://baustellen.strassen.baden-wuerttemberg.de/bis_wfs/wfs&Version=1.1.0&Request=GetCapabilities
* featureType = bis:Baustelle
* </pre>
*
* @author Leonard Ehrenfried
*/
public class WFSRoadworksPollingGraphUpdater extends PollingGraphUpdater {
protected Graph graph;

private GraphUpdaterManager updaterManager;

private URL url;
private String featureType;
private Query query;

private FeatureSource<SimpleFeatureType, SimpleFeature> featureSource;
private RoadworksSource roadworksSource = new RoadworksSource();

// How much should the geometries be padded with in order to be sure they intersect with graph edges
private static final double SEARCH_RADIUS_M = 1;
private static final double SEARCH_RADIUS_DEG = SphericalDistanceLibrary.metersToDegrees(SEARCH_RADIUS_M);

// Set the matcher type for the notes, can be overridden in extending classes
private static final NoteMatcher NOTE_MATCHER = StreetNotesService.ALWAYS_MATCHER;

private static Logger LOG = LoggerFactory.getLogger(WFSRoadworksPollingGraphUpdater.class);

/**
* Here the updater can be configured using the properties in the file 'Graph.properties'.
* The property frequencySec is already read and used by the abstract base class.
*/
@Override
protected void configurePolling(Graph graph, JsonNode config) throws Exception {
url = new URL(config.path("url").asText());
featureType = config.path("featureType").asText();
this.graph = graph;
LOG.info("Configured WFS polling updater: frequencySec={}, url={} and featureType={}",
pollingPeriodSeconds, url.toString(), featureType);
}

/**
* Here the updater gets to know its parent manager to execute GraphWriterRunnables.
*/
@Override
public void setGraphUpdaterManager(GraphUpdaterManager updaterManager) {
this.updaterManager = updaterManager;
}

/**
* Setup the WFS data source and add the DynamicStreetNotesSource to the graph
*/
@Override
public void setup(Graph graph) throws IOException, FactoryException {
LOG.info("Setup WFS polling updater");
HashMap<String, Serializable> connectionParameters = new HashMap<>();
connectionParameters.put(WFSDataStoreFactory.URL.key, url);
connectionParameters.put(WFSDataStoreFactory.LENIENT.key, true);
WFSDataStore data = (new WFSDataStoreFactory()).createDataStore(connectionParameters);
query = new Query(featureType); // Read only single feature type from the source
query.setCoordinateSystem(CRS.decode("EPSG:4326", true)); // Get coordinates in WGS-84
featureSource = data.getFeatureSource(featureType);
graph.roadworksSource = roadworksSource;
}

@Override
public void teardown() {
LOG.info("Teardown WFS polling updater");
}

/**
* The function is run periodically by the update manager.
* The extending class should provide the getNote method. It is not implemented here
* as the requirements for different updaters can be vastly different dependent on the data source.
*/
@Override
protected void runPolling() throws IOException{
LOG.info("Run WFS polling updater with hashcode: {}", this.hashCode());

FeatureIterator<SimpleFeature> features = featureSource.getFeatures(query).features();

List<Edge> blockedEdges = new LinkedList<>();

while ( features.hasNext()){
SimpleFeature feature = features.next();
if (feature.getDefaultGeometry() == null) continue;

Geometry geom = (Geometry) feature.getDefaultGeometry();
Geometry searchArea = geom.buffer(SEARCH_RADIUS_DEG);
Collection<Edge> edges = graph.streetIndex.getEdgesForEnvelope(searchArea.getEnvelopeInternal());


blockedEdges.addAll(edges);
}

roadworksSource.addBlockedEdges(blockedEdges);

updaterManager.execute(new WFSGraphWriter());
}

private class WFSGraphWriter implements GraphWriterRunnable {
public void run(Graph graph) {
graph.roadworksSource = roadworksSource;
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
Expand Down Expand Up @@ -104,7 +105,7 @@ public void setGraphUpdaterManager(GraphUpdaterManager updaterManager) {
@Override
public void setup(Graph graph) throws IOException, FactoryException {
LOG.info("Setup WFS polling updater");
HashMap<String, Object> connectionParameters = new HashMap<>();
HashMap<String, Serializable> connectionParameters = new HashMap<>();
connectionParameters.put(WFSDataStoreFactory.URL.key, url);
WFSDataStore data = (new WFSDataStoreFactory()).createDataStore(connectionParameters);
query = new Query(featureType); // Read only single feature type from the source
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/org/opentripplanner/ConstantsForTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class ConstantsForTests {

public static final String OSLO_MINIMAL_OSM = "src/test/resources/oslo/oslo_osm_minimal.pbf";

public static final String HERRENBERG_OSM = "src/test/resources/herrenberg/herrenberg.osm.pbf";

public static final String HSL_MINIMAL_GTFS = "src/test/resources/hsl/hsl_gtfs_minimal.zip";

public static final String VERMONT_GTFS = "/vermont/ruralcommunity-flex-vt-us.zip";
Expand Down
Loading