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.Collection;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -72,58 +73,61 @@ public class Cli {
eyePosition.getLatLon(), eyeHeight, minDistance, maxDistance, initStop - initStart);
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) {
final int cDist = d;
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();
LOG.info("Computed {} layers ({} ms)", reversedLayers.size(), layersEnd - layersStart);
LOG.info("Computed {} layers ({} ms)", dLayers.size(), layersEnd - layersStart);
long horizonStart = System.currentTimeMillis();
// We compute the horizon and delete any layer that is behind the horizon
// and is not used at all for the landscape.
// For this, we are using the fact that we computed the layers starting
// with the farthest ones in the build loop.
DistanceLayer horizonLayer = reversedLayers.stream()
DistanceLayer horizonLayer = dLayers.values().stream()
.reduce((a, b) -> a.mergeWith(b))
.get();
reversedLayers = reversedLayers.stream()
.dropWhile(l -> horizonLayer.isLower(l))
.collect(Collectors.toList());
LOG.info("Horizon is from {} to {}", horizonLayer.nearDistance, horizonLayer.farDistance);
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
// are not useful for the display.
maxDistance = reversedLayers.stream()
maxDistance = hLayers.values().stream()
.map(l -> l.farDistance)
.max(Integer::compareTo)
.get();
minDistance = reversedLayers.stream()
minDistance = hLayers.values().stream()
.map(l -> l.nearDistance)
.min(Integer::compareTo)
.get();
dc.updateMinMaxVertAngle(reversedLayers);
dc.updateMinMaxVertAngle(hLayers.values());
long horizonEnd = System.currentTimeMillis();
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();
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
// labels as well as displaying features that are hidden from the observation
// point.
Predicate<ObservedFeature> isVisibleFilter = (feature) -> {
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()
@ -152,7 +156,7 @@ public class Cli {
}
for (ObservedFeature feature : labeledFeatures.values()) {
var featureLayer = dLayers.floorEntry(feature.distance);
var featureLayer = hLayers.floorEntry(feature.distance);
if (featureLayer == null) {
LOG.info("Unable to find layer for feature {}", feature);
continue;
@ -166,12 +170,12 @@ public class Cli {
long mergeStart = System.currentTimeMillis();
List<DistanceLayer> sortedLayers = new ArrayList<>();
Integer cDist = dLayers.firstKey();
Integer cDist = hLayers.firstKey();
DistanceLayer cLayer = dLayers.get(cDist);
while (cDist < dLayers.lastKey()) {
Integer nDist = dLayers.higherKey(cDist);
DistanceLayer nLayer = dLayers.get(nDist);
DistanceLayer cLayer = hLayers.get(cDist);
while (cDist < hLayers.lastKey()) {
Integer nDist = hLayers.higherKey(cDist);
DistanceLayer nLayer = hLayers.get(nDist);
var bothFeatures = Stream.concat(cLayer.getHorizonFeatures().stream(),nLayer.getHorizonFeatures().stream())
.filter(isVisibleFilter)
@ -209,7 +213,7 @@ public class Cli {
LOG.info(" Removed {}", missingFeatures);
}
continueMerge = mergeScore >= bothScore * 0.1;
continueMerge = mergeScore >= bothScore;
}
if (!continueMerge) {
@ -258,7 +262,7 @@ public class Cli {
int x = dc.getXAtBearing(feature.bearing);
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) {
LOG.debug("Feature {} is hidden behind terrain", feature.name);
continue;
@ -286,7 +290,7 @@ public class Cli {
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) {
LOG.debug("Feature {} is not visible, hidden behind terrain.", feature.name);
continue;

View file

@ -56,21 +56,31 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
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) {
double yNear = near.data[d];
double yFar = far.data[d];
if (yNear > yFar) {
if (yNear >= yFar) {
output[d] = yNear;
anyNear = true;
countNear += 1;
} else {
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())
.filter(f -> isAtHorizon(f, output))
@ -80,7 +90,7 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
outNonHorizonFeatures.addAll(this.nonHorizonFeatures);
outNonHorizonFeatures.addAll(other.nonHorizonFeatures);
return new DistanceLayer(outputNearDistance, far.farDistance, output, outHorizonFeatures, outNonHorizonFeatures);
return new DistanceLayer(outputNearDistance, outputFarDistance, output, outHorizonFeatures, outNonHorizonFeatures);
}
/**