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

Elevation handling improvements #4033

Merged
merged 15 commits into from
Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -13,7 +13,11 @@ public static String mapElevation(List<P2<Double>> pairs) {
for (P2<Double> pair : pairs) {
str.append(Math.round(pair.first));
str.append(",");
str.append(Math.round(pair.second * 10.0) / 10.0);
if (Double.isNaN(pair.second)) {
str.append("NaN");
} else {
str.append(Math.round(pair.second * 10.0) / 10.0);
}
str.append(",");
}
if (str.length() > 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.opentripplanner.api.mapping;

import static org.opentripplanner.api.mapping.ElevationMapper.mapElevation;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
Expand Down Expand Up @@ -114,6 +116,7 @@ else if (domain.getPathwayId() != null) {
api.intermediateStops = placeMapper.mapStopArrivals(domain.getIntermediateStops());
}
api.legGeometry = PolylineEncoder.createEncodings(domain.getLegGeometry());
api.legElevation = mapElevation(domain.getLegElevation());
api.steps = walkStepMapper.mapWalkSteps(domain.getWalkSteps());
api.alerts = concatenateAlerts(
streetNoteMaperMapper.mapToApi(domain.getStreetNotes()),
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/opentripplanner/api/model/ApiLeg.java
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ public class ApiLeg {
*/
public EncodedPolylineBean legGeometry;

/**
* The elevation profile as a comma-separated list of x,y values. x is the distance from the start of the leg, y is the elevation at this
* distance.
*/
public String legElevation;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use some more efficient data structure for this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that what the WalkStep also uses?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, but for example the same encoded polylines could be used as for the legGeometry.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Encoded polyline can't be used due to the different ranges the numbers have, so the existing string seems like the best solution, and so can stay the same.


/**
* A series of turn by turn instructions used for walking, biking and driving.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class ItinerariesCalculateLegTotals {
int waitingTimeSeconds;
boolean walkOnly = true;
boolean streetOnly = true;
double totalElevationGained = 0.0;
double totalElevationLost = 0.0;

public ItinerariesCalculateLegTotals(List<Leg> legs) {
if (legs.isEmpty()) { return; }
Expand Down Expand Up @@ -45,6 +47,10 @@ else if(leg.isOnStreetNonTransit()) {
if (!leg.isOnStreetNonTransit()) {
this.streetOnly = false;
}
if (leg.getElevationGained() != null && leg.getElevationLost() != null) {
this.totalElevationGained += leg.getElevationGained();
this.totalElevationLost += leg.getElevationLost();
}
}
this.waitingTimeSeconds = totalDurationSeconds
- (transitTimeSeconds + nonTransitTimeSeconds);
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/opentripplanner/model/plan/Itinerary.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ public Itinerary(List<Leg> legs) {
this.waitingTimeSeconds = totals.waitingTimeSeconds;
this.walkOnly = totals.walkOnly;
this.streetOnly = totals.streetOnly;
this.elevationGained = totals.totalElevationGained;
this.elevationLost = totals.totalElevationLost;
}

/**
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/org/opentripplanner/model/plan/Leg.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Set;
import java.util.TimeZone;
import org.locationtech.jts.geom.LineString;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.model.Agency;
import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.model.FeedScopedId;
Expand Down Expand Up @@ -254,6 +255,30 @@ default List<StopArrival> getIntermediateStops() {
*/
LineString getLegGeometry();

/**
* The leg's elevation profile.
*/
default List<P2<Double>> getLegElevation() {
return null;
}

/**
* How much elevation is gained, in total, over the course of the leg, in meters. See
* elevationLost.
*/
default Double getElevationGained() {
return null;
}

/**
* How much elevation is lost, in total, over the course of the leg, in meters. As an example,
* a trip that went from the top of Mount Everest straight down to sea level, then back up K2,
* then back down again would have an elevationLost of Everest + K2.
*/
default Double getElevationLost() {
return null;
}

/**
* A series of turn by turn instructions used for walking, biking and driving.
*/
Expand Down
53 changes: 53 additions & 0 deletions src/main/java/org/opentripplanner/model/plan/StreetLeg.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.List;
import java.util.Set;
import org.locationtech.jts.geom.LineString;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.model.StreetNote;
import org.opentripplanner.model.base.ToStringBuilder;
Expand All @@ -30,6 +31,12 @@ public class StreetLeg implements Leg {

private final LineString legGeometry;

private List<P2<Double>> legElevation;

private Double elevationLost = null;

private Double elevationGained = null;

private final List<WalkStep> walkSteps;

private final Set<StreetNote> streetNotes = new HashSet<>();
Expand All @@ -53,6 +60,7 @@ public StreetLeg(
double distanceMeters,
int generalizedCost,
LineString geometry,
List<P2<Double>> elevation,
List<WalkStep> walkSteps
) {
if (mode.isTransit()) {
Expand All @@ -66,9 +74,11 @@ public StreetLeg(
this.from = from;
this.to = to;
this.generalizedCost = generalizedCost;
this.legElevation = elevation;
this.legGeometry = geometry;
this.walkSteps = walkSteps;

updateElevationChanges();
}

@Override
Expand Down Expand Up @@ -138,6 +148,21 @@ public LineString getLegGeometry() {
return legGeometry;
}

@Override
public List<P2<Double>> getLegElevation() {
return legElevation;
}

@Override
public Double getElevationGained() {
return elevationGained;
}

@Override
public Double getElevationLost() {
return elevationLost;
}

@Override
public List<WalkStep> getWalkSteps() {
return walkSteps;
Expand Down Expand Up @@ -191,11 +216,39 @@ public String toString() {
.addNum("cost", generalizedCost)
.addObj("gtfsPathwayId", pathwayId)
.addObj("legGeometry", legGeometry)
.addStr("legElevation", legElevation != null ? legElevation.toString() : null)
.addNum("elevationGained", elevationGained, "m")
.addNum("elevationLost", elevationLost, "m")
.addCol("walkSteps", walkSteps)
.addCol("streetNotes", streetNotes)
.addBool("walkingBike", walkingBike)
.addBool("rentedVehicle", rentedVehicle)
.addStr("bikeRentalNetwork", vehicleRentalNetwork)
.toString();
}

private void updateElevationChanges() {
if (legElevation != null) {
double elevationGained = 0.0;
double elevationLost = 0.0;

Double lastElevation = null;
for (final P2<Double> p2 : legElevation) {
double elevation = p2.second;
if (lastElevation != null) {
double change = elevation - lastElevation;
if (change > 0) {
elevationGained += change;
}
else if (change < 0) {
elevationLost -= change;
}
}
lastElevation = elevation;
}

this.elevationGained = elevationGained;
this.elevationLost = elevationLost;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.stream.Collectors;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
import org.opentripplanner.api.resource.CoordinateArrayListSequence;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.ext.flex.FlexibleTransitLeg;
import org.opentripplanner.ext.flex.edgetype.FlexTripEdge;
import org.opentripplanner.model.StreetNote;
Expand Down Expand Up @@ -220,6 +223,7 @@ private Leg generateFlexLeg(List<State> states) {
/**
* Generate one leg of an itinerary from a list of {@link State}.
*
*
* @param states The list of states to base the leg on
* @param previousStep the previous walk step, so that the first relative turn direction is
* calculated correctly
Expand Down Expand Up @@ -264,6 +268,7 @@ private StreetLeg generateLeg(List<State> states, WalkStep previousStep) {
distanceMeters,
(int) (lastState.getWeight() - firstState.getWeight()),
geometry,
makeElevation(edges, firstState.getOptions().geoidElevation),
walkSteps
);

Expand Down Expand Up @@ -367,6 +372,73 @@ private void addStreetNotes(StreetLeg leg, List<State> states) {
}
}

private List<P2<Double>> makeElevation(
List<Edge> edges,
boolean geoidElevation
) {
ArrayList<P2<Double>> elevationProfile = new ArrayList<>();

double heightOffset = geoidElevation ? ellipsoidToGeoidDifference : 0;

double distanceOffset = 0;
for (final Edge edge : edges) {
if (edge.getDistanceMeters() > 0) {
elevationProfile.addAll(
encodeElevationProfileWithNaN(edge, distanceOffset, heightOffset));
distanceOffset += edge.getDistanceMeters();
}
}

// Remove repeated values, preserving the first and last value
for (int i = elevationProfile.size() - 3; i >= 0; i--) {
var first = elevationProfile.get(i);
var second = elevationProfile.get(i + 1);
var third = elevationProfile.get(i + 2);

if (Objects.equals(first.second, second.second) && Objects.equals(second.second, third.second)) {
elevationProfile.remove(i + 1);
}
else if (first.second.isNaN() && second.second.isNaN() && third.second.isNaN()) {
elevationProfile.remove(i + 1);
}
else if (Objects.equals(first, second)) {
elevationProfile.remove(i + 1);
}
}

if (elevationProfile.stream().allMatch(p2 -> p2.second.isNaN())) {
return null;
}

return elevationProfile;
}

private static List<P2<Double>> encodeElevationProfileWithNaN(Edge edge, double distanceOffset, double heightOffset) {
var elevations = encodeElevationProfile(edge, distanceOffset, heightOffset);
if (elevations.isEmpty()) {
return List.of(new P2<>(distanceOffset, Double.NaN), new P2<>(distanceOffset + edge.getDistanceMeters(), Double.NaN));
}
return elevations;
}

private static List<P2<Double>> encodeElevationProfile(Edge edge, double distanceOffset, double heightOffset) {
ArrayList<P2<Double>> out = new ArrayList<P2<Double>>();

if (!(edge instanceof StreetEdge elevEdge)) {
return out;
}
if (elevEdge.getElevationProfile() == null) {
return out;
}

Coordinate[] coordArr = elevEdge.getElevationProfile().toCoordinateArray();
for (final Coordinate coordinate : coordArr) {
out.add(new P2<>(coordinate.x + distanceOffset, coordinate.y + heightOffset));
}

return out;
}

/**
* Make a {@link Place} to add to a {@link Leg}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ private List<Leg> mapNonTransitLeg(
transfer.getDistanceMeters(),
toOtpDomainCost(pathLeg.generalizedCost()),
GeometryUtils.makeLineString(transfer.getCoordinates()),
null,
List.of()
));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ private Leg streetLeg(TraverseMode mode, int startTime, int endTime, Place to, i
speed(mode) * (endTime - startTime),
legCost,
null,
null,
List.of()
);

Expand Down
Loading