Compare commits
1 commit
master
...
contrib/re
Author | SHA1 | Date | |
---|---|---|---|
3070cc3576 |
9 changed files with 551 additions and 124 deletions
55
nbactions.xml
Normal file
55
nbactions.xml
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<actions>
|
||||
<action>
|
||||
<actionName>run</actionName>
|
||||
<packagings>
|
||||
<packaging>jar</packaging>
|
||||
</packagings>
|
||||
<goals>
|
||||
<goal>process-classes</goal>
|
||||
<goal>org.codehaus.mojo:exec-maven-plugin:3.0.0:exec</goal>
|
||||
</goals>
|
||||
<properties>
|
||||
<exec.vmArgs></exec.vmArgs>
|
||||
<exec.args>${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}</exec.args>
|
||||
<exec.appArgs></exec.appArgs>
|
||||
<exec.mainClass>ch.inf3.horizoncut.cli.Cli</exec.mainClass>
|
||||
<exec.executable>java</exec.executable>
|
||||
</properties>
|
||||
</action>
|
||||
<action>
|
||||
<actionName>debug</actionName>
|
||||
<packagings>
|
||||
<packaging>jar</packaging>
|
||||
</packagings>
|
||||
<goals>
|
||||
<goal>process-classes</goal>
|
||||
<goal>org.codehaus.mojo:exec-maven-plugin:3.0.0:exec</goal>
|
||||
</goals>
|
||||
<properties>
|
||||
<exec.vmArgs>-agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address}</exec.vmArgs>
|
||||
<exec.args>${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}</exec.args>
|
||||
<exec.appArgs></exec.appArgs>
|
||||
<exec.mainClass>ch.inf3.horizoncut.cli.Cli</exec.mainClass>
|
||||
<exec.executable>java</exec.executable>
|
||||
<jpda.listen>true</jpda.listen>
|
||||
</properties>
|
||||
</action>
|
||||
<action>
|
||||
<actionName>profile</actionName>
|
||||
<packagings>
|
||||
<packaging>jar</packaging>
|
||||
</packagings>
|
||||
<goals>
|
||||
<goal>process-classes</goal>
|
||||
<goal>org.codehaus.mojo:exec-maven-plugin:3.0.0:exec</goal>
|
||||
</goals>
|
||||
<properties>
|
||||
<exec.vmArgs></exec.vmArgs>
|
||||
<exec.args>${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}</exec.args>
|
||||
<exec.mainClass>ch.inf3.horizoncut.cli.Cli</exec.mainClass>
|
||||
<exec.executable>java</exec.executable>
|
||||
<exec.appArgs></exec.appArgs>
|
||||
</properties>
|
||||
</action>
|
||||
</actions>
|
5
pom.xml
5
pom.xml
|
@ -81,5 +81,10 @@
|
|||
<artifactId>gson</artifactId>
|
||||
<version>2.8.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.e175.klaus</groupId>
|
||||
<artifactId>solarpositioning</artifactId>
|
||||
<version>0.1.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -47,11 +47,12 @@ public class Cli {
|
|||
|
||||
Tile atOrigin = tileMap.getByLatLon(eyePosition.gridLat, eyePosition.gridLon);
|
||||
double eyeHeight = atOrigin.elevation(eyePosition.row, eyePosition.col);
|
||||
eyePosition.setHeight(eyeHeight);
|
||||
|
||||
double minHorizAngle = 107; //107; // 135 ~Brunegghorn
|
||||
double maxHorizAngle = 226; //226; // 197 ~Combin de Valsorey
|
||||
|
||||
DisplayCalculator dc = new DisplayCalculator(width, height, minHorizAngle, maxHorizAngle, eyePosition, eyeHeight);
|
||||
DisplayCalculator dc = new DisplayCalculator(width, height, minHorizAngle, maxHorizAngle, eyePosition);
|
||||
|
||||
int maxDistance = 100_000;
|
||||
int minDistance = 500;
|
||||
|
@ -76,9 +77,7 @@ public class Cli {
|
|||
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);
|
||||
dLayers.put(d, new DistanceLayer(d - SLICE_DISTANCE, d, data));
|
||||
dLayers.put(d, new DistanceLayer(DistanceLayer.atDistance(d - SLICE_DISTANCE, d, tileMap, dc)));
|
||||
}
|
||||
|
||||
long layersEnd = System.currentTimeMillis();
|
||||
|
@ -93,23 +92,23 @@ public class Cli {
|
|||
.reduce((a, b) -> a.mergeWith(b))
|
||||
.get();
|
||||
|
||||
LOG.info("Horizon is from {} to {}", horizonLayer.nearDistance, horizonLayer.farDistance);
|
||||
LOG.info("Horizon is from {} to {}", horizonLayer.getNearDistance(), horizonLayer.getFarDistance());
|
||||
|
||||
Integer fKey = dLayers.firstKey();
|
||||
Integer lKey = dLayers.ceilingKey(horizonLayer.farDistance);
|
||||
Integer lKey = dLayers.ceilingKey(horizonLayer.getFarDistance());
|
||||
|
||||
LOG.info("Split horizon from {} ({}) to {} ({})", fKey, fKey, lKey, horizonLayer.farDistance);
|
||||
LOG.info("Split horizon from {} ({}) to {} ({})", fKey, fKey, lKey, horizonLayer.getFarDistance());
|
||||
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 = hLayers.values().stream()
|
||||
.map(l -> l.farDistance)
|
||||
.map(l -> l.getFarDistance())
|
||||
.max(Integer::compareTo)
|
||||
.get();
|
||||
|
||||
minDistance = hLayers.values().stream()
|
||||
.map(l -> l.nearDistance)
|
||||
.map(l -> l.getNearDistance())
|
||||
.min(Integer::compareTo)
|
||||
.get();
|
||||
|
||||
|
@ -126,12 +125,12 @@ public class Cli {
|
|||
// point.
|
||||
|
||||
Predicate<ObservedFeature> isVisibleFilter = (feature) -> {
|
||||
int x = dc.getXAtBearing(feature.bearing);
|
||||
return DistanceLayer.isVisible(hLayers.values(), feature.distance, x, feature.visibleAngle);
|
||||
int x = dc.getXAtBearing(feature.getBearing());
|
||||
return DistanceLayer.isVisible(hLayers.values(), feature.getDistance(), x, feature.getAngle());
|
||||
};
|
||||
|
||||
TreeMap<Integer, ObservedFeature> labeledFeatures = allFeatures.stream()
|
||||
.filter(f -> dc.isDisplayed(f.bearing, f.visibleAngle))
|
||||
.filter(f -> dc.isDisplayed(f.getBearing(), f.getAngle()))
|
||||
.filter(isVisibleFilter)
|
||||
.collect(Collectors.toMap(f -> f.getObservedX(), f -> f, (a, b) -> Feature.highest(a, b), TreeMap::new));
|
||||
|
||||
|
@ -156,7 +155,7 @@ public class Cli {
|
|||
}
|
||||
|
||||
for (ObservedFeature feature : labeledFeatures.values()) {
|
||||
var featureLayer = hLayers.floorEntry(feature.distance);
|
||||
var featureLayer = hLayers.floorEntry(feature.getDistance());
|
||||
if (featureLayer == null) {
|
||||
LOG.info("Unable to find layer for feature {}", feature);
|
||||
continue;
|
||||
|
@ -206,7 +205,7 @@ public class Cli {
|
|||
.collect(Collectors.toList());
|
||||
|
||||
LOG.info("Merging layers {} and {} ({}) would generate {} feature score: {}",
|
||||
cLayer.nearDistance, nLayer.nearDistance, bothScore,
|
||||
cLayer.getNearDistance(), nLayer.getNearDistance(), bothScore,
|
||||
mergeScore, continueMerge);
|
||||
|
||||
if (!missingFeatures.isEmpty()) {
|
||||
|
@ -236,7 +235,7 @@ public class Cli {
|
|||
// 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));
|
||||
float dPos = (float) ((maxDistance - layer.getNearDistance()) / (maxDistance * 1.0f));
|
||||
Color layerColor = Color.getHSBColor(dPos * 1f, 1.0f, 1.0f);
|
||||
|
||||
g.setColor(layerColor);
|
||||
|
@ -244,7 +243,9 @@ public class Cli {
|
|||
Polygon p = new Polygon();
|
||||
p.addPoint(0, height);
|
||||
for (int x = 0; x < width; x++) {
|
||||
double visibleAngle = layer.data[x];
|
||||
double bearing = dc.getBearingAtX(x);
|
||||
double visibleAngle = layer.getAtBearing(bearing);
|
||||
|
||||
int y = dc.getYAtAngle(visibleAngle);
|
||||
|
||||
p.addPoint(x, y);
|
||||
|
@ -254,15 +255,15 @@ public class Cli {
|
|||
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);
|
||||
if (!dc.isDisplayed(feature.getBearing(), feature.getAngle())) {
|
||||
LOG.debug("Feature {} is not visible at bearing {} and angle {}", feature.name, feature.getBearing(), feature.getAngle());
|
||||
continue;
|
||||
}
|
||||
|
||||
int x = dc.getXAtBearing(feature.bearing);
|
||||
int y = dc.getYAtAngle(feature.visibleAngle);
|
||||
int x = dc.getXAtBearing(feature.getBearing());
|
||||
int y = dc.getYAtAngle(feature.getAngle());
|
||||
|
||||
boolean isVisible = DistanceLayer.isVisible(hLayers.values(), feature.distance, x, feature.visibleAngle);
|
||||
boolean isVisible = DistanceLayer.isVisible(hLayers.values(), feature.getDistance(), x, feature.getAngle());
|
||||
if (!isVisible) {
|
||||
LOG.debug("Feature {} is hidden behind terrain", feature.name);
|
||||
continue;
|
||||
|
@ -283,22 +284,22 @@ public class Cli {
|
|||
g.setFont(font);
|
||||
|
||||
for (ObservedFeature feature : labeledFeatures.values()) {
|
||||
if (!dc.isDisplayed(feature.bearing, feature.visibleAngle)) {
|
||||
LOG.debug("Feature {} is not visible at bearing {} and angle {}", feature.name, feature.bearing, feature.visibleAngle);
|
||||
if (!dc.isDisplayed(feature.getBearing(), feature.getAngle())) {
|
||||
LOG.debug("Feature {} is not visible at bearing {} and angle {}", feature.name, feature.getBearing(), feature.getAngle());
|
||||
continue;
|
||||
}
|
||||
|
||||
int x = dc.getXAtBearing(feature.bearing);
|
||||
int x = dc.getXAtBearing(feature.getBearing());
|
||||
|
||||
boolean isVisible = DistanceLayer.isVisible(hLayers.values(), feature.distance, x, feature.visibleAngle);
|
||||
boolean isVisible = DistanceLayer.isVisible(hLayers.values(), feature.getDistance(), x, feature.getAngle());
|
||||
if (!isVisible) {
|
||||
LOG.debug("Feature {} is not visible, hidden behind terrain.", feature.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
int y = dc.getYAtAngle(feature.visibleAngle);
|
||||
int y = dc.getYAtAngle(feature.getAngle());
|
||||
|
||||
LOG.debug("Peak {} is at bearing {} ({}x{})", feature.name, feature.bearing, x, y);
|
||||
LOG.debug("Peak {} is at bearing {} ({}x{})", feature.name, feature.getBearing(), x, y);
|
||||
|
||||
String title = String.format("%s (%.0f)", feature.name, feature.getScore());
|
||||
|
||||
|
@ -334,24 +335,4 @@ public class Cli {
|
|||
}
|
||||
}
|
||||
|
||||
static final double[] computeAtDistance(TileMap tileMap, int d, DisplayCalculator dc) {
|
||||
|
||||
double[] output = new double[dc.getWidth()];
|
||||
|
||||
for (int x = 0; x < dc.getWidth(); x += 1) {
|
||||
double bearing = dc.getBearingAtX(x);
|
||||
Position target = Position.get(dc.getEyePosition(), Math.toRadians(bearing), d);
|
||||
|
||||
Tile atPos = tileMap.getByLatLon(target.gridLat, target.gridLon);
|
||||
double elevation = atPos.elevation(target.row, target.col);
|
||||
|
||||
int dMeters = dc.getEyePosition().distanceToMeters(target);
|
||||
double visibleAngle = dc.calculateVisibilityAngle(elevation, dMeters);
|
||||
|
||||
output[x] = visibleAngle;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
259
src/main/java/ch/inf3/horizoncut/cli/Cli2.java
Normal file
259
src/main/java/ch/inf3/horizoncut/cli/Cli2.java
Normal file
|
@ -0,0 +1,259 @@
|
|||
package ch.inf3.horizoncut.cli;
|
||||
|
||||
import ch.inf3.horizoncut.data.BasicLayer;
|
||||
import ch.inf3.horizoncut.data.DisplayCalculator;
|
||||
import ch.inf3.horizoncut.data.Position;
|
||||
import ch.inf3.horizoncut.data.Tile;
|
||||
import ch.inf3.horizoncut.data.TileMap;
|
||||
import ch.inf3.horizoncut.data.TileReader;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Polygon;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import javax.imageio.ImageIO;
|
||||
import net.e175.klaus.solarpositioning.AzimuthZenithAngle;
|
||||
import net.e175.klaus.solarpositioning.DeltaT;
|
||||
import net.e175.klaus.solarpositioning.SPA;
|
||||
import net.e175.klaus.solarpositioning.SunriseTransitSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Cli2 {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Cli2.class);
|
||||
|
||||
private static final int SLICE_DISTANCE = 50;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
long initStart = System.currentTimeMillis();
|
||||
Position eyePosition = new Position(46.28747, 7.44159);
|
||||
TileMap tileMap = new TileReader("/home/valdor/Downloads/SRTM1/").readHGTFiles(eyePosition, 2);
|
||||
|
||||
final int width = 7200;
|
||||
final int height = 1000;
|
||||
|
||||
Tile atOrigin = tileMap.getByLatLon(eyePosition.gridLat, eyePosition.gridLon);
|
||||
double eyeHeight = atOrigin.elevation(eyePosition.row, eyePosition.col) + 5;
|
||||
eyePosition.setHeight(eyeHeight);
|
||||
|
||||
double minHorizAngle = 0;
|
||||
double maxHorizAngle = 359;
|
||||
|
||||
DisplayCalculator dc = new DisplayCalculator(width, height, minHorizAngle, maxHorizAngle, eyePosition);
|
||||
|
||||
int maxDistance = 100_000;
|
||||
int minDistance = 100;
|
||||
|
||||
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();
|
||||
final NavigableMap<Integer, BasicLayer> dLayers = new TreeMap<>();
|
||||
|
||||
for (int d = maxDistance; d > minDistance; d -= SLICE_DISTANCE) {
|
||||
dLayers.put(d, BasicLayer.atDistance(d - SLICE_DISTANCE, d, tileMap, dc));
|
||||
}
|
||||
|
||||
long layersEnd = System.currentTimeMillis();
|
||||
LOG.info("Computed {} layers ({} ms)", dLayers.size(), layersEnd - layersStart);
|
||||
|
||||
long flattenStart = System.currentTimeMillis();
|
||||
|
||||
BasicLayer horizonLayer = dLayers.values().stream()
|
||||
.reduce((a, b) -> a.mergeWith(b))
|
||||
.get();
|
||||
|
||||
long flattenEnd = System.currentTimeMillis();
|
||||
LOG.info("Merged horizon from {} layers in {} ms - {} to {}", dLayers.size(),
|
||||
flattenEnd - flattenStart,
|
||||
horizonLayer.getNearDistance(), horizonLayer.getFarDistance());
|
||||
|
||||
computeSunVisible(horizonLayer, eyePosition);
|
||||
|
||||
long sunStart = System.currentTimeMillis();
|
||||
|
||||
AzimuthZenithAngle[] sunPosition = new AzimuthZenithAngle[24];
|
||||
|
||||
for (int i = 0; i < 24; i += 1) {
|
||||
ZonedDateTime dateTime = ZonedDateTime.now()
|
||||
.withHour(i)
|
||||
.withMinute(0);
|
||||
|
||||
// replace SPA with Grena3 as needed
|
||||
AzimuthZenithAngle position = SPA.calculateSolarPosition(
|
||||
dateTime,
|
||||
eyePosition.getLatitude(), // latitude (degrees)
|
||||
eyePosition.getLongitude(), // longitude (degrees)
|
||||
eyePosition.getHeight(), // elevation (m)
|
||||
DeltaT.estimate(dateTime.toLocalDate())); // delta T (s)
|
||||
|
||||
sunPosition[i] = position;
|
||||
}
|
||||
|
||||
ZonedDateTime dt = ZonedDateTime.now();
|
||||
|
||||
SunriseTransitSet res = SPA.calculateSunriseTransitSet(dt,
|
||||
eyePosition.getLatitude(),
|
||||
eyePosition.getLongitude(),
|
||||
DeltaT.estimate(dt.toLocalDate()));
|
||||
|
||||
LOG.info("Transits are {}", res);
|
||||
|
||||
long sunEnd = System.currentTimeMillis();
|
||||
LOG.info("Computed sun position in {}ms", sunEnd - sunStart);
|
||||
|
||||
long renderStart = System.currentTimeMillis();
|
||||
|
||||
dc.setMinMaxVertAngle(80, 180);
|
||||
|
||||
BufferedImage bufferedImg = new BufferedImage(width, height + 1, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = (Graphics2D) bufferedImg.getGraphics();
|
||||
|
||||
g.setColor(Color.RED);
|
||||
|
||||
Polygon p = new Polygon();
|
||||
p.addPoint(0, height);
|
||||
for (int x = 0; x < width; x++) {
|
||||
double bearing = dc.getBearingAtX(x);
|
||||
double visibleAngle = horizonLayer.getAtBearing(bearing);
|
||||
|
||||
int y = dc.getYAtAngle(visibleAngle);
|
||||
|
||||
p.addPoint(x, y);
|
||||
}
|
||||
|
||||
p.addPoint(width, height);
|
||||
g.drawPolygon(p);
|
||||
|
||||
for (int i = 0; i < 360; i += 10) {
|
||||
if (i % 90 == 0) {
|
||||
g.setColor(Color.WHITE);
|
||||
} else {
|
||||
g.setColor(Color.GRAY);
|
||||
}
|
||||
|
||||
int x = dc.getXAtBearing(i);
|
||||
|
||||
g.drawLine(x, 0, x, height);
|
||||
g.drawString(i + "", x + 5, 20);
|
||||
}
|
||||
for (double i = dc.getMinHorizAngle(); i < dc.getMaxHorizAngle(); i += 5) {
|
||||
if (i % 90 == 0) {
|
||||
g.setColor(Color.WHITE);
|
||||
} else {
|
||||
g.setColor(Color.GRAY);
|
||||
}
|
||||
|
||||
int y = dc.getYAtAngle(i);
|
||||
|
||||
g.drawLine(0, y, width, y);
|
||||
g.drawString(i + "", 20, y + 5);
|
||||
}
|
||||
|
||||
g.setColor(Color.YELLOW);
|
||||
for (int i = 0; i < 24; i++){
|
||||
AzimuthZenithAngle sp = sunPosition[i];
|
||||
int x = dc.getXAtBearing(sp.getAzimuth());
|
||||
int y = dc.getYAtAngle(180 - sp.getZenithAngle());
|
||||
|
||||
g.fillOval(x - 10, y - 10, 20, 20);
|
||||
g.drawString(String.format("%02d:00", i), x - 40, y);
|
||||
}
|
||||
|
||||
long renderEnd = System.currentTimeMillis();
|
||||
LOG.info("Rendered panorama ({} ms)", renderEnd - renderStart);
|
||||
|
||||
g.dispose();
|
||||
|
||||
try {
|
||||
String imageName = String.format("image-%05d.png", 1);
|
||||
File outputfile = new File("/home/valdor/", imageName);
|
||||
outputfile.createNewFile();
|
||||
|
||||
ImageIO.write(bufferedImg, "png", outputfile);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Exception while writing image", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static AzimuthZenithAngle computeSunAtTime(Position eyePosition, ZonedDateTime dateTime) {
|
||||
return SPA.calculateSolarPosition(
|
||||
dateTime,
|
||||
eyePosition.getLatitude(), // latitude (degrees)
|
||||
eyePosition.getLongitude(), // longitude (degrees)
|
||||
eyePosition.getHeight(), // elevation (m)
|
||||
DeltaT.estimate(dateTime.toLocalDate())); // delta T (s)
|
||||
}
|
||||
|
||||
public static ZonedDateTime computeSunVisible(BasicLayer dl, Position eyePosition, ZonedDateTime beforeDate, ZonedDateTime afterDate) {
|
||||
long rangeDiffSeconds = afterDate.toEpochSecond() - beforeDate.toEpochSecond();
|
||||
|
||||
if (rangeDiffSeconds < 60) {
|
||||
return beforeDate;
|
||||
}
|
||||
|
||||
ZonedDateTime middleDate = beforeDate.plusSeconds(rangeDiffSeconds / 2);
|
||||
AzimuthZenithAngle beforePosition = computeSunAtTime(eyePosition, beforeDate);
|
||||
AzimuthZenithAngle middlePosition = computeSunAtTime(eyePosition, middleDate);
|
||||
AzimuthZenithAngle afterPosition = computeSunAtTime(eyePosition, afterDate);
|
||||
|
||||
boolean sunBeforeVisible = isSunVisible(dl, beforePosition);
|
||||
boolean sunMiddleVisible = isSunVisible(dl, middlePosition);
|
||||
boolean sunAfterVisible = isSunVisible(dl, afterPosition);
|
||||
|
||||
if (sunBeforeVisible && sunAfterVisible) {
|
||||
throw new IllegalArgumentException("Sun is visible both before and after. Invalid dates");
|
||||
}
|
||||
|
||||
if (sunBeforeVisible) {
|
||||
if (sunMiddleVisible) {
|
||||
return computeSunVisible(dl, eyePosition, middleDate, afterDate);
|
||||
} else {
|
||||
return computeSunVisible(dl, eyePosition, beforeDate, middleDate);
|
||||
}
|
||||
} else {
|
||||
if (sunMiddleVisible) {
|
||||
return computeSunVisible(dl, eyePosition, beforeDate, middleDate);
|
||||
} else {
|
||||
return computeSunVisible(dl, eyePosition, middleDate, afterDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void computeSunVisible(BasicLayer dl, Position eyePosition) {
|
||||
ZonedDateTime sunriseDate = computeSunVisible(dl, eyePosition,
|
||||
ZonedDateTime.now()
|
||||
.withHour(3)
|
||||
.withMinute(0)
|
||||
.withSecond(0),
|
||||
ZonedDateTime.now()
|
||||
.withHour(12)
|
||||
.withMinute(0)
|
||||
.withSecond(0));
|
||||
|
||||
LOG.info("Found sunrise at {}", sunriseDate);
|
||||
ZonedDateTime sunsetDate = computeSunVisible(dl, eyePosition,
|
||||
ZonedDateTime.now()
|
||||
.withHour(15)
|
||||
.withMinute(0),
|
||||
ZonedDateTime.now()
|
||||
.withHour(23)
|
||||
.withMinute(0));
|
||||
|
||||
LOG.info("Found sunset at {}", sunsetDate);
|
||||
}
|
||||
|
||||
public static boolean isSunVisible(BasicLayer dl, AzimuthZenithAngle sp) {
|
||||
double sunBearing = sp.getAzimuth();
|
||||
double sunAngle = 180 - sp.getZenithAngle();
|
||||
double terrainAngle = dl.getAtBearing(sunBearing);
|
||||
|
||||
return sunAngle >= terrainAngle;
|
||||
}
|
||||
}
|
153
src/main/java/ch/inf3/horizoncut/data/BasicLayer.java
Normal file
153
src/main/java/ch/inf3/horizoncut/data/BasicLayer.java
Normal file
|
@ -0,0 +1,153 @@
|
|||
package ch.inf3.horizoncut.data;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BasicLayer implements Comparable<BasicLayer>, Cloneable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BasicLayer.class);
|
||||
|
||||
protected final int nearDistance;
|
||||
protected final int farDistance;
|
||||
protected final double[] data;
|
||||
|
||||
protected final DisplayCalculator dc;
|
||||
|
||||
protected BasicLayer(int nearDistance, int farDistance, double[] data, DisplayCalculator dc) {
|
||||
this.nearDistance = nearDistance;
|
||||
this.farDistance = farDistance;
|
||||
this.data = data;
|
||||
this.dc = dc;
|
||||
}
|
||||
|
||||
public static BasicLayer atDistance(int nearDistance, int farDistance, TileMap tileMap, DisplayCalculator dc) {
|
||||
|
||||
double[] output = new double[dc.getWidth()];
|
||||
|
||||
for (int x = 0; x < dc.getWidth(); x += 1) {
|
||||
double bearing = dc.getBearingAtX(x);
|
||||
Position target = Position.get(dc.getEyePosition(), Math.toRadians(bearing), nearDistance);
|
||||
|
||||
Tile atPos = tileMap.getByLatLon(target.gridLat, target.gridLon);
|
||||
double elevation = atPos.elevation(target.row, target.col);
|
||||
|
||||
int dMeters = dc.getEyePosition().distanceToMeters(target);
|
||||
double visibleAngle = dc.calculateVisibilityAngle(elevation, dMeters);
|
||||
|
||||
output[x] = visibleAngle;
|
||||
}
|
||||
|
||||
return new BasicLayer(nearDistance, farDistance, output, dc);
|
||||
}
|
||||
|
||||
public int getNearDistance() {
|
||||
return nearDistance;
|
||||
}
|
||||
|
||||
public int getFarDistance() {
|
||||
return farDistance;
|
||||
}
|
||||
|
||||
public double getAtBearing(double bearing) {
|
||||
int x = dc.getXAtBearing(bearing);
|
||||
|
||||
return data[x];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(BasicLayer other) {
|
||||
return Integer.compare(this.nearDistance, other.nearDistance);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BasicLayer clone() {
|
||||
double[] cloneData = Arrays.copyOf(data, data.length);
|
||||
|
||||
return new BasicLayer(nearDistance, farDistance, cloneData, dc);
|
||||
}
|
||||
|
||||
public BasicLayer mergeWith(BasicLayer other) {
|
||||
BasicLayer near = this.compareTo(other) <= 0 ? this : other;
|
||||
BasicLayer far = this.compareTo(other) > 0 ? this : other;
|
||||
|
||||
double[] output = new double[near.data.length];
|
||||
|
||||
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) {
|
||||
output[d] = yNear;
|
||||
countNear += 1;
|
||||
} else {
|
||||
output[d] = yFar;
|
||||
countFar += 1;
|
||||
}
|
||||
}
|
||||
|
||||
int tolerance = near.data.length / 100;
|
||||
|
||||
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);
|
||||
|
||||
return new BasicLayer(outputNearDistance, outputFarDistance, output, dc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given layer is always lower than the current layer.
|
||||
*
|
||||
* @param other
|
||||
* @return
|
||||
*/
|
||||
public boolean isLower(BasicLayer other) {
|
||||
for (int d = 0; d < this.data.length; d += 1) {
|
||||
double yThis = this.data[d];
|
||||
double yOther = other.data[d];
|
||||
|
||||
if (yThis == 0 || yOther == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (yOther >= yThis) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public double getMax() {
|
||||
return Arrays.stream(this.data)
|
||||
.boxed()
|
||||
.filter(e -> !e.isNaN())
|
||||
.max(Double::compareTo)
|
||||
.orElse(0.0);
|
||||
}
|
||||
|
||||
public static boolean isVisible(Collection<? extends BasicLayer> sortedLayers, int distance, int x, double visibleAngle) {
|
||||
for (BasicLayer l : sortedLayers) {
|
||||
if (l.farDistance > (distance - 200)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
double c = l.data[x];
|
||||
|
||||
if (c >= visibleAngle) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -11,12 +11,11 @@ public class DisplayCalculator {
|
|||
final double maxHorizAngle;
|
||||
|
||||
final Position eyePosition;
|
||||
final double eyeHeight;
|
||||
|
||||
double minVertAngle;
|
||||
double maxVertAngle;
|
||||
|
||||
public DisplayCalculator(int width, int height, double minHorizAngle, double maxHorizAngle, Position eyePosition, double eyeHeight) {
|
||||
public DisplayCalculator(int width, int height, double minHorizAngle, double maxHorizAngle, Position eyePosition) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
|
@ -24,7 +23,6 @@ public class DisplayCalculator {
|
|||
this.maxHorizAngle = maxHorizAngle;
|
||||
|
||||
this.eyePosition = eyePosition;
|
||||
this.eyeHeight = eyeHeight;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
|
@ -44,7 +42,7 @@ public class DisplayCalculator {
|
|||
}
|
||||
|
||||
public double getEyeHeight() {
|
||||
return eyeHeight;
|
||||
return eyePosition.getHeight();
|
||||
}
|
||||
|
||||
public Position getEyePosition() {
|
||||
|
@ -89,6 +87,11 @@ public class DisplayCalculator {
|
|||
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setMinMaxVertAngle(double minVertAngle, double maxVertAngle) {
|
||||
this.minVertAngle = minVertAngle;
|
||||
this.maxVertAngle = maxVertAngle;
|
||||
}
|
||||
|
||||
public void updateMinMaxVertAngle(Collection<DistanceLayer> nLayers) {
|
||||
// We dont want to compte the min angle to avoid looking at the ground
|
||||
|
@ -101,14 +104,14 @@ public class DisplayCalculator {
|
|||
}
|
||||
|
||||
public double calculateVisibilityAngle(double targetVisibleHeight, int targetDistance) {
|
||||
if (eyeHeight == targetVisibleHeight) {
|
||||
if (eyePosition.getHeight() == targetVisibleHeight) {
|
||||
return 90.0;
|
||||
} else if (eyeHeight < targetVisibleHeight) {
|
||||
double op = targetVisibleHeight - eyeHeight;
|
||||
} else if (eyePosition.getHeight() < targetVisibleHeight) {
|
||||
double op = targetVisibleHeight - eyePosition.getHeight();
|
||||
double ad = targetDistance;
|
||||
return Math.toDegrees(Math.atan(op / ad)) + 90;
|
||||
} else {
|
||||
return Math.toDegrees(Math.atan(targetDistance / (eyeHeight - targetVisibleHeight)));
|
||||
return Math.toDegrees(Math.atan(targetDistance / (eyePosition.getHeight() - targetVisibleHeight)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,36 +10,32 @@ import java.util.stream.Stream;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
||||
public class DistanceLayer extends BasicLayer {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DistanceLayer.class);
|
||||
|
||||
public final int nearDistance;
|
||||
public final int farDistance;
|
||||
public final double[] data;
|
||||
|
||||
public final Collection<ObservedFeature> horizonFeatures;
|
||||
public final Collection<ObservedFeature> nonHorizonFeatures;
|
||||
|
||||
public DistanceLayer(int nearDistance, int farDistance, double[] data) {
|
||||
this(nearDistance, farDistance, data, new ArrayList<>(), new ArrayList<>());
|
||||
public DistanceLayer(int nearDistance, int farDistance, double[] data, DisplayCalculator dc) {
|
||||
this(nearDistance, farDistance, data, new ArrayList<>(), new ArrayList<>(), dc);
|
||||
}
|
||||
|
||||
private DistanceLayer(int nearDistance, int farDistance, double[] data,
|
||||
Collection<ObservedFeature> horizonFeatures, Collection<ObservedFeature> nonHorizonFeatures) {
|
||||
this.nearDistance = nearDistance;
|
||||
this.farDistance = farDistance;
|
||||
this.data = data;
|
||||
Collection<ObservedFeature> horizonFeatures, Collection<ObservedFeature> nonHorizonFeatures,
|
||||
DisplayCalculator dc) {
|
||||
super(nearDistance, farDistance, data, dc);
|
||||
|
||||
this.horizonFeatures = horizonFeatures;
|
||||
this.nonHorizonFeatures = nonHorizonFeatures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(DistanceLayer other) {
|
||||
return Integer.compare(this.nearDistance, other.nearDistance);
|
||||
public DistanceLayer(BasicLayer atDistance) {
|
||||
this(atDistance.nearDistance, atDistance.farDistance,
|
||||
Arrays.copyOf(atDistance.data, atDistance.data.length),
|
||||
atDistance.dc);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected DistanceLayer clone() {
|
||||
double[] cloneData = Arrays.copyOf(data, data.length);
|
||||
|
@ -47,7 +43,7 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
|||
Collection<ObservedFeature> horizonFeatures = new ArrayList<>(this.horizonFeatures);
|
||||
Collection<ObservedFeature> nonHorizonFeatures = new ArrayList<>(this.nonHorizonFeatures);
|
||||
|
||||
return new DistanceLayer(nearDistance, farDistance, cloneData, horizonFeatures, nonHorizonFeatures);
|
||||
return new DistanceLayer(nearDistance, farDistance, cloneData, horizonFeatures, nonHorizonFeatures, dc);
|
||||
}
|
||||
|
||||
public DistanceLayer mergeWith(DistanceLayer other) {
|
||||
|
@ -90,30 +86,7 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
|||
outNonHorizonFeatures.addAll(this.nonHorizonFeatures);
|
||||
outNonHorizonFeatures.addAll(other.nonHorizonFeatures);
|
||||
|
||||
return new DistanceLayer(outputNearDistance, outputFarDistance, output, outHorizonFeatures, outNonHorizonFeatures);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given layer is always lower than the current layer.
|
||||
*
|
||||
* @param other
|
||||
* @return
|
||||
*/
|
||||
public boolean isLower(DistanceLayer other) {
|
||||
for (int d = 0; d < this.data.length; d += 1) {
|
||||
double yThis = this.data[d];
|
||||
double yOther = other.data[d];
|
||||
|
||||
if (yThis == 0 || yOther == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (yOther >= yThis) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return new DistanceLayer(outputNearDistance, outputFarDistance, output, outHorizonFeatures, outNonHorizonFeatures, dc);
|
||||
}
|
||||
|
||||
public void addFeature(ObservedFeature feature) {
|
||||
|
@ -140,7 +113,7 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
|||
|
||||
private static boolean isAtHorizon(ObservedFeature feature, double[] data) {
|
||||
int xFeature = feature.getObservedX();
|
||||
double angleFeature = feature.visibleAngle;
|
||||
double angleFeature = feature.getAngle();
|
||||
|
||||
double angleThis = data[xFeature];
|
||||
|
||||
|
@ -155,28 +128,4 @@ public class DistanceLayer implements Comparable<DistanceLayer>, Cloneable {
|
|||
return this.horizonFeatures.size();
|
||||
}
|
||||
|
||||
public double getMax() {
|
||||
return Arrays.stream(this.data)
|
||||
.boxed()
|
||||
.filter(e -> !e.isNaN())
|
||||
.max(Double::compareTo)
|
||||
.orElse(0.0);
|
||||
}
|
||||
|
||||
public static boolean isVisible(Collection<DistanceLayer> sortedLayers, int distance, int x, double visibleAngle) {
|
||||
for (DistanceLayer l : sortedLayers) {
|
||||
if (l.farDistance > (distance - 200)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
double c = l.data[x];
|
||||
|
||||
if (c >= visibleAngle) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ 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 int distance;
|
||||
private final double visibleAngle;
|
||||
private final double bearing;
|
||||
|
||||
private final DisplayCalculator observator;
|
||||
|
||||
|
@ -31,4 +31,16 @@ public class ObservedFeature extends Feature implements Comparable<ObservedFeatu
|
|||
public int getObservedY() {
|
||||
return observator.getYAtAngle(visibleAngle);
|
||||
}
|
||||
|
||||
public double getBearing() {
|
||||
return this.bearing;
|
||||
}
|
||||
|
||||
public double getAngle() {
|
||||
return this.visibleAngle;
|
||||
}
|
||||
|
||||
public int getDistance() {
|
||||
return distance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ public class Position {
|
|||
|
||||
private final double lat;
|
||||
private final double lon;
|
||||
|
||||
private double height;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Position.class);
|
||||
|
||||
|
@ -119,6 +121,14 @@ public class Position {
|
|||
public String getLatLon() {
|
||||
return String.format("(%.4f, %.4f)", getLatitude(), getLongitude());
|
||||
}
|
||||
|
||||
public void setHeight(double height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
@ -160,7 +170,7 @@ public class Position {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Position[gridLat=" + gridLat + ", gridLon=" + gridLon + ", col=" + col + ", row=" + row + "]";
|
||||
return "Position[gridLat=" + gridLat + ", gridLon=" + gridLon + ", col=" + col + ", row=" + row + ", height=" + height + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue