Skip to content

Commit

Permalink
Add elevation debug tile layer
Browse files Browse the repository at this point in the history
  • Loading branch information
flaktack committed Mar 28, 2022
1 parent 104ed4b commit 7c3ebf4
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static org.opentripplanner.util.ElevationUtils.computeEllipsoidToGeoidDifference;

import com.fasterxml.jackson.databind.JsonNode;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
Expand Down Expand Up @@ -106,9 +105,6 @@ public class ElevationModule implements GraphBuilderModule {
*/
private final double elevationUnitMultiplier;

/** the graph being built */
private Graph graph;

/** A concurrent hashmap used for storing geoid difference values at various coordinates */
private final ConcurrentHashMap<Integer, Double> geoidDifferenceCache = new ConcurrentHashMap<>();

Expand All @@ -117,6 +113,9 @@ public class ElevationModule implements GraphBuilderModule {

private final ThreadLocal<Coverage> coverageInterpolatorThreadLocal = new ThreadLocal<>();

private double minElevation = Double.MAX_VALUE;
private double maxElevation = Double.MIN_VALUE;

/** used only for testing purposes */
public ElevationModule(ElevationGridCoverageFactory factory) {
this(
Expand Down Expand Up @@ -151,17 +150,6 @@ public ElevationModule(
this.distanceBetweenSamplesM = distanceBetweenSamplesM;
}

/**
* Gets the desired amount of processors to use for elevation calculations from the build-config setting. It will
* return at least 1 processor and no more than the maximum available processors. The default return value is 1
* processor.
*/
public static int fromConfig(JsonNode elevationModuleParallelism) {
int maxProcessors = Runtime.getRuntime().availableProcessors();
int minimumProcessors = 1;
return Math.max(minimumProcessors, Math.min(elevationModuleParallelism.asInt(minimumProcessors), maxProcessors));
}

@Override
public void buildGraph(
Graph graph,
Expand All @@ -170,7 +158,6 @@ public void buildGraph(
) {
Instant start = Instant.now();
this.issueStore = issueStore;
this.graph = graph;
gridCoverageFactory.fetchData(graph);

graph.setDistanceBetweenElevationSamples(this.distanceBetweenSamplesM);
Expand Down Expand Up @@ -271,9 +258,19 @@ public void buildGraph(
HashMap<Vertex, Double> extraElevation = (HashMap<Vertex, Double>) extra.get(ElevationPoint.class);
assignMissingElevations(graph, edgesWithCalculatedElevations, extraElevation);

updateElevationMetadata(graph);

LOG.info("Finished elevation processing in {}s", Duration.between(start, Instant.now()).toSeconds());
}

private void updateElevationMetadata(Graph graph) {
if (nPointsOutsideDEM.get() < nPointsEvaluated.get()) {
graph.hasElevation = true;
graph.minElevation = minElevation;
graph.maxElevation = maxElevation;
}
}

static class ElevationRepairState {
/* This uses an intuitionist approach to elevation inspection */
public StreetEdge backEdge;
Expand Down Expand Up @@ -728,9 +725,16 @@ private double getElevation(Coverage coverage, double x, double y) throws PointO
nPointsOutsideDEM.incrementAndGet();
throw e;
}

var elevation = (values[0] * elevationUnitMultiplier) -
(includeEllipsoidToGeoidDifference ? getApproximateEllipsoidToGeoidDifference(y, x) : 0);

minElevation = Math.min(minElevation, elevation);
maxElevation = Math.max(maxElevation, elevation);

nPointsEvaluated.incrementAndGet();
return (values[0] * elevationUnitMultiplier) -
(includeEllipsoidToGeoidDifference ? getApproximateEllipsoidToGeoidDifference(y, x) : 0);

return elevation;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,14 @@
import java.awt.Color;

/**
* A default ColorPalette with two ranges: min-max-maxMax. It modify the hue between two colors in
* A default ColorPalette with two ranges: min-max-maxMax. It modifies the hue between two colors in
* the first range (here from green to red, via blue), and modify the brightness in the second range
* (from red to black).
*
*
* @author laurent
*/
public class DefaultScalarColorPalette implements ScalarColorPalette {

private final double min;

private final double max;

private final double maxMax;

public DefaultScalarColorPalette(double min, double max, double maxMax) {
this.min = min;
this.max = max;
this.maxMax = maxMax;
}
public record DefaultScalarColorPalette(double min, double max, double maxMax)
implements ScalarColorPalette {

@Override
public Color getColor(double value) {
Expand All @@ -33,7 +22,8 @@ public Color getColor(double value) {
x = 1.0f;
}
return Color.getHSBColor(0.0f, 1.0f, 0.7f - x * 0.7f);
} else {
}
else {
// Green (hue=0.3) to red (hue=1.0) gradient
float x = (float) ((value - min) / (max - min));
if (x < 0.0f) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,38 @@

import com.jhlabs.awt.ShapeStroke;
import com.jhlabs.awt.TextStroke;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.image.BufferedImage;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import org.locationtech.jts.awt.IdentityPointTransformation;
import org.locationtech.jts.awt.PointShapeFactory;
import org.locationtech.jts.awt.ShapeWriter;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.linearref.LengthLocationMap;
import org.locationtech.jts.linearref.LocationIndexedLine;
import org.locationtech.jts.operation.buffer.BufferParameters;
import org.locationtech.jts.operation.buffer.OffsetCurveBuilder;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.model.T2;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.vertextype.StreetVertex;

import java.awt.Polygon;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Collection;
import java.util.Comparator;
import java.util.stream.Collectors;

/**
* A TileRenderer implementation which get all edges/vertex in the bounding box of the tile, and
* call a EdgeVertexRenderer for getting rendering attributes of each (color, string label...).
Expand Down Expand Up @@ -67,6 +79,14 @@ public interface EdgeVertexRenderer {
*/
String getName();

default boolean hasEdgeSegments(Edge edge) {
return false;
}

default Iterable<T2<Double, Color>> edgeSegments(Edge edge) {
return List.of();
}

default int vertexSorter(Vertex v1, Vertex v2) {
return defaultVertexComparator.compare(v1, v2);
}
Expand Down Expand Up @@ -111,13 +131,13 @@ public void renderTile(TileRenderContext context) {
.getVerticesForEnvelope(bboxWithMargins)
.stream()
.sorted(evRenderer::vertexSorter)
.collect(Collectors.toList());
.toList();

Collection<Edge> edges = context.graph.getStreetIndex().getEdgesForEnvelope(bboxWithMargins)
.stream()
.distinct()
.sorted(evRenderer::edgeSorter)
.collect(Collectors.toList());
.toList();

// Note: we do not use the transform inside the shapeWriter, but do it ourselves
// since it's easier for the offset to work in pixel size.
Expand Down Expand Up @@ -179,8 +199,26 @@ public void renderTile(TileRenderContext context) {
Shape offsetShape = shapeWriter.toShape(offsetLine);

context.graphics.setStroke(hasGeom ? halfStroke : halfDashedStroke);
context.graphics.setColor(evAttrs.color);
context.graphics.draw(offsetShape);

if (evRenderer.hasEdgeSegments(edge)) {
LocationIndexedLine line = new LocationIndexedLine(offsetLine);
LengthLocationMap locater = new LengthLocationMap(offsetLine);
var offsetLength = offsetLine.getLength();

var previousLocation = line.getStartIndex();
for (var p2 : evRenderer.edgeSegments(edge)) {
var currentLocation = locater.getLocation(offsetLength * p2.first);
var segmentGeometry = line.extractLine(previousLocation, currentLocation);
var segmentShape = shapeWriter.toShape(segmentGeometry);
context.graphics.setColor(p2.second);
context.graphics.draw(segmentShape);

previousLocation = currentLocation;
}
} else {
context.graphics.setColor(evAttrs.color);
context.graphics.draw(offsetShape);
}
if (lineWidth > 6.0f) {
context.graphics.setColor(Color.WHITE);
context.graphics.setStroke(arrowStroke);
Expand Down
105 changes: 105 additions & 0 deletions src/main/java/org/opentripplanner/inspector/ElevationEdgeRenderer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.opentripplanner.inspector;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.opentripplanner.common.model.T2;
import org.opentripplanner.inspector.EdgeVertexTileRenderer.EdgeVertexRenderer;
import org.opentripplanner.inspector.EdgeVertexTileRenderer.EdgeVisualAttributes;
import org.opentripplanner.inspector.EdgeVertexTileRenderer.VertexVisualAttributes;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;

class ElevationEdgeRenderer implements EdgeVertexRenderer {

private final ScalarColorPalette colorPalette;

ElevationEdgeRenderer(Graph graph) {
if (graph.hasElevation) {
colorPalette = new DefaultScalarColorPalette(
graph.minElevation,
(graph.minElevation + graph.maxElevation) / 2,
graph.maxElevation
);
} else {
colorPalette = new DefaultScalarColorPalette(0, 0, 0);
}
}

@Override
public boolean renderEdge(
Edge e, EdgeVisualAttributes attrs
) {
return true;
}

@Override
public boolean renderVertex(
Vertex v, VertexVisualAttributes attrs
) {
var elevation = findElevationForVertex(v);
if (elevation != null) {
attrs.color = colorPalette.getColor(elevation);
attrs.label = elevation.toString();
return true;
}
else {
return false;
}
}

@Override
public boolean hasEdgeSegments(Edge edge) {
return true;
}

@Override
public Iterable<T2<Double, Color>> edgeSegments(Edge edge) {
if (edge instanceof StreetEdge) {
if (((StreetEdge) edge).hasElevationExtension()) {
var edgeLength = edge.getDistanceMeters();
var color = Color.DARK_GRAY;

var t2 = new ArrayList<T2<Double, Color>>();
var profile = ((StreetEdge) edge).getElevationProfile();
for(int i = 0; i < profile.size(); ++i) {
var point = profile.getCoordinate(i);
if (i != 0) {
t2.add(new T2<>(point.x / edgeLength, color));
}
color = colorPalette.getColor(point.y);
}
return t2;
} else {
return List.of(new T2<>(1.0d, Color.GRAY));
}
} else {
return List.of(new T2<>(1.0d, Color.LIGHT_GRAY));
}
}

private Double findElevationForVertex(Vertex v) {
return Stream.concat(
v.getIncomingStreetEdges()
.stream()
.filter(StreetEdge::hasElevationExtension)
.map(streetEdge -> streetEdge.getElevationProfile()
.getCoordinate(streetEdge.getElevationProfile().size() - 1).y),
v.getOutgoingStreetEdges()
.stream()
.filter(StreetEdge::hasElevationExtension)
.map(streetEdge -> streetEdge.getElevationProfile()
.getCoordinate(0).y)
)
.findAny()
.orElse(null);
}

@Override
public String getName() {
return "Elevation";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public TileRendererManager(Graph graph) {
renderers.put("traversal", new EdgeVertexTileRenderer(
new TraversalPermissionsEdgeRenderer()));
renderers.put("wheelchair", new EdgeVertexTileRenderer(new WheelchairEdgeRenderer()));
renderers.put("elevation", new EdgeVertexTileRenderer(new ElevationEdgeRenderer(graph)));
}

public void registerRenderer(String layer, TileRenderer tileRenderer) {
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/opentripplanner/routing/graph/Graph.java
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,21 @@ public class Graph implements Serializable {
*/
public Double ellipsoidToGeoidDifference = 0.0;

/**
* Does this graph contain elevation data?
*/
public boolean hasElevation = false;

/**
* If this graph contains elevation data, the minimum value.
*/
public Double minElevation = null;

/**
* If this graph contains elevation data, the maximum value.
*/
public Double maxElevation = null;

/** Parent stops **/
public Map<FeedScopedId, Station> stationById = new HashMap<>();

Expand Down

0 comments on commit 7c3ebf4

Please sign in to comment.