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

Speed shapefile matching #812

Draft
wants to merge 18 commits into
base: dev
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ public void writeGeotiff (OutputStream out) {
for (int x = 0; x < extents.width; x++) {
for (int n = 0; n < travelTimeResult.nSamplesPerPoint; n++) {
val = travelTimeResult.values[n][(y * extents.width + x)];
if (val < FastRaptorWorker.UNREACHED) raster.setSample(x, y, n, val);
if (val < FastRaptorWorker.UNREACHED) {
raster.setSample(x, y, n, val);
} else {
raster.setSample(x, y, n, 999);
}
}
}
}
Expand Down Expand Up @@ -199,4 +203,4 @@ public void writeGeotiff (OutputStream out) {

}

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.conveyal.r5.analyst.scenario;

import com.conveyal.r5.shapefile.ShapefileMatcher;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.JavaType;
Expand Down
11 changes: 5 additions & 6 deletions src/main/java/com/conveyal/r5/analyst/scenario/ShapefileLts.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import com.conveyal.analysis.datasource.DataSourceException;
import com.conveyal.file.FileStorageFormat;
import com.conveyal.file.FileStorageKey;
import com.conveyal.r5.rastercost.ElevationLoader;
import com.conveyal.r5.shapefile.ShapefileMatcher;
import com.conveyal.r5.shapefile.SpeedMatcher;
import com.conveyal.r5.transit.TransportNetwork;
import com.conveyal.r5.transit.TransportNetworkCache;
import com.conveyal.r5.util.ExceptionUtils;
Expand All @@ -24,8 +23,6 @@
*/
public class ShapefileLts extends Modification {

public String ltsDataSource;

/**
* ID of the linear shapefile DataSource containing bicycle LTS to be matched to streets.
* We must assume its type because the workers don't have access to the DataStore metadata.
Expand All @@ -35,6 +32,8 @@ public class ShapefileLts extends Modification {
/** The name of the numeric attribute within the ltsDataSource containing LTS values from 1-4. */
public String ltsAttribute = "lts";

public double matchLimitMeters = 3.0;

private FileStorageKey fileStorageKey;

private File localFile;
Expand All @@ -59,9 +58,9 @@ public boolean apply (TransportNetwork network) {
// ModifyStreets, where all affected edges are marked deleted and then recreated in the augmented lists.
// The appraoch here assumes a high percentage of edges changed, while ModifyStreets assumes a small percentage.
network.streetLayer.edgeStore.flags = new TIntArrayList(network.streetLayer.edgeStore.flags);
ShapefileMatcher shapefileMatcher = new ShapefileMatcher(network.streetLayer);
SpeedMatcher shapefileMatcher = new SpeedMatcher(network.streetLayer);
try {
shapefileMatcher.match(localFile.getAbsolutePath(), ltsAttribute);
shapefileMatcher.match(localFile.getAbsolutePath(), ltsAttribute, matchLimitMeters);
} catch (Exception e) {
addError(ExceptionUtils.shortAndLongString(e));
}
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/conveyal/r5/shapefile/LtsMatcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.conveyal.r5.shapefile;

import com.conveyal.r5.streets.EdgeStore;
import com.conveyal.r5.streets.StreetLayer;
import org.opengis.feature.simple.SimpleFeature;

import static com.conveyal.r5.streets.EdgeStore.EdgeFlag.BIKE_LTS_EXPLICIT;
import static com.conveyal.r5.streets.EdgeStore.intToLts;

public class LtsMatcher extends ShapefileMatcher {

public LtsMatcher (StreetLayer streets) {
super(streets);
}

/**
* Copy LTS attribute from the supplied feature to the pair of edges, setting the BIKE_LTS_EXPLICIT flag. This
* will prevent Conveyal OSM-inferred LTS from overwriting the shapefile-derived LTS.
*
* In current usage this is applied after the OSM is already completely loaded and converted to network edges, so
* it overwrites any data from OSM. Perhaps instead of BIKE_LTS_EXPLICIT we should have an LTS source flag:
* OSM_INFERRED, OSM_EXPLICIT, SHAPEFILE_MATCH etc. This could also apply to things like speeds and slopes. The
* values could be retained only for the duration of network building unless we have a reason to keep them.
*/
@Override
void setEdgePair (SimpleFeature feature, int attributeIndex, EdgeStore.Edge edge) {
// Set flags on forward and backward edges to match those on feature attribute
// TODO reuse code from LevelOfTrafficStressLabeler.label()
int lts = ((Number) feature.getAttribute(attributeIndex)).intValue();
if (lts < 1 || lts > 4) {
LOG.error("Clamping LTS value to range [1...4]. Value in attribute is {}", lts);
}
EdgeStore.EdgeFlag ltsFlag = intToLts(lts);
edge.setFlag(BIKE_LTS_EXPLICIT);
edge.setFlag(ltsFlag);
edge.advance();
edge.setFlag(BIKE_LTS_EXPLICIT);
edge.setFlag(ltsFlag);
}
}
74 changes: 39 additions & 35 deletions src/main/java/com/conveyal/r5/shapefile/ShapefileMatcher.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.conveyal.r5.shapefile;

import com.conveyal.r5.common.SphericalDistanceLibrary;
import com.conveyal.r5.streets.EdgeStore;
import com.conveyal.r5.streets.StreetLayer;
import com.conveyal.r5.util.LambdaCounter;
import com.conveyal.r5.util.ShapefileReader;
import org.locationtech.jts.algorithm.distance.DiscreteHausdorffDistance;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.index.strtree.STRtree;
import org.locationtech.jts.operation.distance.DistanceOp;
import org.opengis.feature.simple.SimpleFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -18,9 +21,6 @@
import java.util.List;
import java.util.stream.IntStream;

import static com.conveyal.r5.streets.EdgeStore.intToLts;
import static com.conveyal.r5.streets.EdgeStore.EdgeFlag.BIKE_LTS_EXPLICIT;

/**
* The class ShapefileMain converts shapefiles to OSM data, which is in turn converted to R5 street networks.
* For various reasons we may prefer to load true OSM data, then overlay the data from the shapefile onto the OSM.
Expand All @@ -30,7 +30,7 @@
* two main ways: by creating new nodes wherever shapes cross, or by assuming any nodes in the same location are the
* same node (and not two nodes stacked vertically for example). The former does not allow for separation of tunnels
* and bridges. The latter requires exactly placed nodes on two or more features for every intersection. For example,
* the end of a road at a T insersection requires a node in the same location on the perpendicular road.
* the end of a road at a T intersection requires a node in the same location on the perpendicular road.
* It would be possible to hybridize these approaches, for example automatically inserting nodes at T intersections but
* requiring explicit duplicate nodes at crossing intersections to distinguish them from bridges and tunnels. The
* details get tricky though: specifically, how close does a line or point have to be to another before it's connected?
Expand All @@ -51,34 +51,34 @@
*
* This class matches a supplied shapefile to an already-built network.
*/
public class ShapefileMatcher {
public abstract class ShapefileMatcher {

public static final Logger LOG = LoggerFactory.getLogger(ShapefileMatcher.class);

private STRtree featureIndex;
private StreetLayer streets;
private int ltsAttributeIndex = -1;
private int attributeIndex = -1;

private double matchLimitMeters;

public ShapefileMatcher (StreetLayer streets) {
this.streets = streets;
}

/**
* Match each pair of edges in the street layer to a feature in the shapefile. Copy LTS attribute from that feature
* to the pair of edges, setting the BIKE_LTS_EXPLICIT flag. This can prevent Conveyal OSM-inferred LTS from
* overwriting the shapefile-derived LTS if this matching process is applied during network build.
* In current usage this is applied after the OSM is already completely loaded and converted to network edges, so
* it overwrites any data from OSM. Perhaps instead of BIKE_LTS_EXPLICIT we should have an LTS source flag:
* OSM_INFERRED, OSM_EXPLICIT, SHAPEFILE_MATCH etc. This could also apply to things like speeds and slopes.
* The values could be retained only for the duration of network building unless we have a reason to keep them.
* Match each pair of edges in the street layer to a feature in the shapefile, then set flags on the edges.
*/
public void match (String shapefileName, String attributeName) {
public void match (String shapefileName, String attributeName, double matchLimitMeters) {
try {
indexFeatures(shapefileName, attributeName);
} catch (Throwable t) {
throw new RuntimeException("Could not load and index shapefile.", t);
}

this.matchLimitMeters = matchLimitMeters;

LOG.info("Matching edges and setting bike LTS flags...");

// Even single-threaded this is pretty fast for small extracts, but it's readily paralellized.
final LambdaCounter edgePairCounter =
new LambdaCounter(LOG, streets.edgeStore.nEdgePairs(), 25_000, "Edge pair {}/{}");
Expand All @@ -87,17 +87,7 @@ public void match (String shapefileName, String attributeName) {
LineString edgeGeometry = edge.getGeometry();
SimpleFeature bestFeature = findBestMatch(edgeGeometry);
if (bestFeature != null) {
// Set flags on forward and backward edges to match those on feature attribute
// TODO reuse code from LevelOfTrafficStressLabeler.label()
int lts = ((Number) bestFeature.getAttribute(ltsAttributeIndex)).intValue();
if (lts < 1 || lts > 4) {
LOG.error("LTS should be in range [1...4]. Value in attribute is {}", lts);
}
edge.setFlag(BIKE_LTS_EXPLICIT);
edge.setLts(lts);
edge.advance();
edge.setFlag(BIKE_LTS_EXPLICIT);
edge.setLts(lts);
setEdgePair(bestFeature, attributeIndex, edge);
edgePairCounter.increment();
}
});
Expand All @@ -107,37 +97,51 @@ public void match (String shapefileName, String attributeName) {
streets.edgeStore.nEdgePairs() - edgePairCounter.getCount());
}


/**
* Set the appropriate edge flag to the value of the specified attribute in the corresponding (matched) feature.
* Subclasses are responsible for implementation details on which flag to set.
*/
void setEdgePair (SimpleFeature feature, int attributeIndex, EdgeStore.Edge edge) {
// Do nothing; subclasses should override.
}

// Match metric is currently Hausdorff distance, eventually replace with something that accounts for overlap length.
// For features within the given matchLimitMeters, choose the closest based on Hausdorff distance.
// This could eventually be replaced with something that accounts for overlap length.

private SimpleFeature findBestMatch (LineString edgeGeometry) {
SimpleFeature bestFeature = null;
double bestDistance = Double.POSITIVE_INFINITY;
List<SimpleFeature> features = featureIndex.query(edgeGeometry.getEnvelopeInternal());
for (SimpleFeature feature : features) {
// Note that we're using unprojected coordinates so x distance is exaggerated realtive to y.
DiscreteHausdorffDistance dhd = new DiscreteHausdorffDistance(extractLineString(feature), edgeGeometry);
double distance = dhd.distance();
// distance = overlap(extractLineString(feature), edgeGeometry);
if (bestDistance > distance) {
bestDistance = distance;
bestFeature = feature;
Coordinate[] nearestPoints = new DistanceOp(extractLineString(feature), edgeGeometry).nearestPoints();
if (SphericalDistanceLibrary.fastDistance(nearestPoints[0], nearestPoints[1]) < matchLimitMeters) {
// Note that we're using unprojected coordinates so x distance is exaggerated relative to y.
DiscreteHausdorffDistance dhd = new DiscreteHausdorffDistance(extractLineString(feature), edgeGeometry);
double distance = dhd.distance();
// distance = overlap(extractLineString(feature), edgeGeometry);
if (bestDistance > distance) {
bestDistance = distance;
bestFeature = feature;
}
}
}
return bestFeature;
}

// Index is in floating-point WGS84
public void indexFeatures (String shapefileName, String attributeName) throws Throwable {
private void indexFeatures (String shapefileName, String attributeName) throws Throwable {
featureIndex = new STRtree();
ShapefileReader reader = new ShapefileReader(new File(shapefileName));
Envelope envelope = reader.wgs84Bounds();
LOG.info("Indexing shapefile features");
// TODO add wgs84List(), pre-unwrap linestrings and attributes
reader.wgs84Stream().forEach(feature -> {
LineString featureGeom = extractLineString(feature);
featureIndex.insert(featureGeom.getEnvelopeInternal(), feature);
});
featureIndex.build(); // Index is now immutable.
ltsAttributeIndex = reader.findAttribute(attributeName, Number.class);
attributeIndex = reader.findAttribute(attributeName, Number.class);
}

// All the repetitive casting for multilinestring features containing a single linestring.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public class ShapefileMatcherMain {

public static void main (String[] args) throws Throwable {
StreetLayer streetLayer = loadStreetLayer();
ShapefileMatcher shapefileMatcher = new ShapefileMatcher(streetLayer);
shapefileMatcher.match(SHAPE_FILE, SHAPE_FILE_ATTRIBUTE);
ShapefileMatcher shapefileMatcher = new LtsMatcher(streetLayer);
shapefileMatcher.match(SHAPE_FILE, SHAPE_FILE_ATTRIBUTE, 2.0);
}

private static StreetLayer loadStreetLayer () {
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/com/conveyal/r5/shapefile/SpeedMatcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.conveyal.r5.shapefile;

import com.conveyal.r5.streets.EdgeStore;
import com.conveyal.r5.streets.StreetLayer;
import org.opengis.feature.simple.SimpleFeature;

public class SpeedMatcher extends ShapefileMatcher {

static double KPH_PER_MPH = 1.609344;
public SpeedMatcher (StreetLayer streets) {
super(streets);
}

/**
* Set the speed value of an edge using a supplied feature with a speed attribute (in kilometers per hour)
*/
@Override
void setEdgePair (SimpleFeature feature, int attributeIndex, EdgeStore.Edge edge) {
if (feature.getAttribute(attributeIndex) != null) {
double speedKph = KPH_PER_MPH * ((Number) feature.getAttribute(attributeIndex)).doubleValue();
edge.setSpeedKph(speedKph);
edge.advance();
edge.setSpeedKph(speedKph);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
package com.conveyal.r5.streets;

import com.conveyal.r5.common.GeometryUtils;
import com.conveyal.r5.profile.ProfileRequest;
import com.conveyal.r5.profile.StreetMode;
import com.conveyal.r5.rastercost.CostField;
import com.conveyal.r5.rastercost.SunLoader;
import org.apache.commons.math3.util.FastMath;
import org.locationtech.jts.algorithm.Angle;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -40,12 +34,14 @@ public MultistageTraversalTimeCalculator (TraversalTimeCalculator base, List<Cos
public int traversalTimeSeconds (EdgeStore.Edge currentEdge, StreetMode streetMode, ProfileRequest req) {
final int baseTraversalTimeSeconds = base.traversalTimeSeconds(currentEdge, streetMode, req);
int t = baseTraversalTimeSeconds;
for (CostField costField : costFields) {
t += costField.additionalTraversalTimeSeconds(currentEdge, baseTraversalTimeSeconds);
}
if (t < 1) {
LOG.warn("Cost was negative or zero. Clamping to 1 second.");
t = 1;
if (!StreetMode.CAR.equals(streetMode)) {
for (CostField costField : costFields) {
t += costField.additionalTraversalTimeSeconds(currentEdge, baseTraversalTimeSeconds);
}
if (t < 1) {
LOG.warn("Cost was negative or zero. Clamping to 1 second.");
t = 1;
}
}
return t;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ private TransportNetworkConfig loadNetworkConfig (String networkId) {
network.transitLayer.buildDistanceTables(null);

Set<StreetMode> buildGridsForModes = Sets.newHashSet(StreetMode.WALK);
buildGridsForModes.add(StreetMode.CAR);
if (networkConfig != null && networkConfig.buildGridsForModes != null) {
buildGridsForModes.addAll(networkConfig.buildGridsForModes);
}
Expand Down
Loading