
org.opentripplanner.graph_builder.module.ned.MissingElevationHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otp Show documentation
Show all versions of otp Show documentation
The OpenTripPlanner multimodal journey planning system
The newest version!
package org.opentripplanner.graph_builder.module.ned;
import java.util.HashMap;
import java.util.Map;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
import org.opentripplanner.astar.model.BinHeap;
import org.opentripplanner.framework.lang.DoubleUtils;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.graph_builder.issues.ElevationFlattened;
import org.opentripplanner.graph_builder.issues.ElevationProfileFailure;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.StreetEdge;
import org.opentripplanner.street.model.edge.StreetElevationExtensionBuilder;
import org.opentripplanner.street.model.vertex.Vertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Assigns elevation to edges which don't yet have elevation set, which may be for example tunnels,
* bridges or islands.
*
* Elevation may be missing from a {@link StreetEdge} for two reasons: 1. the source DEM files
* contained no data for the whole geometry 2. {@link StreetEdge#isSlopeOverride()} is set
*
* The elevation for missing edges is set through its vertices, with the elevation for the from/to
* vertices being used to set the elevation profile.
*
* -
* The source elevations are determined for vertices using edges with an existing elevation
* profile, along with values from the {@code ele} tag
*
* -
* All vertices within {@code maxElevationPropagationMeters} of vertices with elevation
* without elevation are visited
*
* -
* Foreach vertex without elevation the first two paths from a vertex with elevation are used
* to interpolate elevations
*
* -
* If a vertex only had a single path, then the last known elevation is used
*
*
* Once elevations for vertices are interpolated they are used to set the elevation profile
* for the incoming / outgoing StreetEdges
*
*
*/
class MissingElevationHandler {
private static final Logger LOG = LoggerFactory.getLogger(MissingElevationHandler.class);
private final DataImportIssueStore issueStore;
private final Map existingElevationForVertices;
private final double maxElevationPropagationMeters;
public MissingElevationHandler(
DataImportIssueStore issueStore,
Map existingElevationsForVertices,
double maxElevationPropagationMeters
) {
this.issueStore = issueStore;
this.existingElevationForVertices = existingElevationsForVertices;
this.maxElevationPropagationMeters = maxElevationPropagationMeters;
}
/**
* Assign missing elevations by interpolating from nearby points with known elevation; also handle
* osm ele tags
*/
void run() {
LOG.debug("Assigning missing elevations");
// elevation for each vertex (known or interpolated)
var pq = createPriorityQueue(existingElevationForVertices);
var elevations = new HashMap<>(existingElevationForVertices);
// Grow an SPT outward from vertices with known elevations into regions where the
// elevation is not known. When branches meet, follow the back pointers through the region
// of unknown elevation, setting elevations via interpolation.
propagateElevationToNearbyVertices(pq, elevations);
// Assign elevations to street edges based on the vertices
elevations
.keySet()
.forEach(vertex -> {
vertex
.getIncomingStreetEdges()
.forEach(edge -> assignElevationToEdgeIfPossible(elevations, edge));
vertex
.getOutgoingStreetEdges()
.forEach(edge -> assignElevationToEdgeIfPossible(elevations, edge));
});
}
private BinHeap createPriorityQueue(Map elevations) {
var pq = new BinHeap();
elevations.forEach(
(
(vertex, elevation) -> {
vertex
.getIncoming()
.forEach(edge -> {
if (edge.getDistanceMeters() < maxElevationPropagationMeters) {
pq.insert(
new ElevationRepairState(
vertex,
elevation,
edge.getFromVertex(),
edge.getDistanceMeters()
),
edge.getDistanceMeters()
);
}
});
vertex
.getOutgoing()
.forEach(edge -> {
if (edge.getDistanceMeters() < maxElevationPropagationMeters) {
pq.insert(
new ElevationRepairState(
vertex,
elevation,
edge.getToVertex(),
edge.getDistanceMeters()
),
edge.getDistanceMeters()
);
}
});
}
)
);
return pq;
}
private void propagateElevationToNearbyVertices(
BinHeap pq,
Map elevations
) {
// Stores vertices without elevation which were visited by a single path from a vertices with elevation
var pending = new HashMap();
while (!pq.empty()) {
ElevationRepairState currentState = pq.extract_min();
if (elevations.containsKey(currentState.currentVertex)) {
continue;
}
if (pending.containsKey(currentState.currentVertex)) {
var otherState = pending.get(currentState.currentVertex);
if (otherState.initialVertex != currentState.currentVertex) {
interpolateElevationsAlongBackPath(otherState, currentState, elevations, pending);
interpolateElevationsAlongBackPath(currentState, otherState, elevations, pending);
} else {
continue;
}
} else {
pending.put(currentState.currentVertex, currentState);
}
for (Edge e : currentState.currentVertex.getIncoming()) {
var nsVertex = e.getFromVertex();
var nsDistance = currentState.distance + e.getDistanceMeters();
if (elevations.containsKey(nsVertex) || nsDistance > maxElevationPropagationMeters) {
continue;
}
pq.insert(new ElevationRepairState(currentState, nsVertex, nsDistance), nsDistance);
}
for (Edge e : currentState.currentVertex.getOutgoing()) {
var nsVertex = e.getToVertex();
var nsDistance = currentState.distance + e.getDistanceMeters();
if (elevations.containsKey(nsVertex) || nsDistance > maxElevationPropagationMeters) {
continue;
}
pq.insert(new ElevationRepairState(currentState, nsVertex, nsDistance), nsDistance);
}
}
// Copy elevation to all pending vertices (where only one path with an initial elevation was found)
pending.forEach((vertex, elevationRepairState) -> {
if (!elevations.containsKey(vertex)) {
elevations.put(vertex, lastKnowElevationForState(elevationRepairState, elevations));
}
});
}
private Double lastKnowElevationForState(
ElevationRepairState elevationRepairState,
Map elevations
) {
var backState = elevationRepairState.previousState;
while (backState != null) {
if (elevations.containsKey(backState.currentVertex)) {
return elevations.get(backState.currentVertex);
}
backState = backState.previousState;
}
return elevationRepairState.initialElevation;
}
private void interpolateElevationsAlongBackPath(
ElevationRepairState stateToBackTrack,
ElevationRepairState alternateState,
Map elevations,
Map pending
) {
var elevationDiff = alternateState.initialElevation - stateToBackTrack.initialElevation;
var totalDistance = stateToBackTrack.distance + alternateState.distance;
var currentState = stateToBackTrack;
while (currentState != null) {
if (!elevations.containsKey(currentState.currentVertex)) {
var elevation =
currentState.initialElevation + elevationDiff * (currentState.distance / totalDistance);
elevation = DoubleUtils.roundTo1Decimal(elevation);
elevations.put(currentState.currentVertex, elevation);
pending.remove(currentState.currentVertex);
}
currentState = currentState.previousState;
}
}
private void assignElevationToEdgeIfPossible(Map elevations, StreetEdge edge) {
if (edge.getElevationProfile() != null) {
return;
}
Double fromElevation = elevations.get(edge.getFromVertex());
Double toElevation = elevations.get(edge.getToVertex());
if (fromElevation == null || toElevation == null) {
if (!edge.isElevationFlattened() && !edge.isSlopeOverride()) {
issueStore.add(new ElevationProfileFailure(edge, "Failed to propagate elevation data"));
}
return;
}
Coordinate[] coords = new Coordinate[] {
new Coordinate(0, fromElevation),
new Coordinate(edge.getDistanceMeters(), toElevation),
};
PackedCoordinateSequence profile = new PackedCoordinateSequence.Double(coords);
try {
StreetElevationExtensionBuilder
.of(edge)
.withElevationProfile(profile)
.withComputed(true)
.build()
.ifPresent(edge::setElevationExtension);
if (edge.isElevationFlattened()) {
issueStore.add(new ElevationFlattened(edge));
}
} catch (Exception ex) {
issueStore.add(new ElevationProfileFailure(edge, ex.getMessage()));
}
}
private static class ElevationRepairState {
final ElevationRepairState previousState;
final Vertex initialVertex;
final Double initialElevation;
final Vertex currentVertex;
final Double distance;
ElevationRepairState(
ElevationRepairState previousState,
Vertex currentVertex,
Double distance
) {
this.previousState = previousState;
this.initialVertex = previousState.initialVertex;
this.initialElevation = previousState.initialElevation;
this.currentVertex = currentVertex;
this.distance = distance;
}
ElevationRepairState(
Vertex initialVertex,
Double initialElevation,
Vertex currentVertex,
Double distance
) {
this.previousState = null;
this.initialVertex = initialVertex;
this.initialElevation = initialElevation;
this.currentVertex = currentVertex;
this.distance = distance;
}
@Override
public String toString() {
return (
"ElevationRepairState{" +
"initialVertex=" +
initialVertex +
", initialElevation=" +
initialElevation +
", currentVertex=" +
currentVertex +
", distance=" +
distance +
'}'
);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy