refactor: Refactor Features processing
This commit is contained in:
parent
578f8bdf6e
commit
6ae6d2e69f
8 changed files with 472 additions and 208 deletions
|
@ -2,25 +2,29 @@ package ch.inf3.horizoncut.cli;
|
||||||
|
|
||||||
import ch.inf3.horizoncut.data.DisplayCalculator;
|
import ch.inf3.horizoncut.data.DisplayCalculator;
|
||||||
import ch.inf3.horizoncut.data.DistanceLayer;
|
import ch.inf3.horizoncut.data.DistanceLayer;
|
||||||
import ch.inf3.horizoncut.data.GeoJsonExport;
|
import ch.inf3.horizoncut.data.Feature;
|
||||||
import ch.inf3.horizoncut.data.Peak;
|
import ch.inf3.horizoncut.data.ObservedFeature;
|
||||||
import ch.inf3.horizoncut.data.Position;
|
import ch.inf3.horizoncut.data.Position;
|
||||||
import ch.inf3.horizoncut.data.Tile;
|
import ch.inf3.horizoncut.data.Tile;
|
||||||
import ch.inf3.horizoncut.data.TileMap;
|
import ch.inf3.horizoncut.data.TileMap;
|
||||||
import ch.inf3.horizoncut.data.TileReader;
|
import ch.inf3.horizoncut.data.TileReader;
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Polygon;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.Shape;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -33,178 +37,286 @@ public class Cli {
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
||||||
Position c = new Position(46.326570, 7.466880);
|
long initStart = System.currentTimeMillis();
|
||||||
TileMap tileMap = new TileReader("/home/valdor/Downloads/SRTM1/").readHGTFiles(c, 2);
|
Position eyePosition = new Position(46.326570, 7.466880);
|
||||||
|
TileMap tileMap = new TileReader("/home/valdor/Downloads/SRTM1/").readHGTFiles(eyePosition, 2);
|
||||||
|
|
||||||
final int width = 8000;
|
final int width = 8000;
|
||||||
final int height = 2000;
|
final int height = 2000;
|
||||||
|
|
||||||
Tile atOrigin = tileMap.getByLatLon(c.gridLat, c.gridLon);
|
Tile atOrigin = tileMap.getByLatLon(eyePosition.gridLat, eyePosition.gridLon);
|
||||||
double eyeHeight = atOrigin.elevation(c.row, c.col);
|
double eyeHeight = atOrigin.elevation(eyePosition.row, eyePosition.col);
|
||||||
LOG.info("Eye at {} is at {}", c.getLatLon(), eyeHeight);
|
|
||||||
|
|
||||||
double minHorizAngle = 135; //107; // 135 ~Brunegghorn
|
double minHorizAngle = 107; //107; // 135 ~Brunegghorn
|
||||||
double maxHorizAngle = 226; //226; // 197 ~Combin de Valsorey
|
double maxHorizAngle = 226; //226; // 197 ~Combin de Valsorey
|
||||||
|
|
||||||
DisplayCalculator dc = new DisplayCalculator(width, height, minHorizAngle, maxHorizAngle, eyeHeight);
|
DisplayCalculator dc = new DisplayCalculator(width, height, minHorizAngle, maxHorizAngle, eyePosition, eyeHeight);
|
||||||
|
|
||||||
int maxDistance = 100_000;
|
int maxDistance = 100_000;
|
||||||
int minDistance = 500;
|
int minDistance = 500;
|
||||||
|
|
||||||
LOG.info("Distances of {} to {}", minDistance, maxDistance);
|
Collection<Feature> rawFeatures = new ArrayList<>();
|
||||||
|
rawFeatures.addAll(Feature.readFeatures("data/osm-overpass-peaks.geojson", tileMap, 150, true));
|
||||||
|
rawFeatures.addAll(Feature.readFeatures("data/osm-overpass-villages.geojson", tileMap, height - 50, false));
|
||||||
|
|
||||||
BufferedImage img = new BufferedImage(width, height + 1, BufferedImage.TYPE_INT_RGB);
|
Collection<ObservedFeature> allFeatures = rawFeatures.stream()
|
||||||
|
.map(f -> f.observe(dc))
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
Collection<DistanceLayer> layers = new ArrayList<>(maxDistance);
|
BufferedImage bufferedImg = new BufferedImage(width, height + 1, BufferedImage.TYPE_INT_RGB);
|
||||||
|
Graphics2D g = (Graphics2D) bufferedImg.getGraphics();
|
||||||
|
|
||||||
|
long initStop = System.currentTimeMillis();
|
||||||
|
LOG.info("Initialized system for computations from {}@{}m with distances of {} to {} ({} ms)",
|
||||||
|
eyePosition.getLatLon(), eyeHeight, minDistance, maxDistance, initStop - initStart);
|
||||||
|
|
||||||
|
long layersStart = System.currentTimeMillis();
|
||||||
|
Collection<DistanceLayer> reversedLayers = new ArrayList<>(maxDistance);
|
||||||
for (int d = maxDistance; d > minDistance; d -= SLICE_DISTANCE) {
|
for (int d = maxDistance; d > minDistance; d -= SLICE_DISTANCE) {
|
||||||
final int cDist = d;
|
final int cDist = d;
|
||||||
double[] data = computeAtDistance(c, tileMap, cDist, dc);
|
double[] data = computeAtDistance(tileMap, cDist, dc);
|
||||||
layers.add(new DistanceLayer(d - SLICE_DISTANCE, d, data));
|
reversedLayers.add(new DistanceLayer(d - SLICE_DISTANCE, d, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long layersEnd = System.currentTimeMillis();
|
||||||
|
LOG.info("Computed {} layers ({} ms)", reversedLayers.size(), layersEnd - layersStart);
|
||||||
|
|
||||||
|
long horizonStart = System.currentTimeMillis();
|
||||||
// We compute the horizon and delete any layer that is behind the horizon
|
// We compute the horizon and delete any layer that is behind the horizon
|
||||||
// and is not used at all for the landscape.
|
// and is not used at all for the landscape.
|
||||||
// For this, we are using the fact that we computed the layers starting
|
// For this, we are using the fact that we computed the layers starting
|
||||||
// with the farthest ones in the build loop.
|
// with the farthest ones in the build loop.
|
||||||
DistanceLayer horizonLayer = layers.stream()
|
DistanceLayer horizonLayer = reversedLayers.stream()
|
||||||
.reduce((a, b) -> a.mergeWith(b))
|
.reduce((a, b) -> a.mergeWith(b))
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
layers = layers.stream()
|
reversedLayers = reversedLayers.stream()
|
||||||
.dropWhile(l -> horizonLayer.isLower(l))
|
.dropWhile(l -> horizonLayer.isLower(l))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
LOG.info("There are {} layers remaining after horizon cleanup", layers.size());
|
|
||||||
|
|
||||||
// We recompute the min and max distances after the cleanup of layers that
|
// We recompute the min and max distances after the cleanup of layers that
|
||||||
// are not useful for the display.
|
// are not useful for the display.
|
||||||
maxDistance = layers.stream()
|
maxDistance = reversedLayers.stream()
|
||||||
.map(l -> l.farDistance)
|
.map(l -> l.farDistance)
|
||||||
.max(Integer::compareTo)
|
.max(Integer::compareTo)
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
minDistance = layers.stream()
|
minDistance = reversedLayers.stream()
|
||||||
.map(l -> l.nearDistance)
|
.map(l -> l.nearDistance)
|
||||||
.min(Integer::compareTo)
|
.min(Integer::compareTo)
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
LOG.info("Distances of {} to {}", minDistance, maxDistance);
|
dc.updateMinMaxVertAngle(reversedLayers);
|
||||||
|
|
||||||
Collection<DistanceLayer> nLayers = layers.stream()
|
long horizonEnd = System.currentTimeMillis();
|
||||||
.collect(Collectors.groupingBy(l -> l.nearDistance / 1, Collectors.reducing((a, b) -> a.mergeWith(b))))
|
LOG.info("There are {} layers remaining after horizon cleanup at distances {} to {} ({} ms)",
|
||||||
.values().stream()
|
reversedLayers.size(), minDistance, maxDistance, horizonEnd - horizonStart);
|
||||||
.flatMap(o -> o.stream())
|
|
||||||
.sorted((a, b) -> b.compareTo(a))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
nLayers = DistanceLayer.filterInvisible(nLayers);
|
long labelsStart = System.currentTimeMillis();
|
||||||
|
TreeMap<Integer, DistanceLayer> dLayers = reversedLayers.stream()
|
||||||
|
.collect(Collectors.toMap(l -> l.nearDistance, l -> l, (a, b) -> a.mergeWith(b), TreeMap::new));
|
||||||
|
|
||||||
LOG.info("There are {} layers remaining after layer merges", nLayers.size());
|
// We start processing the features to avoid overlapping features in the
|
||||||
|
// labels as well as displaying features that are hidden from the observation
|
||||||
|
// point.
|
||||||
|
|
||||||
// We dont want to compte the min angle to avoid looking at the ground
|
Predicate<ObservedFeature> isVisibleFilter = (feature) -> {
|
||||||
// just before the position ...
|
int x = dc.getXAtBearing(feature.bearing);
|
||||||
double minVertAngle = 80;
|
return DistanceLayer.isVisible(dLayers.values(), feature.distance, x, feature.visibleAngle);
|
||||||
double maxVertAngle = nLayers.stream()
|
};
|
||||||
.map(l -> l.getMax())
|
|
||||||
.max(Double::compareTo)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
maxVertAngle += 2;
|
TreeMap<Integer, ObservedFeature> labeledFeatures = allFeatures.stream()
|
||||||
|
.filter(f -> dc.isDisplayed(f.bearing, f.visibleAngle))
|
||||||
|
.filter(isVisibleFilter)
|
||||||
|
.collect(Collectors.toMap(f -> f.getObservedX(), f -> f, (a, b) -> Feature.highest(a, b), TreeMap::new));
|
||||||
|
|
||||||
LOG.info("Displaying angles are {} - {}", minVertAngle, maxVertAngle);
|
Integer cCol = labeledFeatures.firstKey();
|
||||||
|
while (cCol < labeledFeatures.lastKey()) {
|
||||||
|
Integer nCol = labeledFeatures.higherKey(cCol);
|
||||||
|
|
||||||
// We loop in descending order to avoid writing further points on top
|
if (nCol - cCol <= 25) {
|
||||||
// of nearer points.
|
double nScore = labeledFeatures.get(nCol).getScore();
|
||||||
for (DistanceLayer layer : nLayers) {
|
double cScore = labeledFeatures.get(cCol).getScore();
|
||||||
float dPos = (float) ((maxDistance - layer.nearDistance) / (maxDistance * 1.0f));
|
|
||||||
int color = Color.HSBtoRGB(dPos * 0.5f, 1.0f, 1.0f);
|
|
||||||
|
|
||||||
for (int x = 0; x < width; x++) {
|
if (nScore > cScore) {
|
||||||
double visibleAngle = layer.data[x];
|
LOG.debug("Removing {} ({}) to keep {} ({}) at X {}", labeledFeatures.get(cCol), cScore, labeledFeatures.get(nCol), nScore, cCol);
|
||||||
double pos = (visibleAngle - minVertAngle) * (height / (maxVertAngle - minVertAngle));
|
labeledFeatures.remove(cCol);
|
||||||
|
cCol = nCol;
|
||||||
int y = (int) (height - pos - 1);
|
} else {
|
||||||
|
labeledFeatures.remove(nCol);
|
||||||
if (y <= 0 || y > height) {
|
|
||||||
y = height;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
img.setRGB(x, y, color);
|
cCol = nCol;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Graphics2D g = (Graphics2D) img.getGraphics();
|
for (ObservedFeature feature : labeledFeatures.values()) {
|
||||||
Font font = new Font("Arial", Font.BOLD, 12);
|
var featureLayer = dLayers.floorEntry(feature.distance);
|
||||||
|
if (featureLayer == null) {
|
||||||
|
LOG.info("Unable to find layer for feature {}", feature);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
featureLayer.getValue().addFeature(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
long labelsEnd = System.currentTimeMillis();
|
||||||
|
LOG.info("Computed {} visible labels ({} ms)", labeledFeatures.size(), labelsEnd - labelsStart);
|
||||||
|
|
||||||
|
long mergeStart = System.currentTimeMillis();
|
||||||
|
List<DistanceLayer> sortedLayers = new ArrayList<>();
|
||||||
|
Integer cDist = dLayers.firstKey();
|
||||||
|
|
||||||
|
DistanceLayer cLayer = dLayers.get(cDist);
|
||||||
|
while (cDist < dLayers.lastKey()) {
|
||||||
|
Integer nDist = dLayers.higherKey(cDist);
|
||||||
|
DistanceLayer nLayer = dLayers.get(nDist);
|
||||||
|
|
||||||
|
var bothFeatures = Stream.concat(cLayer.getHorizonFeatures().stream(),nLayer.getHorizonFeatures().stream())
|
||||||
|
.filter(isVisibleFilter)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
DistanceLayer mLayer = cLayer.mergeWith(nLayer);
|
||||||
|
var mergeFeatures = nLayer.getHorizonFeatures().stream()
|
||||||
|
.filter(isVisibleFilter)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
boolean continueMerge = false;
|
||||||
|
|
||||||
|
if (mergeFeatures.isEmpty()) {
|
||||||
|
// If no features are present on the merge result, we always continue;
|
||||||
|
continueMerge = true;
|
||||||
|
} else {
|
||||||
|
double bothScore = bothFeatures.stream()
|
||||||
|
.map(Feature::getScore)
|
||||||
|
.reduce(Double::sum)
|
||||||
|
.orElse(0.0);
|
||||||
|
double mergeScore = mergeFeatures.stream()
|
||||||
|
.map(Feature::getScore)
|
||||||
|
.reduce(Double::sum)
|
||||||
|
.orElse(0.0);
|
||||||
|
|
||||||
|
var missingFeatures = bothFeatures.stream()
|
||||||
|
.filter(f -> !mergeFeatures.contains(f))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
LOG.info("Merging layers {} and {} ({}) would generate {} feature score: {}",
|
||||||
|
cLayer.nearDistance, nLayer.nearDistance, bothScore,
|
||||||
|
mergeScore, continueMerge);
|
||||||
|
|
||||||
|
if (!missingFeatures.isEmpty()) {
|
||||||
|
LOG.info(" Removed {}", missingFeatures);
|
||||||
|
}
|
||||||
|
|
||||||
|
continueMerge = mergeScore >= bothScore * 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!continueMerge) {
|
||||||
|
sortedLayers.add(cLayer);
|
||||||
|
cLayer = nLayer;
|
||||||
|
} else {
|
||||||
|
cLayer = mLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
cDist = nDist;
|
||||||
|
}
|
||||||
|
|
||||||
|
sortedLayers.add(cLayer);
|
||||||
|
|
||||||
|
long mergeEnd = System.currentTimeMillis();
|
||||||
|
LOG.info("There are {} layers remaining after layer merges for {} features ({} ms)",
|
||||||
|
sortedLayers.size(), labeledFeatures.size(), mergeEnd - mergeStart);
|
||||||
|
|
||||||
|
long renderStart = System.currentTimeMillis();
|
||||||
|
// We loop in descending order to avoid writing further points on top
|
||||||
|
// of nearer points.
|
||||||
|
for (DistanceLayer layer : sortedLayers) {
|
||||||
|
float dPos = (float) ((maxDistance - layer.nearDistance) / (maxDistance * 1.0f));
|
||||||
|
Color layerColor = Color.getHSBColor(dPos * 1f, 1.0f, 1.0f);
|
||||||
|
|
||||||
|
g.setColor(layerColor);
|
||||||
|
|
||||||
|
Polygon p = new Polygon();
|
||||||
|
p.addPoint(0, height);
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
double visibleAngle = layer.data[x];
|
||||||
|
int y = dc.getYAtAngle(visibleAngle);
|
||||||
|
|
||||||
|
p.addPoint(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.addPoint(width, height);
|
||||||
|
g.drawPolygon(p);
|
||||||
|
|
||||||
|
for (ObservedFeature feature : layer.getFeatures()) {
|
||||||
|
if (!dc.isDisplayed(feature.bearing, feature.visibleAngle)) {
|
||||||
|
LOG.debug("Feature {} is not visible at bearing {} and angle {}", feature.name, feature.bearing, feature.visibleAngle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = dc.getXAtBearing(feature.bearing);
|
||||||
|
int y = dc.getYAtAngle(feature.visibleAngle);
|
||||||
|
|
||||||
|
boolean isVisible = DistanceLayer.isVisible(dLayers.values(), feature.distance, x, feature.visibleAngle);
|
||||||
|
if (!isVisible) {
|
||||||
|
LOG.debug("Feature {} is hidden behind terrain", feature.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape s = new Rectangle(x -5 , y - 5, 10, 10);
|
||||||
|
|
||||||
|
if (layer.isAtHorizon(feature)) {
|
||||||
|
g.fill(s);
|
||||||
|
} else {
|
||||||
|
g.draw(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Font font = new Font("Helvetica", Font.BOLD, 12);
|
||||||
g.setColor(Color.white);
|
g.setColor(Color.white);
|
||||||
g.setFont(font);
|
g.setFont(font);
|
||||||
|
|
||||||
Collection<Peak> peaks = readFeatures("data/osm-overpass-peaks.geojson");
|
for (ObservedFeature feature : labeledFeatures.values()) {
|
||||||
Collection<Peak> villages = readFeatures("data/osm-overpass-villages.geojson");
|
if (!dc.isDisplayed(feature.bearing, feature.visibleAngle)) {
|
||||||
for (int i = 0; i < 2; i++) {
|
LOG.debug("Feature {} is not visible at bearing {} and angle {}", feature.name, feature.bearing, feature.visibleAngle);
|
||||||
Collection<Peak> feature;
|
continue;
|
||||||
int txtPosition;
|
|
||||||
boolean displayInvisible;
|
|
||||||
|
|
||||||
if (i == 0) {
|
|
||||||
feature = peaks;
|
|
||||||
txtPosition = 150;
|
|
||||||
displayInvisible = false;
|
|
||||||
} else {
|
|
||||||
feature = villages;
|
|
||||||
txtPosition = height - 50;
|
|
||||||
displayInvisible = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Peak peak : feature) {
|
int x = dc.getXAtBearing(feature.bearing);
|
||||||
double peakBearing = Math.toDegrees(Position.bearing(c, peak.position));
|
|
||||||
if (!dc.isVisible(peakBearing)) {
|
|
||||||
LOG.info("Peak {} is not visible at bearing {}", peak.name, peakBearing);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int peakDistance = c.distanceToMeters(peak.position);
|
boolean isVisible = DistanceLayer.isVisible(dLayers.values(), feature.distance, x, feature.visibleAngle);
|
||||||
|
if (!isVisible) {
|
||||||
Tile atPos = tileMap.getByLatLon(peak.position.gridLat, peak.position.gridLon);
|
LOG.debug("Feature {} is not visible, hidden behind terrain.", feature.name);
|
||||||
double elevation = atPos.elevation(peak.position.row, peak.position.col);
|
continue;
|
||||||
|
|
||||||
double visibleAngle = dc.calculateVisibilityAngle(elevation, peakDistance);
|
|
||||||
|
|
||||||
int x = dc.getXAtBearing(peakBearing);
|
|
||||||
|
|
||||||
boolean isVisible = DistanceLayer.isVisible(layers, peakDistance, x, visibleAngle);
|
|
||||||
if (!isVisible && !displayInvisible) {
|
|
||||||
LOG.info("Peak {} is not visible, hidden behind terrain.", peak.name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visibleAngle < minVertAngle) {
|
|
||||||
LOG.info("Peak {} is not visible, too low.", peak.name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
double pos = (visibleAngle - minVertAngle) * (height / (maxVertAngle - minVertAngle));
|
|
||||||
int y = (int) (height - pos - 1);
|
|
||||||
|
|
||||||
LOG.info("Peak {} is at bearing {} ({}x{})", peak.name, peakBearing, x, y);
|
|
||||||
|
|
||||||
String title = String.format("%s", peak.name);
|
|
||||||
|
|
||||||
AffineTransform old = g.getTransform();
|
|
||||||
g.rotate(Math.toRadians(-45), x, txtPosition);
|
|
||||||
g.drawString(title, x, txtPosition);
|
|
||||||
g.setTransform(old);
|
|
||||||
|
|
||||||
g.drawLine(x, y, x, txtPosition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int y = dc.getYAtAngle(feature.visibleAngle);
|
||||||
|
|
||||||
|
LOG.debug("Peak {} is at bearing {} ({}x{})", feature.name, feature.bearing, x, y);
|
||||||
|
|
||||||
|
String title = String.format("%s (%.0f)", feature.name, feature.getScore());
|
||||||
|
|
||||||
|
AffineTransform old = g.getTransform();
|
||||||
|
g.rotate(Math.toRadians(-45), x, feature.textPosition);
|
||||||
|
g.drawString(title, x, feature.textPosition);
|
||||||
|
g.setTransform(old);
|
||||||
|
|
||||||
|
g.drawLine(x, y, x, feature.textPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 360; i += 5) {
|
g.setColor(Color.gray);
|
||||||
|
for (int i = 0; i < 360; i += 1) {
|
||||||
int x = dc.getXAtBearing(i);
|
int x = dc.getXAtBearing(i);
|
||||||
|
|
||||||
g.drawLine(x, 0, x, height);
|
g.drawLine(x, 0, x, height);
|
||||||
g.drawString(i + "", x, 20);
|
g.drawString(i + "", x + 5, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long renderEnd = System.currentTimeMillis();
|
||||||
|
LOG.info("Rendered panorama ({} ms)", renderEnd - renderStart);
|
||||||
|
|
||||||
g.dispose();
|
g.dispose();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -212,25 +324,24 @@ public class Cli {
|
||||||
File outputfile = new File("/home/valdor/", imageName);
|
File outputfile = new File("/home/valdor/", imageName);
|
||||||
outputfile.createNewFile();
|
outputfile.createNewFile();
|
||||||
|
|
||||||
ImageIO.write(img, "png", outputfile);
|
ImageIO.write(bufferedImg, "png", outputfile);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
System.out.println("Exception while writing image: " + ex);
|
LOG.error("Exception while writing image", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final double[] computeAtDistance(Position eyeCoordinates, TileMap tileMap,
|
static final double[] computeAtDistance(TileMap tileMap, int d, DisplayCalculator dc) {
|
||||||
int d, DisplayCalculator dc) {
|
|
||||||
|
|
||||||
double[] output = new double[dc.getWidth()];
|
double[] output = new double[dc.getWidth()];
|
||||||
|
|
||||||
for (int x = 0; x < dc.getWidth(); x += 1) {
|
for (int x = 0; x < dc.getWidth(); x += 1) {
|
||||||
double bearing = dc.getBearingAtX(x);
|
double bearing = dc.getBearingAtX(x);
|
||||||
Position target = Position.get(eyeCoordinates, Math.toRadians(bearing), d);
|
Position target = Position.get(dc.getEyePosition(), Math.toRadians(bearing), d);
|
||||||
|
|
||||||
Tile atPos = tileMap.getByLatLon(target.gridLat, target.gridLon);
|
Tile atPos = tileMap.getByLatLon(target.gridLat, target.gridLon);
|
||||||
double elevation = atPos.elevation(target.row, target.col);
|
double elevation = atPos.elevation(target.row, target.col);
|
||||||
|
|
||||||
int dMeters = eyeCoordinates.distanceToMeters(target);
|
int dMeters = dc.getEyePosition().distanceToMeters(target);
|
||||||
double visibleAngle = dc.calculateVisibilityAngle(elevation, dMeters);
|
double visibleAngle = dc.calculateVisibilityAngle(elevation, dMeters);
|
||||||
|
|
||||||
output[x] = visibleAngle;
|
output[x] = visibleAngle;
|
||||||
|
@ -239,28 +350,4 @@ public class Cli {
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Collection<Peak> readFeatures(String fileName) throws IOException {
|
|
||||||
Collection<Peak> peaks = new ArrayList<>();
|
|
||||||
|
|
||||||
try ( FileReader fr = new FileReader(fileName)) {
|
|
||||||
Gson gson = new GsonBuilder().create();
|
|
||||||
GeoJsonExport export = gson.fromJson(fr, GeoJsonExport.class);
|
|
||||||
|
|
||||||
for (GeoJsonExport.Feature feature : export.features) {
|
|
||||||
double[] coordinates = feature.geometry.coordinates;
|
|
||||||
String name = feature.properties.name;
|
|
||||||
Integer elevation = 0; // Integer.parseInt(feature.properties.ele);
|
|
||||||
|
|
||||||
if (name == null || name.isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Peak peak = new Peak(coordinates[1], coordinates[0], name, elevation);
|
|
||||||
peaks.add(peak);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return peaks;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,29 @@
|
||||||
package ch.inf3.horizoncut.data;
|
package ch.inf3.horizoncut.data;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
public class DisplayCalculator {
|
public class DisplayCalculator {
|
||||||
|
|
||||||
final int width;
|
final int width;
|
||||||
final int height;
|
final int height;
|
||||||
|
|
||||||
final double minHorizAngle;
|
final double minHorizAngle;
|
||||||
final double maxHorizAngle;
|
final double maxHorizAngle;
|
||||||
|
|
||||||
|
final Position eyePosition;
|
||||||
final double eyeHeight;
|
final double eyeHeight;
|
||||||
|
|
||||||
public DisplayCalculator(int width, int height, double minHorizAngle, double maxHorizAngle, double eyeHeight) {
|
double minVertAngle;
|
||||||
|
double maxVertAngle;
|
||||||
|
|
||||||
|
public DisplayCalculator(int width, int height, double minHorizAngle, double maxHorizAngle, Position eyePosition, double eyeHeight) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
|
|
||||||
this.minHorizAngle = minHorizAngle;
|
this.minHorizAngle = minHorizAngle;
|
||||||
this.maxHorizAngle = maxHorizAngle;
|
this.maxHorizAngle = maxHorizAngle;
|
||||||
|
|
||||||
|
this.eyePosition = eyePosition;
|
||||||
this.eyeHeight = eyeHeight;
|
this.eyeHeight = eyeHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,12 +47,17 @@ public class DisplayCalculator {
|
||||||
return eyeHeight;
|
return eyeHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Position getEyePosition() {
|
||||||
|
return eyePosition;
|
||||||
|
}
|
||||||
|
|
||||||
public double getAngleStep() {
|
public double getAngleStep() {
|
||||||
return (maxHorizAngle - minHorizAngle) / width;
|
return (maxHorizAngle - minHorizAngle) / width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVisible(double bearing) {
|
public boolean isDisplayed(double bearing, double visibleAngle) {
|
||||||
return (bearing <= maxHorizAngle && bearing >= minHorizAngle);
|
return (bearing <= maxHorizAngle && bearing >= minHorizAngle)
|
||||||
|
&& (visibleAngle <= maxVertAngle && visibleAngle >= minVertAngle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,6 +78,28 @@ public class DisplayCalculator {
|
||||||
return (int) ((bearing - minHorizAngle) / getAngleStep());
|
return (int) ((bearing - minHorizAngle) / getAngleStep());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getYAtAngle(double visibleAngle) {
|
||||||
|
double pos = (visibleAngle - minVertAngle) * (height / (maxVertAngle - minVertAngle));
|
||||||
|
|
||||||
|
int y = (int) (height - pos - 1);
|
||||||
|
|
||||||
|
if (y <= 0 || y > height) {
|
||||||
|
y = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateMinMaxVertAngle(Collection<DistanceLayer> nLayers) {
|
||||||
|
// We dont want to compte the min angle to avoid looking at the ground
|
||||||
|
// just before the position ...
|
||||||
|
this.minVertAngle = 80;
|
||||||
|
this.maxVertAngle = nLayers.stream()
|
||||||
|
.map(l -> l.getMax())
|
||||||
|
.max(Double::compareTo)
|
||||||
|
.orElse(0.0) + 3;
|
||||||
|
}
|
||||||
|
|
||||||
public double calculateVisibilityAngle(double targetVisibleHeight, int targetDistance) {
|
public double calculateVisibilityAngle(double targetVisibleHeight, int targetDistance) {
|
||||||
if (eyeHeight == targetVisibleHeight) {
|
if (eyeHeight == targetVisibleHeight) {
|
||||||
return 90.0;
|
return 90.0;
|
||||||
|
|
|
@ -3,19 +3,36 @@ package ch.inf3.horizoncut.data;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DistanceLayer.class);
|
||||||
|
|
||||||
public final int nearDistance;
|
public final int nearDistance;
|
||||||
public final int farDistance;
|
public final int farDistance;
|
||||||
public final double[] data;
|
public final double[] data;
|
||||||
|
|
||||||
|
public final Collection<ObservedFeature> horizonFeatures;
|
||||||
|
public final Collection<ObservedFeature> nonHorizonFeatures;
|
||||||
|
|
||||||
public DistanceLayer(int nearDistance, int farDistance, double[] data) {
|
public DistanceLayer(int nearDistance, int farDistance, double[] data) {
|
||||||
|
this(nearDistance, farDistance, data, new ArrayList<>(), new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DistanceLayer(int nearDistance, int farDistance, double[] data,
|
||||||
|
Collection<ObservedFeature> horizonFeatures, Collection<ObservedFeature> nonHorizonFeatures) {
|
||||||
this.nearDistance = nearDistance;
|
this.nearDistance = nearDistance;
|
||||||
this.farDistance = farDistance;
|
this.farDistance = farDistance;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
|
||||||
|
this.horizonFeatures = horizonFeatures;
|
||||||
|
this.nonHorizonFeatures = nonHorizonFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -26,7 +43,11 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
||||||
@Override
|
@Override
|
||||||
protected DistanceLayer clone() {
|
protected DistanceLayer clone() {
|
||||||
double[] cloneData = Arrays.copyOf(data, data.length);
|
double[] cloneData = Arrays.copyOf(data, data.length);
|
||||||
return new DistanceLayer(nearDistance, farDistance, cloneData);
|
|
||||||
|
Collection<ObservedFeature> horizonFeatures = new ArrayList<>(this.horizonFeatures);
|
||||||
|
Collection<ObservedFeature> nonHorizonFeatures = new ArrayList<>(this.nonHorizonFeatures);
|
||||||
|
|
||||||
|
return new DistanceLayer(nearDistance, farDistance, cloneData, horizonFeatures, nonHorizonFeatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DistanceLayer mergeWith(DistanceLayer other) {
|
public DistanceLayer mergeWith(DistanceLayer other) {
|
||||||
|
@ -51,7 +72,15 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
||||||
|
|
||||||
int outputNearDistance = anyNear ? near.nearDistance : far.nearDistance;
|
int outputNearDistance = anyNear ? near.nearDistance : far.nearDistance;
|
||||||
|
|
||||||
return new DistanceLayer(outputNearDistance, far.farDistance, output);
|
List<ObservedFeature> outHorizonFeatures = Stream.concat(this.horizonFeatures.parallelStream(), other.horizonFeatures.parallelStream())
|
||||||
|
.filter(f -> isAtHorizon(f, output))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<ObservedFeature> outNonHorizonFeatures = new ArrayList<>();
|
||||||
|
outNonHorizonFeatures.addAll(this.nonHorizonFeatures);
|
||||||
|
outNonHorizonFeatures.addAll(other.nonHorizonFeatures);
|
||||||
|
|
||||||
|
return new DistanceLayer(outputNearDistance, far.farDistance, output, outHorizonFeatures, outNonHorizonFeatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,6 +106,45 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addFeature(ObservedFeature feature) {
|
||||||
|
if (feature.requireHorizon) {
|
||||||
|
this.horizonFeatures.add(feature);
|
||||||
|
} else {
|
||||||
|
this.nonHorizonFeatures.add(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<ObservedFeature> getHorizonFeatures() {
|
||||||
|
return Collections.unmodifiableCollection(this.horizonFeatures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<ObservedFeature> getFeatures() {
|
||||||
|
return Stream.concat(this.horizonFeatures.stream(), this.nonHorizonFeatures.stream())
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAtHorizon(ObservedFeature feature) {
|
||||||
|
return isAtHorizon(feature, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAtHorizon(ObservedFeature feature, double[] data) {
|
||||||
|
int xFeature = feature.getObservedX();
|
||||||
|
double angleFeature = feature.visibleAngle;
|
||||||
|
|
||||||
|
double angleThis = data[xFeature];
|
||||||
|
|
||||||
|
boolean isAtHorizon = Math.abs(angleThis - angleFeature) < 0.2;
|
||||||
|
|
||||||
|
LOG.debug("Feature {}, {} vs {} -> Horizon {}", feature.name, angleFeature, angleThis, isAtHorizon);
|
||||||
|
|
||||||
|
return isAtHorizon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int countHorizonFeatures() {
|
||||||
|
return this.horizonFeatures.size();
|
||||||
|
}
|
||||||
|
|
||||||
public double getMax() {
|
public double getMax() {
|
||||||
return Arrays.stream(this.data)
|
return Arrays.stream(this.data)
|
||||||
.boxed()
|
.boxed()
|
||||||
|
@ -85,37 +153,8 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
||||||
.orElse(0.0);
|
.orElse(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Collection<DistanceLayer> filterInvisible(Collection<DistanceLayer> input) {
|
public static boolean isVisible(Collection<DistanceLayer> sortedLayers, int distance, int x, double visibleAngle) {
|
||||||
|
for (DistanceLayer l : sortedLayers) {
|
||||||
List<DistanceLayer> sorted = input.stream()
|
|
||||||
.sorted()
|
|
||||||
.map(d -> d.clone())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
int layerSize = sorted.get(0).data.length;
|
|
||||||
|
|
||||||
for (int x = 0; x < layerSize; x++) {
|
|
||||||
double maxAngle = Double.NEGATIVE_INFINITY;
|
|
||||||
for (DistanceLayer l : sorted) {
|
|
||||||
double c = l.data[x];
|
|
||||||
|
|
||||||
if (c >= maxAngle) {
|
|
||||||
maxAngle = c;
|
|
||||||
} else {
|
|
||||||
l.data[x] = Double.NaN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sorted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isVisible(Collection<DistanceLayer> layers, int distance, int x, double visibleAngle) {
|
|
||||||
List<DistanceLayer> sorted = layers.stream()
|
|
||||||
.sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
for (DistanceLayer l : sorted) {
|
|
||||||
if (l.farDistance > (distance - 200)) {
|
if (l.farDistance > (distance - 200)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
87
src/main/java/ch/inf3/horizoncut/data/Feature.java
Normal file
87
src/main/java/ch/inf3/horizoncut/data/Feature.java
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package ch.inf3.horizoncut.data;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class Feature {
|
||||||
|
|
||||||
|
private final Position position;
|
||||||
|
private final double elevation;
|
||||||
|
|
||||||
|
private final double score;
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
public final int textPosition;
|
||||||
|
public final boolean requireHorizon;
|
||||||
|
|
||||||
|
protected Feature(Position position, double elevation, String name, int textPosition, boolean requireHorizon, double score) {
|
||||||
|
this.position = position;
|
||||||
|
this.elevation = elevation;
|
||||||
|
this.name = name;
|
||||||
|
this.textPosition = textPosition;
|
||||||
|
this.requireHorizon = requireHorizon;
|
||||||
|
this.score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservedFeature observe(DisplayCalculator observation) {
|
||||||
|
return new ObservedFeature(this.position, this.elevation, observation, this.name, this.textPosition, this.requireHorizon, this.score);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<Feature> readFeatures(String fileName, TileMap tileMap, int textPosition, boolean requireHorizon) throws IOException {
|
||||||
|
Collection<Feature> peaks = new ArrayList<>();
|
||||||
|
|
||||||
|
try (FileReader fr = new FileReader(fileName)) {
|
||||||
|
Gson gson = new GsonBuilder().create();
|
||||||
|
GeoJsonExport export = gson.fromJson(fr, GeoJsonExport.class);
|
||||||
|
|
||||||
|
for (GeoJsonExport.Feature feature : export.features) {
|
||||||
|
double[] coordinates = feature.geometry.coordinates;
|
||||||
|
String name = feature.properties.name;
|
||||||
|
|
||||||
|
if (name == null || name.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Position featurePosition = new Position(coordinates[1], coordinates[0]);
|
||||||
|
|
||||||
|
Tile atPos = tileMap.getByLatLon(featurePosition.gridLat, featurePosition.gridLon);
|
||||||
|
double elevation = atPos.elevation(featurePosition.row, featurePosition.col);
|
||||||
|
|
||||||
|
double score = elevation;
|
||||||
|
if (feature.properties.prominence != null) {
|
||||||
|
score += 10 * Integer.parseInt(feature.properties.prominence);
|
||||||
|
}
|
||||||
|
if (feature.properties.wikidata != null) {
|
||||||
|
score += 9_000;
|
||||||
|
}
|
||||||
|
|
||||||
|
Feature peak = new Feature(featurePosition, elevation, name, textPosition, requireHorizon, score);
|
||||||
|
peaks.add(peak);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return peaks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends Feature> T highest(T... features) {
|
||||||
|
return Arrays.stream(features)
|
||||||
|
.max((a, b) -> Double.compare(a.getScore(), b.getScore()))
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getScore() {
|
||||||
|
return this.score;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name + "(score=" + score + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,6 +15,9 @@ public class GeoJsonExport {
|
||||||
public static class Properties {
|
public static class Properties {
|
||||||
public String ele;
|
public String ele;
|
||||||
public String name;
|
public String name;
|
||||||
|
|
||||||
|
public String prominence;
|
||||||
|
public String wikidata;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Geometry {
|
public static class Geometry {
|
||||||
|
|
34
src/main/java/ch/inf3/horizoncut/data/ObservedFeature.java
Normal file
34
src/main/java/ch/inf3/horizoncut/data/ObservedFeature.java
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package ch.inf3.horizoncut.data;
|
||||||
|
|
||||||
|
public class ObservedFeature extends Feature implements Comparable<ObservedFeature> {
|
||||||
|
|
||||||
|
public final int distance;
|
||||||
|
public final double visibleAngle;
|
||||||
|
public final double bearing;
|
||||||
|
|
||||||
|
private final DisplayCalculator observator;
|
||||||
|
|
||||||
|
public ObservedFeature(Position position, double elevation, DisplayCalculator observator, String name, int textPosition, boolean requireHorizon, double score) {
|
||||||
|
super(position, elevation, name, textPosition, requireHorizon, score);
|
||||||
|
|
||||||
|
this.observator = observator;
|
||||||
|
|
||||||
|
this.distance = observator.eyePosition.distanceToMeters(position);
|
||||||
|
this.visibleAngle = observator.calculateVisibilityAngle(elevation, distance);
|
||||||
|
|
||||||
|
this.bearing = Math.toDegrees(Position.bearing(observator.eyePosition, position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(ObservedFeature other) {
|
||||||
|
return Double.compare(this.bearing, other.bearing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getObservedX() {
|
||||||
|
return observator.getXAtBearing(bearing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getObservedY() {
|
||||||
|
return observator.getYAtAngle(visibleAngle);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
package ch.inf3.horizoncut.data;
|
|
||||||
|
|
||||||
public class Peak {
|
|
||||||
|
|
||||||
public final Position position;
|
|
||||||
public final String name;
|
|
||||||
public final int elevation;
|
|
||||||
|
|
||||||
public Peak(Position position, String name, int elevation) {
|
|
||||||
this.position = position;
|
|
||||||
this.name = name;
|
|
||||||
this.elevation = elevation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Peak(double lat, double lon, String name, int elevation) {
|
|
||||||
this.position = new Position(lat, lon);
|
|
||||||
this.name = name;
|
|
||||||
this.elevation = elevation;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -108,8 +108,7 @@ public class Tile {
|
||||||
|
|
||||||
EmptyTile(int lat, int lon, ShortBuffer data) {
|
EmptyTile(int lat, int lon, ShortBuffer data) {
|
||||||
super(lat, lon, data);
|
super(lat, lon, data);
|
||||||
|
LOG.debug("Failed to access tile at {},{}. Using zero elevations in this region.", lat, lon);
|
||||||
System.out.println("Failed to access tile at lat/lon " + lat + "/" + lon + ". Using zero elevations in this region.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Add table
Reference in a new issue