feat: Refactor to use TreeMap directly for layers

This commit is contained in:
Thomas Schwery 2022-04-04 21:49:24 +02:00
parent 764688e58a
commit 6d541bbe49
2 changed files with 44 additions and 30 deletions

View file

@ -21,6 +21,7 @@ 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.List;
import java.util.NavigableMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -72,58 +73,61 @@ public class Cli {
eyePosition.getLatLon(), eyeHeight, minDistance, maxDistance, initStop - initStart); eyePosition.getLatLon(), eyeHeight, minDistance, maxDistance, initStop - initStart);
long layersStart = System.currentTimeMillis(); long layersStart = System.currentTimeMillis();
Collection<DistanceLayer> reversedLayers = new ArrayList<>(maxDistance); final NavigableMap<Integer, DistanceLayer> dLayers = new TreeMap<>();
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(tileMap, cDist, dc); double[] data = computeAtDistance(tileMap, cDist, dc);
reversedLayers.add(new DistanceLayer(d - SLICE_DISTANCE, d, data)); dLayers.put(d, new DistanceLayer(d - SLICE_DISTANCE, d, data));
} }
long layersEnd = System.currentTimeMillis(); long layersEnd = System.currentTimeMillis();
LOG.info("Computed {} layers ({} ms)", reversedLayers.size(), layersEnd - layersStart); LOG.info("Computed {} layers ({} ms)", dLayers.size(), layersEnd - layersStart);
long horizonStart = System.currentTimeMillis(); 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 = reversedLayers.stream() DistanceLayer horizonLayer = dLayers.values().stream()
.reduce((a, b) -> a.mergeWith(b)) .reduce((a, b) -> a.mergeWith(b))
.get(); .get();
reversedLayers = reversedLayers.stream() LOG.info("Horizon is from {} to {}", horizonLayer.nearDistance, horizonLayer.farDistance);
.dropWhile(l -> horizonLayer.isLower(l))
.collect(Collectors.toList()); Integer fKey = dLayers.firstKey();
Integer lKey = dLayers.ceilingKey(horizonLayer.farDistance);
LOG.info("Split horizon from {} ({}) to {} ({})", fKey, fKey, lKey, horizonLayer.farDistance);
final NavigableMap<Integer, DistanceLayer> hLayers = dLayers.subMap(fKey, true, lKey, true);
// 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 = reversedLayers.stream() maxDistance = hLayers.values().stream()
.map(l -> l.farDistance) .map(l -> l.farDistance)
.max(Integer::compareTo) .max(Integer::compareTo)
.get(); .get();
minDistance = reversedLayers.stream() minDistance = hLayers.values().stream()
.map(l -> l.nearDistance) .map(l -> l.nearDistance)
.min(Integer::compareTo) .min(Integer::compareTo)
.get(); .get();
dc.updateMinMaxVertAngle(reversedLayers); dc.updateMinMaxVertAngle(hLayers.values());
long horizonEnd = System.currentTimeMillis(); long horizonEnd = System.currentTimeMillis();
LOG.info("There are {} layers remaining after horizon cleanup at distances {} to {} ({} ms)", LOG.info("There are {} layers remaining after horizon cleanup at distances {} to {} ({} ms)",
reversedLayers.size(), minDistance, maxDistance, horizonEnd - horizonStart); hLayers.size(), minDistance, maxDistance, horizonEnd - horizonStart);
long labelsStart = System.currentTimeMillis(); 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));
// We start processing the features to avoid overlapping features in the // We start processing the features to avoid overlapping features in the
// labels as well as displaying features that are hidden from the observation // labels as well as displaying features that are hidden from the observation
// point. // point.
Predicate<ObservedFeature> isVisibleFilter = (feature) -> { Predicate<ObservedFeature> isVisibleFilter = (feature) -> {
int x = dc.getXAtBearing(feature.bearing); int x = dc.getXAtBearing(feature.bearing);
return DistanceLayer.isVisible(dLayers.values(), feature.distance, x, feature.visibleAngle); return DistanceLayer.isVisible(hLayers.values(), feature.distance, x, feature.visibleAngle);
}; };
TreeMap<Integer, ObservedFeature> labeledFeatures = allFeatures.stream() TreeMap<Integer, ObservedFeature> labeledFeatures = allFeatures.stream()
@ -152,7 +156,7 @@ public class Cli {
} }
for (ObservedFeature feature : labeledFeatures.values()) { for (ObservedFeature feature : labeledFeatures.values()) {
var featureLayer = dLayers.floorEntry(feature.distance); var featureLayer = hLayers.floorEntry(feature.distance);
if (featureLayer == null) { if (featureLayer == null) {
LOG.info("Unable to find layer for feature {}", feature); LOG.info("Unable to find layer for feature {}", feature);
continue; continue;
@ -166,12 +170,12 @@ public class Cli {
long mergeStart = System.currentTimeMillis(); long mergeStart = System.currentTimeMillis();
List<DistanceLayer> sortedLayers = new ArrayList<>(); List<DistanceLayer> sortedLayers = new ArrayList<>();
Integer cDist = dLayers.firstKey(); Integer cDist = hLayers.firstKey();
DistanceLayer cLayer = dLayers.get(cDist); DistanceLayer cLayer = hLayers.get(cDist);
while (cDist < dLayers.lastKey()) { while (cDist < hLayers.lastKey()) {
Integer nDist = dLayers.higherKey(cDist); Integer nDist = hLayers.higherKey(cDist);
DistanceLayer nLayer = dLayers.get(nDist); DistanceLayer nLayer = hLayers.get(nDist);
var bothFeatures = Stream.concat(cLayer.getHorizonFeatures().stream(),nLayer.getHorizonFeatures().stream()) var bothFeatures = Stream.concat(cLayer.getHorizonFeatures().stream(),nLayer.getHorizonFeatures().stream())
.filter(isVisibleFilter) .filter(isVisibleFilter)
@ -209,7 +213,7 @@ public class Cli {
LOG.info(" Removed {}", missingFeatures); LOG.info(" Removed {}", missingFeatures);
} }
continueMerge = mergeScore >= bothScore * 0.1; continueMerge = mergeScore >= bothScore;
} }
if (!continueMerge) { if (!continueMerge) {
@ -258,7 +262,7 @@ public class Cli {
int x = dc.getXAtBearing(feature.bearing); int x = dc.getXAtBearing(feature.bearing);
int y = dc.getYAtAngle(feature.visibleAngle); int y = dc.getYAtAngle(feature.visibleAngle);
boolean isVisible = DistanceLayer.isVisible(dLayers.values(), feature.distance, x, feature.visibleAngle); boolean isVisible = DistanceLayer.isVisible(hLayers.values(), feature.distance, x, feature.visibleAngle);
if (!isVisible) { if (!isVisible) {
LOG.debug("Feature {} is hidden behind terrain", feature.name); LOG.debug("Feature {} is hidden behind terrain", feature.name);
continue; continue;
@ -286,7 +290,7 @@ public class Cli {
int x = dc.getXAtBearing(feature.bearing); int x = dc.getXAtBearing(feature.bearing);
boolean isVisible = DistanceLayer.isVisible(dLayers.values(), feature.distance, x, feature.visibleAngle); boolean isVisible = DistanceLayer.isVisible(hLayers.values(), feature.distance, x, feature.visibleAngle);
if (!isVisible) { if (!isVisible) {
LOG.debug("Feature {} is not visible, hidden behind terrain.", feature.name); LOG.debug("Feature {} is not visible, hidden behind terrain.", feature.name);
continue; continue;

View file

@ -56,21 +56,31 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
double[] output = new double[near.data.length]; double[] output = new double[near.data.length];
boolean anyNear = false; int countNear = 0;
int countFar = 0;
for (int d = 0; d < near.data.length; d += 1) { for (int d = 0; d < near.data.length; d += 1) {
double yNear = near.data[d]; double yNear = near.data[d];
double yFar = far.data[d]; double yFar = far.data[d];
if (yNear > yFar) { if (yNear >= yFar) {
output[d] = yNear; output[d] = yNear;
anyNear = true; countNear += 1;
} else { } else {
output[d] = yFar; output[d] = yFar;
countFar += 1;
} }
} }
int tolerance = near.data.length / 100;
int outputNearDistance = anyNear ? near.nearDistance : far.nearDistance; int outputNearDistance = (countNear > tolerance) ? near.nearDistance : far.nearDistance;
int outputFarDistance = (countFar > tolerance) ? far.farDistance : near.farDistance;
LOG.debug("Merging {}-{} ({}) with {}-{} ({}) {}",
near.nearDistance, near.farDistance, countNear,
far.nearDistance, far.farDistance, countFar,
tolerance);
List<ObservedFeature> outHorizonFeatures = Stream.concat(this.horizonFeatures.parallelStream(), other.horizonFeatures.parallelStream()) List<ObservedFeature> outHorizonFeatures = Stream.concat(this.horizonFeatures.parallelStream(), other.horizonFeatures.parallelStream())
.filter(f -> isAtHorizon(f, output)) .filter(f -> isAtHorizon(f, output))
@ -80,7 +90,7 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
outNonHorizonFeatures.addAll(this.nonHorizonFeatures); outNonHorizonFeatures.addAll(this.nonHorizonFeatures);
outNonHorizonFeatures.addAll(other.nonHorizonFeatures); outNonHorizonFeatures.addAll(other.nonHorizonFeatures);
return new DistanceLayer(outputNearDistance, far.farDistance, output, outHorizonFeatures, outNonHorizonFeatures); return new DistanceLayer(outputNearDistance, outputFarDistance, output, outHorizonFeatures, outNonHorizonFeatures);
} }
/** /**