Skip to content

Commit

Permalink
improvement(OsmModule): Implement two-way mobility costs.
Browse files Browse the repository at this point in the history
  • Loading branch information
binh-dam-ibigroup committed Apr 10, 2024
1 parent 9e33f86 commit 6c5e3ee
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public static String getKey(String id, String from, String to) {
return String.format("%s:%s=>%s", id, from, to);
}

/** Helper to build a key of the form "id:from=>to" for an OSM way. */
public static String getKey(String id, long from, long to) {
return String.format("%s:%d=>%d", id, from, to);
}

private static void parseRow(
int lineNumber,
CsvReader reader,
Expand All @@ -59,6 +64,7 @@ private static void parseRow(
long fromNode = Long.parseLong(reader.get("Upstream Node"), 10);
long toNode = Long.parseLong(reader.get("Downstream Node"), 10);
String id = reader.get("Way Id");
String key = getKey(id, fromNode, toNode);
float lengthMeters = ONE_MILE_IN_METERS * Float.parseFloat(reader.get("Link Length"));

// The weight map has to be a HashMap instead of an EnumMap so that it is correctly
Expand All @@ -77,7 +83,7 @@ private static void parseRow(
}
}

map.put(id, new MobilityProfileData(lengthMeters, fromNode, toNode, weightMap));
map.put(key, new MobilityProfileData(lengthMeters, fromNode, toNode, weightMap));
} catch (NumberFormatException | NullPointerException e) {
LOG.warn(
"Skipping mobility profile data at line {}: missing/invalid data in column {}.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.collect.Iterables;
import gnu.trove.iterator.TLongIterator;
import gnu.trove.list.TLongList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand All @@ -15,6 +16,7 @@
import org.locationtech.jts.geom.LineString;
import org.opentripplanner.ext.mobilityprofile.MobilityProfile;
import org.opentripplanner.ext.mobilityprofile.MobilityProfileData;
import org.opentripplanner.ext.mobilityprofile.MobilityProfileParser;
import org.opentripplanner.ext.mobilityprofile.MobilityProfileRouting;
import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.framework.geometry.SphericalDistanceLibrary;
Expand Down Expand Up @@ -588,63 +590,81 @@ private StreetEdge getEdgeForStreet(
String startId = startEndpoint.getLabel().toString();
String endId = endEndpoint.getLabel().toString();

// For testing, indicate the OSM node ids (remove prefixes).
String startShortId = startId.replace("osm:node:", "");
String endShortId = endId.replace("osm:node:", "");
String nameWithNodeIds = String.format(
"%s (%s, %s→%s)",
name,
way.getId(),
startShortId,
endShortId
);
seb.withName(nameWithNodeIds);
try {
long startShortId = Long.parseLong(startId.replace("osm:node:", ""), 10);
long endShortId = Long.parseLong(endId.replace("osm:node:", ""), 10);

// For testing, indicate the OSM node ids (remove prefixes).
String nameWithNodeIds = String.format(
"%s (%s, %s→%s)",
name,
way.getId(),
startShortId,
endShortId
);
seb.withName(nameWithNodeIds);

String wayId = Long.toString(way.getId(), 10);
var edgeMobilityCostMap = mobilityProfileData.get(wayId);
if (edgeMobilityCostMap != null) {
// Check whether the nodes for this way match the nodes from mobility profile data.
if (
startShortId.equals(Long.toString(edgeMobilityCostMap.fromNode(), 10)) &&
endShortId.equals(Long.toString(edgeMobilityCostMap.toNode(), 10)) ||
startShortId.equals(Long.toString(edgeMobilityCostMap.toNode(), 10)) &&
endShortId.equals(Long.toString(edgeMobilityCostMap.fromNode(), 10))
) {
// If the from/to nodes match, then assign the cost directly
seb.withProfileCosts(edgeMobilityCostMap.costs());

// Append an indication that this edge uses a full profile cost.
nameWithNodeIds = String.format("%s ☑", nameWithNodeIds);
// System.out.printf("Way (full length): %s size %d%n", nameWithNodeIds, edgeMobilityCostMap.costs().size());
System.out.printf(
"%s %f%n",
nameWithNodeIds,
edgeMobilityCostMap.costs().get(MobilityProfile.WCHAIRE)
);
} else {
// Otherwise, pro-rate the cost to the length of the edge.
float ratio = (float) (length / edgeMobilityCostMap.lengthInMeters());
String wayId = Long.toString(way.getId(), 10);
TLongList nodeRefs = way.getNodeRefs();
int startIndex = nodeRefs.indexOf(startShortId);
int endIndex = nodeRefs.indexOf(endShortId);
boolean isReverse = endIndex < startIndex;

// Use the start and end nodes of the OSM way per the OSM data to lookup the mobility costs.
long wayFromId = nodeRefs.get(0);
long wayToId = nodeRefs.get(nodeRefs.size() - 1);
String key = isReverse
? MobilityProfileParser.getKey(wayId, wayToId, wayFromId)
: MobilityProfileParser.getKey(wayId, wayFromId, wayToId);

var edgeMobilityCostMap = mobilityProfileData.get(key);
if (edgeMobilityCostMap != null) {
// Check whether the nodes for this way match the nodes from mobility profile data.
if (
startShortId == edgeMobilityCostMap.fromNode() &&
endShortId == edgeMobilityCostMap.toNode() ||
startShortId == edgeMobilityCostMap.toNode() &&
endShortId == edgeMobilityCostMap.fromNode()
) {
// If the from/to nodes match, then assign the cost directly
seb.withProfileCosts(edgeMobilityCostMap.costs());

// Append an indication that this edge uses a full profile cost.
nameWithNodeIds = String.format("%s ☑", nameWithNodeIds);
// System.out.printf("Way (full length): %s size %d%n", nameWithNodeIds, edgeMobilityCostMap.costs().size());
System.out.printf(
"%s %f%n",
nameWithNodeIds,
edgeMobilityCostMap.costs().get(MobilityProfile.WCHAIRE)
);
} else {
// Otherwise, pro-rate the cost to the length of the edge.
float ratio = (float) (length / edgeMobilityCostMap.lengthInMeters());

Map<MobilityProfile, Float> proRatedProfileCosts = MobilityProfileRouting.getProRatedProfileCosts(
edgeMobilityCostMap.costs(),
ratio
);
seb.withProfileCosts(proRatedProfileCosts);

// Append an indication that this edge uses a partial profile cost.
nameWithNodeIds = String.format("%s r%4.3f l%4.3f", nameWithNodeIds, ratio, length);
// System.out.printf("Way (partial): %s size %d%n", nameWithNodeIds, proRatedProfileCosts.size());
System.out.printf(
"%s %f%n",
nameWithNodeIds,
proRatedProfileCosts.get(MobilityProfile.WCHAIRE)
);
}

Map<MobilityProfile, Float> proRatedProfileCosts = MobilityProfileRouting.getProRatedProfileCosts(
edgeMobilityCostMap.costs(),
ratio
);
seb.withProfileCosts(proRatedProfileCosts);

// Append an indication that this edge uses a partial profile cost.
nameWithNodeIds = String.format("%s r%4.3f l%4.3f", nameWithNodeIds, ratio, length);
// System.out.printf("Way (partial): %s size %d%n", nameWithNodeIds, proRatedProfileCosts.size());
System.out.printf(
"%s %f%n",
nameWithNodeIds,
proRatedProfileCosts.get(MobilityProfile.WCHAIRE)
);
seb.withName(nameWithNodeIds);
// LOG.info("Applied mobility profile costs between nodes {}-{}", startShortId, endShortId);
// Keep tab of node pairs for which mobility profile costs have been mapped.
mappedMobilityProfileEntries.add(key);
}

seb.withName(nameWithNodeIds);
// LOG.info("Applied mobility profile costs between nodes {}-{}", startShortId, endShortId);
// Keep tab of node pairs for which mobility profile costs have been mapped.
mappedMobilityProfileEntries.add(wayId);
} catch (NumberFormatException nfe) {
// Don't do anything related to mobility profiles if node ids are non-numerical.
LOG.info("Not applying mobility costs for link {}:{}→{}", way.getId(), startEndpoint.getLabel(), endEndpoint.getLabel());
}
}

Expand Down

0 comments on commit 6c5e3ee

Please sign in to comment.