Skip to content

Commit

Permalink
Match only features within distance limit
Browse files Browse the repository at this point in the history
Without this change an edge can be matched to a feature if their bounding boxes overlap, even if they are far apart.
  • Loading branch information
ansoncfit committed Aug 18, 2023
1 parent df74063 commit 7689347
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
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.transit.TransportNetwork;
import com.conveyal.r5.transit.TransportNetworkCache;
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 @@ -61,7 +60,7 @@ public boolean apply (TransportNetwork network) {
network.streetLayer.edgeStore.flags = new TIntArrayList(network.streetLayer.edgeStore.flags);
ShapefileMatcher shapefileMatcher = new ShapefileMatcher(network.streetLayer);
try {
shapefileMatcher.match(localFile.getAbsolutePath(), ltsAttribute);
shapefileMatcher.match(localFile.getAbsolutePath(), ltsAttribute, matchLimitMeters);
} catch (Exception e) {
addError(ExceptionUtils.shortAndLongString(e));
}
Expand Down
31 changes: 21 additions & 10 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,7 +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;

/**
Expand Down Expand Up @@ -59,6 +61,8 @@ public class ShapefileMatcher {
private StreetLayer streets;
private int ltsAttributeIndex = -1;

private double matchLimitMeters;

public ShapefileMatcher (StreetLayer streets) {
this.streets = streets;
}
Expand All @@ -72,12 +76,15 @@ public ShapefileMatcher (StreetLayer streets) {
* 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.
*/
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 =
Expand Down Expand Up @@ -107,19 +114,23 @@ public void match (String shapefileName, String attributeName) {
streets.edgeStore.nEdgePairs() - edgePairCounter.getCount());
}

// 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ 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.match(SHAPE_FILE, SHAPE_FILE_ATTRIBUTE, 2.0);
}

private static StreetLayer loadStreetLayer () {
Expand Down

0 comments on commit 7689347

Please sign in to comment.