Better
This commit is contained in:
parent
ca771a7edf
commit
d7f844d566
12 changed files with 198 additions and 251 deletions
|
|
@ -14,6 +14,7 @@ import com.conveyal.r5.api.util.LegMode;
|
|||
import com.conveyal.r5.api.util.TransitModes;
|
||||
import com.conveyal.r5.kryo.KryoNetworkSerializer;
|
||||
import com.conveyal.r5.profile.StreetMode;
|
||||
import com.conveyal.gtfs.model.Stop;
|
||||
import com.conveyal.r5.transit.TransitLayer;
|
||||
import com.conveyal.r5.transit.TransportNetwork;
|
||||
import com.conveyal.r5.transit.path.RouteSequence;
|
||||
|
|
@ -33,6 +34,19 @@ import java.util.List;
|
|||
/** R5 routing: network loading, spatial filtering, travel time computation. */
|
||||
public class Router {
|
||||
|
||||
// Coarse GB bounding box used to tell "a stop that should have linked to a
|
||||
// street but didn't" (inside GB) from stops that legitimately never link
|
||||
// (continental coach termini, neutralised out-of-area stops).
|
||||
private static final double UK_MIN_LAT = 49.0;
|
||||
private static final double UK_MAX_LAT = 61.0;
|
||||
private static final double UK_MIN_LON = -9.0;
|
||||
private static final double UK_MAX_LON = 2.5;
|
||||
// If more than this fraction of transit stops fail to link to the street
|
||||
// network the build is systemically broken (wrong/incomplete OSM extract, or
|
||||
// corrupt coordinates) rather than the usual tiny residue of unroutable
|
||||
// stops, so fail loudly instead of silently shipping a half-linked network.
|
||||
private static final double MAX_UNLINKED_STOP_FRACTION = 0.10;
|
||||
|
||||
private static final int ZOOM = 9; // R5 enforces range 9-12
|
||||
private static final int MAX_GRID_CELLS = 4_900_000; // under R5's 5M limit
|
||||
// 30-minute peak window: RAPTOR cost is linear in (toTime-fromTime)/60.
|
||||
|
|
@ -168,6 +182,85 @@ public class Router {
|
|||
|
||||
System.err.printf(" Transit: %,d stops, %,d routes, %,d patterns, %,d services%n",
|
||||
stops, routes, patterns, services);
|
||||
|
||||
validateStopLinkage(transitLayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify transit stops are connected to the walkable street network. A stop
|
||||
* whose coordinate lands nowhere near a street gets streetVertexForStop == -1
|
||||
* (R5's "unlinked" sentinel); it is then silently unroutable — no access,
|
||||
* egress, or distance table — so trains pass through but nobody can board or
|
||||
* alight (the Tottenham Court Road failure mode, upstream of the coordinate
|
||||
* fix). A handful of stops legitimately never link (continental coach termini,
|
||||
* neutralised out-of-area stops); a large fraction signals a systemic break
|
||||
* (wrong OSM extract, or a whole mode's coordinates corrupt) and fails.
|
||||
*
|
||||
* streetVertexForStop is persisted in the network cache, so unlinked stops are
|
||||
* counted on both fresh and cached loads. Per-stop coordinates come from the
|
||||
* transient stopForIndex, which is only populated on a fresh fromDirectory
|
||||
* build; on a cached load coordinates are unavailable and only counts are logged.
|
||||
*/
|
||||
private static void validateStopLinkage(TransitLayer transitLayer) {
|
||||
int n = transitLayer.getStopCount();
|
||||
// stopForIndex is transient: on a cached (Kryo) load it comes back empty
|
||||
// (size 0), not null, so require it to be fully populated (fresh build)
|
||||
// before indexing into it; otherwise fall back to counts-only.
|
||||
boolean haveCoords = transitLayer.stopForIndex != null
|
||||
&& transitLayer.stopForIndex.size() == n;
|
||||
int unlinked = 0;
|
||||
int unlinkedInGb = 0;
|
||||
List<String> sample = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (transitLayer.streetVertexForStop.get(i) != -1) {
|
||||
continue; // linked
|
||||
}
|
||||
unlinked++;
|
||||
if (!haveCoords) {
|
||||
continue;
|
||||
}
|
||||
Stop stop = transitLayer.stopForIndex.get(i);
|
||||
if (stop == null) {
|
||||
continue;
|
||||
}
|
||||
double lat = stop.stop_lat;
|
||||
double lon = stop.stop_lon;
|
||||
boolean inGb = lat >= UK_MIN_LAT && lat <= UK_MAX_LAT
|
||||
&& lon >= UK_MIN_LON && lon <= UK_MAX_LON;
|
||||
if (inGb) {
|
||||
unlinkedInGb++;
|
||||
if (sample.size() < 25) {
|
||||
String id = transitLayer.stopIdForIndex.get(i);
|
||||
String name = transitLayer.stopNames != null && i < transitLayer.stopNames.size()
|
||||
? transitLayer.stopNames.get(i) : "";
|
||||
sample.add(String.format("%s '%s' (%.5f, %.5f)", id, name, lat, lon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double fraction = n == 0 ? 0.0 : (double) unlinked / n;
|
||||
if (haveCoords) {
|
||||
System.err.printf(
|
||||
" Stop linkage: %,d/%,d stops NOT linked to street network (%.2f%%), %,d inside GB%n",
|
||||
unlinked, n, fraction * 100.0, unlinkedInGb);
|
||||
for (String s : sample) {
|
||||
System.err.println(" unlinked (GB): " + s);
|
||||
}
|
||||
} else {
|
||||
System.err.printf(
|
||||
" Stop linkage: %,d/%,d stops NOT linked to street network (%.2f%%) "
|
||||
+ "(coords unavailable on cached load)%n",
|
||||
unlinked, n, fraction * 100.0);
|
||||
}
|
||||
|
||||
if (fraction > MAX_UNLINKED_STOP_FRACTION) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"R5 linked only %.1f%% of transit stops to the street network "
|
||||
+ "(%,d of %,d unlinked). This indicates a systemic linking failure "
|
||||
+ "(wrong/incomplete OSM extract, or corrupt stop coordinates); "
|
||||
+ "unlinked stops are silently unroutable.",
|
||||
(1.0 - fraction) * 100.0, unlinked, n));
|
||||
}
|
||||
}
|
||||
|
||||
static void validateTransitServices(TransportNetwork network, LocalDate date) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue