diff --git a/src/main/java/ch/inf3/horizoncut/cli/Cli.java b/src/main/java/ch/inf3/horizoncut/cli/Cli.java index 50bac51..79c01d6 100644 --- a/src/main/java/ch/inf3/horizoncut/cli/Cli.java +++ b/src/main/java/ch/inf3/horizoncut/cli/Cli.java @@ -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 reversedLayers = new ArrayList<>(maxDistance); + final NavigableMap 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 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 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 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 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 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; diff --git a/src/main/java/ch/inf3/horizoncut/data/DistanceLayer.java b/src/main/java/ch/inf3/horizoncut/data/DistanceLayer.java index 96e891d..1e49cb6 100644 --- a/src/main/java/ch/inf3/horizoncut/data/DistanceLayer.java +++ b/src/main/java/ch/inf3/horizoncut/data/DistanceLayer.java @@ -56,21 +56,31 @@ public class DistanceLayer implements Comparable, 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 outHorizonFeatures = Stream.concat(this.horizonFeatures.parallelStream(), other.horizonFeatures.parallelStream()) .filter(f -> isAtHorizon(f, output)) @@ -80,7 +90,7 @@ public class DistanceLayer implements Comparable, 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); } /**