diff --git a/src/main/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverter.java b/src/main/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverter.java index 21b8518181a..c742150689d 100644 --- a/src/main/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverter.java +++ b/src/main/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverter.java @@ -46,6 +46,7 @@ public abstract class GraphPathToTripPlanConverter { private static final double MAX_ZAG_DISTANCE = 30; // TODO add documentation, what is a "zag"? private static final double WALK_LEG_DISTANCE_EPSILON = 2.0; private static final double WALK_LEG_DURATION_EPSILON = 5e3; + private static final double MIN_ELEVATION_DISTANCE_DIFFERENCE = 5.0; /** * Generates a TripPlan from a set of paths @@ -164,7 +165,7 @@ public static Itinerary generateItinerary(GraphPath path, boolean showIntermedia calculateTimes(itinerary, states); - calculateElevations(itinerary, edges); + calculateElevations(itinerary); itinerary.walkDistance = lastState.getWalkDistance(); @@ -512,27 +513,32 @@ private static void calculateTimes(Itinerary itinerary, State[] states) { /** * Calculate the elevationGained and elevationLost fields of an {@link Itinerary}. * - * @param itinerary The itinerary to calculate the elevation changes for - * @param edges The edges that go with the itinerary + * @param itinerary The itinerary to calculate the elevation changes for and from */ - private static void calculateElevations(Itinerary itinerary, Edge[] edges) { - for (Edge edge : edges) { - if (!(edge instanceof StreetEdge)) continue; - - StreetEdge edgeWithElevation = (StreetEdge) edge; - PackedCoordinateSequence coordinates = edgeWithElevation.getElevationProfile(); - - if (coordinates == null) continue; - // TODO Check the test below, AFAIU current elevation profile has 3 dimensions. - if (coordinates.getDimension() != 2) continue; - - for (int i = 0; i < coordinates.size() - 1; i++) { - double change = coordinates.getOrdinate(i + 1, 1) - coordinates.getOrdinate(i, 1); - - if (change > 0) { - itinerary.elevationGained += change; - } else if (change < 0) { - itinerary.elevationLost -= change; + private static void calculateElevations(Itinerary itinerary) { + for (Leg leg : itinerary.legs) { + Double lastElevation = null; + for (WalkStep step : leg.walkSteps) { + List> elevationValues = step.elevation; + // Calculate elevation change between steps + if (lastElevation != null && elevationValues.size() > 0) { + double change = elevationValues.get(0).second - lastElevation; + if (change > 0) { + itinerary.elevationGained += change; + } else if (change < 0) { + itinerary.elevationLost -= change; + } + lastElevation = elevationValues.get(elevationValues.size() - 1).second; + } + // Calculate elevation changes between elevation values of a step + for (int i = 0; i < elevationValues.size() - 1; i++) { + double change = elevationValues.get(i + 1).second - + elevationValues.get(i).second; + if (change > 0) { + itinerary.elevationGained += change; + } else if (change < 0) { + itinerary.elevationLost -= change; + } } } } @@ -1007,7 +1013,6 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk // succession; this is probably a U-turn. steps.remove(last - 1); - lastStep.distance += twoBack.distance; // A U-turn to the left, typical in the US. @@ -1033,12 +1038,10 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk step.distance += twoBack.distance; distance += step.distance; if (twoBack.elevation != null) { - if (step.elevation == null) { + if (step.elevation == null || step.elevation.size() == 0) { step.elevation = twoBack.elevation; } else { - for (P2 d : twoBack.elevation) { - step.elevation.add(new P2(d.first + step.distance, d.second)); - } + step.elevation = appendElevationProfile(step.elevation, twoBack.elevation, step.distance); } } } @@ -1046,16 +1049,15 @@ public static List generateWalkSteps(Graph graph, State[] states, Walk } } else { if (!createdNewStep && step.elevation != null) { - List> s = encodeElevationProfile(edge, distance, + List> elevationValues = encodeElevationProfile(edge, distance, backState.getOptions().geoidElevation ? -graph.ellipsoidToGeoidDifference : 0); if (step.elevation != null && step.elevation.size() > 0) { - step.elevation.addAll(s); + step.elevation = appendElevationProfile(step.elevation, elevationValues, 0); } else { - step.elevation = s; + step.elevation = elevationValues; } + distance += edge.getDistance(); } - distance += edge.getDistance(); - } // increment the total length for this step @@ -1113,6 +1115,21 @@ private static WalkStep createWalkStep(Graph graph, State s, Locale wantedLocale return step; } + private static List> appendElevationProfile(List> profile, List> profileToAdd, double distanceOffset) { + if (profileToAdd.size() > 0) { + Double lastElevationDistance = profile.get(profile.size() - 1).first; + Double firstElevationDistance = profileToAdd.get(0).first + distanceOffset; + if (firstElevationDistance - lastElevationDistance > MIN_ELEVATION_DISTANCE_DIFFERENCE) { + profile.add(new P2(firstElevationDistance, profileToAdd.get(0).second)); + } + for (int j = 1; j < profileToAdd.size(); j++) { + P2 elevationPair = profileToAdd.get(j); + profile.add(new P2(elevationPair.first + distanceOffset, elevationPair.second)); + } + } + return profile; + } + private static List> encodeElevationProfile(Edge edge, double distanceOffset, double heightOffset) { if (!(edge instanceof StreetEdge)) { return new ArrayList>(); @@ -1123,8 +1140,14 @@ private static List> encodeElevationProfile(Edge edge, double distanc } ArrayList> out = new ArrayList>(); Coordinate[] coordArr = elevEdge.getElevationProfile().toCoordinateArray(); + Double lastDistance = null; for (int i = 0; i < coordArr.length; i++) { - out.add(new P2(coordArr[i].x + distanceOffset, coordArr[i].y + heightOffset)); + if (lastDistance == null || coordArr[i].x - lastDistance > + MIN_ELEVATION_DISTANCE_DIFFERENCE) { + out.add(new P2(coordArr[i].x + distanceOffset, + coordArr[i].y + heightOffset)); + lastDistance = coordArr[i].x; + } } return out; } diff --git a/src/main/java/org/opentripplanner/graph_builder/linking/SimpleStreetSplitter.java b/src/main/java/org/opentripplanner/graph_builder/linking/SimpleStreetSplitter.java index 7ec0cecdf46..6bd38424ab1 100644 --- a/src/main/java/org/opentripplanner/graph_builder/linking/SimpleStreetSplitter.java +++ b/src/main/java/org/opentripplanner/graph_builder/linking/SimpleStreetSplitter.java @@ -416,21 +416,13 @@ private SplitterVertex split (StreetEdge edge, LinearLocation ll, boolean tempor // every edge can be split exactly once, so this is a valid label SplitterVertex v; if (temporarySplit) { - v = new TemporarySplitterVertex("split from " + edge.getId(), splitPoint.x, splitPoint.y, - edge, endVertex); - if (edge.isWheelchairAccessible()) { - ((TemporarySplitterVertex) v).setWheelchairAccessible(true); - } else { - ((TemporarySplitterVertex) v).setWheelchairAccessible(false); - } + v = new TemporarySplitterVertex("split from " + edge.getId(), splitPoint.x, splitPoint.y, edge, endVertex); } else { - v = new SplitterVertex(graph, "split from " + edge.getId(), splitPoint.x, splitPoint.y, - edge); + v = new SplitterVertex(graph, "split from " + edge.getId(), splitPoint.x, splitPoint.y, edge); } - // make the edges - // TODO this is using the StreetEdge implementation of split, which will discard elevation information - // on edges that have it + // Split the 'edge' at 'v' in 2 new edges and connect these 2 edges to the + // existing vertices P2 edges = edge.split(v, !temporarySplit); if (destructiveSplitting) { diff --git a/src/main/java/org/opentripplanner/routing/edgetype/PartialStreetEdge.java b/src/main/java/org/opentripplanner/routing/edgetype/PartialStreetEdge.java deleted file mode 100644 index fbcd5f0006e..00000000000 --- a/src/main/java/org/opentripplanner/routing/edgetype/PartialStreetEdge.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.opentripplanner.routing.edgetype; - -import com.vividsolutions.jts.geom.Coordinate; -import com.vividsolutions.jts.geom.LineString; -import org.opentripplanner.common.TurnRestriction; -import org.opentripplanner.routing.graph.Edge; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.routing.vertextype.StreetVertex; - -import java.util.List; -import org.opentripplanner.util.I18NString; -import org.opentripplanner.util.NonLocalizedString; - -/** - * Represents a sub-segment of a StreetEdge. - * - * TODO we need a way to make sure all temporary edges are recorded as such and assigned a routingcontext when they are - * created. That list should probably be in the routingContext itself instead of the created StreetLocation. - */ -public class PartialStreetEdge extends StreetWithElevationEdge { - - private static final long serialVersionUID = 1L; - - /** - * The edge on which this lies. - */ - private StreetEdge parentEdge; - - public PartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, StreetVertex v2, - LineString geometry, I18NString name, double length) { - super(v1, v2, geometry, name, length, parentEdge.getPermission(), false); - setCarSpeed(parentEdge.getCarSpeed()); - this.parentEdge = parentEdge; - } - - - //For testing only - public PartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, StreetVertex v2, - LineString geometry, String name, double length) { - this(parentEdge, v1, v2, geometry, new NonLocalizedString(name), length); - } - - /** - * Partial edges are always partial. - */ - @Override - public boolean isPartial() { - return true; - } - - /** - * Have the ID of their parent. - */ - @Override - public int getId() { - return parentEdge.getId(); - } - - /** - * Have the inbound angle of their parent. - */ - @Override - public int getInAngle() { - return parentEdge.getInAngle(); - } - - /** - * Have the outbound angle of their parent. - */ - @Override - public int getOutAngle() { - return parentEdge.getInAngle(); - } - - /** - * Have the turn restrictions of their parent. - */ - @Override - protected List getTurnRestrictions(Graph graph) { - return graph.getTurnRestrictions(parentEdge); - } - - /** - * This implementation makes it so that TurnRestrictions on the parent edge are applied to this edge as well. - */ - @Override - public boolean isEquivalentTo(Edge e) { - return (e == this || e == parentEdge); - } - - @Override - public boolean isReverseOf(Edge e) { - Edge other = e; - if (e instanceof PartialStreetEdge) { - other = ((PartialStreetEdge) e).parentEdge; - } - - // TODO(flamholz): is there a case where a partial edge has a reverse of its own? - return parentEdge.isReverseOf(other); - } - - @Override - public boolean isRoundabout() { - return parentEdge.isRoundabout(); - } - - /** - * Returns true if this edge is trivial - beginning and ending at the same point. - */ - public boolean isTrivial() { - Coordinate fromCoord = this.getFromVertex().getCoordinate(); - Coordinate toCoord = this.getToVertex().getCoordinate(); - return fromCoord.equals(toCoord); - } - - public StreetEdge getParentEdge() { - return parentEdge; - } - - @Override - public String toString() { - return "PartialStreetEdge(" + this.getName() + ", " + this.getFromVertex() + " -> " - + this.getToVertex() + " length=" + this.getDistance() + " carSpeed=" - + this.getCarSpeed() + " parentEdge=" + parentEdge + ")"; - } -} diff --git a/src/main/java/org/opentripplanner/routing/edgetype/StreetEdge.java b/src/main/java/org/opentripplanner/routing/edgetype/StreetEdge.java index 8de66142f53..79c90e48720 100644 --- a/src/main/java/org/opentripplanner/routing/edgetype/StreetEdge.java +++ b/src/main/java/org/opentripplanner/routing/edgetype/StreetEdge.java @@ -242,13 +242,6 @@ public double getDistance() { return length_mm / 1000.0; // CONVERT FROM FIXED MILLIMETERS TO FLOAT METERS } - /** - * Accessor to retrive the length in mm, for subclasses only. - */ - protected int getLength_mm() { - return length_mm; - } - @Override public State traverse(State s0) { final RoutingRequest options = s0.getOptions(); @@ -816,6 +809,13 @@ protected void calculateLengthFromGeometry () { length_mm = (int) (accumulatedMeters * 1000); } + /** Create new StreetEdge */ + protected StreetEdge createEdge(StreetVertex fromVertex, StreetVertex toVertex, + LineString geometry, I18NString name, double length, StreetTraversalPermission permission, + boolean back) { + return new StreetEdge(fromVertex, toVertex, geometry, name, length, permission, back); + } + /** Split this street edge and return the resulting street edges */ public P2 split(SplitterVertex v, boolean destructive) { P2 geoms = GeometryUtils.splitGeometryAtPoint(getGeometry(), v.getCoordinate()); @@ -824,8 +824,8 @@ public P2 split(SplitterVertex v, boolean destructive) { StreetEdge e2 = null; if (destructive) { - e1 = new StreetEdge((StreetVertex) fromv, v, geoms.first, name, 0, permission, this.isBack()); - e2 = new StreetEdge(v, (StreetVertex) tov, geoms.second, name, 0, permission, this.isBack()); + e1 = createEdge((StreetVertex) fromv, (StreetVertex) v, geoms.first, name, 0f, permission, this.isBack()); + e2 = createEdge((StreetVertex) v, (StreetVertex) tov, geoms.second, name, 0f, permission, this.isBack()); // copy the wayId to the split edges, so we can trace them back to their parent if need be e1.wayId = this.wayId; @@ -879,13 +879,11 @@ public P2 split(SplitterVertex v, boolean destructive) { } } else { if (((TemporarySplitterVertex) v).isEndVertex()) { - e1 = new TemporaryPartialStreetEdge(this, (StreetVertex) fromv, (TemporarySplitterVertex) v, geoms.first, name, 0); - e1.calculateLengthFromGeometry(); + e1 = new TemporaryPartialStreetEdge(this, (StreetVertex) fromv, v, geoms.first, name); e1.setNoThruTraffic(this.isNoThruTraffic()); e1.setStreetClass(this.getStreetClass()); } else { - e2 = new TemporaryPartialStreetEdge(this, (TemporarySplitterVertex) v, (StreetVertex) tov, geoms.second, name, 0); - e2.calculateLengthFromGeometry(); + e2 = new TemporaryPartialStreetEdge(this, v, (StreetVertex) tov, geoms.second, name); e2.setNoThruTraffic(this.isNoThruTraffic()); e2.setStreetClass(this.getStreetClass()); } diff --git a/src/main/java/org/opentripplanner/routing/edgetype/StreetWithElevationEdge.java b/src/main/java/org/opentripplanner/routing/edgetype/StreetWithElevationEdge.java index 455975ac6ae..924ec6af2e9 100644 --- a/src/main/java/org/opentripplanner/routing/edgetype/StreetWithElevationEdge.java +++ b/src/main/java/org/opentripplanner/routing/edgetype/StreetWithElevationEdge.java @@ -2,9 +2,10 @@ import org.opentripplanner.common.geometry.CompactElevationProfile; import org.opentripplanner.common.geometry.PackedCoordinateSequence; -import org.opentripplanner.common.geometry.SphericalDistanceLibrary; +import org.opentripplanner.common.model.P2; import org.opentripplanner.routing.util.ElevationUtils; import org.opentripplanner.routing.util.SlopeCosts; +import org.opentripplanner.routing.vertextype.SplitterVertex; import org.opentripplanner.routing.vertextype.StreetVertex; import com.vividsolutions.jts.geom.LineString; @@ -32,6 +33,8 @@ public class StreetWithElevationEdge extends StreetEdge { private double effectiveWalkFactor = 1.0; + private boolean elevationSet = false; + /** * Remember to call the {@link #setElevationProfile(PackedCoordinateSequence, boolean)} to initiate elevation data. */ @@ -70,9 +73,45 @@ public boolean setElevationProfile(PackedCoordinateSequence elev, boolean comput bicycleSafetyFactor *= costs.lengthMultiplier; bicycleSafetyFactor += costs.slopeSafetyCost / getDistance(); + elevationSet = true; return costs.flattened; } + /** Create new StreetWithElevationEdge */ + @Override + protected StreetEdge createEdge(StreetVertex fromVertex, StreetVertex toVertex, + LineString geometry, I18NString name, double length, StreetTraversalPermission permission, + boolean back) { + return new StreetWithElevationEdge(fromVertex, toVertex, geometry, name, length, permission, back); + } + + /** Split this street edge and return the resulting street edges */ + @Override + public P2 split(SplitterVertex v, boolean destructive) { + P2 edges = super.split(v, destructive); + StreetWithElevationEdge firstEdge = (StreetWithElevationEdge) edges.first; + // TemporaryPartialStreetEdges should already have elevation + if (firstEdge != null && !firstEdge.hasElevation()) { + firstEdge.setElevationProfile( + ElevationUtils.getPartialElevationProfile( + this.getElevationProfile(), 0, firstEdge.getDistance() + ), + false + ); + } + StreetWithElevationEdge secondEdge = (StreetWithElevationEdge) edges.second; + if (secondEdge != null && !secondEdge.hasElevation()) { + secondEdge.setElevationProfile( + ElevationUtils.getPartialElevationProfile( + this.getElevationProfile(), + this.getDistance() - secondEdge.getDistance(), this.getDistance() + ), + false + ); + } + return edges; + } + @Override public PackedCoordinateSequence getElevationProfile() { return CompactElevationProfile.uncompactElevationProfile(packedElevationProfile); @@ -107,6 +146,13 @@ public double getSlopeWalkSpeedEffectiveLength() { return effectiveWalkFactor * getDistance(); } + /** + * Has elevation been already set for this edge. + */ + public boolean hasElevation() { + return elevationSet; + } + @Override public String toString() { return "StreetWithElevationEdge(" + getId() + ", " + getName() + ", " + fromv + " -> " diff --git a/src/main/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdge.java b/src/main/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdge.java index a15d9ce3590..5293822f18c 100644 --- a/src/main/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdge.java +++ b/src/main/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdge.java @@ -1,61 +1,183 @@ package org.opentripplanner.routing.edgetype; +import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.LineString; -import org.opentripplanner.routing.location.TemporaryStreetLocation; +import org.opentripplanner.common.TurnRestriction; +import org.opentripplanner.common.model.P2; +import org.opentripplanner.routing.graph.Edge; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.util.ElevationUtils; +import org.opentripplanner.routing.vertextype.SplitterVertex; import org.opentripplanner.routing.vertextype.StreetVertex; -import org.opentripplanner.routing.vertextype.TemporarySplitterVertex; +import org.opentripplanner.routing.vertextype.TemporaryVertex; import org.opentripplanner.util.I18NString; -final public class TemporaryPartialStreetEdge extends PartialStreetEdge implements TemporaryEdge { - public TemporaryPartialStreetEdge(StreetEdge parentEdge, TemporaryStreetLocation v1, - TemporaryStreetLocation v2, LineString geometry, I18NString name, double length) { - super(parentEdge, v1, v2, geometry, name, length); +import java.util.List; - if (v1.isEndVertex()) { - throw new IllegalStateException("A temporary edge is directed away from an end vertex"); - } else if (!v2.isEndVertex()) { - throw new IllegalStateException("A temporary edge is directed towards a start vertex"); - } + +final public class TemporaryPartialStreetEdge extends StreetWithElevationEdge implements TemporaryEdge { + + private static final long serialVersionUID = 1L; + + /** + * The edge on which this lies. + */ + private StreetEdge parentEdge; + + + /** + * Create a new partial street edge along the given 'parentEdge' from 'v1' to 'v2'. + * If the length is negative, a new length is calculated from the geometry. + * The elevation data is calculated using the 'parentEdge' and given 'length'. + */ + public TemporaryPartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, StreetVertex v2, + LineString geometry, I18NString name, double length) { + super(v1, v2, geometry, name, length, parentEdge.getPermission(), false); + this.parentEdge = parentEdge; + setCarSpeed(parentEdge.getCarSpeed()); + setElevationProfileUsingParents(); + + // Assert that the edge is going in the right direction [only possible if vertex is temporary] + assertEdgeIsNotDirectedAwayFromTemporaryEndVertex(v1); + assertEdgeIsDirectedTowardsTemporaryEndVertex(v2); } - public TemporaryPartialStreetEdge(StreetEdge parentEdge, TemporaryStreetLocation v1, - StreetVertex v2, LineString geometry, I18NString name, double length) { - super(parentEdge, v1, v2, geometry, name, length); + /** + * Create a new partial street edge along the given 'parentEdge' from 'v1' to 'v2'. + * The length is calculated using the provided geometry. + * The elevation data is calculated using the 'parentEdge' and the calculated 'length'. + */ + TemporaryPartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, StreetVertex v2, + LineString geometry, I18NString name) { + super(v1, v2, geometry, name, 0, parentEdge.getPermission(), false); + this.parentEdge = parentEdge; + setCarSpeed(parentEdge.getCarSpeed()); - if (v1.isEndVertex()) { - throw new IllegalStateException("A temporary edge is directed away from an end vertex"); - } + // No length is known, so we use the provided geometry to estimate it + calculateLengthFromGeometry(); + setElevationProfileUsingParents(); + + // Assert that the edge is going in the right direction [only possible if vertex is temporary] + assertEdgeIsNotDirectedAwayFromTemporaryEndVertex(v1); + assertEdgeIsDirectedTowardsTemporaryEndVertex(v2); + } + + /** + * Partial edges are always partial. + */ + @Override + public boolean isPartial() { + return true; } - public TemporaryPartialStreetEdge(StreetEdge parentEdge, TemporarySplitterVertex v1, - StreetVertex v2, LineString geometry, I18NString name, double length) { - super(parentEdge, v1, v2, geometry, name, length); + /** + * Have the ID of their parent. + */ + @Override + public int getId() { + return parentEdge.getId(); + } - if (v1.isEndVertex()) { - throw new IllegalStateException("A temporary edge is directed away from an end vertex"); - } + /** + * Have the inbound angle of their parent. + */ + @Override + public int getInAngle() { + return parentEdge.getInAngle(); } - public TemporaryPartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, - TemporaryStreetLocation v2, LineString geometry, I18NString name, double length) { - super(parentEdge, v1, v2, geometry, name, length); + /** + * Have the outbound angle of their parent. + */ + @Override + public int getOutAngle() { + return parentEdge.getInAngle(); + } - if (!v2.isEndVertex()) { - throw new IllegalStateException("A temporary edge is directed towards a start vertex"); - } + /** + * Have the turn restrictions of their parent. + */ + @Override + protected List getTurnRestrictions(Graph graph) { + return graph.getTurnRestrictions(parentEdge); } - public TemporaryPartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, - TemporarySplitterVertex v2, LineString geometry, I18NString name, double length) { - super(parentEdge, v1, v2, geometry, name, length); + /** + * This implementation makes it so that TurnRestrictions on the parent edge are applied to this edge as well. + */ + @Override + public boolean isEquivalentTo(Edge e) { + return (e == this || e == parentEdge); + } - if (!v2.isEndVertex()) { - throw new IllegalStateException("A temporary edge is directed towards a start vertex"); + @Override + public boolean isReverseOf(Edge e) { + Edge other = e; + if (e instanceof TemporaryPartialStreetEdge) { + other = ((TemporaryPartialStreetEdge) e).parentEdge; } + + // TODO(flamholz): is there a case where a partial edge has a reverse of its own? + return parentEdge.isReverseOf(other); + } + + @Override + public boolean isRoundabout() { + return parentEdge.isRoundabout(); + } + + /** + * Returns true if this edge is trivial - beginning and ending at the same point. + */ + public boolean isTrivial() { + Coordinate fromCoord = this.getFromVertex().getCoordinate(); + Coordinate toCoord = this.getToVertex().getCoordinate(); + return fromCoord.equals(toCoord); + } + + public StreetEdge getParentEdge() { + return parentEdge; } @Override public String toString() { - return "Temporary" + super.toString(); + return "TemporaryPartialStreetEdge(" + this.getName() + ", " + this.getFromVertex() + " -> " + + this.getToVertex() + " length=" + this.getDistance() + " carSpeed=" + + this.getCarSpeed() + " parentEdge=" + parentEdge + ")"; + } + + private void assertEdgeIsNotDirectedAwayFromTemporaryEndVertex(StreetVertex v1) { + if(v1 instanceof TemporaryVertex) { + if (((TemporaryVertex)v1).isEndVertex()) { + throw new IllegalStateException("A temporary edge is directed away from an end vertex"); + } + } + } + + private void assertEdgeIsDirectedTowardsTemporaryEndVertex(StreetVertex v2) { + if(v2 instanceof TemporaryVertex) { + if (!((TemporaryVertex)v2).isEndVertex()) { + throw new IllegalStateException("A temporary edge is directed towards a start vertex"); + } + } + } + + private void setElevationProfileUsingParents() { + if (getParentEdge().getFromVertex().equals(this.getFromVertex())) { + setElevationProfile( + ElevationUtils.getPartialElevationProfile( + getParentEdge().getElevationProfile(), 0, getDistance() + ), + false + ); + } else { + setElevationProfile( + ElevationUtils.getPartialElevationProfile( + getParentEdge().getElevationProfile(), getParentEdge().getDistance() - + getDistance(), getParentEdge().getDistance() + ), + false + ); + } } } diff --git a/src/main/java/org/opentripplanner/routing/impl/StreetVertexIndexServiceImpl.java b/src/main/java/org/opentripplanner/routing/impl/StreetVertexIndexServiceImpl.java index 1a638165998..f4310127e4f 100644 --- a/src/main/java/org/opentripplanner/routing/impl/StreetVertexIndexServiceImpl.java +++ b/src/main/java/org/opentripplanner/routing/impl/StreetVertexIndexServiceImpl.java @@ -175,17 +175,12 @@ private static void createHalfLocation(TemporaryStreetLocation base, I18NString TemporaryPartialStreetEdge temporaryPartialStreetEdge = new TemporaryPartialStreetEdge( street, fromv, base, geometries.first, name, lengthIn); - temporaryPartialStreetEdge.setElevationProfile(ElevationUtils - .getPartialElevationProfile(street.getElevationProfile(), 0, lengthIn), false); temporaryPartialStreetEdge.setNoThruTraffic(street.isNoThruTraffic()); temporaryPartialStreetEdge.setStreetClass(street.getStreetClass()); } else { TemporaryPartialStreetEdge temporaryPartialStreetEdge = new TemporaryPartialStreetEdge( street, base, tov, geometries.second, name, lengthOut); - temporaryPartialStreetEdge.setElevationProfile(ElevationUtils - .getPartialElevationProfile(street.getElevationProfile(), lengthIn, - lengthIn + lengthOut), false); temporaryPartialStreetEdge.setStreetClass(street.getStreetClass()); temporaryPartialStreetEdge.setNoThruTraffic(street.isNoThruTraffic()); } diff --git a/src/main/java/org/opentripplanner/routing/services/notes/DynamicStreetNotesSource.java b/src/main/java/org/opentripplanner/routing/services/notes/DynamicStreetNotesSource.java index 983abec4153..cbc1166e00a 100644 --- a/src/main/java/org/opentripplanner/routing/services/notes/DynamicStreetNotesSource.java +++ b/src/main/java/org/opentripplanner/routing/services/notes/DynamicStreetNotesSource.java @@ -2,7 +2,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.SetMultimap; -import org.opentripplanner.routing.edgetype.PartialStreetEdge; +import org.opentripplanner.routing.edgetype.TemporaryPartialStreetEdge; import org.opentripplanner.routing.graph.Edge; import java.util.Set; @@ -33,8 +33,8 @@ public DynamicStreetNotesSource() { @Override public Set getNotes(Edge edge) { /* If the edge is temporary, we look for notes in it's parent edge. */ - if (edge instanceof PartialStreetEdge) { - edge = ((PartialStreetEdge) edge).getParentEdge(); + if (edge instanceof TemporaryPartialStreetEdge) { + edge = ((TemporaryPartialStreetEdge) edge).getParentEdge(); } Set maas = notesForEdge.get(edge); if (maas == null || maas.isEmpty()) { diff --git a/src/main/java/org/opentripplanner/routing/services/notes/StaticStreetNotesSource.java b/src/main/java/org/opentripplanner/routing/services/notes/StaticStreetNotesSource.java index 9988b8a1f34..af8ab6419fe 100644 --- a/src/main/java/org/opentripplanner/routing/services/notes/StaticStreetNotesSource.java +++ b/src/main/java/org/opentripplanner/routing/services/notes/StaticStreetNotesSource.java @@ -7,9 +7,7 @@ import org.opentripplanner.common.model.T2; import org.opentripplanner.routing.alertpatch.Alert; -import org.opentripplanner.routing.core.State; -import org.opentripplanner.routing.core.TraverseMode; -import org.opentripplanner.routing.edgetype.PartialStreetEdge; +import org.opentripplanner.routing.edgetype.TemporaryPartialStreetEdge; import org.opentripplanner.routing.graph.Edge; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,8 +56,8 @@ void addNote(Edge edge, Alert note, NoteMatcher matcher) { @Override public Set getNotes(Edge edge) { /* If the edge is temporary, we look for notes in it's parent edge. */ - if (edge instanceof PartialStreetEdge) { - edge = ((PartialStreetEdge) edge).getParentEdge(); + if (edge instanceof TemporaryPartialStreetEdge) { + edge = ((TemporaryPartialStreetEdge) edge).getParentEdge(); } Set maas = notesForEdge.get(edge); if (maas == null || maas.isEmpty()) { diff --git a/src/main/java/org/opentripplanner/routing/util/ElevationUtils.java b/src/main/java/org/opentripplanner/routing/util/ElevationUtils.java index e0e6bb97ac0..fc6bdad1dff 100644 --- a/src/main/java/org/opentripplanner/routing/util/ElevationUtils.java +++ b/src/main/java/org/opentripplanner/routing/util/ElevationUtils.java @@ -4,7 +4,7 @@ import java.util.List; import org.opentripplanner.common.geometry.PackedCoordinateSequence; -import org.opentripplanner.routing.util.elevation.ToblersHickingFunction; +import org.opentripplanner.routing.util.elevation.ToblersHikingFunction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,7 +24,7 @@ public class ElevationUtils { /** * If the calculated factor is more than this constant, we ignore the calculated factor and use this - * constant in stead. See ths table in {@link ToblersHickingFunction} for a mapping between the + * constant in stead. See ths table in {@link ToblersHikingFunction} for a mapping between the * factor and angels(degree and percentage). A factor of 3 with take effect for slopes with a * incline above 31.4% and a decline below 41.4%. The worlds steepest road ia about 35%, and the * steepest climes in Tour De France is usually in the range 8-12%. Some walking paths may be quite @@ -32,7 +32,7 @@ public class ElevationUtils { */ private static final double MAX_SLOPE_WALK_EFFECTIVE_LENGTH_FACTOR = 3; - private static final ToblersHickingFunction toblerWalkingFunction = new ToblersHickingFunction(MAX_SLOPE_WALK_EFFECTIVE_LENGTH_FACTOR); + private static final ToblersHikingFunction toblerWalkingFunction = new ToblersHikingFunction(MAX_SLOPE_WALK_EFFECTIVE_LENGTH_FACTOR); private static double[] getLengthsFromElevation(CoordinateSequence elev) { @@ -253,7 +253,7 @@ public static double slopeSpeedCoefficient(double slope, double altitude) { /** *

- * We use the Tobler function {@link ToblersHickingFunction} to calculate this. + * We use the Tobler function {@link ToblersHikingFunction} to calculate this. *

*

* When testing this we get good results in general, but for some edges @@ -281,43 +281,43 @@ public static PackedCoordinateSequence getPartialElevationProfile( if (end > length) end = length; - boolean started = false; - boolean finished = false; Coordinate lastCoord = null; for (Coordinate coord : coordinateArray) { if (coord.x >= start && coord.x <= end) { - coordList.add(new Coordinate(coord.x - start, coord.y)); - if (!started) { - started = true; - if (lastCoord == null) { - //no need to interpolate as this is the first coordinate - continue; - } - // interpolate start coordinate - double run = coord.x - lastCoord.x; - if (run < 1) { - //tiny runs are likely to lead to errors, so we'll skip them - continue; - } + // No point to try to interpolate the start coordinate when the current coord is + // the first coordinate or the current coordinate is close to the last coordinate + if (coordList.size() == 0 && lastCoord != null && + coord.x - start - lastCoord.x >= 1) { + double run = coord.x - start - lastCoord.x; double p = (coord.x - start) / run; double rise = coord.y - lastCoord.y; - Coordinate interpolatedStartCoordinate = new Coordinate(0, lastCoord.y + p * rise); - coordList.add(0, interpolatedStartCoordinate); + Coordinate interpolatedStartCoordinate = + new Coordinate(0, lastCoord.y + p * rise); + coordList.add(interpolatedStartCoordinate); + lastCoord = interpolatedStartCoordinate; } - } else if (coord.x > end && !finished && started && lastCoord != null) { - finished = true; - // interpolate end coordinate - double run = coord.x - lastCoord.x; - if (run < 1) { - //tiny runs are likely to lead to errors, so we'll skip them - continue; + Coordinate fixedCoord = new Coordinate(coord.x - start, coord.y); + coordList.add(fixedCoord); + lastCoord = fixedCoord; + } else if (coord.x > end) { + if (lastCoord != null && coord.x - start - lastCoord.x >= 1) { + // interpolate end coordinate + double run = coord.x - start - lastCoord.x; + double p = (end - start - lastCoord.x) / run; + double rise = coord.y - lastCoord.y; + Coordinate interpolatedEndCoordinate = + new Coordinate(end - start, lastCoord.y + p * rise); + coordList.add(interpolatedEndCoordinate); + break; + } else if (lastCoord == null) { + // no last coordinate to interpolate from + // so just use the elevation from the current coordinate + coordList.add(new Coordinate(end - start, coord.y)); + break; } - double p = (end - lastCoord.x) / run; - double rise = coord.y - lastCoord.y; - Coordinate interpolatedEndCoordinate = new Coordinate(end, lastCoord.y + p * rise); - coordList.add(interpolatedEndCoordinate); + } else { + lastCoord = new Coordinate(coord.x - start, coord.y); } - lastCoord = coord; } Coordinate coordArr[] = new Coordinate[coordList.size()]; diff --git a/src/main/java/org/opentripplanner/routing/util/elevation/ToblersHickingFunction.java b/src/main/java/org/opentripplanner/routing/util/elevation/ToblersHikingFunction.java similarity index 97% rename from src/main/java/org/opentripplanner/routing/util/elevation/ToblersHickingFunction.java rename to src/main/java/org/opentripplanner/routing/util/elevation/ToblersHikingFunction.java index 0e9e102f2ca..7193d779b6e 100644 --- a/src/main/java/org/opentripplanner/routing/util/elevation/ToblersHickingFunction.java +++ b/src/main/java/org/opentripplanner/routing/util/elevation/ToblersHikingFunction.java @@ -78,7 +78,7 @@ the License, or (at your option) any later version. * −24,2 | −45 % | 3,41 * */ -public class ToblersHickingFunction { +public class ToblersHikingFunction { /** * The exponential growth factor in Tobler´s function. @@ -103,7 +103,7 @@ public class ToblersHickingFunction { * distance multiplier. Must be > 1.0. See the table in the class documentation * for finding reasonable values for this constant. */ - public ToblersHickingFunction(double walkDistMultiplierMaxLimit) { + public ToblersHikingFunction(double walkDistMultiplierMaxLimit) { if(walkDistMultiplierMaxLimit < 1.0) { throw new IllegalArgumentException("The 'walkDistMultiplierMaxLimit' is " + walkDistMultiplierMaxLimit + ", but must be greater then 1."); diff --git a/src/main/java/org/opentripplanner/routing/vertextype/TemporarySplitterVertex.java b/src/main/java/org/opentripplanner/routing/vertextype/TemporarySplitterVertex.java index 2d75b20c82a..76c8d6ddf37 100644 --- a/src/main/java/org/opentripplanner/routing/vertextype/TemporarySplitterVertex.java +++ b/src/main/java/org/opentripplanner/routing/vertextype/TemporarySplitterVertex.java @@ -19,6 +19,7 @@ public class TemporarySplitterVertex extends SplitterVertex implements Temporary public TemporarySplitterVertex(String label, double x, double y, StreetEdge streetEdge, boolean endVertex) { super(null, label, x, y, streetEdge); this.endVertex = endVertex; + this.wheelchairAccessible = streetEdge.isWheelchairAccessible(); } @Override diff --git a/src/test/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverterTest.java b/src/test/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverterTest.java index 8647cf77d91..e38277f66cd 100644 --- a/src/test/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverterTest.java +++ b/src/test/java/org/opentripplanner/api/resource/GraphPathToTripPlanConverterTest.java @@ -45,7 +45,6 @@ import org.opentripplanner.routing.edgetype.FreeEdge; import org.opentripplanner.routing.edgetype.LegSwitchingEdge; import org.opentripplanner.routing.edgetype.OnBoardDepartPatternHop; -import org.opentripplanner.routing.edgetype.PartialStreetEdge; import org.opentripplanner.routing.edgetype.PatternDwell; import org.opentripplanner.routing.edgetype.PatternHop; import org.opentripplanner.routing.edgetype.PatternInterlineDwell; @@ -59,6 +58,7 @@ import org.opentripplanner.routing.edgetype.StreetTransitLink; import org.opentripplanner.routing.edgetype.StreetTraversalPermission; import org.opentripplanner.routing.edgetype.StreetWithElevationEdge; +import org.opentripplanner.routing.edgetype.TemporaryPartialStreetEdge; import org.opentripplanner.routing.edgetype.TimetableSnapshot; import org.opentripplanner.routing.edgetype.TransitBoardAlight; import org.opentripplanner.routing.edgetype.TripPattern; @@ -76,6 +76,7 @@ import org.opentripplanner.routing.vertextype.OnboardDepartVertex; import org.opentripplanner.routing.vertextype.PatternArriveVertex; import org.opentripplanner.routing.vertextype.PatternDepartVertex; +import org.opentripplanner.routing.vertextype.StreetVertex; import org.opentripplanner.routing.vertextype.TransitStop; import org.opentripplanner.routing.vertextype.TransitStopArrive; import org.opentripplanner.routing.vertextype.TransitStopDepart; @@ -588,7 +589,7 @@ private GraphPath[] buildPaths() { v50, v52, 0); StreetEdge e53p = new StreetEdge(v52, v54, l53, "Edge 53", 1.0, StreetTraversalPermission.ALL, false); - PartialStreetEdge e53 = new PartialStreetEdge(e53p, v52, v54, l53, "Edge 53", 1.0); + TemporaryPartialStreetEdge e53 = newTemporaryPartialStreetEdge(e53p, v52, v54, l53, "Edge 53", 1.0); StreetBikeRentalLink e55 = new StreetBikeRentalLink( v54, v56); RentABikeOffEdge e57 = new RentABikeOffEdge( @@ -901,28 +902,7 @@ private void compare(Itinerary itinerary, Type type) { } compareStopIds(stopIds, type); - /* - * This four-dimensional array is indexed as follows: - * - * [X][ ][ ][ ] The leg number - * [ ][X][ ][ ] The walk step number - * [ ][ ][X][ ] 0 for the start of the walk step, 1 for the end - * [ ][ ][ ][X] 0 for the distance traveled, 1 for the actual elevation - * - * Although technically, this particular array is not jagged, some of its elements are null. - */ - Double[][][][] elevations = new Double[9][2][2][2]; - for (int i = 0; i < elevations.length; i++) { - for (int j = 0; j < elevations[i].length; j++) { - if (steps[i].length <= j) break; - for (int k = 0; k < elevations[i][j].length; k++) { - if (steps[i][j].elevation.size() <= k) break; - elevations[i][j][k][0] = steps[i][j].elevation.get(k).first; - elevations[i][j][k][1] = steps[i][j].elevation.get(k).second; - } - } - } - compareElevations(elevations, type); + compareElevations(steps, type); } /** Compare all simple itinerary fields to their expected values. */ @@ -1894,7 +1874,36 @@ private void compareStopIds(FeedScopedId[][] stopIds, Type type) { } /** Compare the elevations to their expected values, step by step. */ - private void compareElevations(Double[][][][] elevations, Type type) { + private void compareElevations(WalkStep[][] steps, Type type) { + + /* + * This four-dimensional array is indexed as follows: + * + * [X][ ][ ][ ] The leg number + * [ ][X][ ][ ] The walk step number + * [ ][ ][X][ ] 0 for the start of the walk step, 1 for the end + * [ ][ ][ ][X] 0 for the distance traveled, 1 for the actual elevation + * + * Although technically, this particular array is not jagged, some of its elements are null. + */ + Double[][][][] elevations = new Double[9][2][2][2]; + for (int i = 0; i < elevations.length; i++) { + for (int j = 0; j < elevations[i].length; j++) { + if (steps[i].length <= j) break; + Double lastDistance = null; + for (int k = 0; k < elevations[i][j].length; k++) { + if (steps[i][j].elevation.size() <= k) break; + elevations[i][j][k][0] = steps[i][j].elevation.get(k).first; + elevations[i][j][k][1] = steps[i][j].elevation.get(k).second; + lastDistance = steps[i][j].elevation.get(k).second; + } + if (lastDistance != null) { + // Test that the total distance of the step equals the last elevation distance + assertEquals(steps[i][j].distance, lastDistance, 0.0); + } + } + } + if (type == Type.FORWARD || type == Type.BACKWARD) { assertEquals(0.0, elevations[0][0][0][0], 0.0); assertEquals(0.0, elevations[0][0][0][1], 0.0); @@ -1993,6 +2002,11 @@ private void compareElevations(Double[][][][] elevations, Type type) { assertNull(elevations[8][1][1][1]); } + + static TemporaryPartialStreetEdge newTemporaryPartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, StreetVertex v2, LineString geometry, String name, double length) { + return new TemporaryPartialStreetEdge(parentEdge, v1, v2, geometry, new NonLocalizedString(name), length); + } + /** * This class extends the {@link CalendarServiceData} class to allow for easier testing. * It includes methods to return both the set of service ids and the time zone used for testing. diff --git a/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java b/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java index 4b06ef868b1..40a16d42c05 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java @@ -12,12 +12,14 @@ import gnu.trove.map.hash.TObjectIntHashMap; import org.junit.Test; import org.opentripplanner.common.geometry.GeometryUtils; +import org.opentripplanner.common.geometry.PackedCoordinateSequence; import org.opentripplanner.common.geometry.SphericalDistanceLibrary; import org.opentripplanner.common.model.P2; import org.opentripplanner.profile.StopTreeCache; import org.opentripplanner.routing.edgetype.StreetEdge; import org.opentripplanner.routing.edgetype.StreetTransitLink; import org.opentripplanner.routing.edgetype.StreetTraversalPermission; +import org.opentripplanner.routing.edgetype.StreetWithElevationEdge; import org.opentripplanner.routing.graph.Edge; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Vertex; @@ -78,6 +80,47 @@ public void testSplitting () { } } + /** + * Makes sure edges resulting from the split have their parent's elevation data split among them + */ + @Test + public void testSplittingWithElevation () { + GeometryFactory gf= GeometryUtils.getGeometryFactory(); + double x = -122.123; + double y = 37.363; + + StreetVertex v0 = new IntersectionVertex(null, "zero", x, y); + StreetVertex v1 = new IntersectionVertex(null, "one", x + 0.00005, y + 0.00005); + LineString geom = gf.createLineString(new Coordinate[] { v0.getCoordinate(), v1.getCoordinate() }); + double dist = SphericalDistanceLibrary.distance(v0.getCoordinate(), v1.getCoordinate()); + StreetWithElevationEdge s0 = new StreetWithElevationEdge(v0, v1, geom, "test", dist, StreetTraversalPermission.ALL, false); + + int x_length = 10; + Coordinate[] c = new Coordinate[6]; + // [(0, 10), (2, 8), (4, 6), (6, 4), (8, 2), (10, 0)] + for (int i = 0; i <= x_length; i += 2) { + c[i / 2] = new Coordinate(i, x_length - i); + } + PackedCoordinateSequence elevationProfile = new PackedCoordinateSequence.Double(c, 2); + s0.setElevationProfile(elevationProfile, false); + + SplitterVertex sv0 = new SplitterVertex(null, "split", x + 0.000025, y + 0.000025, s0); + + P2 sp0 = s0.split(sv0, true); + + StreetWithElevationEdge first = (StreetWithElevationEdge) sp0.first; + StreetWithElevationEdge second = (StreetWithElevationEdge) sp0.second; + Coordinate[] firstElevation = first.getElevationProfile().toCoordinateArray(); + Coordinate[] secondElevation = second.getElevationProfile().toCoordinateArray(); + assertEquals(s0.getDistance(), first.getDistance() + second.getDistance(), 0); + assertEquals(0, firstElevation[0].x, 0); + assertEquals(first.getDistance(), firstElevation[firstElevation.length - 1].x, 0); + assertEquals(0, secondElevation[0].x, 0); + // TODO distance and elevation distance are off by 1mm + assertEquals(second.getDistance(), secondElevation[secondElevation.length - 1].x, 0.0011); + assertTrue(firstElevation[0].y > secondElevation[0].y); + } + /** * Test that all the stops are linked identically * to the street network on two builds of similar graphs diff --git a/src/test/java/org/opentripplanner/routing/edgetype/PartialStreetEdgeTest.java b/src/test/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdgeTest.java similarity index 91% rename from src/test/java/org/opentripplanner/routing/edgetype/PartialStreetEdgeTest.java rename to src/test/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdgeTest.java index cfd3f2fb855..082c914c417 100644 --- a/src/test/java/org/opentripplanner/routing/edgetype/PartialStreetEdgeTest.java +++ b/src/test/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdgeTest.java @@ -25,7 +25,7 @@ import com.vividsolutions.jts.geom.LineString; import org.opentripplanner.util.NonLocalizedString; -public class PartialStreetEdgeTest { +public class TemporaryPartialStreetEdgeTest { private Graph graph; private IntersectionVertex v1, v2, v3, v4; @@ -49,7 +49,7 @@ public void setUp() throws Exception { @Test public void testConstruction() { - PartialStreetEdge pEdge = new PartialStreetEdge(e1, v1, v2, e1.getGeometry(), + TemporaryPartialStreetEdge pEdge = newTemporaryPartialStreetEdge(e1, v1, v2, e1.getGeometry(), "partial e1", e1.getDistance()); assertTrue(pEdge.isEquivalentTo(e1)); @@ -69,9 +69,9 @@ public void testTraversal() { options.setRoutingContext(graph, v1, v2); // Partial edge with same endpoints as the parent. - PartialStreetEdge pEdge1 = new PartialStreetEdge(e1, v1, v2, e1.getGeometry(), + TemporaryPartialStreetEdge pEdge1 = newTemporaryPartialStreetEdge(e1, v1, v2, e1.getGeometry(), "partial e1", e1.getDistance()); - PartialStreetEdge pEdge2 = new PartialStreetEdge(e2, v2, v3, e2.getGeometry(), + TemporaryPartialStreetEdge pEdge2 = newTemporaryPartialStreetEdge(e2, v2, v3, e2.getGeometry(), "partial e2", e2.getDistance()); // Traverse both the partial and parent edges. @@ -173,9 +173,9 @@ public void testTraversalOfSubdividedEdge() { @Test public void testReverseEdge() { - PartialStreetEdge pEdge1 = new PartialStreetEdge(e1, v1, v2, e1.getGeometry(), + TemporaryPartialStreetEdge pEdge1 = newTemporaryPartialStreetEdge(e1, v1, v2, e1.getGeometry(), "partial e1", e1.getDistance()); - PartialStreetEdge pEdge2 = new PartialStreetEdge(e1Reverse, v2, v1, e1Reverse.getGeometry(), + TemporaryPartialStreetEdge pEdge2 = newTemporaryPartialStreetEdge(e1Reverse, v2, v1, e1Reverse.getGeometry(), "partial e2", e1Reverse.getDistance()); assertFalse(e1.isReverseOf(pEdge1)); @@ -192,9 +192,11 @@ public void testReverseEdge() { assertTrue(pEdge2.isReverseOf(pEdge1)); } - /**** - * Private Methods - ****/ + /* Private Methods */ + + static TemporaryPartialStreetEdge newTemporaryPartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, StreetVertex v2, LineString geometry, String name, double length) { + return new TemporaryPartialStreetEdge(parentEdge, v1, v2, geometry, new NonLocalizedString(name), length); + } private IntersectionVertex vertex(String label, double lat, double lon) { IntersectionVertex v = new IntersectionVertex(graph, label, lat, lon); diff --git a/src/test/java/org/opentripplanner/routing/util/TestElevationUtils.java b/src/test/java/org/opentripplanner/routing/util/TestElevationUtils.java index d946f378142..3ca74b68821 100644 --- a/src/test/java/org/opentripplanner/routing/util/TestElevationUtils.java +++ b/src/test/java/org/opentripplanner/routing/util/TestElevationUtils.java @@ -5,6 +5,7 @@ import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateSequence; import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory; +import org.opentripplanner.common.geometry.PackedCoordinateSequence; public class TestElevationUtils extends TestCase { @@ -49,4 +50,51 @@ public void testCalculateSlopeWalkEffectiveLengthFactor() { // 45% downhill hit the MAX_SLOPE_WALK_EFFECTIVE_LENGTH_FACTOR=3 again assertEquals(300.0, ElevationUtils.calculateEffectiveWalkLength(100, -45), 0.1); } + + public void testGetPartialElevationProfile() { + int x_length = 10; + Coordinate[] c = new Coordinate[6]; + // [(0, 10), (2, 8), (4, 6), (6, 4), (8, 2), (10, 0)] + for (int i = 0; i <= x_length; i += 2) { + c[i / 2] = new Coordinate(i, x_length - i); + } + PackedCoordinateSequence elevationProfile = new PackedCoordinateSequence.Double(c, 2); + + // Test that partial elevation profile from start to finish matched original profile + PackedCoordinateSequence fullProfile = + ElevationUtils.getPartialElevationProfile(elevationProfile, 0, x_length); + assertEquals(elevationProfile.getDimension(), fullProfile.getDimension()); + assertEquals(elevationProfile.size(), fullProfile.size()); + assertEquals(elevationProfile.getX(0), fullProfile.getX(0)); + assertEquals(elevationProfile.getY(0), fullProfile.getY(0)); + assertEquals(elevationProfile.getX(5), fullProfile.getX(5)); + assertEquals(elevationProfile.getY(5), fullProfile.getY(5)); + + // Test that partial elevation profile from start to half way has same start as full profile + // and that its last elevation value is between the closest values from the full profile + Double halfWay = 5.0; + PackedCoordinateSequence firstHalfProfile = + ElevationUtils.getPartialElevationProfile(elevationProfile, 0, halfWay); + assertEquals(4, firstHalfProfile.size()); + assertEquals(elevationProfile.getX(0), firstHalfProfile.getX(0)); + assertEquals(elevationProfile.getY(0), firstHalfProfile.getY(0)); + assertEquals(halfWay, firstHalfProfile.getX(3)); + assertEquals(elevationProfile.getY(3) + + (elevationProfile.getY(3) - elevationProfile.getY(4)) / 2, + firstHalfProfile.getY(3)); + + // Test that partial elevation profile from halfway to end has x values ranging from 0 to + // (end - halfway), that the first elevation value is between the closest values from + // the full profile and that the end has the same elevation value as full profile's end + PackedCoordinateSequence lastHalfProfile = + ElevationUtils.getPartialElevationProfile(elevationProfile, halfWay, 10); + assertEquals(4, lastHalfProfile.size()); + assertEquals(0.0, lastHalfProfile.getX(0)); + assertEquals(elevationProfile.getY(3) + + (elevationProfile.getY(3) - elevationProfile.getY(4)) / 2, + lastHalfProfile.getY(0)); + assertEquals(halfWay, lastHalfProfile.getX(3)); + assertEquals(fullProfile.getY(5), + lastHalfProfile.getY(3)); + } } diff --git a/src/test/java/org/opentripplanner/routing/util/elevation/ToblersHickingFunctionTest.java b/src/test/java/org/opentripplanner/routing/util/elevation/ToblersHikingFunctionTest.java similarity index 92% rename from src/test/java/org/opentripplanner/routing/util/elevation/ToblersHickingFunctionTest.java rename to src/test/java/org/opentripplanner/routing/util/elevation/ToblersHikingFunctionTest.java index 477a9c35293..bbc16d2dd5b 100644 --- a/src/test/java/org/opentripplanner/routing/util/elevation/ToblersHickingFunctionTest.java +++ b/src/test/java/org/opentripplanner/routing/util/elevation/ToblersHikingFunctionTest.java @@ -3,9 +3,9 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.opentripplanner.routing.util.elevation.ToblersHickingFunctionTest.TestCase.tc; +import static org.opentripplanner.routing.util.elevation.ToblersHikingFunctionTest.TestCase.tc; -public class ToblersHickingFunctionTest { +public class ToblersHikingFunctionTest { private static final double CUT_OFF_LIMIT = 3.2; @@ -32,7 +32,7 @@ public void calculateHorizontalWalkingDistanceMultiplier() { tc(-45, CUT_OFF_LIMIT) }; - ToblersHickingFunction f = new ToblersHickingFunction(CUT_OFF_LIMIT); + ToblersHikingFunction f = new ToblersHikingFunction(CUT_OFF_LIMIT); for (TestCase it : testCases) { double distMultiplier = f.calculateHorizontalWalkingDistanceMultiplier(it.dx, it.dh);