Disabled external gits

This commit is contained in:
choelzl 2022-04-07 18:43:21 +02:00
parent 182267a8cb
commit 88cb3426ad
Signed by: sora
GPG Key ID: A362EA0491E2EEA0
1067 changed files with 102374 additions and 6 deletions

1
Alpano

@ -1 +0,0 @@
Subproject commit 7792b5cd98f77942a04e32474594aaf0a18749b2

13
Alpano/.classpath Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="con" path="org.eclipse.fx.ide.jdt.core.JAVAFX_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
<accessrules>
<accessrule kind="accessible" pattern="java/**"/>
</accessrules>
</classpathentry>
<classpathentry excluding="**" kind="src" path="tests"/>
<classpathentry kind="output" path="bin"/>
</classpath>

5
Alpano/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.DS_Store
*.hgt
*.hgt*
bin/*
/bin/

17
Alpano/.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>alpano</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,11 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

21069
Alpano/alps.txt Normal file

File diff suppressed because it is too large Load Diff

BIN
Alpano/rapport.pages Normal file

Binary file not shown.

BIN
Alpano/rapport.pdf Normal file

Binary file not shown.

View File

@ -0,0 +1,98 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano;
import static ch.epfl.alpano.Preconditions.checkArgument;
import static ch.epfl.alpano.Math2.floorMod;
import static ch.epfl.alpano.Math2.PI2;
/**
* Utility class of commonly used calculations on azimuths
*
*/
public interface Azimuth {
/**
* Checks if the azimuth is canonical
*
* @param azimuth
* @return true if the given azimuth is in [0,2PI[
*/
public static boolean isCanonical(double azimuth) {
return (azimuth >= 0 && azimuth < PI2);
}
/**
* modular value of a given azimuth angle
*
* @param azimuth
* @return azimuth in [0, 2PI[
*/
public static double canonicalize(double azimuth) {
return floorMod(azimuth, PI2);
}
/**
* mathematical counterpart of the azimuth value
*
* @param azimuth
* @return azimuth value; expressed clockwise
* @throws IllegalArgumentException
* if azimuth input is not canonical
* @see isCanonical(double azimuth)
*/
public static double toMath(double azimuth) {
checkArgument(isCanonical(azimuth));
return canonicalize(PI2 - azimuth);
}
/**
* azimuth counterpart of the mathematical value
*
* @param angle
* @return angle value; expressed counterclockwise
* @throws IllegalArgumentException
* if angle input is not canonical
* @see isCanonical(double azimuth)
*/
public static double fromMath(double angle) {
checkArgument(isCanonical(angle));
return canonicalize(PI2 - angle);
}
/**
* cardinal points of a given azimuth angle
*
* @param azimuth
* @param n
* : north string
* @param e
* : east string
* @param s
* : south string
* @param w
* : west string
* @return a string expressing the closest cardinal point to the given
* azimuth
*/
public static String toOctantString(double azimuth, String n, String e,
String s, String w) {
checkArgument(isCanonical(azimuth));
String output = "";
double angle = PI2 / 16.0;
if (azimuth <= 3 * angle || azimuth >= 13 * angle)
output += n;
if (azimuth >= 5 * angle && azimuth <= 11 * angle)
output += s;
if (azimuth >= 1 * angle && azimuth <= 7 * angle)
output += e;
if (azimuth >= 9 * angle && azimuth <= 15 * angle)
output += w;
return output;
}
}

View File

@ -0,0 +1,48 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano;
/**
* Utility interface of commonly used distance transformations and known values
*/
public interface Distance {
/**
* Value of the earth radius
*/
public final double EARTH_RADIUS = 6371000;
/**
* Transforms meters to radians
*
* @param meters
* : distance to be converted to radians
* @return radians
*/
public static double toRadians(double meters) {
return meters / EARTH_RADIUS;
}
/**
* Transforms radians to meters
*
* @param radians
* : angle to be converted to meters
* @return meters
*/
public static double toMeters(double radians) {
return EARTH_RADIUS * radians;
}
/**
* Transforms meters to degrees
* @param meters : distance to be converted to degrees
* @return degrees
* @see toRadians
*/
public static double toDegrees(double meters){
return Math.toDegrees(toRadians(meters));
}
}

View File

@ -0,0 +1,116 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano;
import java.util.Locale;
import static java.lang.Math.abs;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static java.lang.Math.PI;
import static java.lang.Math.atan2;
import static java.lang.Math.sqrt;
import static java.lang.Math.toDegrees;
import static ch.epfl.alpano.Preconditions.checkArgument;
import static ch.epfl.alpano.Math2.haversin;
import static ch.epfl.alpano.Distance.toMeters;
import static ch.epfl.alpano.Azimuth.fromMath;
import static ch.epfl.alpano.Azimuth.canonicalize;
/**
* Definition of a geographical point on Earth's surface using spherical
* coordinates.
*/
public class GeoPoint {
private final double longitude;
private final double latitude;
/**
* Constructor of a GeoPoint
*
* @param longitude
* : longitude position
* @param latitude
* : latitude position
* @throws IllegalArgumentException
* if longitude is not in [Pi;Pi] or latitude is not in
* [2Pi;2Pi]
*/
public GeoPoint(double longitude, double latitude) {
checkArgument(!(abs(longitude) > PI || abs(latitude) * 2.0 > PI));
this.longitude = longitude;
this.latitude = latitude;
}
/**
* Returns the longitude value of the GeoPoint
*
* @return longitude
*/
public double longitude() {
return this.longitude;
}
/**
* Returns the latitude value of the GeoPoint
*
* @return latitude
*/
public double latitude() {
return this.latitude;
}
/**
* Computes the distance between two points
*
* @param that
* : a GeoPoint
* @return distance in meters between two points
*/
public double distanceTo(GeoPoint that) {
return toMeters(
2 * Math.asin(sqrt(haversin(this.latitude - that.latitude)
+ cos(this.latitude) * cos(that.latitude)
* haversin(this.longitude - that.longitude))));
}
/**
* Mathematical angular distance from a geoPoint to another
*
* @param that
* @return angle expressed in radian in the mathematical convention
*/
private double mathAzimuthTo(GeoPoint that) {
return atan2(
(sin(this.longitude - that.longitude) * cos(that.latitude)),
(cos(this.latitude) * sin(that.latitude)
- sin(this.latitude) * cos(that.latitude)
* cos(this.longitude - that.longitude)));
}
/**
* Angular distance from a geoPoint to another in azimuth
*
* @param that
* : a GeoPoint
* @return angle expressed in radian in the azimuth convention
*/
public double azimuthTo(GeoPoint that) {
return fromMath(canonicalize(mathAzimuthTo(that)));
}
/**
* Redefines the to String method
*
* @return a string , [lon,lat], lon and lat being the longitude and
* latitude values of the GeoPoint
*/
@Override
public String toString() {
return String.format((Locale) null, "(%.4f,%.4f)",
toDegrees(this.longitude), toDegrees(this.latitude));
}
}

View File

@ -0,0 +1,179 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano;
import java.util.Locale;
import java.util.Objects;
import static ch.epfl.alpano.Preconditions.checkArgument;
import static java.lang.Math.min;
import static java.lang.Math.max;
/**
* Definition of a one-dimensional interval
*/
public final class Interval1D {
private final int includedFrom;
private final int includedTo;
/**
* Constructor of an Interval
*
* @param includedFrom
* : min bound of the interval
* @param includedTo
* : max bound of the interval
* @throws IllegalArgumentException
* if min bound is bigger than max bound
*/
public Interval1D(int includedFrom, int includedTo) {
checkArgument(includedTo >= includedFrom);
this.includedFrom = includedFrom;
this.includedTo = includedTo;
}
/**
* Returns the min value of the interval
*
* @return includedFrom
*/
public int includedFrom() {
return this.includedFrom;
}
/**
* Return the max value of the interval
*
* @return includeTo
*/
public int includedTo() {
return this.includedTo;
}
/**
* Checks if the value v is contained in the interval
*
* @param v
* : integer
* @return true if v belongs to the interval
*/
public boolean contains(int v) {
return ((v >= this.includedFrom()) && (v <= this.includedTo()));
}
/**
* Returns the number of elements contained in an interval
*
* @return size of the interval
*/
public int size() {
return (this.includedTo() - this.includedFrom() + 1);
}
/**
* Returns the number of elements shared between both intervals
*
* @param that
* : an interval
* @return size of the Intersection between both intervals
* @see isUniunableWith(Interval that)
*/
public int sizeOfIntersectionWith(Interval1D that) {
int max = min(this.includedTo(), that.includedTo());
int min = max(this.includedFrom(), that.includedFrom());
int size = max - min + 1;
return (size < 0) ? 0 : size;
}
/**
* Returns a new Interval containing all elements of both intervals
*
* @param that
* : an interval
* @return new Interval of the area generated by the bounding union of both
* intervals
*/
public Interval1D boundingUnion(Interval1D that) {
int max = max(this.includedTo(), that.includedTo());
int min = min(this.includedFrom(), that.includedFrom());
return new Interval1D(min, max);
}
/**
* Checks if intervals have an intersection
*
* @param that
* : an interval
* @return true if intervals intersects
*/
public boolean isUnionableWith(Interval1D that) {
return this.size() + that.size()
- this.sizeOfIntersectionWith(that) == this.boundingUnion(that)
.size();
}
/**
* Returns the intersection of both intervals as a new interval
*
* @param that
* : an interval
* @return new Interval generated from the intersection of both intervals
* @throws IllegalArgumentException
* if intervals do not intersect
* @see isUnionableWith(Interval1D that)
*/
public Interval1D union(Interval1D that) {
checkArgument(isUnionableWith(that));
int max = max(this.includedTo(), that.includedTo());
int min = min(this.includedFrom(), that.includedFrom());
return new Interval1D(min, max);
}
/**
* Compare an objects with the interval
*
* @param thatO
* : an object
* @return true if the object is an Interval with the same min and max
* bounds
* @see includedFrom() && includedTo()
*/
@Override
public boolean equals(Object thatO) {
return (thatO instanceof Interval1D
&& this.includedFrom() == ((Interval1D) thatO).includedFrom()
&& this.includedTo() == ((Interval1D) thatO).includedTo());
}
/**
* Redefines the hashCode for the method equals
*
* @return an integer, hash of min and max bounds
* @see equals(Object thatO)
*/
@Override
public int hashCode() {
return Objects.hash(includedFrom(), includedTo());
}
/**
* Redefines the toString method
*
* @return a string , [min..max], min and max being the min and max values
* of the interval
*/
@Override
public String toString() {
Locale l = null;
return String.format(l, "[%d..%d]", this.includedFrom(),
this.includedTo());
}
}

View File

@ -0,0 +1,164 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano;
import java.util.Objects;
import static ch.epfl.alpano.Preconditions.checkArgument;
/**
* Definition of a two dimensional interval
*
* @see Interval1D
*/
public final class Interval2D {
private final Interval1D iX;
private final Interval1D iY;
/**
* Constructor of a 2D interval based on two given 1D intervals
*
* @param iX
* @param iY
* @throws NullPointerException if iX or iY are null
*/
public Interval2D(Interval1D iX, Interval1D iY) {
Objects.requireNonNull(iX);
Objects.requireNonNull(iY);
this.iX = iX;
this.iY = iY;
}
/**
* First 1D X-wise component of the 2D interval
*
* @return Interval1D
*/
public Interval1D iX() {
return this.iX;
}
/**
* Second Y-wise 1D component of the 2D interval
*
* @return Interval1D
*/
public Interval1D iY() {
return this.iY;
}
/**
* Checks if the value (x,y) is contained in the 2D interval
*
* @param x
* : x value of the tuple
* @param y
* : y value of the tuple
* @return true iff the 2D interval contains a given tuple
*/
public boolean contains(int x, int y) {
return (iX().contains(x) && iY().contains(y));
}
/**
* Return the number of elements in the 2D interval
*
* @return cardinality of the 2D interval
*/
public int size() {
return (iX().size() * iY().size());
}
/**
* Returns the size of the interval between two 2D intervals
*
* @param that
* : the other interval to check intersection with
* @return cardinality of the intersection interval of this interval an
* other given 2D interval
*/
public int sizeOfIntersectionWith(Interval2D that) {
return (this.iX().sizeOfIntersectionWith(that.iX())
* this.iY().sizeOfIntersectionWith(that.iY()));
}
/**
* The interval containing 2 given intersecting 2D intervals
*
* @param that
* : the compared 2D interval
* @return 2D interval of the bounding union of two given 2D intervals
*/
public Interval2D boundingUnion(Interval2D that) {
return new Interval2D(this.iX().boundingUnion(that.iX()),
this.iY().boundingUnion(that.iY()));
}
/**
*
* @param that
* : the other interval to check union with
* @return true if the intersection of this interval with another exists
* false otherwise
*/
public boolean isUnionableWith(Interval2D that) {
return (this.size() + that.size()
- this.sizeOfIntersectionWith(that) == this.boundingUnion(that)
.size());
}
/**
* Returns the union of two 2D intervals
*
* @param that
* : the other interval to check intersection with
* @return the 2D interval intersection of this interval with another
* @throws IllegalArgumentException if they are not unionable
*/
public Interval2D union(Interval2D that) {
checkArgument(this.isUnionableWith(that));
return this.boundingUnion(that);
}
/**
* Compare an object with the interval
*
* @param thatO
* : an object
* @return true if the object is a 2D Interval with the same size and
* Intervals
* @see Interval.equals(object thatO)
*/
@Override
public boolean equals(Object thatO) {
return ((thatO instanceof Interval2D)
&& this.iX().equals(((Interval2D) thatO).iX())
&& this.iY().equals(((Interval2D) thatO).iY()));
}
/**
* Redefines the hashCode for the method equals
*
* @return an integer, hash of the hashes of both intervals
* @see equals(Object thatO)
*/
@Override
public int hashCode() {
return Objects.hash(iX().includedFrom(),iX().includedTo(), iY().includedFrom(),iY().includedTo());
}
/**
* Redefines the toString method
*
* @return a string in the form : [x1,y1]x[x2,y2] where xi, yi are the
* boundaries of a 1D interval Ii
*/
@Override
public String toString() {
return this.iX() + "x" + this.iY();
}
}

View File

@ -0,0 +1,178 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano;
import java.util.function.DoubleUnaryOperator;
import static ch.epfl.alpano.Preconditions.checkArgument;
import static java.lang.Math.PI;
import static java.lang.Math.sin;
import static java.lang.Math.floor;
import static java.lang.Math.abs;
/**
* Utility interface of commonly used math calculations
*
* @see Math
*/
public interface Math2 {
/**
* Pi*2
*
* @see Math.PI
*/
public static final double PI2 = 2 * PI;
/**
* Returns the square of a value
*
* @param x
* @return square function
*/
public static double sq(double x) {
return x * x;
}
/**
* floor division remainder of x over y
*
* @param x
* @param y
* @return largest positive integer smaller or equal to x/y float value
*/
public static double floorMod(double x, double y) {
return x - y * floor(x / y);
}
/**
* Computes the haversin
*
* @param x
* : a double, an angle
* @return sinus of a half angle squared
*/
public static double haversin(double x) {
return sq(sin(x / 2));
}
/**
* Computes the angular distance
*
* @param a1
* @param a2
* @return distance between two angles
*/
public static double angularDistance(double a1, double a2) {
return floorMod(a2 - a1 + PI, PI2) - PI;
}
/**
* Linear interpolation
*
* @param y0
* : y value before x
* @param y1
* : y value after x
* @param x
* : a point
* @return linear interpolation at x
*/
public static double lerp(double y0, double y1, double x) {
return (y1 - y0) * x + y0;
}
/**
* Bilinear interpolation
*
* @param z00
* : z coordinate
* @param z10
* : z coordinate
* @param z01
* : z coordinate
* @param z11
* : z coordinate
* @param x
* : x coordinate of a point
* @param y
* : y coordinate of a point
* @return bilinear interpolation at (x,y)
* @see lerp(double y0, double y1, double x) for a linear interpolation
*/
public static double bilerp(double z00, double z10, double z01, double z11,
double x, double y) {
return (lerp(z00,z10,x)+lerp(z00,z01,y)+ ((z00 + z11) - (z10 + z01)) * x * y-z00);
/*return (z10 - z00) * x + (z01 - z00) * y
+ ((z00 + z11) - (z10 + z01)) * x * y + z00;*/
}
/**
* the first interval of size dX containing a root for the input function,
* properly contained in a given interval
*
* @param f
* : input function
* @param minX
* : starting interval's lower bound
* @param maxX
* : starting interval's upper bound
* @param dX
* : interval search's step size
* @return lower bound (as double) of the first interval of size dX
* containing a 0
* @throws IllegalArgumentException if minX is smaller than maxX or if dX is negative or null
*/
public static double firstIntervalContainingRoot(DoubleUnaryOperator f,
double minX, double maxX, double dX) {
checkArgument(minX<=maxX);
checkArgument(dX>0);
double newLowerBound = minX + dX;
if (newLowerBound > maxX) {
return ((f.applyAsDouble(minX) * f.applyAsDouble(maxX)) > 0)
? Double.POSITIVE_INFINITY : maxX - dX;
}
if (f.applyAsDouble(minX) * f.applyAsDouble(newLowerBound) < 0) {
return minX;
}
return firstIntervalContainingRoot(f, newLowerBound, maxX, dX);
}
/**
* Dichotomy algorithm implementation that finds the lower bound of the
* interval of size epsilon containing a root
*
* @param f
* : input function
* @param x1
* : starting interval's lower bound
* @param x2
* : starting interval's upper bound
* @param epsilon
* : smallest interval size allowed of this implementation
* @return lower bound (as double) of the interval of size epsilon
* containing a zero
* @throws IllegalArgumentException
* if the function returns values of same sign
*/
public static double improveRoot(DoubleUnaryOperator f, double x1,
double x2, double epsilon) {
checkArgument(!(f.applyAsDouble(x1) * f.applyAsDouble(x2) > 0));
while (abs(x1 - x2) > epsilon) {
double avgX = (x1 + x2) * 0.5;
double avgImage = f.applyAsDouble(avgX);
if (avgImage == 0)
return avgX;
else if (avgImage * f.applyAsDouble(x1) > 0)
x1 = avgX;
else
x2 = avgX;
}
return x1;
}
}

View File

@ -0,0 +1,349 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano;
import static ch.epfl.alpano.Preconditions.checkBounds;
import java.util.Arrays;
/**
* Panorama data holder computed using PanoramaComputer class using given
* panorama parameters
*
* @see PanoramaComputer
* @see PanoramaParameters
* @see Panorama.Builder
*/
public final class Panorama {
private final float[] distance;
private final float[] longitude;
private final float[] latitude;
private final float[] altitude;
private final float[] slope;
private final PanoramaParameters parameters;
/**
* Panorama constructor
*
* @param parameters
* : the PanoramaParameters
* @param distance
* : float table containing the distances
* @param longitude
* : float table containing the longitudes
* @param latitude
* : float table containing the latitudes
* @param altitude
* : float table containing the altitudes
* @param slope
* : float table containing the slopes
*/
private Panorama(PanoramaParameters parameters, float[] distance,
float[] longitude, float[] latitude, float[] altitude,
float[] slope) {
this.distance = distance;
this.longitude = longitude;
this.latitude = latitude;
this.altitude = altitude;
this.slope = slope;
this.parameters = parameters;
}
/**
* Returns the parameters of the panorama
*
* @return the PanoramaParameters of the panorama
*/
public PanoramaParameters parameters() {
return this.parameters;
}
/**
* Returns the distance given a certain index x,y
*
* @param x
* horizontal index
* @param y
* : vertical index
* @return the distance given x,y
* @throws IndexOutOfBoundsException
* if x or y are out of bounds
*/
public float distanceAt(int x, int y) {
checkBounds(parameters().isValidSampleIndex(x, y));
return this.distance[parameters().linearSampleIndex(x, y)];
}
/**
* Returns the distance given a certain index x,y
*
* @param x
* horizontal index
* @param y
* : vertical index
* @param d
* : the default distance
* @return the distance given x,y or the default distance if x or y are out
* of bounds
*/
public float distanceAt(int x, int y, float d) {
if (parameters().isValidSampleIndex(x, y))
return this.distance[parameters().linearSampleIndex(x, y)];
else
return d;
}
/**
* Returns the latitude given a certain index x,y
*
* @param x
* horizontal index
* @param y
* : vertical index
* @return the latitude given x,y
* @throws IndexOutOfBoundsException
* if x or y are out of bounds
*/
public float latitudeAt(int x, int y) {
checkBounds(parameters().isValidSampleIndex(x, y));
return this.latitude[parameters().linearSampleIndex(x, y)];
}
/**
* Returns the longitude given a certain index x,y
*
* @param x
* horizontal index
* @param y
* : vertical index
* @return the longitude given x,y
* @throws IndexOutOfBoundsException
* if x or y are out of bounds
*/
public float longitudeAt(int x, int y) {
checkBounds(parameters().isValidSampleIndex(x, y));
return this.longitude[parameters().linearSampleIndex(x, y)];
}
/**
* Returns the altitude given a certain index x,y
*
* @param x
* horizontal index
* @param y
* : vertical index
* @return the altitude given x,y
* @throws IndexOutOfBoundsException
* if x or y are out of bounds
*/
public float elevationAt(int x, int y) {
checkBounds(parameters().isValidSampleIndex(x, y));
return this.altitude[parameters().linearSampleIndex(x, y)];
}
/**
* Returns the slope given a certain index x,y
*
* @param x
* horizontal index
* @param y
* : vertical index
* @return the slope given x,y
* @throws IndexOutOfBoundsException
* if x or y are out of bounds
*/
public float slopeAt(int x, int y) {
checkBounds(parameters().isValidSampleIndex(x, y));
return this.slope[parameters().linearSampleIndex(x, y)];
}
/**
* Panorama Builder:
*
* Gathers data for a Panorama and builds it.
*
* @see Panorama
* @see PanoramaComputer
*/
public final static class Builder {
private float[] distance;
private float[] longitude;
private float[] latitude;
private float[] altitude;
private float[] slope;
private final PanoramaParameters parameters;
/**
* Constructor of a panorama builder
*
* @param pp
* : the panorama parameters
* @throws NullPointerException
* if the PanoramaParameters object is null
*/
public Builder(PanoramaParameters pp) {
this.parameters = pp;
int size = pp.width() * pp.height();
this.distance = new float[size];
this.longitude = new float[size];
this.latitude = new float[size];
this.altitude = new float[size];
this.slope = new float[size];
Arrays.fill(distance, Float.POSITIVE_INFINITY);
}
/**
* Enters the distance into the table at a given index
*
* @param x
* : horizontal index
* @param y
* : vertical index
* @param distance
* : the value to enter in the table
* @return the builder (itself)
* @throws IllegalStateException
* if the Panorama has already been created
* @throws IndexOutOfBoundsException
* if x or y are out of bounds
* @see checkBuilder
*/
public Builder setDistanceAt(int x, int y, float distance) {
checkBuilder();
checkBounds(this.parameters.isValidSampleIndex(x, y));
this.distance[this.parameters.linearSampleIndex(x, y)] = distance;
return this;
}
/**
* Enters the longitude into the table at a given index
*
* @param x
* : horizontal index
* @param y
* : vertical index
* @param longitude
* : the value to enter in the table
* @return the builder (itself)
* @throws IllegalStateException
* if the panorama has already been created
* @throws IndexOutOfBoundsException
* if x or y are out of bounds
* @see checkBuilder
*/
public Builder setLongitudeAt(int x, int y, float longitude) {
checkBuilder();
checkBounds(this.parameters.isValidSampleIndex(x, y));
this.longitude[this.parameters.linearSampleIndex(x, y)] = longitude;
return this;
}
/**
* Enters the latitude into the table at a given index
*
* @param x
* : horizontal index
* @param y
* : vertical index
* @param latitude
* : the value to enter in the table
* @return the builder (itself)
* @throws IllegalStateException
* if the panorama has already been created
* @throws IndexOutOfBoundsException
* if x or y are out of bounds
* @see checkBuilder
*/
public Builder setLatitudeAt(int x, int y, float latitude) {
checkBuilder();
checkBounds(this.parameters.isValidSampleIndex(x, y));
this.latitude[this.parameters.linearSampleIndex(x, y)] = latitude;
return this;
}
/**
* Enters the altitude into the table at a given index
*
* @param x
* : horizontal index
* @param y
* : vertical index
* @param elevation
* : the value to enter in the table
* @return the builder (itself)
* @throws IllegalStateException
* if the panorama has already been created
* @throws IndexOutOfBoundsException
* if x or y are out of bounds
* @see checkBuilder
*/
public Builder setElevationAt(int x, int y, float elevation) {
checkBuilder();
checkBounds(this.parameters.isValidSampleIndex(x, y));
this.altitude[this.parameters.linearSampleIndex(x, y)] = elevation;
return this;
}
/**
* Enters the slope into the table at a given index
*
* @param x
* : horizontal index
* @param y
* : vertical index
* @param slope
* : the value to enter in the table
* @return the builder (itself)
* @throws IllegalStateException
* if the panorama has already been created
* @throws IndexOutOfBoundsException
* if x or y are out of bounds
* @see checkBuilder
*/
public Builder setSlopeAt(int x, int y, float slope) {
checkBuilder();
checkBounds(this.parameters.isValidSampleIndex(x, y));
this.slope[this.parameters.linearSampleIndex(x, y)] = slope;
return this;
}
/**
* Creates and returns the panorama
*
* @return the panorama
* @throws IllegalStateException
* if the panorama has already been created
* @see checkBuilder
*/
public Panorama build() {
checkBuilder();
Panorama p = new Panorama(parameters, distance, longitude, latitude,
altitude, slope);
this.distance = null;
this.longitude = null;
this.latitude = null;
this.altitude = null;
this.slope = null;
return p;
}
/**
* Checks if the panorama has already been built
*
* @throws IllegalStateException
* if the panorama has already been created
*/
private void checkBuilder() {
if (!(distance != null && longitude != null && latitude != null
&& altitude != null && slope != null))
throw new IllegalStateException();
}
}
}

View File

@ -0,0 +1,132 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano;
import java.util.Objects;
import java.util.function.DoubleUnaryOperator;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.ElevationProfile;
import static java.lang.Math.tan;
import static java.lang.Math.cos;
import static ch.epfl.alpano.Math2.firstIntervalContainingRoot;
import static ch.epfl.alpano.Math2.improveRoot;
import static ch.epfl.alpano.Math2.sq;
/**
* Panorama computer that defines a panorama using its parameters and raycasting
*
* @see Panorama
* @see Panorama.Builder
*/
public final class PanoramaComputer {
private final ContinuousElevationModel cdem;
/**
* Earth Athmosphere Refraction value
*/
private static final double REFRACTION_INDEX = 0.13;
/**
* Height variation constant
*/
public static final double NATURAL_VARIATION = (1 - REFRACTION_INDEX)/ (2 * Distance.EARTH_RADIUS);
/**
* Precision for the first Root computation
*
* @see firstIntervalContainingRoot
*/
private static final int ROOTL = 64;
/**
* Precision for the precise Root computation
*
* @see improveRoot
*/
private static final int ROOTP = 4;
/**
* Builds a panoramaComputer using continuous elevation values
*
* @param dem
* : the ContinuousElevationProfile
*
* @throws NullPointerExcepetion
* : if the parameter is null
*/
public PanoramaComputer(ContinuousElevationModel dem) {
Objects.requireNonNull(dem);
this.cdem = dem;
}
/**
* Uses raycasting and finds its roots to retrieve visible GeoPoints from a
* given perspective
*
* @param pp
* : the Parameters of the panorama to build
* @return Panorama instance which has its attributes figured out using
* raycasting
*/
public Panorama computePanorama(PanoramaParameters pp) {
Panorama.Builder b = new Panorama.Builder(pp);
double h_azimuth, v_azimuth, rootL, rootP;
ElevationProfile evP;
double ray0 = pp.observerElevation();
double maxDistance = pp.maxDistance();
double lastroot = 0;
DoubleUnaryOperator ray;
for (int x = 0; x < pp.width(); ++x) {
h_azimuth = pp.azimuthForX(x);
lastroot = 0;
rootL = 0;
// Create elevation profile
evP = new ElevationProfile(cdem, pp.observerPosition(), h_azimuth,
maxDistance);
for (int y = pp.height() - 1; y >= 0
&& rootL != Double.POSITIVE_INFINITY; --y) {
v_azimuth = pp.altitudeForY(y);
// Raycast
ray = rayToGroundDistance(evP, ray0, tan(v_azimuth));
rootL = firstIntervalContainingRoot(ray, lastroot, maxDistance,
ROOTL);
if (rootL != Double.POSITIVE_INFINITY) {
rootP = improveRoot(ray, rootL, rootL + ROOTL, ROOTP);
// Fill in values
lastroot = rootP;
GeoPoint hit = evP.positionAt(rootP);
b.setDistanceAt(x, y, (float) (rootP / cos(v_azimuth)))
.setElevationAt(x, y,(float) cdem.elevationAt(hit))
.setLatitudeAt(x, y, (float) hit.latitude())
.setLongitudeAt(x, y, (float) hit.longitude())
.setSlopeAt(x, y, (float) cdem.slopeAt(hit));
}
}
}
return b.build();
}
/**
* distance function between a raycast from a point of view and the profile
*
* @param profile
* : profile elevation data
* @param ray0
* : vertical offset
* @param raySlope
* : vertical angle of view
* @return
*/
public static DoubleUnaryOperator rayToGroundDistance(
ElevationProfile profile, double ray0, double raySlope) {
return (x) -> ray0 + x * raySlope - profile.elevationAt(x)
+ sq(x) *NATURAL_VARIATION;
}
}

View File

@ -0,0 +1,262 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano;
import java.util.Objects;
import static java.lang.Math.abs;
import static ch.epfl.alpano.Math2.angularDistance;
import static ch.epfl.alpano.Preconditions.checkArgument;
import static ch.epfl.alpano.Azimuth.isCanonical;
import static ch.epfl.alpano.Azimuth.canonicalize;
/**
* The parameters of a panorama that are needed to compute a panorama when
* passed to PanoramaComputer class
*
* @see PanoramaComputer
* @see Panorama
*/
public final class PanoramaParameters {
private final GeoPoint observerPoint;
private final int observerElevation;
private final int maxDistance;
private final int width, height;
private final double centerAzimuth;
private final double horizontalFieldOfView, verticalFieldOfView;
private final double dX, dY;
/**
* Constructor of PanoramaParameters
*
* @param observerPoint
* : the origin point of the viewer
* @param observerElevation
* : the origin altitude of the viewer
* @param centerAzimuth
* : the direction the viewer is facing
* @param horizontalFieldOfView
* : the horizontal field of view
* @param maxDistance
* : the maximum render distance
* @param width
* : the width to render(/of the view)
* @param height
* : the height to render(/of the view)
* @throws NullPointerException
* if the observerPoint is null
* @throws IllegalArgumentException
* if the horizontal field of view is smaller or equal to 0 or
* bigger than 2pi, the height or width is negative or zero, the
* max distance is negative or zero,
*
*/
public PanoramaParameters(GeoPoint observerPoint, int observerElevation,
double centerAzimuth, double horizontalFieldOfView, int maxDistance,
int width, int height) {
Objects.requireNonNull(observerPoint);
checkArgument(isCanonical(centerAzimuth));
checkArgument(horizontalFieldOfView > 0);
checkArgument(horizontalFieldOfView <= Math2.PI2);
checkArgument(maxDistance > 0);
checkArgument(width > 0 && height > 0);
this.centerAzimuth = centerAzimuth;
this.horizontalFieldOfView = horizontalFieldOfView;
this.verticalFieldOfView = horizontalFieldOfView * (height - 1)
/ (width - 1);
this.height = height;
this.maxDistance = maxDistance;
this.observerElevation = observerElevation;
this.observerPoint = observerPoint;
this.width = width;
this.dX = horizontalFieldOfView / (width - 1.0);
this.dY = verticalFieldOfView / (height - 1.0);
}
/**
* Converts an horizontal x value to an azimuth angle
*
* @param x
* : the horizontal index to convert
* @return azimuth value corresponding to the horizontal pixel
* @throws IllegalArgumentException
* if x is smaller than zero or larger or equal to the width
*/
public double azimuthForX(double x) {
checkArgument(x >= 0 && x <= width() - 1);
double startAngle = angularDistance(horizontalFieldOfView()/2.0,centerAzimuth());
//centerAzimuth - (horizontalFieldOfView / 2.0);
return canonicalize(startAngle +(x * dX));
}
/**
* Converts an azimuth angle to an horizontal x value
*
* @param a
* : the angle to convert
* @return horizontal index of the pixels corresponding to a given azimuth
* @throws IllegalArgumentException
* if the angle is outside the field of view
*/
public double xForAzimuth(double a) {
checkArgument(isInVisibleFieldX(a));
double startAngle = angularDistance(horizontalFieldOfView()/2.0,centerAzimuth());
return angularDistance(startAngle,a)/ dX;
}
/**
* Checks if the azimuth is within the field of view
*
* @param a
* : the angle to check
* @return true if an azimuth is within the current field of view
*/
private boolean isInVisibleFieldX(double a) {
return abs(angularDistance(centerAzimuth(),a)) <= horizontalFieldOfView() / 2.0;
}
/**
* Converts a vertical y value to a slope (angle)
*
* @param y
* : the vertical index to convert
* @return slope value corresponding to the vertical pixel
* @throws IllegalArgumentException
* if y is smaller than zero or larger or equal to the width
*/
public double altitudeForY(double y) {
checkArgument(y >= 0 && y <= height() - 1);
double startAngle = verticalFieldOfView() / 2.0;
return angularDistance(y*dY,startAngle);
}
/**
* Converts a slope (angle) to an vertical y value
*
* @param a
* : the angle to convert
* @return vertical index of the pixels corresponding to a given slope
* @throws IllegalArgumentException
* if the angle is outside the field of view
*/
public double yForAltitude(double a) {
checkArgument(isInVisibleFieldY(a));
double startAngle = verticalFieldOfView() / 2.0;
return angularDistance(a,startAngle) / dY;
}
/**
* Checks if a vertical angle is within the field of view
*
* @param a
* : angle to check
* @return true if a vertical angle is within the current field of view
*/
private boolean isInVisibleFieldY(double a) {
return abs(a) <= verticalFieldOfView() / 2.0;
}
/**
* Checks if a 2D index is contained in the image
*
* @param x
* : horizontal index
* @param y
* : vertical index
* @return true, if: 0<=x<height and 0<=y<width
*/
protected boolean isValidSampleIndex(int x, int y) {
return (x < width() && y < height() && x >= 0 && y >= 0);
}
/**
* Converts a 2D index in a 1D index
*
* @param x
* : horizontal index
* @param y
* : vertical index
* @return the 1D index value
*/
protected int linearSampleIndex(int x, int y) {
return (y * width() + x);
}
/**
* Returns the position of the viewer
*
* @return the viewer GeoPoint
*/
public GeoPoint observerPosition() {
return observerPoint;
}
/**
* Returns the altitude of the viewer
*
* @return the viewer altitude as an Integer
*/
public int observerElevation() {
return observerElevation;
}
/**
* Returns the direction the viewer is facing
*
* @return the azimuth the viewer is facing
*/
public double centerAzimuth() {
return centerAzimuth;
}
/**
* horizontal field of view angle
*
* @return horizontal field of view
*/
public double horizontalFieldOfView() {
return horizontalFieldOfView;
}
/**
* vertical field of view angle
*
* @return vertical field of view
*/
public double verticalFieldOfView() {
return verticalFieldOfView;
}
/**
* Returns the maximal render distance
*
* @return the maximal distance
*/
public int maxDistance() {
return maxDistance;
}
/**
* Returns the width of the view
*
* @return the width of the view
*/
public int width() {
return width;
}
/**
* Returns the height of the view
*
* @return the height of the view
*/
public int height() {
return height;
}
}

View File

@ -0,0 +1,112 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano;
/**
* Interface commonly used as argument check for exception throwing
*
* @see IllegalArgumentExcepetion
* @see NullPointerException
* @see IndexOutOfBoundsException
*/
public interface Preconditions {
/**
* Throws an error with a message based on a boolean
*
* @param b
* : boolean
* @param message
* : string
* @throws IllegalArgumentException
* with message if the boolean is false
*/
public static void checkArgument(boolean b, String message) {
if (!b)
throw (new IllegalArgumentException(message));
}
/**
* Throws an error based on if a boolean is false
*
* @param b
* : boolean
* @throws IllegalArgumentException
* if the boolean is false
*/
public static void checkArgument(boolean b) {
if (!b)
throw (new IllegalArgumentException());
}
/**
* Throws an error with a message if an double is equal to 0. Works with an
* Integer too
*
* @param i
* : double
* @param message
* : string
* @throws IllegalArgumentException
* with message if the double is equal to 0
*/
public static void checkNonNul(double i, String message) {
if (i == 0.)
throw (new NullPointerException(message));
}
/**
* Throws an error based on if an double is equal to 0. Works with an
* Integer too
*
* @param i
* : double
* @throws IllegalArgumentException
* if the d is equal to 0
*/
public static void checkNonNul(double i) {
if (i == 0.)
throw (new NullPointerException());
}
/**
* Throws an error with a message based on if an Integer is out of bounds
*
* @param val
* : the Integer to check
* @param upper
* : upper bound
* @param lower
* : lower bound
* @param message
* : string
* @throws IndexOutOfBoundsException
* with a message if the value is out of bounds
*/
public static void checkBounds(boolean b,
String message) {
if (!b)
throw (new IndexOutOfBoundsException());
}
/**
* Throws an error based on if an Integer is out of bounds
*
* @param val
* : the Integer to check
* @param upper
* : upper bound
* @param lower
* : lower bound
* @throws IndexOutOfBoundsException
* if the value is out of bounds
*/
public static void checkBounds(boolean b) {
if (!b)
throw (new IndexOutOfBoundsException());
}
}

View File

@ -0,0 +1,141 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.dem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import ch.epfl.alpano.GeoPoint;
/**
* Class used to build composite Elevation profiles
* @see ElevationProfile
*
*/
public class CompositElevationProfile implements Profile {
private List<ElevationProfile> profiles;
private final GeoPoint origin;
/**
* Constructor
* @param c : elevations profiles
*/
public CompositElevationProfile(
ElevationProfile... c) {
this.origin = c[0].origin();
profiles = Arrays.asList(c);
}
/**
* Constructor
* @param c List of ElevationProfiles
*/
public CompositElevationProfile(
List<ElevationProfile> c) {
this.origin = c.get(0).origin();
profiles = c;
}
/**
* Constructor
* @param cDem : Continuous elevation model
* @param trekkingSpots
*/
public CompositElevationProfile(ContinuousElevationModel cDem, List<GeoPoint> trekkingSpots){
this.profiles = new ArrayList<ElevationProfile>();
this.origin = trekkingSpots.get(0);
for (int index = 0; index < trekkingSpots.size()-1; index++){
GeoPoint current= trekkingSpots.get(index);
GeoPoint next = trekkingSpots.get(index+1);
if(!current.equals(next) &&current.distanceTo(next)!=0){
ElevationProfile evP= new ElevationProfile(cDem, current,current.azimuthTo(next), current.distanceTo(next));
this.profiles.add(evP);
}
}
}
/**
* Returns the origin
* @return origin geopoint
*/
public GeoPoint origin() {
return this.origin;
}
/**
* Returns the length
*/
public double length() {
double s = 0;
for (ElevationProfile evP : this.profiles) {
s += evP.length();
}
return s;
}
@Override
public double elevationAt(double x) {
for (ElevationProfile evP : profiles) {
if (x - evP.length() < 0) {
return evP.elevationAt(x);
} else {
x -= evP.length();
}
}
return 0.0;
}
@Override
public double[] position(double x) {
for (ElevationProfile evP : profiles){
if (x-evP.length() < 0){
return evP.position(x);
}else {x-=evP.length();}
}
return null;
}
@Override
public double slopeAt(double x) {
for (ElevationProfile evP : profiles) {
if (x - evP.length() < 0) {
return evP.slopeAt(x);
} else {
x -= evP.length();
}
}
return 0.0;
}
@Override
public GeoPoint positionAt(double x) {
for (ElevationProfile evP : profiles) {
if (x - evP.length() < 0) {
return evP.positionAt(x);
} else {
x -= evP.length();
}
}
throw new IllegalArgumentException();
}
}

View File

@ -0,0 +1,72 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.dem;
import static ch.epfl.alpano.Preconditions.checkArgument;
import static ch.epfl.alpano.Preconditions.checkNonNul;
import ch.epfl.alpano.Interval2D;
/**
* Class taking multiple DEMs and merging them into one Composite DEM
*
* @see DiscreteElevationModel
*/
final class CompositeDiscreteElevationModel implements DiscreteElevationModel {
private final DiscreteElevationModel dem1, dem2;
private final Interval2D extent;
/**
* Constructor of a composite discrete elevation model, by uniting two DEMs
*
* @param dem1
* : the first DEM
* @param dem2
* : the second DEM
* @throws NullPointerException
* if one of the two DEMs has size 0
*/
CompositeDiscreteElevationModel(DiscreteElevationModel dem1,
DiscreteElevationModel dem2) {
checkNonNul(dem1.extent().size());
checkNonNul(dem2.extent().size());
this.dem1 = dem1;
this.dem2 = dem2;
this.extent = dem1.extent().union(dem2.extent());
}
@Override
public void close() throws Exception {
dem1.close();
dem2.close();
}
@Override
public Interval2D extent() {
return extent;
}
/**
* Returns the elevation of a point in the containing DEM
*
* @param x:
* x coordinate
* @param y:
* y coordinate
* @return the elevation of the point.
* @throws IllegalArgumentException
* if the point is not contained in the composite DEM
*/
@Override
public double elevationSample(int x, int y) {
checkArgument(this.extent.contains(x, y));
if (dem1.extent().contains(x, y))
return dem1.elevationSample(x, y);
else
return dem2.elevationSample(x, y);
}
}

View File

@ -0,0 +1,125 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.dem;
import ch.epfl.alpano.GeoPoint;
import static ch.epfl.alpano.Preconditions.checkNonNul;
import static ch.epfl.alpano.Distance.EARTH_RADIUS;
import static ch.epfl.alpano.Math2.bilerp;
import static ch.epfl.alpano.Math2.sq;
import static ch.epfl.alpano.dem.DiscreteElevationModel.sampleIndex;
import static ch.epfl.alpano.dem.DiscreteElevationModel.SAMPLES_PER_RADIAN;
/**
* An elevation model defined for every coordinates of real numbers built from a
* discontinuous elevation model using bi-dimentional interpolation on known
* elevation values
*
* @see DiscreteElevationModel
*/
public final class ContinuousElevationModel {
private DiscreteElevationModel dem;
private static final int SAMPLE_DISTANCE = 1;
/**
* Constructor, builds a Continuous elevation model
*
* @param dem:
* a discrete elevation model
* @throws NullPointerException
* if the dem's size equals zero
*/
public ContinuousElevationModel(DiscreteElevationModel dem) {
checkNonNul(dem.extent().size());
this.dem = dem;
}
/**
* Returns the elevation at a given point using bilinear interpolation
*
* @param p
* : the GeoPoint from which we want to know the elevation
* @return the elevation at the point as a double
* @see bilerp(double z00, double z10, double z01, double z11, double x,
* double y)
*/
public double elevationAt(GeoPoint p) {
double x = sampleIndex(p.longitude());
double y = sampleIndex(p.latitude());
int x1 = (int) Math.floor(x);
int y1 = (int) Math.floor(y);
return bilerp(getElevationAt(x1, y1),
getElevationAt(x1 + SAMPLE_DISTANCE, y1),
getElevationAt(x1, y1 + SAMPLE_DISTANCE),
getElevationAt(x1 + SAMPLE_DISTANCE, y1 + SAMPLE_DISTANCE),
x - x1, y - y1);
}
/**
* Returns the elevation at a given point (x,y)
*
* @param x
* : the x coordinate of the point
* @param y
* : the y coordinate of the point
* @return the elevation at the given coordinates or 0 if the point is out
* of range
* @see elevationSample(int x, int y)
*/
private double getElevationAt(int x, int y) {
return (dem.extent().contains(x, y)) ? dem.elevationSample(x, y) : 0;
/*
* if(dem.extent().contains(x, y)){ return dem.elevationSample(x, y); }
* return 0;
*/
}
/**
* Returns the angular elevation at a given point (x,y)
*
* @param x
* : the x coordinate of the point
* @param y
* : the y coordinate of the point
* @return the slope at the point as a double
* @see getElevationAt(int x , int y)
* @see slopeAt(GeoPoint p)
*/
private double theta(int x, int y) {
double zA2 = sq(
getElevationAt(x + SAMPLE_DISTANCE, y) - getElevationAt(x, y));
double zB2 = sq(
getElevationAt(x, y + SAMPLE_DISTANCE) - getElevationAt(x, y));
double d = EARTH_RADIUS / SAMPLES_PER_RADIAN;
return Math.acos(d / Math.sqrt(zA2 + zB2 + sq(d)));
}
/**
* Returns the slope at a given point using bilinear interpolation
*
* @param p
* : the GeoPoint from which we want to know the slope
* @return the slope at the point as a double
* @see bilerp(double z00, double z10, double z01, double z11, double x,
* double y)
*/
public double slopeAt(GeoPoint p) {
double x = sampleIndex(p.longitude());
double y = sampleIndex(p.latitude());
int x1 = (int) Math.floor(x);
int y1 = (int) Math.floor(y);
return bilerp(theta(x1, y1), theta(x1 + SAMPLE_DISTANCE, y1),
theta(x1, y1 + SAMPLE_DISTANCE),
theta(x1 + SAMPLE_DISTANCE, y1 + SAMPLE_DISTANCE), x - x1,
y - y1);
}
}

View File

@ -0,0 +1,72 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.dem;
import ch.epfl.alpano.Interval2D;
import static ch.epfl.alpano.Preconditions.checkArgument;
import static java.lang.Math.PI;
/**
* Interface of a DEM
*/
public interface DiscreteElevationModel extends AutoCloseable {
/**
* Amount of elevation samples per degree
*/
public static final int SAMPLES_PER_DEGREE = 3600;
/**
* Amount of elevation samples per radian
*/
public static final double SAMPLES_PER_RADIAN = (180.0 / PI)
* SAMPLES_PER_DEGREE;
/**
* Returns the amount of samples for a given angle
*
* @param angle
* : an angle in radian
* @return the amount of samples for the angle
* @see SAMPLE_PER_RADIAN
*/
public static double sampleIndex(double angle) {
return angle * SAMPLES_PER_RADIAN;
}
/**
* Abstract function that returns the 2D interval of a DEM
*
* @return the 2D interval of the DEM
*/
public Interval2D extent();
/**
* Abstract function that returns the elevation at a given point (x,y) of a
* DEM
*
* @param x
* : the x coordinate of the point
* @param y
* : the y coordinate of the point
* @return the elevation at the point as a double
*/
public double elevationSample(int x, int y);
/**
* Creates and returns a new composite DEM containing two DEMs
*
* @param that
* : one of the two DEMs
* @return a new composite DEM obtained with the union of two DEMs
* @throws IllegalArgumentException
* if the two DEMs can not form a union
* @see isUnionableWith(Interval2D that)
*/
default CompositeDiscreteElevationModel union(DiscreteElevationModel that) {
checkArgument(this.extent().isUnionableWith(that.extent()));
return new CompositeDiscreteElevationModel(this, that);
}
}

View File

@ -0,0 +1,154 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.dem;
import ch.epfl.alpano.GeoPoint;
import static ch.epfl.alpano.Math2.PI2;
import static ch.epfl.alpano.Distance.toRadians;
import static ch.epfl.alpano.Math2.lerp;
import static ch.epfl.alpano.Math2.floorMod;
import static java.util.Objects.requireNonNull;
import static ch.epfl.alpano.Preconditions.checkArgument;
import static ch.epfl.alpano.Azimuth.isCanonical;
/**
* Class Computing an elevation profile
*
* @see ContinuousElevationModel
* @see GeoPoint
*/
public final class ElevationProfile implements Profile{
private final ContinuousElevationModel elevationModel;
private final GeoPoint origin;
private final double azimuth;
private final double[][] discretePositions;
private final double length;
private static final double MSTEP = 4096.0; // Amount of steps to do
/**
* Constructor of an Elevation profile, computes all the points at the
* beginning and saves them in a file
*
* @param elevationModel
* : a ContinuousElevationModel that the ElevationProfile will
* use to build itself
* @param origin
* : origin GeoPoint
* @param azimuth
* : the azimuth angle to use
* @param length
* : the distance up to which the profile shall be computed
* @throws IllegalArgumentException
* : if length is negative of azimuth is not canonical
*/
public ElevationProfile(ContinuousElevationModel elevationModel,
GeoPoint origin, double azimuth, double length) {
checkArgument(length > 0);
checkArgument(isCanonical(azimuth));
requireNonNull(elevationModel);
requireNonNull(origin);
this.azimuth = azimuth;
this.origin = origin;
this.elevationModel = elevationModel;
this.length = length;
discretePositions = new double[(int) (length / MSTEP) + 2][2];
for (int i = 0; i < discretePositions.length; i++) {
discretePositions[i] = position(i * MSTEP);
}
}
public double length(){
return this.length;
}
public GeoPoint origin(){
return this.origin;
}
/**
* Gets the elevation at a distance x
*
* @param x
* @return elevation at the point of a distance x
*/
public double elevationAt(double x) {
checkArgument(x <= length && x >= 0);
return elevationModel.elevationAt(positionAt(x));
}
/**
* Computes the GeoPoint at a distance x
*
* @param x
* @return returns the position as a GeoPoint at a distance x
*/
public GeoPoint positionAt(double x) {
checkArgument(x <= length && x >= 0);
int offSet = (int) x / (int) MSTEP;
double lambda = lerp(discretePositions[offSet][0],
discretePositions[offSet + 1][0], x / MSTEP - offSet);
double phi = lerp(discretePositions[offSet][1],
discretePositions[offSet + 1][1], x / MSTEP - offSet);
return new GeoPoint(lambda, phi);
}
/**
* Rounds the value x depending on the value of mStep
*
* @param x
* @param mStep
* @return relative rounded x value
*/
public double relativeRound(double x, double mStep) {
return Math.round(x / mStep) * mStep;
}
/**
* Returns the position of a point along the azimuth
*
* @param x
* @return coordinates of a point at a distance x of the origin
*/
public double[] position(double x) {
double sinphi0 = Math.sin(origin.latitude());
double cosphi0 = Math.cos(origin.latitude());
double sinx = Math.sin(toRadians(x));
double cosx = Math.cos(toRadians(x));
double sina = Math.sin(PI2 - azimuth);
double cosa = Math.cos(PI2 - azimuth);
double phi = Math.asin(sinphi0 * cosx + cosphi0 * sinx * cosa);
double a = origin.longitude() - Math.asin(sina * sinx / Math.cos(phi))
+ Math.PI;
double lambda = floorMod(a, PI2) - Math.PI;
return new double[] { lambda, phi };
}
/**
* Gets the slope at the distance x
*
* @param x
* @return the slope at the distance x
*/
public double slopeAt(double x) {
checkArgument(x <= length && x >= 0);
return elevationModel.slopeAt(positionAt(x));
}
public double effort(){
double de;
double ef=length;
for(int i =0; i<length-1;++i){
de = elevationAt(i+1)-elevationAt(i);
ef+= 9.81*Math.abs(de);
}
return ef;
}
}

View File

@ -0,0 +1,114 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.dem;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel.MapMode;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ch.epfl.alpano.Interval1D;
import ch.epfl.alpano.Interval2D;
import static ch.epfl.alpano.Preconditions.checkArgument;
/**
* Class building a DEM using a HGT file
*
* @see DiscreteElevationModel
*/
public class HgtDiscreteElevationModel implements DiscreteElevationModel {
public static final long HGT_SAMPLES = 12967201;
private ShortBuffer b;
private FileInputStream s;
private Interval2D extent;
private static final Pattern P = Pattern
.compile("([NS])(\\d{2})([EW])(\\d{3})\\.hgt");
/**
* Constructor for a HGT DEM
*
* @param file
* : The HGT file
* @throws IllegalArgumentException
* if the file is not valid in length
* @see DiscreteElevationModel
*/
public HgtDiscreteElevationModel(File file) {
String name = file.getName();
Matcher m = checkFileName(name);
checkArgument(file.length() == HGT_SAMPLES * 2);
int latMult = (m.group(1).charAt(0) == 'N' ? 1 : -1)
* SAMPLES_PER_DEGREE;
int lat = Integer.parseInt(m.group(2));
int lonMult = (m.group(3).charAt(0) == 'E' ? 1 : -1)
* SAMPLES_PER_DEGREE;
int lon = Integer.parseInt(m.group(4));
Interval1D ex1 = new Interval1D((lon * lonMult),
(lon * lonMult + SAMPLES_PER_DEGREE));
Interval1D ex2 = new Interval1D((lat * latMult),
(lat * latMult + SAMPLES_PER_DEGREE));
extent = new Interval2D(ex1, ex2);
try {
s = new FileInputStream(file);
b = s.getChannel().map(MapMode.READ_ONLY, 0, file.length())
.asShortBuffer();
s.getChannel().close();
s.close();
} catch (IOException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
@Override
public void close() {
}
@Override
public Interval2D extent() {
return extent;
}
/**
* Returns the elevation of a point using the data from a HGT file
*
* @param x:
* x coordinate
* @param y:
* y coordinate
* @return the height of the point in the HGT file at the given coordinates
*/
@Override
public double elevationSample(int x, int y) {
checkArgument(this.extent().contains(x, y));
return b.get((x - extent.iX().includedFrom())
+ (extent.iY().includedTo() - y) * (60 * 60 + 1));
}
/**
* Checks a String for a HGT file name using regex.
*
* @param txt
* : the file name to check
* @returns the Matcher m (String with regex applied)
* @throws IllegalArgumentException
* if the file name is not valid
*/
private static final Matcher checkFileName(String txt) {
Matcher m = P.matcher(txt);
checkArgument((txt.length() == 11 && m.find()));
int lat = Integer.parseInt(m.group(2));
int lon = Integer.parseInt(m.group(4));
checkArgument(!(lat > 90 || lon > 180));
return m;
}
}

View File

@ -0,0 +1,23 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.dem;
import ch.epfl.alpano.GeoPoint;
/**
* Interface describing an elevation profile
*
*/
public interface Profile {
public double length();
public double elevationAt(double x);
public GeoPoint positionAt(double x);
public double[] position(double x);
public double slopeAt(double x);
}

View File

@ -0,0 +1,736 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import static ch.epfl.alpano.Math2.sq;
import static ch.epfl.alpano.PanoramaComputer.NATURAL_VARIATION;
import static ch.epfl.alpano.summit.GazetteerParser.readSummitsFrom;
import static java.awt.Desktop.getDesktop;
import static java.lang.Math.abs;
import static java.lang.Math.atan;
import static java.lang.Math.toDegrees;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.imageio.ImageIO;
import ch.epfl.alpano.Azimuth;
import ch.epfl.alpano.GeoPoint;
import ch.epfl.alpano.dem.CompositElevationProfile;
import ch.epfl.alpano.summit.Summit;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.StringConverter;
/**
* Main class used for GUI Controls the program through events
*/
public class Alpano extends Application {
private static final String ALPANO_WINDOW_TITLE = "Alpano";
private static final PanoramaUserParameters DEFAULT_PARAMS = PredefinedPanoramas.JURA_ALPS.get();
private static final String SUMMIT_FILENAME = "alps.txt";
private static final List<Summit> SUMMIT_LIST;
private static final PanoramaComputerBean pcbean;
private static final PanoramaParametersBean ppbean;
private static final MapViewBean mvbean;
private static final CursorInfo cursor = new CursorInfo();
static final String REPAINT_NOTICE_TEXT = "CLICK HERE TO REPAINT PANORAMA";
static final String UPDATE_NOTICE_TEXT = "CLICK HERE TO UPDATE PANORAMA";
static {
List<Summit> slist;
try {
slist = readSummitsFrom(new File(SUMMIT_FILENAME));
} catch (IOException e) {
slist = Collections.emptyList();
// No summits found in file, empty list
}
SUMMIT_LIST = slist;
pcbean = new PanoramaComputerBean(SUMMIT_LIST);
ppbean = new PanoramaParametersBean(DEFAULT_PARAMS);
mvbean = new MapViewBean(pcbean);
}
/**
* Main Fuction ran at the start of the program
* @param args : program arguments
*/
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
root.setCenter(makePanoPane());
root.setBottom(makeParamsGrid());
root.setTop(makeMenu());
Scene scene = new Scene(root, 1100, 700);
primaryStage.setTitle(ALPANO_WINDOW_TITLE);
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setOnHidden(e -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
} finally {
Platform.exit();
}
});
}
/**
* Builds a stack pane containing the panogroup and all the notices
* @return a stack pane for the panorama group and notices
*/
private Node makePanoPane() {
ScrollPane panoScrollPane = new ScrollPane(makePanoGroup());
return new StackPane(panoScrollPane, makePainterNotice(),
makeDownloadNotice(), makeUpdateNotice());
}
/**
* Builds a grid of nodes and binds the input fields to the computable
* variables in PanoramaParameterBean
*
* @return node grid displaying the parameters
*/
private Node makeParamsGrid() {
GridPane grid = new GridPane();
Map<UserParameter, Label> labels = new EnumMap<UserParameter, Label>(
UserParameter.class);
Map<UserParameter, Node> fields = new EnumMap<UserParameter, Node>(
UserParameter.class);
int nbParams = 0;
for (UserParameter parameter : UserParameter.values()) {
Label label = new Label(parameter.label());
labels.put(parameter, label);
GridPane.setHalignment(label, HPos.RIGHT);
fields.put(parameter, makeField(parameter));
nbParams++;
if (nbParams % 3 == 0) {
int nbRow = nbParams / 3 - 1;
grid.addRow(nbRow,
labels.get(UserParameter.values()[3 * nbRow]),
fields.get(UserParameter.values()[3 * nbRow]),
labels.get(UserParameter.values()[3 * nbRow + 1]),
fields.get(UserParameter.values()[3 * nbRow + 1]),
labels.get(UserParameter.values()[3 * nbRow + 2]),
fields.get(UserParameter.values()[3 * nbRow + 2]));
}
}
grid.addColumn(6, makeCursorInfo());
ColumnConstraints colL = new ColumnConstraints();
colL.setPrefWidth(150);
ColumnConstraints colT = new ColumnConstraints();
colT.setPrefWidth(80);
ColumnConstraints colA = new ColumnConstraints();
colA.setPrefWidth(300);
grid.getColumnConstraints().setAll(colL, colT, colL, colT, colL, colT,
colA);
return grid;
}
/**
* Makes a field for the params grid
* @param parameter : parameter of the field
* @return the field
*/
private static Node makeField(UserParameter parameter) {
if (!parameter.equals(UserParameter.SUPER_SAMPLING_EXPONENT)) {
return makeTextField(parameter);
} else {
return makeSuperSamplingField(parameter);
}
}
/**
* Makes a text field for the param
* @param parameter : parameter to use
* @return a text field
*/
private static Node makeTextField(UserParameter parameter) {
TextField field = new TextField();
StringConverter<Integer> stringConverter = new FixedPointStringConverter(
parameter.scale());
TextFormatter<Integer> formatter = new TextFormatter<>(stringConverter);
field.setTextFormatter(formatter);
field.setAlignment(Pos.CENTER_RIGHT);
formatter.valueProperty()
.bindBidirectional(ppbean.getProperty(parameter));
return field;
}
/**
* Makes a Choice box for the supersampling parameter
* @param parameter : parameter
* @return a choice box field for the supersampling parameter
*/
private static Node makeSuperSamplingField(UserParameter parameter) {
ChoiceBox<Integer> field = new ChoiceBox<Integer>(
FXCollections.observableArrayList(0, 1, 2));
field.setConverter(new LabeledListStringConverter("none", "2x", "4x"));
field.valueProperty().bindBidirectional(ppbean.getProperty(parameter));
return field;
}
/**
* Makes a cursor info text area
* @return a text area holding the cursor info
*/
private static Node makeCursorInfo() {
TextArea cursorInfo = new TextArea();
cursorInfo.setWrapText(true);
cursorInfo.textProperty().bind(cursor.textProperty());
cursorInfo.setMinWidth(100);
cursorInfo.setPrefRowCount(2);
cursorInfo.setEditable(false);
GridPane.setMargin(cursorInfo, new Insets(0, 0, 0, 20));
GridPane.setRowSpan(cursorInfo, 3);
return cursorInfo;
}
/**
* Makes a pane holding the update notice
* @return a pane when clicked updates the pcbean
*/
private static Node makeUpdateNotice() {
Label updateText = new Label(UPDATE_NOTICE_TEXT);
StackPane updateNotice = new StackPane(updateText);
updateNotice.setOpacity(0.9);
updateNotice.setStyle("-fx-background-color: #FFFFFF;");
updateNotice.visibleProperty().bind(ppbean.ParametersProperty()
.isNotEqualTo(pcbean.parametersProperty()));
updateNotice.setOnMouseClicked((MouseEvent event) -> {
pcbean.setPainter(ppbean.painterProperty().get());
pcbean.setParameters(ppbean.ParametersProperty().get());
});
return updateNotice;
}
/**
* Makes a pane holding the repaint notice
* @return a pane when clicked repaints the panorama
*/
private static Node makePainterNotice() {
Label updateText = new Label(REPAINT_NOTICE_TEXT);
StackPane updateNotice = new StackPane(updateText);
updateNotice.setOpacity(0.9);
updateNotice.setStyle("-fx-background-color: #FFFFFF;");
updateNotice.visibleProperty()
.bind(ppbean.painterProperty()
.isNotEqualTo(pcbean.painterProperty())
.and(ppbean.ParametersProperty()
.isEqualTo(pcbean.parametersProperty())));
updateNotice.setOnMouseClicked((MouseEvent event) -> {
pcbean.setPainter(ppbean.painterProperty().get());
});
return updateNotice;
}
/**
* Makes a pane holding the download notice
* @return a pane dispkaying the MapzenManager download info
*/
private static Node makeDownloadNotice() {
Label dlText = new Label();
StackPane dlNotice = new StackPane(dlText);
dlNotice.setOpacity(0.9);
dlNotice.setStyle("-fx-background-color: #FFFFFF;");
dlNotice.visibleProperty().bind(pcbean.downloadProperty());
dlText.textProperty().bind(pcbean.downloadTextProperty());
dlText.setAlignment(Pos.CENTER);
return dlNotice;
}
/**
* Makes a ImageView holding the image of the panorama
* @return an ImageView with bound to the panorama image
*/
private static Node makePanoView() {
ImageView panoView = new ImageView();
panoView.imageProperty().bind(pcbean.imageProperty());
panoView.fitWidthProperty().bind(ppbean.WidthProperty());
panoView.setPreserveRatio(true);
panoView.setSmooth(true);
return panoView;
}
/**
* Makes a pane holding the labels of the panorama
* @return a pane holding the labels
*/
private static Node makeLabelsPane() {
Pane labelsPane = new Pane();
labelsPane.prefWidthProperty().bind(ppbean.WidthProperty());
labelsPane.prefHeightProperty().bind(ppbean.HeightProperty());
Bindings.bindContent(labelsPane.getChildren(), pcbean.getLabels());
return labelsPane;
}
/**
* Makes an ImageView holding the map image
* @return ImageView holding the map image
*/
private static ImageView makeMapView() {
ImageView panoView = new ImageView();
panoView.imageProperty().bind(mvbean.mapView());
panoView.setFitWidth(720);
panoView.setPreserveRatio(true);
panoView.setSmooth(true);
return panoView;
}
/**
* Builds the trekking stage
* @param ps : stage
* @return the trekking stage
*/
private static Stage makeTrekkingStage(Stage ps) {
BorderPane p = new BorderPane();
///////////////////////////////// makeMapPan
///////////////////////////////// makeMapGroup
ImageView im = makeMapView();
Pane lines = new Pane();
lines.setPrefWidth(720);
lines.setPrefHeight(720);
StackPane panoGroup = new StackPane(im, lines);
Bindings.bindContent(lines.getChildren(), mvbean.tl().get());
panoGroup.setOnMouseClicked((MouseEvent e) -> {
switch(e.getClickCount()){
case 1:
mvbean.addTs(e.getX(),e.getY());
break;
case 2:
mvbean.zoom(e.getButton());
break;
}
});
/////////////////////////////////
Pane panoScrollPane = new Pane(panoGroup);
StackPane mapPane = new StackPane(panoScrollPane);
////////////////////////////////
Button compute = new Button("Compute");
compute.setOnAction(e -> makeElevationProfileStage().show());
Button clear = new Button("clear");
p.setCenter(mapPane);
Button save = new Button("Save");
GridPane grid = new GridPane();
grid.add(compute, 0, 0);
grid.add(clear, (int) compute.getScaleX(), 0);
grid.add(save, 50, 0);
Button path = new Button("path");
path.setOnAction(e -> mvbean.buildEndToEndPath());
grid.add(path, 100, 0);
p.setBottom(grid);
Stage s = new Stage();
Scene scene = new Scene(p);
s.setScene(scene);
clear.setOnAction(e -> mvbean.clearTs());
return s;
}
/**
* Builds a view of the composite elevation profile
* @return a ImageView holding the elevation profile
*/
private static Node makeElevationView() {
ImageView elevationView = new ImageView();
ArrayList<GeoPoint> pts = new ArrayList<GeoPoint>();
pts.addAll(mvbean.cts().get());
pts.addAll(mvbean.ts().get());
elevationView.imageProperty()
.set(ElevationProfileRenderer.renderElevation(
new CompositElevationProfile(pcbean.cDem().get(),pts)));
// MapViewRenderer.trekkingProfile(pcbean.cDem())));
//
elevationView.setFitWidth(700);
elevationView.setPreserveRatio(true);
elevationView.setSmooth(true);
return elevationView;
}
/**
* Builds a label holding the elevation profile max height data
* @return a label with the max height
*/
private static Node makeElevationProfileData() {
Label t = new Label();
Long d = Math.round(ElevationProfileRenderer.currentMaxHeight.get());
t.setText(d.toString() + " m");
return t;
}
/**
* Builds a pane holding the bottom information for the elevation profile
* @return a pane for the bottom of the elevation profile
*/
private static Node makeElevationBottomPane() {
Text t0 = new Text(-100, 0, "0");
int d = (int)Math.round(
ElevationProfileRenderer.currentElevationProfileLength.get());
GridPane pp = new GridPane();
StackPane p = new StackPane();
p.setPrefWidth(ElevationProfileRenderer.WIDTH);
// p.setPrefHeight(ElevationProfileRenderer.HEIGHT);
Text t1 = new Text(p.widthProperty().get() - 20, 0,
new FixedPointStringConverter(3).toString(d)+ " km");
pp.add(t0, 0, 0);
pp.add(t1, 1, 0);
ColumnConstraints colN = new ColumnConstraints();
colN.setMinWidth(700);
ColumnConstraints colL = new ColumnConstraints();
colL.setPrefWidth(700);
pp.getColumnConstraints().setAll(colN, colL);
p.getChildren().add(pp);
return p;
}
/**
* Builds a pane holding the elevation profile group
* @return a stack pane with the elevation group
*/
private static Node makeElevationPane() {
Pane scrollPane = new Pane(makeElevationGroup());
return new StackPane(scrollPane);
};
/**
* Builds a window holding the elevation profile stuff
* @return a stage for the elevation profile
*/
private static Stage makeElevationProfileStage() {
BorderPane p = new BorderPane();
p.setCenter(makeElevationPane());
p.setLeft(makeElevationProfileData());
p.setBottom(makeElevationBottomPane());
Stage s = new Stage();
Scene scene = new Scene(p);
s.setScene(scene);
return s;
}
/**
* Builds a pane holding the elevation profile group
* @return a stack pane for the elevation profile
*/
private static Node makeElevationGroup() {
StackPane panoGroup = new StackPane(makeElevationView());
return panoGroup;
}
/**
* Builds a pane holding the panorama group
* @return a stack pane for the panorama group
*/
private static Node makePanoGroup() {
StackPane panoGroup = new StackPane(makePanoView(), makeLabelsPane());
panoGroup.setOnMouseMoved((MouseEvent e) -> {
int x = (int) e.getX();
int y = (int) e.getY();
cursor.update(x, y);
});
panoGroup.setOnMouseClicked((MouseEvent e) -> {
String qy = String.format((Locale) null, "mlat=%.4f&mlon=%.4f",
toDegrees(cursor.lat()), toDegrees(cursor.lon()));// ;
String fg = String.format((Locale) null, "map=15/%.4f/%.4f",
toDegrees(cursor.lat()), toDegrees(cursor.lon()));
URI osmURI;
try {
osmURI = new URI("http", "www.openstreetmap.org", "/", qy, fg);
getDesktop().browse(osmURI);
} catch (IOException e1) {
// Do nothing
} catch (URISyntaxException e1) {
// Do Nothing
}
});
return panoGroup;
}
/**
* Menu Button used for the trecking profile
* @return a button opening the mapview window
*/
private static Menu trekkingProfile() {
Label settingsL = new Label("Trekking Profile");
settingsL.setOnMouseClicked(e -> {
makeTrekkingStage(null).show();
});
Menu settings = new Menu();
settings.setGraphic(settingsL);
settings.setDisable(true);
pcbean.cDem().addListener(e -> settings.setDisable(false));
return settings;
}
/**
* Builds the menu top bar
* @return the menu bar
*/
private static MenuBar makeMenu() {
MenuBar menu = new MenuBar();
menu.getMenus().addAll(saveMenu(), predefinedPanoramaMenu(),
painterMenu(), trekkingProfile(), quitMenu());
return menu;
}
/**
* Button that saves the current panorama without labels
* @return a button that saves the panorama
*/
private static Menu saveMenu() {
Label saveL = new Label("Save");
saveL.setOnMouseClicked(e -> {
if (pcbean.getImage() != null) {
String name = Integer
.toString(pcbean.getParameters().hashCode()) + ".png";
try {
ImageIO.write(
SwingFXUtils.fromFXImage(pcbean.getImage(), null),
"png", new File(name));
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
Menu save = new Menu();
save.setGraphic(saveL);
return save;
}
/**
* Menu holding all the predefined Panoramas
* @return a menu with all predefined panoramas
*/
private static Menu predefinedPanoramaMenu() {
Menu ppMenu = new Menu("Predefined Panorama");
for (PredefinedPanoramas pp : PredefinedPanoramas.values()) {
MenuItem ppItem = new MenuItem(pp.toString());
ppItem.setOnAction(e -> {
ppbean.updateProperties(pp.get());
});
ppMenu.getItems().add(ppItem);
}
return ppMenu;
}
/**
* Menu holding all custom painters
* @return a menu with all custom painters
*/
private static Menu painterMenu() {
Menu cpMenu = new Menu("Painter");
Menu gradItem = new Menu("Gradient Painter");
cpMenu.getItems().add(gradItem);
ToggleGroup toggleGroup = new ToggleGroup();
for (CustomPainters cp : CustomPainters.values()) {
RadioMenuItem cpItem = new RadioMenuItem(cp.displayText());
cpItem.setToggleGroup(toggleGroup);
cpItem.setOnAction(e -> {
ppbean.setPainter(cp);
});
if (pcbean.painterProperty().equals(cp))
cpItem.setSelected(true);
if (cp.isGradient()) {
Canvas cv = new Canvas(20, 20);
GraphicsContext gc = cv.getGraphicsContext2D();
gc.setFill(cp.gradient().get());
gc.fillRect(0, 0, 20, 20);
cv.prefHeight(20);
cv.setRotate(180);
cpItem.setGraphic(cv);
gradItem.getItems().add(cpItem);
} else {
cpMenu.getItems().add(cpItem);
}
}
return cpMenu;
}
/**
* Quit button that closes the program
* @return a quit button
*/
private static Menu quitMenu() {
Label quitL = new Label("Quit");
quitL.setOnMouseClicked(e -> Platform.exit());
Menu quit = new Menu();
quit.setGraphic(quitL);
return quit;
}
/**
* Class used to manage the cursor on the panorama pane
*/
private static final class CursorInfo {
private ObjectProperty<String> textProperty = new SimpleObjectProperty<String>();
private double lon, lat, dist = Double.POSITIVE_INFINITY;
/**
* Updates the x,y position of the cursor
* @param x
* @param y
*/
public void update(int x, int y) {
int ss = pcbean.getParameters().superSamplingExponent();
x *= Math.pow(2, ss);
y *= Math.pow(2, ss);
if (isValid(x, y)) {
lat = pcbean.getPanorama().latitudeAt(x, y);
lon = pcbean.getPanorama().longitudeAt(x, y);
dist = pcbean.getPanorama().distanceAt(x, y);
double alt = pcbean.getPanorama().elevationAt(x, y);
GeoPoint point = new GeoPoint(lon, lat);
double azi = pcbean.getParameters().panoramaDisplayParameters()
.observerPosition().azimuthTo(point);
double slope = (alt
- pcbean.getParameters().panoramaDisplayParameters()
.observerElevation()
- (sq(dist) * NATURAL_VARIATION)) / dist;
double ele = toDegrees(atan(slope));
String slat = lat < 0 ? "S" : "N";
String slon = lon < 0 ? "W" : "E";
textProperty.set(String.format((Locale) null,
"Position : %.4f°%s %.4f°%s \nDistance : %.1f km \nAltitude : %d m \nAzimut : %.1f° (%s) Elévation : %.1f°",
abs(toDegrees(lat)), slat, abs(toDegrees(lon)), slon,
(dist / 1_000.0), (int) alt, toDegrees(azi),
Azimuth.toOctantString(azi, "N", "S", "E", "W"), ele));
}
}
/**
*
* @return the textProperty object to display the cursor information
*/
public ObjectProperty<String> textProperty() {
return textProperty;
}
/**
* Returns the longitude at the cursors position
*
* @return longitude at cursors last valid position
*/
public double lon() {
return lon;
}
/**
* Returns the latitude at the cursors position
*
* @return latitude at cursors last valid position
*/
public double lat() {
return lat;
}
/**
* Returns if the x,y index is valid (not too far away)
*
* @param x
* : x index
* @param y
* : y index
* @return if the position at x,y is valid
*/
public boolean isValid(int x, int y) {
return pcbean.getPanorama() != null
&& pcbean.getPanorama().distanceAt(x, y,
(float) Double.POSITIVE_INFINITY) < Double.POSITIVE_INFINITY;
}
}
}

View File

@ -0,0 +1,114 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import java.util.function.DoubleUnaryOperator;
import ch.epfl.alpano.Math2;
import ch.epfl.alpano.Panorama;
import static java.lang.Math.max;
import static java.lang.Math.min;
/**
* ChannelPainter Interface
*/
public interface ChannelPainter {
/**
* Returns the value at (x,y)
* @param x : x index
* @param y : y index
* @return value at the point (x,y)
*/
public float valueAt(int x, int y);
/**
* Computes the max distance the the neighbors
* @param p : panorama to use
* @return max distance for a given (x,y)
*/
public static ChannelPainter maxDistanceToNeighbors(Panorama p){
return (x,y)->{
float dx1 = p.distanceAt(x-1, y,0);
float dx2 = p.distanceAt(x+1, y,0);
float dy1 = p.distanceAt(x, y-1,0);
float dy2 = p.distanceAt(x, y+1,0);
return max(max(dx1,dx2),max(dy1,dy2))-p.distanceAt(x, y);
};
}
/**
* Adds a value to the current value of a given (x,y)
* @param val : value to be added
* @return value at (x,y) with added value
*/
public default ChannelPainter add(double val){
return (x,y)->(float)(this.valueAt(x, y)+val);
}
/**
* Subtracts a value to the current value of a given (x,y)
* @param val : value to be subtracted
* @return value at (x,y) with subtracted value
*/
public default ChannelPainter sub(double val){
return (x,y)->(float)(this.valueAt(x, y)-val);
}
/**
* Multiplies by a value to the current value of a given (x,y)
* @param val : value to be multiplied by
* @return value at (x,y) multiplied by value
*/
public default ChannelPainter mul(double val){
return (x,y)->(float)(this.valueAt(x, y)*val);
}
/**
* Divides by a value to the current value of a given (x,y)
* @param val : value to be divided by
* @return value at (x,y) divided by value
*/
public default ChannelPainter div(double val){
return (x,y)->(float)(this.valueAt(x, y)/val);
}
/**
* Applies a function to the value of a given(x,y)
* @param f : the DoubleUnaryOperator function to be used
* @return value at (x,y) passed through a function
*/
public default ChannelPainter map(DoubleUnaryOperator f){
return (x,y)->(float) (f.applyAsDouble(this.valueAt(x, y)));
}
/**
* Inverts the value of a given (x,y)
* @return value at (x,y) inverted
*/
public default ChannelPainter inverted(){
return (x,y)->(float) (1-this.valueAt(x, y));
}
/**
* Clamps the value of a given (x,y)
* @return value at (x,y) clamped
*/
public default ChannelPainter clamped(){
return (x,y)->(float) (max(0,min(this.valueAt(x, y),1)));
}
/**
* Cycles the value of a given (x,y)
* @return value at (x,y) cycled
*/
public default ChannelPainter cycling(){
return (x,y)->(float) (Math2.floorMod((this.valueAt(x, y)),1));
}
}

View File

@ -0,0 +1,432 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import static java.lang.Math.min;
import static java.lang.Math.max;
import ch.epfl.alpano.Panorama;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelReader;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
/**
* Enum of custom Painters
*/
public enum CustomPainters implements Painters{
DEFAULT("Default", p->Painters.DefaultPainter(p) ),
BORDERWB("BW Borders", p->Painters.OutlinePainter(p)),
GRADDEF(Gradient.DEFAULT),
GRADDEMPOST(Gradient.DEMPOSTER),
GRADMAYERS(Gradient.MEYERS),
GRADNORDISK(Gradient.NORDISK),
GRADWIKI(Gradient.WIKI);
private final String displayText;
private final Painters imp;
private final Gradient gradient;
private final GradientImage gradientIm;
private CustomPainters(String name, Painters imp){
this.displayText = name;
this.imp = imp;
this.gradient=null;
this.gradientIm=null;
}
private CustomPainters(Gradient grad){
this.displayText = "Gradient "+grad.displayText();
this.imp = p->Painters.GradientPainter(p,grad);
this.gradient=grad;
this.gradientIm = new GradientImage(grad.get());
}
/**
*
* @return the display text of the custom painter
*/
public String displayText(){
return displayText;
}
@Override
public ImagePainter painter(Panorama p) {
return imp.painter(p);
}
/**
* Transforms elevation and slope to Color
* @param alt : altitude
* @param slope : slope
* @return Color
*/
public Color elevationToColor(double alt, double slope) {
if(isGradient()){
double a = Math.max(0,Math.min((alt-10.0)/10_000.0,1));
float hue = Painters.AltitudeToHue(gradientIm, a);
float sat = Painters.AltitudeToSaturation(gradientIm, a);
float bri = Painters.SlopeToBrightness(slope);
return Color.hsb(hue, sat, bri);
}
return null;
}
/**
*
* @return true if gradient painter
*/
public boolean isGradient() {
return gradient!=null;
}
/**
*
* @return Gradient if gradient painter, else returns null
*/
public Gradient gradient(){
return gradient;
}
}
/**
* Functional Painters interface
*/
interface Painters{
/**
* Maximal Height
*/
final static double HMAX = 10_000.0;
/**
* Abstract function used by the enum to return an image painter
* @param p : the panorama
* @return an ImagePainter
*/
public abstract ImagePainter painter(Panorama p);
/**
* Default painter as given by the teacher
* @param p : panorama
* @return default ImagePainter
*/
public static ImagePainter DefaultPainter(Panorama p){
ChannelPainter d = p::distanceAt;
ChannelPainter sl = p::slopeAt;
ChannelPainter h = d.div(100000).cycling().mul(360);
ChannelPainter s = d.div(200000).clamped().inverted();
ChannelPainter b = sl.mul(2).div(Math.PI).inverted().mul(0.7).add(0.3);
ChannelPainter o =
d.map(dist -> dist == Float.POSITIVE_INFINITY ? 0 : 1);
return ImagePainter.hsb(h, s, b, o);
}
/**
* Outline painter as given by the teacher
* Paints the borders in black/grey
* @param p : panorama
* @return outline ImagePainter
*/
public static ImagePainter OutlinePainter(Panorama p){
ChannelPainter gray =
ChannelPainter.maxDistanceToNeighbors(p)
.sub(500)
.div(4500)
.clamped()
.inverted();
ChannelPainter distance = p::distanceAt;
ChannelPainter opacity =
distance.map(d -> d == Float.POSITIVE_INFINITY ? 0 : 1);
return ImagePainter.gray(gray, opacity);
}
/**
* Gradient painter, paints using a Gradient
* @param p : panorama
* @param lgrad : Gradient to use
* @return Gradient ImagePainter
*/
public static ImagePainter GradientPainter(Panorama p, Gradient lgrad){
GradientImage grad = new GradientImage(lgrad.get());
ChannelPainter alt = p::elevationAt;
ChannelPainter d = p::distanceAt;
ChannelPainter sl = p::slopeAt;
ChannelPainter h = alt.sub(10).div(HMAX).clamped().map(a->{
return AltitudeToHue(grad, a);
});
ChannelPainter s = alt.sub(10).div(HMAX).clamped().map(a->{
return AltitudeToSaturation(grad,a);
});
ChannelPainter b = sl.mul(2).div(Math.PI).inverted().mul(0.7).add(0.3);
ChannelPainter o =
d.map(dist -> dist == Float.POSITIVE_INFINITY ? 0 : 1);
return ImagePainter.hsb(h, s, b, o);
}
/**
* Transforms slope to brightness
* @param slope
* @return 0-1 brightness value
*/
static float SlopeToBrightness(double slope){
return (float) ((1-(slope*2/Math.PI))*0.7+0.3);
}
/**
* Transforms altitude to saturation
* @param grad : GradientImage
* @param a : altitude 0-1 value
* @return 0-1 saturation value
*/
static float AltitudeToSaturation(GradientImage grad, double a){
float r = (float)(grad.RedAt(a));
float g = (float)(grad.GreenAt(a));
float b = (float)(grad.BlueAt(a));
float cmin = min(min(r,g),b);
float cmax = max(max(r,g),b);
if(cmax-cmin==0)
return 0;
else return (cmax-cmin)/(1-Math.abs(cmax+cmin-1));
}
/**
* Transforms altitude to hue
* @param grad : GradientImage
* @param a : altitude 0-1 value
* @return 0-1 hue value
*/
static float AltitudeToHue(GradientImage grad, double a){
float r = (float)(grad.RedAt(a));
float g = (float)(grad.GreenAt(a));
float b = (float)(grad.BlueAt(a));
float cmin = min(min(r,g),b);
float cmax = max(max(r,g),b);
float hu = 60f;
if(cmin==cmax){
hu*=0f;
}else{
if(cmax==r)
hu*= (g-b)/(cmax-cmin);
else if(cmax==g)
hu*= 2f+ ((b-r)/(cmax-cmin));
else
hu*= 4f+ ((r-g)/(cmax-cmin));
if(hu<0)
hu+=360;
}
return Math.round(hu);
}
}
/**
* Class used to get values from a LinearGardient
*/
class GradientImage{
private final PixelReader ir;
/**
* Number of steps on the gradient
*/
private final static int SIZE = 1_000;
private final Canvas cv;
/**
* Constructor of a gradient image
* Creates a snapshot of a canvas containing the gradient
* and saves the pixel reader to that image, to read the values
* from it when we need them.
* @param grad : LinearGradient
*/
public GradientImage(LinearGradient grad){
cv = new Canvas(1,SIZE);
GraphicsContext gc = cv.getGraphicsContext2D();
gc.setFill(grad);
gc.fillRect(0, 0, 1, SIZE);
ir = cv.snapshot(null, null).getPixelReader();
}
/**
* Returns the color on the image at a coordinate x
* @param x : x coordinate
* @return Color at x
*/
public Color colorAt(double x){
return ir.getColor(0,(int)(x*SIZE));
}
/**
* Gets the Red value at x
* @param x : x coordinate
* @return red value
*/
public double RedAt(double x){
return ir.getColor(0,(int)(x*SIZE)).getRed();
}
/**
* Gets the Green value at x
* @param x : x coordinate
* @return green value
*/
public double GreenAt(double x){
return ir.getColor(0,(int)(x*SIZE)).getGreen();
}
/**
* Gets the blue value at x
* @param x : x coordinate
* @return blue value
*/
public double BlueAt(double x){
return ir.getColor(0,(int)(x*SIZE)).getBlue();
}
/**
* Returns the canvas containing the gradient
* @return canvas
*/
public Canvas canvas(){
return cv;
}
}
/**
* Enum defining various LinearGradients
*/
enum Gradient{
DEFAULT("DEM 1",new LinearGradient(0,0,0,1,true,CycleMethod.NO_CYCLE,
new Stop(0.0,Color.rgb(105, 152, 133)),
new Stop(0.005,Color.rgb(118, 169, 146)),
new Stop(0.02,Color.rgb(131, 181, 155)),
new Stop(0.06,Color.rgb(165, 192, 167)),
new Stop(0.1,Color.rgb(211, 201, 179)),
new Stop(0.2,Color.rgb(212, 184, 164)),
new Stop(0.3,Color.rgb(212, 192, 181)),
new Stop(0.4,Color.rgb(214, 209, 206)),
new Stop(0.5,Color.rgb(222, 221, 222)),
new Stop(0.6,Color.rgb(238, 238, 238)),
new Stop(0.7,Color.rgb(245, 245, 245)),
new Stop(1.0,Color.rgb(245, 245, 245))
)),
DEM6B("DEM 2",new LinearGradient(0,0,0,1,true,CycleMethod.NO_CYCLE,
new Stop(0.0,Color.rgb(51, 102, 0)),
new Stop(0.1,Color.rgb(129, 195, 31)),
new Stop(0.2,Color.rgb(255, 255, 204)),
new Stop(0.4,Color.rgb(244, 189, 69)),
new Stop(0.5,Color.rgb(102, 51, 12)),
new Stop(0.6,Color.rgb(102, 51, 0)),
new Stop(0.8,Color.rgb(245, 245, 245)),
new Stop(1.0,Color.rgb(245, 245, 245))
)),
DEMPOSTER("DEM Poster",new LinearGradient(0,0,0,1,true,CycleMethod.NO_CYCLE,
new Stop(0.0,Color.rgb(0, 97, 71)),
new Stop(0.05,Color.rgb(16, 122, 47)),
new Stop(0.1,Color.rgb(232, 215, 125)),
new Stop(0.17,Color.rgb(161, 67, 0)),
new Stop(0.21,Color.rgb(158, 0, 0)),
new Stop(0.28,Color.rgb(110, 110, 110)),
new Stop(0.4,Color.rgb(245, 245, 245)),
new Stop(1.0,Color.rgb(245, 245, 245))
)),
MEYERS("Mayers Encyclopedia",new LinearGradient(0,0,0,1,true,CycleMethod.NO_CYCLE,
new Stop(0.0,Color.rgb(200,219, 225)),
new Stop(0.001,Color.rgb(178, 202, 153)),
new Stop(0.02,Color.rgb(178, 202, 153)),
new Stop(0.02,Color.rgb(215, 224, 199)),
new Stop(0.05,Color.rgb(215, 224, 199)),
new Stop(0.05,Color.rgb(208, 195, 180)),
new Stop(0.1,Color.rgb(208, 195, 180)),
new Stop(0.1,Color.rgb(155, 115, 93)),
new Stop(0.2,Color.rgb(155, 115, 93)),
new Stop(0.2,Color.rgb(100, 53, 32)),
new Stop(0.3,Color.rgb(100, 53, 32)),
new Stop(0.3,Color.rgb(43, 10, 11)),
new Stop(0.4,Color.rgb(43, 10, 11)),
new Stop(1.0,Color.rgb(245, 245, 245))
)),
NORDISK("Nordisk Encyclopedia",new LinearGradient(0,0,0,1,true,CycleMethod.NO_CYCLE,
new Stop(0.0,Color.rgb(200,219, 225)),
new Stop(0.001,Color.rgb(188, 187, 128)),
new Stop(0.02,Color.rgb(188, 187, 128)),
new Stop(0.02,Color.rgb(207, 199, 153)),
new Stop(0.05,Color.rgb(207, 199, 153)),
new Stop(0.05,Color.rgb(227, 194, 165)),
new Stop(0.1,Color.rgb(227, 194, 165)),
new Stop(0.1,Color.rgb(215, 158, 120)),
new Stop(0.2,Color.rgb(215, 158, 120)),
new Stop(0.2,Color.rgb(213, 130, 85)),
new Stop(0.3,Color.rgb(213, 130, 85)),
new Stop(0.3,Color.rgb(93, 42, 19)),
new Stop(0.4,Color.rgb(93, 42, 19)),
new Stop(1.0,Color.rgb(245, 245, 245))
)),
WIKI("Wikipedia Style",new LinearGradient(0,0,0,1,true,CycleMethod.NO_CYCLE,
new Stop(0.0,Color.rgb(216,242, 254)),
new Stop(0.001,Color.rgb(172, 208, 165)),
new Stop(0.01,Color.rgb(148, 191, 139)),
new Stop(0.05,Color.rgb(168, 198, 143)),
new Stop(0.1,Color.rgb(189, 204, 150)),
new Stop(0.15,Color.rgb(209, 215, 171)),
new Stop(0.17,Color.rgb(225, 228, 181)),
new Stop(0.2,Color.rgb(239, 235, 192)),
new Stop(0.22,Color.rgb(232, 225, 182)),
new Stop(0.24,Color.rgb(222, 214, 163)),
new Stop(0.26,Color.rgb(211, 202, 130)),
new Stop(0.28,Color.rgb(195, 167, 107)),
new Stop(0.3,Color.rgb(185, 152, 90)),
new Stop(0.35,Color.rgb(170, 135, 83)),
new Stop(0.4,Color.rgb(172, 154, 124)),
new Stop(0.45,Color.rgb(186, 174, 154)),
new Stop(0.5,Color.rgb(202, 195, 184)),
new Stop(0.55,Color.rgb(224, 222, 216)),
new Stop(0.8,Color.rgb(245, 244, 242)),
new Stop(1.0,Color.rgb(245, 245, 245))
)),;
private final String displayText;
private final LinearGradient grad;
/**
* Constructor of the enum
* @param name : name of the Gradient
* @param grad : LinearGradient
*/
private Gradient(String name,LinearGradient grad){
this.displayText = name;
this.grad = grad;
}
/**
*
* @return the display text of the Gradient
*/
public String displayText(){
return displayText;
}
/**
*
* @return the LinearGradient of the Gradient
*/
public LinearGradient get(){
return grad;
}
}

View File

@ -0,0 +1,89 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import ch.epfl.alpano.dem.CompositElevationProfile;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
/**
* Interface used to render an elevation profile
*/
public interface ElevationProfileRenderer {
final static double MAX_ELEVATION = 1_500;
final static ObjectProperty<Double> currentMaxHeight = new SimpleObjectProperty<Double>();
final static ObjectProperty<Double> currentElevationProfileLength = new SimpleObjectProperty<Double>();
final static int WIDTH = 800, HEIGHT = 100;
/**
* Computes the maximum elevation of a elevationProfile to
* dynamically set the max y pixel height
* @param evP
* @return max height
*/
public static double maxElevation(CompositElevationProfile evP){
double max = Integer.MIN_VALUE;
for (int x = 0; x < WIDTH; ++x) {
double pX = x * (double) evP.length() / (WIDTH - 1);
double pY = evP.elevationAt(pX);
if (max<pY){
max=pY;
}
}
return max;
}
/**
* builds a black and white image of an elevation profile
* @param evP
* @return Writable Image
*/
public static WritableImage renderElevation(CompositElevationProfile evP){
Color BLACK = Color.BLACK, WHITE = Color.WHITE;
WritableImage i =
new WritableImage(WIDTH, HEIGHT);
double maxEl = maxElevation(evP);
currentMaxHeight.set(maxEl);
currentElevationProfileLength.set(evP.length());
PixelWriter w = i.getPixelWriter();
for (int x = 0; x < WIDTH; ++x) {
double pX = x * (double) evP.length() / (WIDTH - 1);
double pY = evP.elevationAt(pX);
int yL = (int)((pY / maxEl) * (HEIGHT - 1));
for (int y = 0; y < HEIGHT; ++y) {
Color color = y < yL && y>yL-5 ? BLACK : WHITE;
w.setColor(x, HEIGHT - 1 - y, color);
}
}
System.out.println("EVP Effort : "+effort(evP));
return i;
}
public static double effort(CompositElevationProfile evP){
double effort= evP.length();
for (int x = 0; x < evP.length()-1; ++x) {
effort+= 9.81*Math.abs(evP.elevationAt(x+1)-evP.elevationAt(x));
}
return effort;
}
}

View File

@ -0,0 +1,53 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import java.math.BigDecimal;
import java.math.BigInteger;
import javafx.util.StringConverter;
import static java.math.BigDecimal.ROUND_HALF_UP;
/**
* Class used to convert an integer to a string and a string to an integer.
* Allowing us to also set the number of decimal values (defines where to put
* the comma)
*/
public class FixedPointStringConverter extends StringConverter<Integer> {
private final int decimalValues;
/**
* Constructs a converter.
*
* @param decimalValues
*/
public FixedPointStringConverter(int decimalValues) {
this.decimalValues = decimalValues;
}
@Override
public Integer fromString(String string) {
if (string.length() == 0) {
return 0;
}
return new BigDecimal(string)
.setScale(this.decimalValues, ROUND_HALF_UP)
.movePointRight(this.decimalValues).intValue();
}
@Override
public String toString(Integer object) {
if (object != null) {
BigDecimal value = new BigDecimal(new BigInteger(object.toString()),
this.decimalValues);
return (value.stripTrailingZeros()).toPlainString();
}
return "";
}
}

View File

@ -0,0 +1,82 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import javafx.scene.paint.*;
/**
* Interface of an image Painter
*/
public interface ImagePainter {
/**
* Returns the color at the point (x,y)
*
* @param x
* : x index
* @param y
* : y index
* @return color at (x,y)
*/
public Color colorAt(int x, int y);
/**
* Returns the color at (x,y)
*
* @param hue
* : hue channel painter
* @param saturation
* : saturation channel painter
* @param brightness
* : brightness channel painter
* @param opacity
* : opacity channel painter
* @return color in hsbo
* @see colorAt
*/
public static ImagePainter hsb(ChannelPainter hue,
ChannelPainter saturation, ChannelPainter brightness,
ChannelPainter opacity) {
return (x, y) -> {
return Color.hsb(hue.valueAt(x, y), saturation.valueAt(x, y),
brightness.valueAt(x, y), opacity.valueAt(x, y));
};
}
/**
* Returns the color at (x,y)
*
* @param grey
* : grey channel painter
* @param opacity
* : opacity channel painter
* @return color in grey-scale and opacity
* @see ColorAt
*/
public static ImagePainter gray(ChannelPainter grey,
ChannelPainter opacity) {
return (x, y) -> {
return Color.gray(grey.valueAt(x, y), opacity.valueAt(x, y));
};
}
/**
* Returns the color at (x,y)
*
* @param red : red channel painter
* @param green : green channel painter
* @param blue : blue channel painter
* @param opacity
* : opacity channel painter
* @return color in rgb and opacity
* @see ColorAt
*/
public static ImagePainter rgb(ChannelPainter red, ChannelPainter green,ChannelPainter blue,
ChannelPainter opacity) {
return (x, y) -> {
return Color.rgb((int)red.valueAt(x, y),(int)green.valueAt(x, y),(int)blue.valueAt(x, y), opacity.valueAt(x, y));
};
}
}

View File

@ -0,0 +1,39 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import java.util.ArrayList;
import java.util.List;
import javafx.util.StringConverter;
/**
* Class used to define a list of strings to be converted to integers (index of the string in the list)
*/
public class LabeledListStringConverter extends StringConverter<Integer>{
private List<String> listOfString;
/**
* Constructor saving the string of a list
* @param list : list of strings
*/
public LabeledListStringConverter(String...list ){
listOfString = new ArrayList<String>();
for (String string:list){
this.listOfString.add(string);
}
}
@Override
public Integer fromString(String s){
return listOfString.indexOf(s);
}
@Override
public String toString(Integer object) {
return listOfString.get(object);
}
}

View File

@ -0,0 +1,307 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import static ch.epfl.alpano.Math2.firstIntervalContainingRoot;
import static ch.epfl.alpano.Math2.sq;
import static java.lang.Math.round;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.function.DoubleUnaryOperator;
import static ch.epfl.alpano.PanoramaComputer.NATURAL_VARIATION;
import static ch.epfl.alpano.PanoramaComputer.rayToGroundDistance;
import ch.epfl.alpano.PanoramaParameters;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.ElevationProfile;
import ch.epfl.alpano.summit.Summit;
import javafx.scene.Node;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
/**
* Public class, used to get a list of labels and lines for a given panorama.
* Used to display the name of the visible summits.
*
*/
public class Labelizer {
private static final int OFFSET = 20;
private static final int BORDER = 20;
private static final int MARGIN = 200;
private final ContinuousElevationModel elevationModel;
private final List<Summit> summits;
/**
* Constructor of a labelizer
*
* @param elevationModel
* : a Continuous elevation model
* @param summits
* : a list of summits
* @see ContinuousElevationModel
* @see Summits
*/
public Labelizer(ContinuousElevationModel elevationModel,
List<Summit> summits) {
this.elevationModel = elevationModel;
this.summits = summits;
}
/**
* Creates labels from a list of Summmits
*
* @param params
* : parameters
* @return a list of Nodes containing labels and lines Main function
* building a list of labels for given parameters.
*/
public List<Node> labels(PanoramaParameters params) {
List<Node> l = new ArrayList<Node>();
List<VisibleSummitData> visibleSummits = visibleSummits(params);
int xPixel, yPixel;
int lineHeight = 0;
boolean inX, inY;
boolean lineHeightSet = false;
BitSet bitSet = new BitSet(params.width());
for (VisibleSummitData s : visibleSummits) {
xPixel = (int) round(s.xPixel());
yPixel = (int) round(s.yPixel());
inX = (xPixel >= BORDER && xPixel <= (params.width() - BORDER));
inY = (yPixel >= 170);
if (inX && inY && !lineHeightSet) {
lineHeight = yPixel - 20;
lineHeightSet = true;
}
if (inX && inY && isLabelizable(bitSet, xPixel)) {
Text text = new Text();
text.getTransforms().addAll(new Translate(xPixel, lineHeight),
new Rotate(-60, 0, 0));
text.setText(s.summit().name() + " (" + s.summit().elevation()
+ " m)");
Line line = new Line(xPixel, lineHeight, xPixel, yPixel);
l.add(text);
l.add(line);
updateBitSet(bitSet, xPixel);
}
}
return l;
}
/**
* changes false entries to a true entries between 2 indexes
* @param b
* @param xIndex
*/
private void updateBitSet(BitSet b, int xIndex) {
b.set(xIndex, xIndex + OFFSET);
}
/**
* Reads a given bitset and determines if the set of entries between two indexes are updatable (are false)
* @param b
* @param xIndex
* @return true if the set of entries
*/
private boolean isLabelizable(BitSet b, int xIndex) {
return b.get(xIndex, xIndex + OFFSET).cardinality() == 0;
}
/**
* Using a list of summits and their information, computes and returns those
* who are visible.
*
* @param params
* : the PanoramaParameters
* @return a list of visible summits
*/
private List<VisibleSummitData> visibleSummits(PanoramaParameters params) {
List<VisibleSummitData> visible = new ArrayList<VisibleSummitData>();
double root;
boolean inHFOV, inVFOV, inFOV;
ElevationProfile evp;
DoubleUnaryOperator func;
SummitData summitData;
for (Summit s : this.summits) {
summitData = new SummitData(params, s);
inHFOV = Math.abs(params.centerAzimuth()
- summitData.azimuthToSummit()) < params
.horizontalFieldOfView() / 2.;
inVFOV = Math.abs(summitData.slope()) < params.verticalFieldOfView()
/ 2.;
inFOV = summitData.horizontalDistanceToSummit() <= params
.maxDistance();
if (inHFOV && inVFOV && inFOV) {
evp = new ElevationProfile(this.elevationModel,
params.observerPosition(), summitData.azimuthToSummit(),
summitData.horizontalDistanceToSummit());
double slope = -((rayToGroundDistance(evp, params.observerElevation(),
0).applyAsDouble(summitData.horizontalDistanceToSummit()))/summitData.horizontalDistanceToSummit());
func = rayToGroundDistance(evp, params.observerElevation(),
slope);
root = firstIntervalContainingRoot(func, 0,
summitData.horizontalDistanceToSummit(), 64.0);
if (root >= summitData.horizontalDistanceToSummit() - MARGIN) {
visible.add(new VisibleSummitData(params, s));
}
}
}
visible.sort((s1, s2) -> {
return Double.compare(s1.yPixel(), s2.yPixel());
});
return visible;
}
/**
* Enclosed class, used by the labelizer
* Gives access to functions using the summit and the parameters
*
*/
static class SummitData {
private final double azimuthToSummit, slope, variation;
private final double horizontalDistanceToSummit,
verticalDistanceToSummit;
private final PanoramaParameters params;
private final Summit summit;
/**
* Constructor of a SummitData object
* @param params : the panorama parameters
* @param s : the summit
*/
public SummitData(PanoramaParameters params, Summit s) {
this.azimuthToSummit = params.observerPosition()
.azimuthTo(s.position());
this.horizontalDistanceToSummit = params.observerPosition()
.distanceTo(s.position());
this.verticalDistanceToSummit = s.elevation()
- params.observerElevation();
this.variation = sq(horizontalDistanceToSummit) * NATURAL_VARIATION;
this.slope = (verticalDistanceToSummit - variation)
/ horizontalDistanceToSummit;
this.params = params;
this.summit = s;
}
/**
* Protected function returning the panorama parameters
* @return the panorama parameters
*/
PanoramaParameters params() {
return this.params;
}
/**
* Protected function returning the altitude variation
* @return the altitude variation
* @see NATURAL_VARIATION
*/
double variation(){
return this.variation;
}
/**
* Returns the summit
* @return summit
*/
public Summit summit() {
return this.summit;
}
/**
* Azimuth to Summit
* @return azimuth to summit (angle)
*/
public double azimuthToSummit() {
return this.azimuthToSummit;
}
/**
* Horizontal Distance to the summit
* @return horizontal distance between user and summit (m)
*/
public double horizontalDistanceToSummit() {
return this.horizontalDistanceToSummit;
}
/**
* Vertical Distance to the summit
* @return vertical distance between user and summit (m)
* (without the variation)
*/
public double verticalDistanceToSummit() {
return this.verticalDistanceToSummit;
}
/**
* The slope
* @return slope
* (vertical distance divided by horizontal distance)
*/
public double slope() {
return this.slope;
}
}
/**
* A summitData extention for visible summits
* Used to get the x index and y index.
* @see SummitData
*
*/
static class VisibleSummitData extends SummitData {
private final double xIndex, yIndex;
/**
* Constructor for a visible summit
* @param params : the panorama parameters
* @param s : the summit
*/
public VisibleSummitData(PanoramaParameters params, Summit s) {
super(params, s);
this.xIndex = params().xForAzimuth(azimuthToSummit());
this.yIndex = params().yForAltitude(
Math.atan((verticalDistanceToSummit() - variation())
/ horizontalDistanceToSummit()));
}
/**
* Returns the x index of the summit
* @return x index
*/
public double xPixel() {
return this.xIndex;
}
/**
* Returns the y index of the summit
* @return y index
*/
public double yPixel() {
return this.yIndex;
}
}
}

View File

@ -0,0 +1,292 @@
package ch.epfl.alpano.gui;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.toRadians;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import ch.epfl.alpano.GeoPoint;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.image.Image;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseButton;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
public class MapViewBean {
//private final static int MAXSPOTS = 16;
private final static int IMAGE_SIZE = 720;
private double WIDTH = toRadians(0.5);
private double STEP =WIDTH / (IMAGE_SIZE - 1);
/**
* Image property
*/
public final ObjectProperty<Image> mapView= new SimpleObjectProperty<Image>() ;
private final ObjectProperty<ObservableList<GeoPoint>> ts =
new SimpleObjectProperty<ObservableList<GeoPoint>>(FXCollections.observableList(new ArrayList<GeoPoint>()));
private final ObjectProperty<ObservableList<GeoPoint>> cts =
new SimpleObjectProperty<ObservableList<GeoPoint>>(FXCollections.observableList(new ArrayList<GeoPoint>()));
private final ObjectProperty<ObservableList<Line>> tl =
new SimpleObjectProperty<ObservableList<Line>>(FXCollections.observableList(new ArrayList<Line>()));
private final PanoramaComputerBean pcbean;
public MapViewBean(PanoramaComputerBean pcbean){
this.pcbean = pcbean;
pcbean.imageProperty().addListener(e->{
this.renderPanorama();
});
pcbean.parametersProperty().addListener(e->{
this.clearTs();
});
}
public ReadOnlyObjectProperty<ObservableList<GeoPoint>> cts() {
return cts;
}
public ReadOnlyObjectProperty<ObservableList<GeoPoint>> ts() {
return ts;
}
public void addTs(double x, double y){
GeoPoint gp = new GeoPoint(
pixelToLongitude(x),
pixelToLatitude(y));
addTs(gp);
}
public void addTs(GeoPoint gp){
//if(ts.get().size()<MAXSPOTS){
ts.get().add(gp);
if (ts.get().size() >= 2) {
double initX = longitudeToPixel(ts.get()
.get(ts.get().size() - 2));
double initY = latitudeToPixel(ts.get()
.get(ts.get().size() - 2));
double endX = longitudeToPixel(ts.get()
.get(ts.get().size() - 1));
double endY = latitudeToPixel(ts.get()
.get(ts.get().size() - 1));
Line line = new Line(initX, initY, endX, endY);
line.setStroke(Color.RED);
line.setStrokeWidth(2);
tl.get().add(line);
}
//}
}
public ReadOnlyObjectProperty<ObservableList<Line>> tl() {
return tl;
}
public void clearTs(){
ts.get().clear();
cts.get().clear();
tl.get().clear();
}
/**
* Calls path finding implementation on the start and end point of the "trekkingSpots" array
* @param p : the pane used to display the path
* @param pcbean: the computer bean used to compute the path
*/
public void buildEndToEndPath(){
if (ts.get().size()>=2){
ArrayList<GeoPoint> nts = new ArrayList<>();
for(int i = 0;i<ts.get().size()-1;++i){
tl.get().remove(tl.get().size()-ts.get().size()+1+i);
PatherTheta pt = new PatherTheta(pcbean,this);
List<GeoPoint> gps = pt.astar(ts.get().get(i), ts.get().get( i+1));
Collections.reverse(gps);
for(GeoPoint gp : gps){
if(!nts.contains(gp)){
nts.add(gp);
}
}
}
//clearTs();
ts.get().clear();
for(GeoPoint ts : nts){
addTs(ts);
}
cts.get().addAll(ts.get());
ts.get().clear();
ts.get().add(nts.get(nts.size()-1));
}
}
/**
* returns the reference latitude of the map view
* @return the corresponding latitude to y=0 on the map view
*/
private double referenceLat(){
return (pcbean.getParameters().observerLatitudeFix()+WIDTH/2.0);
}
/**
* returns the reference longitude of the map view
* @return the corresponding longtitude to x=0 on the map view
*/
private double referenceLon(){
return (pcbean.getParameters().observerLongitudeFix()-WIDTH/2.0);
}
/**
* iterates on all elevations on a cDem to return the highest one
* this method's purpose is to dynamically set grayscale mapping to a relevant interval of heights
* @param cDem
* @return
*/
private double maxElevation(){
double max = Integer.MIN_VALUE;
for (int x = 0; x < IMAGE_SIZE; ++x) {
double lon = referenceLon() + x * STEP;
for (int y = 0; y < IMAGE_SIZE; ++y) {
double lat = referenceLat() - y * STEP;
GeoPoint gp = new GeoPoint(lon, lat);
double el = (pcbean.cDem().get().elevationAt(gp));
if (max<el){
max = el;
}
}
}
return max;
}
/**
* iterates on all elevations on a cDem to return the lowest one
* this method's purpose is to dynamically set grayscale mapping to a relevant interval of heights
* @param cDem
* @return
*/
private double minElevation(){
double min = Integer.MAX_VALUE;
for (int x = 0; x < IMAGE_SIZE; ++x) {
double lon = referenceLon() + x * STEP;
for (int y = 0; y < IMAGE_SIZE; ++y) {
double lat = referenceLat() - y * STEP;
GeoPoint gp = new GeoPoint(lon, lat);
double el = (pcbean.cDem().get().elevationAt(gp));
if (min>el){
min = el;
}
}
}
return min;
}
/**
* Function used to transform a panorama to an image
* Paints using a gradient painter if the current selected
* painter is a gradient painter, otherwhise uses
* black and white elevation painter
*
* @param p
* : the panorama
* @param l
* : the image painter
* @return image
*/
void renderPanorama() {
CustomPainters cp = pcbean.painterProperty().get();
WritableImage im = new WritableImage(720, 720);
PixelWriter w = im.getPixelWriter();
double minEl = minElevation();
double maxEl = maxElevation();
for (int x = 0; x < IMAGE_SIZE; ++x) {
double lon = referenceLon() + x * STEP;
for (int y = 0; y < IMAGE_SIZE; ++y) {
double lat = referenceLat() - y * STEP;
GeoPoint gp = new GeoPoint(lon, lat);
if(cp.isGradient()){
w.setColor(x, y, cp.elevationToColor(pcbean.cDem().get().elevationAt(gp),pcbean.cDem().get().slopeAt(gp)));
}else{
double el = (pcbean.cDem().get().elevationAt(gp) - minEl)
/ (maxEl - minEl);
w.setColor(x, y, gray(el));
}
}
}
mapView.set(im);
}
/**
*converts a pixel y value to a longitude;
* @param x
* @return
*/
double pixelToLongitude(double x){
return referenceLon() + x * STEP;
}
/**
* converts a pixel x value to a latitude;
* @param y
* @return
*/
double pixelToLatitude(double y){
return referenceLat() - y * STEP;
}
/**
* converts a longitude to a pixel x value
* @param gP
* @return
*/
double longitudeToPixel(GeoPoint gP){
return ((gP.longitude()-referenceLon())/STEP);
}
/**
* converts a latitude to a pixel y value
* @param gP
* @return
*/
double latitudeToPixel(GeoPoint gP){
return ( -(gP.latitude()-referenceLat())/STEP);
}
/**
* makes the grayScale value of a pixel
* @param v1
* @return a color
*/
private static Color gray(double v1) {
double v = max(0, min(v1, 1));
return new Color(v, v, v, 1);
}
public ReadOnlyObjectProperty<Image> mapView() {
return this.mapView;
}
public void zoom(MouseButton mb){
}
}

View File

@ -0,0 +1,216 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import java.beans.Beans;
import java.util.ArrayList;
import java.util.List;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.image.Image;
import ch.epfl.alpano.Panorama;
import ch.epfl.alpano.PanoramaComputer;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.mapzen.MapzenManager;
import ch.epfl.alpano.summit.Summit;
import static ch.epfl.alpano.gui.PanoramaRenderer.renderPanorama;
import static javafx.application.Platform.runLater;
/**
* Class used to link the panorama computing with the Graphical interface (through the PanoramaParametersBean)
*/
public class PanoramaComputerBean extends Beans {
private final ObjectProperty<Panorama> panoramaProperty;
private final ObjectProperty<PanoramaUserParameters> parametersProperty;
private final ObjectProperty<Image> imageProperty;
private final ObjectProperty<ObservableList<Node>> labelsProperty;
private final ObjectProperty<ContinuousElevationModel> cDEM;
private final ObjectProperty<CustomPainters> imp;
private final ObjectProperty<Boolean> downloadProperty;
private final ObjectProperty<String> downloadTextProperty;
private final List<Summit> summits;
private final MapzenManager HGTGetter = new MapzenManager();
/**
* Constructs a bean for a panoramaComputer
* @param summits : a list of summits
*/
public PanoramaComputerBean(List<Summit> summits){
this.cDEM = new SimpleObjectProperty<ContinuousElevationModel>();
this.cDEM.bind(HGTGetter.cDEM());
this.downloadProperty = new SimpleObjectProperty<Boolean>(false);
this.summits = summits;
this.parametersProperty = new SimpleObjectProperty<PanoramaUserParameters>(null);
ObservableList<Node> oList = FXCollections.observableList(new ArrayList<Node>());
this.labelsProperty = new SimpleObjectProperty<ObservableList<Node>>(oList);
this.panoramaProperty = new SimpleObjectProperty<Panorama>(null);
this.imageProperty = new SimpleObjectProperty<Image>(null);
this.imp = new SimpleObjectProperty<CustomPainters>(CustomPainters.GRADDEMPOST);
HGTGetter.cDEM().addListener(
(b, o, n) -> runLater(this::updatePanorama));
this.downloadTextProperty = new SimpleObjectProperty<String>();
HGTGetter.text().addListener(
(b, o, n) -> runLater(this::updateDownloadText));
this.imp.addListener( (b, o, n) -> runLater(this::repaintPanorama));
parametersProperty.addListener((b, o, n) -> this.downloadHGT());
}
/**
* Returns the elevation model property object
* @return ContinuousElevationModel property object
*/
ReadOnlyObjectProperty<ContinuousElevationModel> cDem(){
return this.cDEM;
}
/**
* Returns the panorama property object
* @return panorama property object
*/
ReadOnlyObjectProperty<Panorama> panoramaProperty(){
return panoramaProperty;
}
/**
* Returns the parameters property object
* @return parameters property object
*/
ObjectProperty<PanoramaUserParameters> parametersProperty(){
return parametersProperty;
}
/**
* Returns the image property object
* @return image property object
*/
ReadOnlyObjectProperty<Image> imageProperty(){
return imageProperty;
}
/**
* Returns the labels property object
* @return labels property object
*/
ReadOnlyObjectProperty<ObservableList<Node>> labelsProperty(){
return labelsProperty;
}
/**
* Returns the painter property object
* @return Custom Painter property object
*/
ReadOnlyObjectProperty<CustomPainters> painterProperty() {
return imp;
}
/**
* Returns the download property object
* This object is used to set if Mapzen is currently downloading
* @return boolean download property object
*/
ReadOnlyObjectProperty<Boolean> downloadProperty(){
return downloadProperty;
}
/**
* Returns the labels
* @return labels
*/
ObservableList<Node> getLabels(){
return labelsProperty.get();
}
/**
* Returns the panorama
* @return panorama
*/
Panorama getPanorama(){
return panoramaProperty.get();
}
/**
* Returns the parameters
* @return parameters
*/
PanoramaUserParameters getParameters(){
return parametersProperty.get();
}
/**
* Updates the parameters
* @param newParameters : new PanoramaUserParameters
*/
void setParameters(PanoramaUserParameters newParameters){
this.parametersProperty.set(newParameters);
}
/**
* Updates the custom painter
* @param cp : new painter
*/
void setPainter(CustomPainters cp) {
imp.set(cp);
}
/**
* Retuns the image
* @return image
*/
Image getImage(){
return imageProperty.get();
}
/**
* Text displayed when downloading property object
* @return download text property object
*/
ReadOnlyObjectProperty<String> downloadTextProperty(){
return downloadTextProperty;
}
/**
* Updates the download text getting new text from the MapzenManager
*/
private void updateDownloadText(){
this.downloadTextProperty.set(HGTGetter.text().get());
}
/**
* Downloads the HGT Files
*/
private void downloadHGT(){
this.downloadProperty.set(true);
HGTGetter.update(this.getParameters().panoramaDisplayParameters());
}
/**
* Repaints the panorama
*/
private void repaintPanorama(){
if(this.getPanorama()!=null)
this.imageProperty.set(renderPanorama(this.getPanorama(), imp.get().painter(this.getPanorama())));
}
/**
* Computes the new panorama
*/
private void updatePanorama() {
this.downloadProperty.set(false);
Panorama panorama = new PanoramaComputer(cDEM.get()).computePanorama(this.getParameters().panoramaComputeParameters());
List<Node> newLabels = new Labelizer(cDEM.get(), summits).labels(this.getParameters().panoramaDisplayParameters());
this.panoramaProperty.set(panorama);
this.imageProperty.set(renderPanorama(panorama, imp.get().painter(panorama)));
this.labelsProperty().get().setAll(newLabels);
}
}

View File

@ -0,0 +1,214 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import static javafx.application.Platform.runLater;
import java.beans.Beans;
import java.util.EnumMap;
import java.util.Map;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
/**
* Class used to manage the GUI and the Panorama User Parameters
*/
public class PanoramaParametersBean extends Beans {
private ObjectProperty<PanoramaUserParameters> parametersProperty;
private Map<UserParameter, ObjectProperty<Integer>> bufferParametersProperty = new EnumMap<UserParameter, ObjectProperty<Integer>>(
UserParameter.class);
private ObjectProperty<CustomPainters> painterProperty;
/**
* Constructor initializing the object property for every user parameter and
* adds listeners to them
*
* @param parameters
* : initial parameters to use
*/
public PanoramaParametersBean(PanoramaUserParameters parameters) {
this.parametersProperty = new SimpleObjectProperty<PanoramaUserParameters>(
parameters);
for (UserParameter param : UserParameter.values()) {
ObjectProperty<Integer> property = new SimpleObjectProperty<Integer>(
parameters.get(param));
property.addListener(
(b, o, n) -> runLater(this::synchronizeParameters));
bufferParametersProperty.put(param, property);
}
this.painterProperty = new SimpleObjectProperty<CustomPainters>(CustomPainters.DEFAULT);
}
/**
* Synchronize the parameters by building new PanoramaUserParameters and
* using it to update the current values
*
* @see updateProperties
* @see buildNewParameters
*/
private void synchronizeParameters() {
updateProperties(buildNewParameters());
}
/**
* Updates the parameters using the checked parameters
* @param parameters
*/
public void updateProperties(PanoramaUserParameters parameters) {
this.parametersProperty.set(parameters);
for (UserParameter param : UserParameter.values()) {
this.bufferParametersProperty.get(param).set(parameters.get(param));
}
}
/**
* Updates the painter to use
* @param cp : CustomPainters
*/
public void setPainter(CustomPainters cp) {
this.painterProperty.set(cp);
}
/**
* Compute new PanoramaUserParameters using the current ObjectProperty
* values and returns it
*
* @return new PanoramaUserParameters
*/
private PanoramaUserParameters buildNewParameters() {
return new PanoramaUserParameters(
ObserverLongitudeProperty().getValue(),
ObserverLatitudeProperty().getValue(),
ObserverElevationProperty().getValue(),
CenterAzimuthProperty().getValue(),
HorizontalFieldOfViewProperty().getValue(),
MaxDistanceProperty().getValue(), WidthProperty().getValue(),
HeightProperty().getValue(),
SuperSamplingExponentProperty().getValue());
}
/**
* Returns the map of UserParameter property objects
*
* @return the buffer parameter property
*/
public Map<UserParameter, ObjectProperty<Integer>> bufferParametersProperty() {
return this.bufferParametersProperty;
}
/**
*
* @param parameter
* : a UserParameter
* @return the object property of the parameter
*/
public ObjectProperty<Integer> getProperty(UserParameter parameter) {
return this.bufferParametersProperty.get(parameter);
}
/**
*
* @return the PanoramaUserParamerer property object
*/
public ReadOnlyObjectProperty<PanoramaUserParameters> ParametersProperty() {
return this.parametersProperty;
}
/**
*
* @return the Longitude property object
*/
public ObjectProperty<Integer> ObserverLongitudeProperty() {
return bufferParametersProperty.get(UserParameter.OBSERVER_LONGITUDE);
}
/**
*
* @return the Longitude property object
*/
public ObjectProperty<Integer> ObserverLatitudeProperty() {
return bufferParametersProperty.get(UserParameter.OBSERVER_LATITUDE);
}
/**
*
* @return the Observer Elevation property object
*/
public ObjectProperty<Integer> ObserverElevationProperty() {
return bufferParametersProperty.get(UserParameter.OBSERVER_ELEVATION);
}
/**
*
* @return the Center Azimuth property object
*/
public ObjectProperty<Integer> CenterAzimuthProperty() {
return bufferParametersProperty.get(UserParameter.CENTER_AZIMUTH);
}
/**
*
* @return the Horizontal FOV property object
*/
public ObjectProperty<Integer> HorizontalFieldOfViewProperty() {
return bufferParametersProperty
.get(UserParameter.HORIZONTAL_FIELD_OF_VIEW);
}
/**
*
* @return the Max Distance property object
*/
public ObjectProperty<Integer> MaxDistanceProperty() {
return bufferParametersProperty.get(UserParameter.MAX_DISTANCE);
}
/**
*
* @return the Width property object
*/
public ObjectProperty<Integer> WidthProperty() {
return bufferParametersProperty.get(UserParameter.WIDTH);
}
/**
*
* @return the Height property object
*/
public ObjectProperty<Integer> HeightProperty() {
return bufferParametersProperty.get(UserParameter.HEIGHT);
}
/**
*
* @return the SuperSampling property object
*/
public ObjectProperty<Integer> SuperSamplingExponentProperty() {
return bufferParametersProperty
.get(UserParameter.SUPER_SAMPLING_EXPONENT);
}
/**
*
* @return the CustomPainters property object
*/
public ObjectProperty<CustomPainters> painterProperty() {
return this.painterProperty;
}
}

View File

@ -0,0 +1,36 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import ch.epfl.alpano.Panorama;
import javafx.scene.image.*;
/**
* Interface used to render a panorama
* Draws the image by computing the values pixel by pixel
*/
public interface PanoramaRenderer {
/**
* Function used to transform a panorama to an image
* @param p : the panorama
* @param l : the image painter
* @return image
*/
public static Image renderPanorama(Panorama p, ImagePainter l){
int height = p.parameters().height();
int width = p.parameters().width();
WritableImage im = new WritableImage(width,height);
PixelWriter w = im.getPixelWriter();
for(int x=0; x<width;++x){
for(int y=0; y<height;++y){
w.setColor(x, y, l.colorAt(x, y));
}
}
return im;
}
}

View File

@ -0,0 +1,288 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
import static java.lang.Math.pow;
import static java.lang.Math.toRadians;
import java.util.EnumMap;
import java.util.Map;
import ch.epfl.alpano.GeoPoint;
import ch.epfl.alpano.PanoramaParameters;
/**
* Class containing the user parameters for a panorama
*/
public final class PanoramaUserParameters {
private final Map<UserParameter, Integer> userParameters = new EnumMap<>(
UserParameter.class);
/**
* Constructor of panorama user parameter using the UserParameter enum
*
* @param userParameters : Map of user parameters
* @see UserParameter
*/
public PanoramaUserParameters(Map<UserParameter, Integer> userParameters) {
for (UserParameter key : userParameters.keySet()) {
this.userParameters.put(key, key.sanitize(userParameters.get(key)));
}
}
/**
* Constructor of panorama user parameter using independent values
*
* @param lon
* : longitude
* @param lat
* : latitude
* @param alt
* : altitude
* @param azimuth
* :azimuth
* @param fov
* : field of view
* @param dist
* : max distance
* @param width
* : width
* @param height
* : height
* @param ss
* : super sampling
*/
public PanoramaUserParameters(int lon, int lat, int alt, int azimuth,
int fov, int dist, int width, int height, int ss) {
this(toEnumMap(lon, lat, alt, azimuth, fov, dist, width, height, ss));
}
/**
* Computes and returns PanoramaParameters used to compute the panorama
*
* @return PanoramaParameters
*/
public PanoramaParameters panoramaComputeParameters() {
int width = (int) (pow(2, superSamplingExponent()) * width());
int height = (int) (pow(2, superSamplingExponent()) * height());
return new PanoramaParameters(
new GeoPoint(observerLongitudeFix(), observerLatitudeFix()),
observerElevation(), centerAzimuthFix(),
horizontalFieldOfViewFix(), maxDistance(), width, height);
}
/**
* Computes and returns PanoramaParameters used to display the panorama
*
* @return PanoramaParameters
*/
public PanoramaParameters panoramaDisplayParameters() {
return new PanoramaParameters(
new GeoPoint(observerLongitudeFix(), observerLatitudeFix()),
observerElevation(), centerAzimuthFix(),
horizontalFieldOfViewFix(), maxDistance(), width(), height());
}
/**
* Looks for the value of a user parameter
*
* @param param
* : UserParameter to get
* @return the requested parameter value
*/
public int get(UserParameter param) {
return this.userParameters.get(param);
}
/**
* Returns the observer longitude as interger
*
* @return longitude of the observer
* @see observerLongitudeFix to get the real value
*/
public int observerLongitude() {
return this.get(UserParameter.OBSERVER_LONGITUDE);
}
/**
* Returns the observer longitude
*
* @return longitude of the observer
* @see observerLongitude to get the int value
*/
public double observerLongitudeFix() {
return toRadians(this.get(UserParameter.OBSERVER_LONGITUDE) / 10000.0);
}
/**
* Returns the observer latitude as integer
*
* @return latitude of the observer
* @see observerLatitudeFix to get the real value
*/
public int observerLatitude() {
return this.get(UserParameter.OBSERVER_LATITUDE);
}
/**
* Returns the observer latitude
*
* @return latitude of the observer
* @see observerLatitude to get the int value
*/
public double observerLatitudeFix() {
return toRadians(this.get(UserParameter.OBSERVER_LATITUDE) / 10000.0);
}
/**
* Returns the observer altitude
*
* @return altitude of the observer
*/
public int observerElevation() {
return this.get(UserParameter.OBSERVER_ELEVATION);
}
/**
* Returns the azimuth as integer
*
* @return azimuth
* @see centerAzimuthFix for the real value
*/
public int centerAzimuth() {
return this.get(UserParameter.CENTER_AZIMUTH);
}
/**
* Returns the azimuth
*
* @return azimuth
* @see centerAzimuth for the int value
*/
public double centerAzimuthFix() {
return toRadians(this.get(UserParameter.CENTER_AZIMUTH));
}
/**
* Returns the field of view as integer
*
* @return field of view
* @see horizontalFieldOfViewFix for the real value
*/
public int horizontalFieldOfView() {
return this.get(UserParameter.HORIZONTAL_FIELD_OF_VIEW);
}
/**
* Returns the field of view as integer
*
* @return field of view
* @see horizontalFieldOfView for the int value
*/
public double horizontalFieldOfViewFix() {
return toRadians(this.get(UserParameter.HORIZONTAL_FIELD_OF_VIEW));
}
/**
* Returns the max distance
*
* @return max distance
*/
public int maxDistance() {
return this.get(UserParameter.MAX_DISTANCE);
}
/**
* Returns the image width
*
* @return width
*/
public int width() {
return this.get(UserParameter.WIDTH);
}
/**
* Returns the image height
*
* @return height
*/
public int height() {
return this.get(UserParameter.HEIGHT);
}
/**
* Returns the supersampling exponent
*
* @return supersampling exponent
*/
public int superSamplingExponent() {
return this.get(UserParameter.SUPER_SAMPLING_EXPONENT);
}
@Override
public boolean equals(Object that) {
//is that better than an "isinstanceof" ?
if( that instanceof PanoramaUserParameters){
for(UserParameter parameter : UserParameter.values()){
if( this.get(parameter)!= ((PanoramaUserParameters)that).get(parameter)){
return false;
}
}
return true;
}
return false;
}
@Override
public int hashCode() {
int hash = 0;
int exponent =0;
for(UserParameter parameter : UserParameter.values()){
hash += this.get(parameter)*Math.pow(31, exponent);
exponent ++;
}
if( hash <0)
throw new IndexOutOfBoundsException("hashCode value of PanoramaUserParameter exceeded Integer.MAXVALUE");
return hash;
}
/**
* Builds an EnumMap given some values
*
* @param lon
* @param lat
* @param alt
* @param azimuth
* @param fov
* @param dist
* @param width
* @param height
* @param ss
* @return EnumMap containing the values using UserParameter keys
* @see UserParameter
*/
private static final EnumMap<UserParameter, Integer> toEnumMap(int lon,
int lat, int alt, int azimuth, int fov, int dist, int width,
int height, int ss) {
EnumMap<UserParameter, Integer> map = new EnumMap<UserParameter, Integer>(
UserParameter.class);
map.put(UserParameter.OBSERVER_LONGITUDE, lon);
map.put(UserParameter.OBSERVER_LATITUDE, lat);
map.put(UserParameter.OBSERVER_ELEVATION, alt);
map.put(UserParameter.CENTER_AZIMUTH, azimuth);
map.put(UserParameter.HORIZONTAL_FIELD_OF_VIEW, fov);
map.put(UserParameter.MAX_DISTANCE, dist);
map.put(UserParameter.WIDTH, width);
map.put(UserParameter.HEIGHT, height);
map.put(UserParameter.SUPER_SAMPLING_EXPONENT, ss);
return map;
}
}

View File

@ -0,0 +1,186 @@
/*
* Author: Cédric Heozl
* Sciper: 257844
* Date: 3 Jun 2017
*/
package ch.epfl.alpano.gui;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;
import ch.epfl.alpano.GeoPoint;
import ch.epfl.alpano.dem.ElevationProfile;
public class PatherTheta {
private static final double EPSADMISIBLE =1.0;
private HashMap<PatherPoint,Double> gScore;
private HashMap<PatherPoint,Double> fScore;
private HashSet<PatherPoint> closedSet;
private HashSet<PatherPoint> openSet;
private HashMap<PatherPoint,PatherPoint> cameFrom;
private final PanoramaComputerBean pcbean;
private final MapViewBean mvbean;
/**
* Creates a new instance of a path searcher
* @param pcbean :the panorama computer bean
* @param ppbean m the current panorama parameter bean
*/
public PatherTheta(PanoramaComputerBean pcbean, MapViewBean mvbean) {
this.pcbean = pcbean;
this.mvbean = mvbean;
}
public ArrayList<GeoPoint> astar(GeoPoint start, GeoPoint end){
PatherPoint orig = new PatherPoint(start);
PatherPoint goal = new PatherPoint(end);
PatherPoint current;
double tentativeScore;
closedSet = new HashSet<PatherPoint>();
openSet = new HashSet<PatherPoint>();
openSet.add(orig);
cameFrom = new HashMap<PatherPoint,PatherPoint>();
gScore = new HashMap<PatherPoint,Double>();
gScore.put(orig, 0.0);
fScore = new HashMap<PatherPoint,Double>();
fScore.put(orig, EPSADMISIBLE*orig.distanceTo(goal));
while(openSet.size()>0){
current = getNewCurrent();
if(current.equals(goal))
return reconstructPath(current);
openSet.remove(current);
closedSet.add(current);
for(PatherPoint neighbor : current.getNeighbours()){
if(closedSet.contains(neighbor))
continue;
tentativeScore = gScore.getOrDefault(current, Double.MAX_VALUE) + current.effortTo(neighbor);
if(!openSet.contains(neighbor))
openSet.add(neighbor);
else if(tentativeScore >= gScore.getOrDefault(neighbor, Double.MAX_VALUE))
continue;
cameFrom.put(neighbor, current);
gScore.put(neighbor, tentativeScore);
fScore.put(neighbor, gScore.get(neighbor)+EPSADMISIBLE*neighbor.distanceTo(goal));
}
}
return null;
}
private ArrayList<GeoPoint> reconstructPath(PatherPoint current) {
ArrayList<GeoPoint> path = new ArrayList<>();
path.add(current.gp());
while(cameFrom.containsKey(current)){
current = cameFrom.get(current);
path.add(current.gp());
}
return path;
}
private PatherPoint getNewCurrent(){
PatherPoint output = null;
double minVal = Double.MAX_VALUE;
double newVal;
for(PatherPoint pp : openSet){
newVal = fScore.getOrDefault(pp, Double.MAX_VALUE);
if(newVal < minVal){
minVal = newVal;
output = pp;
}
}
return output;
}
class PatherPoint{
private final GeoPoint gp;
private final int x,y;
public PatherPoint(GeoPoint gp){
this.gp = gp;
this.x = (int) mvbean.longitudeToPixel(gp);
this.y = (int) mvbean.latitudeToPixel(gp);
}
public PatherPoint(int x, int y){
double lon = mvbean.pixelToLongitude(x);
double lat = mvbean.pixelToLatitude(y);
this.gp = new GeoPoint(lon, lat);
this.x = x;
this.y = y;
}
public int x(){
return x;
}
public int y(){
return y;
}
public GeoPoint gp(){
return this.gp;
}
public boolean outOfField(){
return gp==null;
}
public PatherPoint[] getNeighbours(){
PatherPoint[] pps = new PatherPoint[8];
int i =0;
for (int a = -1; a <= 1; a++) {
for (int b = -1; b <= 1; b++) {
if (!(a == 0 && b == 0)) {
pps[i]=new PatherPoint((int)(x()+a),(int)(y()+b));
++i;
}
}
}
return pps;
}
public Double distanceTo(PatherPoint that) {
return this.gp().distanceTo(that.gp());
}
public double effortTo(PatherPoint dest) {
if(this.equals(dest) || gp.distanceTo(dest.gp())==0){
return 0.0;
}
ElevationProfile evp = new ElevationProfile(pcbean.cDem().get(), gp,
gp.azimuthTo(dest.gp()),
gp.distanceTo(dest.gp()));
return evp.effort();
}
@Override
public boolean equals(Object that){
if(that instanceof PatherPoint){
return((this.x == ((PatherPoint)that).x()) && (this.y == ((PatherPoint)that).y()));
}
return false;
}
@Override
public int hashCode(){
return Objects.hash(x(),y());
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
/**
* Enum of predefined panoramas
*/
public enum PredefinedPanoramas {
NIESEN(7_6500, 46_7300, 600, 180, 110,"Niesen"),
JURA_ALPS(6_8087, 47_0085, 1380, 162, 27,"Jura"),
MONT_RACINE(6_8200, 47_0200, 1500, 135, 45,"Mont Racine"),
FINSTERAARHORN(8_1260, 46_5374, 4300, 205, 20,"Finsteraarhorn"),
SAUVABELIN(6_6385, 46_5353, 700, 135, 100,"Sauvabelin"),
PELICAN_BEACH(6_5728, 46_5132, 380, 135, 60,"Pelican Beach");
private PanoramaUserParameters pup;
private String name;
private static final int WIDTH = 2_500;
private static final int HEIGHT = 800;
private static final int DISTANCE = 300_000;
private static final int SS = 0;
/**
* Function used to build the predefined panoramas
* @param lon : longitude
* @param lat : latitude
* @param alt : altitude
* @param azimuth : azimuth
* @param fov : field of view
* @return panorama parameters
* @see PanoramaUserParameters
*/
PredefinedPanoramas(int lon, int lat,int alt, int azimuth, int fov, String name) {
this.name= name;
this.pup = new PanoramaUserParameters(lon, lat, alt, azimuth, fov, DISTANCE,WIDTH,
HEIGHT, SS);
}
/**
*
* @return the PanoramaUserParameters of the panorama
*/
public PanoramaUserParameters get(){
return pup;
}
@Override
public String toString(){
return name;
}
}

View File

@ -0,0 +1,81 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.gui;
/**
* Enum of the user parameters with bounds
*/
public enum UserParameter {
OBSERVER_LATITUDE(-80_0000, 80_0000, "Latitude (º) ",4),
OBSERVER_LONGITUDE(-160_0000, 160_0000, "Longitude (º) ",4),
OBSERVER_ELEVATION(0, 10_000, "Altitude (m) ",0),
CENTER_AZIMUTH(0, 359, "Azimuth (º) ",0),
HORIZONTAL_FIELD_OF_VIEW(1, 360,"Field of view (º) ",0),
MAX_DISTANCE(10_000, 600_000,"View distance (km) ", 3),
WIDTH(30, 16_000, "Width (px) ",0),
HEIGHT(10, 4_000,"Height (px) ", 0),
SUPER_SAMPLING_EXPONENT(0,2,"Supersampling ",0);
private final String label;
private final int scale;
private final int min;
private final int max;
/**
* Constructor of user parameters
*
* @param min
* : min bound value
* @param max
* : max bound value
* @param name
* : display string
* @param scale
* : scale for the FixedPointStringConverter
*/
private UserParameter(int min, int max, String label, int scale) {
this.min = min;
this.max = max;
this.label = label;
this.scale = scale;
}
/**
* Returns the scale value for the FixedPointStringConverter
*
* @return scale value
*/
public int scale() {
return this.scale;
}
/**
* Returns the display text
*
* @return display text
*/
public String label() {
return this.label;
}
private int min() {
return min;
}
private int max() {
return max;
}
/**
* Keeps the entered value in the bounds
*
* @param val
* : value to check
* @return value in the bounds
*/
public int sanitize(int val) {
return ((val < min()) ? min() : (val > max() ? max() : val));
}
}

View File

@ -0,0 +1,186 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.mapzen;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.Observable;
import java.util.zip.GZIPInputStream;
import static ch.epfl.alpano.dem.HgtDiscreteElevationModel.HGT_SAMPLES;
import static ch.epfl.alpano.mapzen.MapzenManager.HGT_FILEPATH;
/**
* MapzenGetter uses the Mapzen web API to download a HGT File.
* @see MapzenManager
*/
public class MapzenGetter extends Observable implements Runnable{
/**
* API Key, Ideal would be to let the user set/use his own instead of ours.
*/
private final static String API_KEY = "mapzen-viEw7G7";
private final static String BASE_URL = "https://tile.mapzen.com/mapzen/terrain/v1/skadi/";
private final static String ZIP_EXT = ".gz";
private final static int MAX_RETRY = 3;
private final String fileName;
private final String url;
private final String filePath;
private final String fileZipPath;
private boolean checked = false;
private int tries = 0;
/**
* Constructor of a Getter
* @param x : longitude
* @param y : latitude
*/
public MapzenGetter(int x, int y) {
this.url = getURL(x,y);
this.fileName =getFileName(x,y);
this.filePath = HGT_FILEPATH+File.separator+this.fileName;
this.fileZipPath = this.filePath+ZIP_EXT;
}
/**
* Starts the getter by checking, downloading, unziping and notifying manager
* @throws UncheckedIOException when an error occurs even after 3 retries
*/
private void getFile(){
if(checked || (this.checkFile()==1)){
try {
download();
unzip();
new File(fileZipPath).delete();
setChanged();
notifyObservers();
} catch (IOException e) {
if(tries<MAX_RETRY){
++tries;
this.checkFile();
getFile();
}else{
throw new UncheckedIOException("Error with a MapzenGetter after 3 tries...",e);
}
}
}
}
/**
* Downloads the file from the Mapzen servers
* @throws IOException
*/
private void download() throws IOException{
URL url = new URL(this.url);
BufferedInputStream bis = new BufferedInputStream(url.openStream());
FileOutputStream fis;
fis = new FileOutputStream(this.fileZipPath);
byte[] buffer = new byte[1024];
int count=0;
while((count = bis.read(buffer,0,1024)) != -1)
{
fis.write(buffer, 0, count);
}
fis.close();
bis.close();
}
/**
* Unzips the file
* @throws IOException
*/
private void unzip() throws IOException{
byte[] buffer = new byte[1024];
GZIPInputStream gzips = new GZIPInputStream(new FileInputStream(this.fileZipPath));
FileOutputStream out = new FileOutputStream(this.filePath);
int len;
while ((len = gzips.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
gzips.close();
out.close();
}
/**
* Returns the url string for a given longitude,latitude
* @param x : longitude
* @param y : latitude
* @return the url
*/
private static final String getURL(int x, int y){
String yStr = ((y>=0)?"N":"S");
int newy = ((y>=0)?y:y*-1);
return String.format(BASE_URL+"%s%02d/%s.gz?api_key=%s",
yStr,newy,getFileName(x,y),API_KEY);
}
/**
* Returns the file name for a given longitude,latitude
* @param x : longitude
* @param y : latitude
* @return the file name
*/
public static final String getFileName(int x, int y){
String yStr = ((y>=0)?"N":"S");
String xStr = ((x>=0)?"E":"W");
x = ((x>=0)?x:x*-1);
y = ((y>=0)?y:y*-1);
return String.format("%s%02d%s%03d.hgt", yStr,y,xStr,x);
}
/**
* Checks if the file exists and is valid, also checks if the folder is ok
* @return 0 if the file exists and is fine, 0 if it needs to be downloaded
*/
public int checkFile(){
File f = new File(filePath);
this.checked = true;
if(f.exists()&&(f.length()==2*HGT_SAMPLES)){
return 0;
}else{
if(f.exists()){
f.delete();
}
f = new File(fileZipPath);
if(f.exists()){
f.delete();
}
if(!new File(HGT_FILEPATH).isDirectory())
new File(HGT_FILEPATH).mkdirs();
return 1;
}
}
@Override
public void run() {
getFile();
}
/**
* Starts this MapzenGetter on a new thread
*/
public void start(){
Thread thread = new Thread(this);
thread.start();
}
}

View File

@ -0,0 +1,191 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.mapzen;
import java.io.File;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Observable;
import java.util.Observer;
import ch.epfl.alpano.PanoramaParameters;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.DiscreteElevationModel;
import ch.epfl.alpano.dem.HgtDiscreteElevationModel;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
/**
* MapzenManager Manages a list of MapzenGetter, observing them, starting them,...
* Used to download any HGT file for the entire earth.
* Supports multiple MapzenGetters on their own thread.
*/
public final class MapzenManager implements Observer{
/**
* Path the the folder containing the HGT Files
*/
public static final String HGT_FILEPATH = "HGT";
private static final int MULTI_DL = 3;
private static final int HGT_RADIUS = 2;
private static final String DEFAULT_TEXT = "HGT File Downloader Startings ...";
private int total;
private static ArrayList<MapzenGetter> getters = new ArrayList<MapzenGetter>();
private ArrayList<MapzenGetter> gettersDL = new ArrayList<MapzenGetter>();
private int latm, latM, lonm, lonM;
private static ObjectProperty<ContinuousElevationModel> cDEM = new SimpleObjectProperty<ContinuousElevationModel>();
private static ObjectProperty<String> text = new SimpleObjectProperty<String>(DEFAULT_TEXT);
/**
* Updates the parameters,
* adds the required files in an area of 4*4 around the observer
*
* @param params : the new parameters
*/
public void update(PanoramaParameters params){
double lon = Math.toDegrees(params.observerPosition().longitude());
double lat = Math.toDegrees(params.observerPosition().latitude());
this.lonm = (int)Math.round(lon-HGT_RADIUS);
this.lonM = (int)Math.round(lon+HGT_RADIUS);
this.latm = (int)Math.round(lat-HGT_RADIUS);
this.latM = (int)Math.round(lat+HGT_RADIUS);
for(int i = lonm ; i<lonM; ++i){
for(int j = latm ; j<latM; ++j){
addGetter(new MapzenGetter(i, j));
}
}
this.total = getters.size();
this.check();
this.dl();
if(isDone())
cDEM.set(getDEM(lonm, lonM, latm, latM));
}
/**
* Adds a MapzenGetter to the list of getters
* @param getter
*/
private void addGetter(MapzenGetter getter){
getter.addObserver(this);
getters.add(getter);
}
/**
* Removes a MapzenGetter from the list
* @param getter
*/
public void removeGetter(MapzenGetter getter){
getters.remove(getter);
}
/**
* Checks all the MapzenGetters
* Removes those who already exists from the download list
*/
public void check(){
ArrayList<MapzenGetter> toRemove = new ArrayList<MapzenGetter>();
for(MapzenGetter getter: getters){
if(getter.checkFile()==0)
toRemove.add(getter);
}
for(MapzenGetter getter: toRemove){
getters.remove(getter);
}
text.set("EXISTING : "+toRemove.size()+"\nMISSING :"+getters.size()+"\n(total:"+total+")");
total = getters.size();
}
/**
* Start download on multiple threads to improve speed
*/
public void dl(){
MapzenGetter gt;
if(getters.size()>0){
while(gettersDL.size()<=MULTI_DL && getters.size()>0){
gt = getters.get(0);
getters.remove(gt);
gettersDL.add(gt);
gt.start();
}
}
}
/**
* @return current amount of downloaded files
*/
public int getProgress(){
return total-(getters.size()+gettersDL.size());
}
/**
* @return true downloads are done
*/
public boolean isDone(){
return ((getters.size()==0) && (gettersDL.size()==0));
}
@Override
public void update(Observable o, Object arg) {
gettersDL.remove((MapzenGetter)o);
text.set(String.format((Locale) null,"DOWNLOAD PROGRESS: %d / %d",getProgress(),total));
if(getters.size()>0)
dl();
if(isDone()){
text.set(String.format((Locale) null,"DOWNLOAD COMPLETED",getProgress(),total));
cDEM.set(getDEM(lonm, lonM, latm, latM));
text.set(String.format((Locale) null,DEFAULT_TEXT,getProgress(),total));
}
}
/**
*
* @return ContinuousElevationModel object property
*/
public ReadOnlyObjectProperty<ContinuousElevationModel> cDEM(){
return cDEM;
}
/**
*
* @return text object property used to display progress and information
*/
public ReadOnlyObjectProperty<String> text(){
return text;
}
/**
* Returns the ContinuousElevatyionModel using a 2D range of longitude/latitudes
* @param lonm : min longitude
* @param lonM : max longitude
* @param latm : min latitude
* @param latM : max latitude
* @return a ContinuousElevationModel of the defined area using the downloaded HGT Files
*/
private final static ContinuousElevationModel getDEM (double lonm, double lonM, double latm, double latM){
DiscreteElevationModel DEM1 = null;
DiscreteElevationModel DEM2 = null;
String fileName;
File file;
for (int i = (int) lonm; i < lonM; ++i) {
for (int j =(int) latm; j < latM; ++j) {
fileName = MapzenGetter.getFileName(i,j);
file = new File(HGT_FILEPATH+File.separator+fileName);
if (DEM1 == null)
DEM1 = new HgtDiscreteElevationModel(file);
DEM1 = new HgtDiscreteElevationModel(file).union(DEM1);
}
if (DEM2 == null)
DEM2 = DEM1;
DEM2 = DEM1.union(DEM2);
DEM1 = null;
}
return new ContinuousElevationModel(DEM2);
}
}

View File

@ -0,0 +1,128 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.summit;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ch.epfl.alpano.GeoPoint;
import java.util.Collections;
/**
* Class parsing the data from a file and returns a list of the Summits it
* contains
*
* @see Summit
*/
public class GazetteerParser {
/**
* Regex Pattern of a line in a Gazetter file
*/
private static final Pattern P = Pattern.compile(
"[ ]+(\\d{1,3}):(\\d{1,3}):(\\d{1,3})[ ]+(\\d{1,2}):(\\d{1,2}):(\\d{1,2})[ ]+(\\d{1,4})[ ]+([A-Z]\\d{0,2})[ ]+([A-Z]\\d{1,2})[ ]+([A-Z]{0,2}\\d{0,2})[ ]+(.*)");
/**
* Private constructor so GazetteerParser can't be instantiated
*/
private GazetteerParser() {
}
/**
* Returns a List with the Summits data extracted from a Gazetteer file.
*
* @param file
* : the file to read
* @throws IOException
* if there is a problem with reading the file or a line
*/
public static List<Summit> readSummitsFrom(File file) throws IOException {
ArrayList<Summit> output = new ArrayList<Summit>();
String line;
Matcher m;
double lon = 0, lat = 0;
try {
FileInputStream s = new FileInputStream(file);
BufferedReader b = new BufferedReader(
new InputStreamReader(s, StandardCharsets.US_ASCII));
while ((line = b.readLine()) != null) {
m = checkLine(line);
lon = CompositeDegreeToRadians(m.group(1), m.group(2),
m.group(3));
lat = CompositeDegreeToRadians(m.group(4), m.group(5),
m.group(6));
output.add(new Summit(m.group(11), new GeoPoint(lon, lat),
Integer.parseInt(m.group(7))));
}
b.close();
s.close();
} catch (IOException e) {
throw new IOException();
} catch (NumberFormatException e){
throw new IOException();
}
return Collections.unmodifiableList(output);
}
/**
* Checks a line using the Regex Pattern P
*
* @param txt
* : the string to analyze
* @return Matcher m with the regex applied
* @throws IOException
* if the line format does not match
*/
private static final Matcher checkLine(String txt) throws IOException {
Matcher m = P.matcher(txt);
if (!m.find()) {
throw new IOException();
}
return m;
}
/**
* Converts separate degrees, minutes, seconds to radians
*
* @param deg
* : the degree value
* @param min
* : the minute value
* @param sec
* : the second value
* @return the angle in radians
*/
private static final double CompositeDegreeToRadians(int deg, int min,
int sec) {
double angle = deg * 1.0 + min / 60.0 + sec / (3600.0);
return Math.toRadians(angle);
}
/**
* Converts separate degrees, minutes, seconds as strings to radians
*
* @param deg
* : the degree value as a string
* @param min
* : the minute value as a string
* @param sec
* : the second value as a string
* @return angle in radians
* @see CompositeDegreeToRadians(int deg, int min, int sec)
*/
private static final double CompositeDegreeToRadians(String deg, String min,
String sec) {
return CompositeDegreeToRadians(Integer.parseInt(deg),
Integer.parseInt(min), Integer.parseInt(sec));
}
}

View File

@ -0,0 +1,71 @@
/*
* Author: Cedric Holzl - Mohamed Khadri
* Sciper: 257844 - 261203
*/
package ch.epfl.alpano.summit;
import java.util.Objects;
import ch.epfl.alpano.GeoPoint;
/**
* Class containing the information of a summit
*/
public final class Summit {
private final String name;
private final GeoPoint position;
private final int elevation;
/**
* Constructor for a Summit
*
* @param name
* : the summit name
* @param position
* : the summit position
* @param elevation
* : the summit altitude
* @throw NullPointerException if name or position are null
*/
public Summit(String name, GeoPoint position, int elevation) {
Objects.requireNonNull(name);
Objects.requireNonNull(position);
this.elevation = elevation;
this.name = name;
this.position = position;
}
/**
* The summit's name
*
* @return the name of the summit
*/
public String name() {
return name;
}
/**
* The geoPoint's position
*
* @return the position of this geoPoint
*/
public GeoPoint position() {
return position;
}
/**
* The geoPoint's elevation
*
* @return the altitude of the summit
*/
public int elevation() {
return elevation;
}
@Override
public String toString() {
return name + " " + position.toString() + " " + elevation;
}
}

View File

@ -0,0 +1,167 @@
package ch.epfl.alpano;
import static ch.epfl.alpano.Azimuth.canonicalize;
import static ch.epfl.alpano.Azimuth.fromMath;
import static ch.epfl.alpano.Azimuth.isCanonical;
import static ch.epfl.alpano.Azimuth.toMath;
import static ch.epfl.alpano.Azimuth.toOctantString;
import static ch.epfl.test.TestRandomizer.RANDOM_ITERATIONS;
import static ch.epfl.test.TestRandomizer.newRandom;
import static java.lang.Math.PI;
import static java.lang.Math.floorMod;
import static java.lang.Math.nextDown;
import static java.lang.Math.round;
import static java.lang.Math.scalb;
import static java.lang.Math.toDegrees;
import static java.lang.Math.toRadians;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import org.junit.Test;
public class AzimuthTest {
@Test
public void isCanonicalIsTrueFor0() {
assertTrue(isCanonical(0));
}
@Test
public void isCanonicalIsFalseFor0Pred() {
assertFalse(isCanonical(nextDown(0)));
}
@Test
public void isCanonicalIsTrueFor2PiPred() {
assertTrue(isCanonical(nextDown(scalb(PI, 1))));
}
@Test
public void isCanonicalIsFalseFor2Pi() {
assertFalse(isCanonical(scalb(PI, 1)));
}
@Test
public void isCanonicalIsTrueForRandomCanonicalAzimuths() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i)
assertTrue(isCanonical(rng.nextDouble() * scalb(PI, 1)));
}
@Test
public void canonicalizeCorrectlyCanonicalizesRoundedRandomAngles() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
int aDeg = rng.nextInt(10_000) - 5_000;
double aRad = toRadians(aDeg);
double canonicalARad = canonicalize(aRad);
assertTrue(0 <= canonicalARad && canonicalARad < scalb(PI, 1));
int canonicalADeg = (int)round(toDegrees(canonicalARad));
if (canonicalADeg == 360)
canonicalADeg = 0;
assertEquals(floorMod(aDeg, 360), canonicalADeg);
}
}
@Test
public void toMathCorrectlyHandles0() {
assertEquals(0d, toMath(0d), 0d);
}
@Test
public void fromMathCorrectlyHandles0() {
assertEquals(0d, fromMath(0d), 0d);
}
@Test
public void toMathWorksForKnownValues() {
int[] vs = new int[] {
0, 0,
1,359,
2,358,
358,2,
359,1,
90, 270,
180, 180,
179,181,
181,179,
270, 90
};
for (int i = 0; i < vs.length; i += 2) {
double a = toMath(toRadians(vs[i]));
assertEquals(toRadians(vs[i+1]), a, 1e-10);
}
}
@Test
public void fromMathWorksForKnownValues() {
int[] vs = new int[] {
0, 0,
1,359,
2,358,
358,2,
359,1,
90, 270,
180, 180,
179,181,
181,179,
270, 90
};
for (int i = 0; i < vs.length; i += 2) {
double a = fromMath(toRadians(vs[i]));
assertEquals(toRadians(vs[i+1]), a, 1e-10);
}
}
@Test
public void toMathAndFromMathAreInverseForRandomValues() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double a = rng.nextDouble() * scalb(PI, 1);
double a2 = fromMath(toMath(a));
assertEquals(a, a2, 1e-10);
double a3 = toMath(fromMath(a));
assertEquals(a, a3, 1e-10);
}
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void toMathThrowsFor2Pi() {
toMath(scalb(PI, 1));
}
@Test(expected = java.lang.IllegalArgumentException.class)
public void fromMathThrowsFor2Pi() {
fromMath(scalb(PI, 1));
}
@Test(expected = IllegalArgumentException.class)
public void toOctantStringThrowsForNonCanonicalAzimuth() {
toOctantString(-1, null, null, null, null);
}
@Test
public void toOctantStringCorrectlyCyclesThroughValues() {
String n = "north", e = "east", s = "south", w = "west";
ArrayList<String> expected = new ArrayList<>();
expected.addAll(Collections.nCopies(45, n));
expected.addAll(Collections.nCopies(45, n+e));
expected.addAll(Collections.nCopies(45, e));
expected.addAll(Collections.nCopies(45, s+e));
expected.addAll(Collections.nCopies(45, s));
expected.addAll(Collections.nCopies(45, s+w));
expected.addAll(Collections.nCopies(45, w));
expected.addAll(Collections.nCopies(45, n+w));
for (int aDeg = 0; aDeg < 360; ++aDeg) {
double aRad = toRadians(floorMod(aDeg - 22, 360));
String os = toOctantString(aRad, n, e, s, w);
assertEquals(expected.get(aDeg), os);
}
}
}

View File

@ -0,0 +1,37 @@
package ch.epfl.alpano;
import static ch.epfl.test.TestRandomizer.RANDOM_ITERATIONS;
import static ch.epfl.test.TestRandomizer.newRandom;
import static java.lang.Math.PI;
import static java.lang.Math.scalb;
import static org.junit.Assert.assertEquals;
import java.util.Random;
import org.junit.Test;
public class DistanceTest {
private static double EARTH_CIRCUMFERENCE = 40_030_174; // rounded to nearest integer
@Test
public void toRadiansAndToMetersAreInverseForRandomValues() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double dRad = rng.nextDouble() * scalb(PI, 1);
double dRad2 = Distance.toRadians(Distance.toMeters(dRad));
assertEquals(dRad, dRad2, 1e-10);
}
}
@Test
public void toMetersIsCorrectForKnownValues() {
assertEquals(0, Distance.toMeters(0), 0);
assertEquals(EARTH_CIRCUMFERENCE, Distance.toMeters(scalb(PI, 1)), 0.5);
}
@Test
public void toRadiansIsCorrectForKnownValues() {
assertEquals(0, Distance.toRadians(0), 0);
assertEquals(scalb(PI, 1), Distance.toRadians(EARTH_CIRCUMFERENCE), 1e-5);
}
}

View File

@ -0,0 +1,71 @@
package ch.epfl.alpano;
import java.io.File;
import javax.imageio.ImageIO;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.DiscreteElevationModel;
import ch.epfl.alpano.dem.HgtDiscreteElevationModel;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.toRadians;
import java.awt.image.BufferedImage;
final class DrawPanorama {
final static File HGT_FILE = new File("HGT"+File.separatorChar+"N46E007.hgt");
final static int IMAGE_WIDTH = 500;
final static int IMAGE_HEIGHT = 200;
final static double ORIGIN_LON = toRadians(7.65);
final static double ORIGIN_LAT = toRadians(46.73);
final static int ELEVATION = 600;
final static double CENTER_AZIMUTH = toRadians(180);
final static double HORIZONTAL_FOV = toRadians(60);
final static int MAX_DISTANCE = 100_000;
final static PanoramaParameters PARAMS =
new PanoramaParameters(new GeoPoint(ORIGIN_LON,
ORIGIN_LAT),
ELEVATION,
CENTER_AZIMUTH,
HORIZONTAL_FOV,
MAX_DISTANCE,
IMAGE_WIDTH,
IMAGE_HEIGHT);
public static void main(String[] as) throws Exception {
try (DiscreteElevationModel dDEM =
new HgtDiscreteElevationModel(HGT_FILE)) {
ContinuousElevationModel cDEM =
new ContinuousElevationModel(dDEM);
Panorama p = new PanoramaComputer(cDEM)
.computePanorama(PARAMS);
BufferedImage i =
new BufferedImage(IMAGE_WIDTH,
IMAGE_HEIGHT,
BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < IMAGE_WIDTH; ++x) {
for (int y = 0; y < IMAGE_HEIGHT; ++y) {
float d = p.distanceAt(x, y);
int c = (d == Float.POSITIVE_INFINITY)
? 0x87_CE_EB
: gray((d - 2_000) / 15_000);
i.setRGB(x, y, c);
}
}
ImageIO.write(i, "png", new File("tests/ch/epfl/alpano/niesen.png"));
}
}
private static int gray(double v) {
double clampedV = max(0, min(v, 1));
int gray = (int) (255.9999 * clampedV);
return (gray << 16) | (gray << 8) | gray;
}
}

View File

@ -0,0 +1,66 @@
package ch.epfl.alpano;
import java.io.File;
import javax.imageio.ImageIO;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.DiscreteElevationModel;
import ch.epfl.alpano.dem.HgtDiscreteElevationModel;
import ch.epfl.alpano.gui.ChannelPainter;
import ch.epfl.alpano.gui.ImagePainter;
import ch.epfl.alpano.gui.PanoramaRenderer;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
import static java.lang.Math.toRadians;
final class DrawPanoramaNew {
final static File HGT_FILE = new File("HGT"+File.separatorChar+"N46E007.hgt");
final static int IMAGE_WIDTH = 2_500;
final static int IMAGE_HEIGHT = 800;
final static double ORIGIN_LON = toRadians(7.65);
final static double ORIGIN_LAT = toRadians(46.73);
final static int ELEVATION = 600;
final static double CENTER_AZIMUTH = toRadians(180);
final static double HORIZONTAL_FOV = toRadians(110);
final static int MAX_DISTANCE = 100_000;
final static PanoramaParameters PARAMS =
new PanoramaParameters(new GeoPoint(ORIGIN_LON,
ORIGIN_LAT),
ELEVATION,
CENTER_AZIMUTH,
HORIZONTAL_FOV,
MAX_DISTANCE,
IMAGE_WIDTH,
IMAGE_HEIGHT);
public static void main(String[] as) throws Exception {
try (DiscreteElevationModel dDEM =
new HgtDiscreteElevationModel(HGT_FILE)) {
ContinuousElevationModel cDEM =
new ContinuousElevationModel(dDEM);
Panorama p = new PanoramaComputer(cDEM)
.computePanorama(PARAMS);
ChannelPainter d = p::distanceAt;
ChannelPainter sl = p::slopeAt;
ChannelPainter h = d.div(100000).cycling().mul(360);
ChannelPainter s = d.div(200000).clamped().inverted();
ChannelPainter b = sl.mul(2).div(Math.PI).inverted().mul(0.7).add(0.3);
ChannelPainter o =
d.map(dist -> dist == Float.POSITIVE_INFINITY ? 0 : 1);
ImagePainter l = ImagePainter.hsb(h, s, b, o);
Image i = PanoramaRenderer.renderPanorama(p, l);
ImageIO.write(SwingFXUtils.fromFXImage(i, null),
"png",
new File("tests/ch/epfl/alpano/niesen-profile.png"));
}
}
}

View File

@ -0,0 +1,63 @@
package ch.epfl.alpano;
import java.io.File;
import java.util.Locale;
import javax.imageio.ImageIO;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.DiscreteElevationModel;
import ch.epfl.alpano.dem.HgtDiscreteElevationModel;
import ch.epfl.alpano.gui.ChannelPainter;
import ch.epfl.alpano.gui.ImagePainter;
import ch.epfl.alpano.gui.PanoramaRenderer;
import ch.epfl.alpano.gui.PanoramaUserParameters;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
import ch.epfl.alpano.gui.PredefinedPanoramas;
final class DrawPanoramaNew2 {
public static void main(String[] as) throws Exception {
PanoramaUserParameters pano = PredefinedPanoramas.JURA_ALPS.get();
ContinuousElevationModel cDEM =generatePredefinedCDEM("./HGT/");
Panorama p = new PanoramaComputer(cDEM)
.computePanorama(pano.panoramaComputeParameters());
ChannelPainter d = p::distanceAt;
ChannelPainter sl = p::slopeAt;
ChannelPainter h = d.div(100000).cycling().mul(360);
ChannelPainter s = d.div(200000).clamped().inverted();
ChannelPainter b = sl.mul(2).div(Math.PI).inverted().mul(0.7).add(0.3);
ChannelPainter o =
d.map(dist -> dist == Float.POSITIVE_INFINITY ? 0 : 1);
ImagePainter l = ImagePainter.hsb(h, s, b, o);
Image i = PanoramaRenderer.renderPanorama(p, l);
ImageIO.write(SwingFXUtils.fromFXImage(i, null),
"png",
new File("tests/ch/epfl/alpano/niesen-profile.png"));
}
private static final ContinuousElevationModel generatePredefinedCDEM(String path){
DiscreteElevationModel DEM1=null;
DiscreteElevationModel DEM2=null;
File file;
for(int i=6;i<=11;++i){
for(int j=45;j<=47;++j){
file = new File(String.format((Locale) null, path+"N%02dE%03d.hgt",j,i));
if(DEM1==null)
DEM1 = new HgtDiscreteElevationModel(file);
DEM1 = new HgtDiscreteElevationModel(file).union(DEM1);
}
if(DEM2==null)
DEM2=DEM1;
DEM2=DEM1.union(DEM2);
DEM1=null;
}
return new ContinuousElevationModel(DEM2);
}
}

View File

@ -0,0 +1,68 @@
package ch.epfl.alpano;
import static org.junit.Assert.*;
import org.junit.Test;
public class GeoPointTest {
@Test(expected = IllegalArgumentException.class)
public void constructorLimitTest1(){
new GeoPoint(0, 2*Math.PI);
}
@Test(expected = IllegalArgumentException.class)
public void constructorLimitTest2(){
new GeoPoint(2*Math.PI, 2*Math.PI);
}
@Test(expected = IllegalArgumentException.class)
public void constructorLimitTest3(){
new GeoPoint(2*Math.PI, 0);
}
@Test
public void distanceToTest1(){
GeoPoint london = new GeoPoint(Math.toRadians(6.631), Math.toRadians(46.521));
GeoPoint moscow = new GeoPoint( Math.toRadians(37.623), Math.toRadians(55.753));
assertEquals(2367000, london.distanceTo(moscow), 1000);//1km imprecision
assertEquals(0,london.azimuthTo(london),0);
}
@Test
public void distanceToTest2(){
GeoPoint epfl = new GeoPoint(Math.toRadians(6.56730), Math.toRadians(46.51781));
GeoPoint eiler = new GeoPoint( Math.toRadians(8.00537), Math.toRadians(46.57756));
assertEquals(110294, epfl.distanceTo(eiler), 150);//150M imprecision
}
@Test
public void distanceToTest3(){
GeoPoint p1 = new GeoPoint(Math.PI,Math.PI/2.0);
GeoPoint p2 = new GeoPoint(Math.PI,Math.PI/2.0);
GeoPoint p3 = new GeoPoint(0,Math.PI/2.0);
GeoPoint p4 = new GeoPoint(Math.PI/10.0,Math.PI/2.0);
assertEquals(p3.distanceTo(p4), p4.distanceTo(p3), 10);
assertEquals(p2.distanceTo(p1), p1.distanceTo(p2), 10);
assertEquals(p3.distanceTo(p4), p1.distanceTo(p2), 10);
}
@Test
public void distanceToTest4(){
GeoPoint p1 = new GeoPoint(Math.PI/2.0,Math.PI/2.0);
GeoPoint p2 = new GeoPoint(-Math.PI/2.0,Math.PI/2.0);
GeoPoint p3 = new GeoPoint(-Math.PI/2.0,Math.PI/2.0);
GeoPoint p4 = new GeoPoint(Math.PI/10.0,Math.PI/2.0);
assertEquals(p3.distanceTo(p4), p4.distanceTo(p3), 10);
assertEquals(p2.distanceTo(p1), p1.distanceTo(p2), 10);
assertEquals(p3.distanceTo(p4), p1.distanceTo(p2), 10);
}
@Test
public void toStringTest(){
GeoPoint g = new GeoPoint(Math.toRadians(-7.6543), Math.toRadians(54.3210));
assertEquals("(-7.6543,54.3210)", g.toString());
}
}

View File

@ -0,0 +1,31 @@
package ch.epfl.alpano;
import static java.lang.Math.toDegrees;
import static java.lang.Math.toRadians;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class GeoPointTestP {
private static GeoPoint CORNAVIN = new GeoPoint(toRadians(6.14308), toRadians(46.21023));
private static GeoPoint M1_EPFL = new GeoPoint(toRadians(6.56599), toRadians(46.52224));
private static GeoPoint FEDERAL_PALACE = new GeoPoint(toRadians(7.44428), toRadians(46.94652));
private static GeoPoint SAENTIS = new GeoPoint(toRadians(9.34324), toRadians(47.24942));
private static GeoPoint MONTE_TAMARO = new GeoPoint(toRadians(8.86598), toRadians(46.10386));
@Test
public void distanceToWorksOnKnownPoints() {
assertEquals(226_000, M1_EPFL.distanceTo(SAENTIS), 10);
assertEquals( 81_890, M1_EPFL.distanceTo(FEDERAL_PALACE), 10);
assertEquals(143_560, FEDERAL_PALACE.distanceTo(MONTE_TAMARO), 10);
assertEquals(269_870, SAENTIS.distanceTo(CORNAVIN), 10);
}
@Test
public void azimuthToWorksOnKnownPoints() {
assertEquals( 68.03, toDegrees(M1_EPFL.azimuthTo(SAENTIS)), 0.01);
assertEquals( 54.50, toDegrees(M1_EPFL.azimuthTo(FEDERAL_PALACE)), 0.01);
assertEquals(130.23, toDegrees(FEDERAL_PALACE.azimuthTo(MONTE_TAMARO)), 0.01);
assertEquals(245.82, toDegrees(SAENTIS.azimuthTo(CORNAVIN)), 0.01);
}
}

View File

@ -0,0 +1,257 @@
package ch.epfl.alpano;
import static ch.epfl.test.TestRandomizer.RANDOM_ITERATIONS;
import static ch.epfl.test.TestRandomizer.newRandom;
import static org.junit.Assert.*;
import java.util.Random;
import org.junit.Test;
public class Interval1DTest {
@Test(expected = IllegalArgumentException.class)
public void testRandomIncludedFromAndToFails() {
Random rng = newRandom();
int a,b;
for (int i = 0; i < RANDOM_ITERATIONS; ++i){
a = rng.nextInt();
b = rng.nextInt();
if(a>b){
new Interval1D(a,b);
}
}
}
@Test
public void testRandomIncludedFromAndTo() {
Random rng = newRandom();
Interval1D interval;
int a,b;
for (int i = 0; i < RANDOM_ITERATIONS; ++i){
a = rng.nextInt();
b = rng.nextInt();
if(a<=b){
interval = new Interval1D(a,b);
assertEquals(interval.includedFrom(),a,0);
assertEquals(interval.includedTo(),b,0);
assertEquals(interval.size(),(b-a+1),0);
}
}
}
@Test
public void testLimitsIncludedFromAndTo() {
Interval1D interval = new Interval1D(0,0);
assertEquals(interval.includedFrom(),0,0);
assertEquals(interval.includedTo(),0,0);
assertEquals(interval.size(),1,0);
}
@Test
public void testContainsTrue() {
Random rng = newRandom();
Interval1D interval;
int a,b,m;
for (int i = 0; i < RANDOM_ITERATIONS; ++i){
a = rng.nextInt(500);
b = rng.nextInt(500);
if(a<=b){
m = (b+a)/2;
interval = new Interval1D(a,b);
assertTrue(interval.contains(m));
}
}
}
@Test
public void testContainsFails() {
Random rng = newRandom();
Interval1D interval;
int a,b,c;
for (int i = 0; i < RANDOM_ITERATIONS; ++i){
a = rng.nextInt(500);
b = rng.nextInt(500);
c = rng.nextInt(500);
if(a<=b && (c<a ||c>b)){
interval = new Interval1D(a,b);
assertFalse(interval.contains(c));
}
}
}
@Test
public void testSizeOfIntersectionWith() {
Interval1D interval1;
Interval1D interval2;
int num[] = {
1,1,0,2,1,
1,2,1,5,2,
1,4,0,2,2,
1,4,0,1,1,
2,4,0,1,0
};
for (int i = 0; i < num.length; i += 5) {
interval1 = new Interval1D(num[i],num[i+1]);
interval2 = new Interval1D(num[i+2],num[i+3]);
assertEquals(interval1.sizeOfIntersectionWith(interval2), interval2.sizeOfIntersectionWith(interval1));
assertEquals(interval1.sizeOfIntersectionWith(interval2), num[i+4], 0);
}
}
@Test
public void testBoundingUnion() {
Interval1D interval1;
Interval1D interval2;
Interval1D interval3;
int num[] = {
1,1,0,2,0,2,
1,1,1,1,1,1,
0,0,0,0,0,0,
0,0,1,1,0,1,
0,1,0,1,0,1,
0,5,1,1,0,5,
0,4,1,5,0,5,
1,5,0,4,0,5,
1,4,0,5,0,5,
0,0,9,9,0,9
};
for (int i = 0; i < num.length; i += 6) {
interval1 = new Interval1D(num[i],num[i+1]);
interval2 = new Interval1D(num[i+2],num[i+3]);
interval3 = new Interval1D(num[i+4],num[i+5]);
assertTrue(interval1.boundingUnion(interval2).equals(interval3));
}
}
@Test
public void testIsUnionableWithTrue() {
Interval1D interval1;
Interval1D interval2;
int num[] = {
1,1,0,2,
1,2,1,5,
1,4,0,2,
1,4,0,1,
2,4,0,2,
0,0,0,1,
0,5,5,5,
0,1,1,1,
-1,1,0,2
};
for (int i = 0; i < num.length; i += 4) {
interval1 = new Interval1D(num[i],num[i+1]);
interval2 = new Interval1D(num[i+2],num[i+3]);
assertEquals(interval1.isUnionableWith(interval2), interval2.isUnionableWith(interval1));
assertTrue(interval1.isUnionableWith(interval2));
}
}
@Test
public void testIsUnionableWithFalse() {
Interval1D interval1;
Interval1D interval2;
int num[] = {
0,0,2,2,
0,0,2,3,
0,0,4,4,
0,4,6,8,
2,2,0,0,
};
for (int i = 0; i < num.length; i += 4) {
interval1 = new Interval1D(num[i],num[i+1]);
interval2 = new Interval1D(num[i+2],num[i+3]);
assertEquals(interval1.isUnionableWith(interval2), interval2.isUnionableWith(interval1));
assertFalse(interval1.isUnionableWith(interval2));
}
}
@Test
public void testUnion() {
Interval1D interval1;
Interval1D interval2;
String str;
int num[] = {
1,1,0,2,0,2,
1,1,1,1,1,1,
0,0,0,0,0,0,
0,1,0,1,0,1,
0,1,0,1,0,1,
0,5,1,1,0,5,
0,4,1,5,0,5,
1,5,0,4,0,5,
1,4,0,5,0,5,
};
for (int i = 0; i < num.length; i += 6) {
interval1 = new Interval1D(num[i],num[i+1]);
interval2 = new Interval1D(num[i+2],num[i+3]);
str =("["+num[i+4]+".."+num[i+5]+"]");
assertEquals(str,interval1.union(interval2).toString());
}
}
@Test(expected = IllegalArgumentException.class)
public void testUnionFails() {
Interval1D interval1 = new Interval1D(0,0);
Interval1D interval2 = new Interval1D(2,2);
interval1.union(interval2);
}
@Test
public void testEqualsObjectTrue() {
Random rng = newRandom();
Interval1D interval1;
Interval1D interval2;
int a,b;
for (int i = 0; i < RANDOM_ITERATIONS; ++i){
a = rng.nextInt(500);
b = rng.nextInt(500);
if(a<=b){
interval1 = new Interval1D(a,b);
interval2 = new Interval1D(a,b);
assertTrue(interval1.equals(interval2));
assertEquals(interval1.equals(interval2), interval2.equals(interval1));
}
}
}
@Test
public void testEqualsObjectFalse() {
Random rng = newRandom();
Interval1D interval1;
String str = "";
int a,b;
for (int i = 0; i < RANDOM_ITERATIONS; ++i){
a = rng.nextInt(500);
b = rng.nextInt(500);
if(a<=b){
interval1 = new Interval1D(a,b);
assertFalse(interval1.equals(str));
assertEquals(interval1.equals(str), str.equals(interval1));
}
}
}
@Test
public void testToString() {
Random rng = newRandom();
Interval1D interval1;
int a,b;
for (int i = 0; i < RANDOM_ITERATIONS; ++i){
a = rng.nextInt(500);
b = rng.nextInt(500);
if(a<=b){
interval1 = new Interval1D(a,b);
assertEquals(interval1.toString(),"["+a+".."+b+"]");
}
}
}
}

View File

@ -0,0 +1,161 @@
package ch.epfl.alpano;
import static ch.epfl.test.ObjectTest.hashCodeIsCompatibleWithEquals;
import static ch.epfl.test.TestRandomizer.RANDOM_ITERATIONS;
import static ch.epfl.test.TestRandomizer.newRandom;
import static java.lang.Math.ceil;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.sqrt;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Random;
import org.junit.Test;
public class Interval1DTestP {
private static Interval1D i_0_9() { return new Interval1D(0, 9); }
private static Interval1D i_0_2() { return new Interval1D(0, 2); }
private static Interval1D i_3_5() { return new Interval1D(3, 5); }
private static Interval1D i_4_6() { return new Interval1D(4, 6); }
private static Interval1D i_6_9() { return new Interval1D(6, 9); }
@Test(expected = IllegalArgumentException.class)
public void constructorFailsForInvalidBounds() {
new Interval1D(1, 0);
}
@Test
public void constructorWorksForSingletonInterval() {
new Interval1D(10, 10);
}
@Test
public void containsIsTrueOnlyForTheIntervalsElements() {
int sqrtIt = (int)ceil(sqrt(RANDOM_ITERATIONS));
Random rng = newRandom();
for (int i = 0; i < sqrtIt; ++i) {
int a = rng.nextInt(200) - 100;
int b = a + rng.nextInt(50);
Interval1D interval = new Interval1D(a, b);
for (int j = 0; j < sqrtIt; ++j) {
int v = rng.nextInt(200) - 100;
assertEquals(a <= v && v <= b, interval.contains(v));
}
}
}
@Test
public void containsWorksAtTheLimit() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
int max = rng.nextInt(2000);
int a = rng.nextInt(max) - 1000;
int b = max - 1000;
Interval1D interval = new Interval1D(a, b);
assertFalse(interval.contains(a - 1));
assertTrue(interval.contains(a));
assertTrue(interval.contains(b));
assertFalse(interval.contains(b + 1));
}
}
@Test
public void sizeWorksOnKnownIntervals() {
assertEquals(10, i_0_9().size());
assertEquals(3, i_0_2().size());
assertEquals(3, i_3_5().size());
assertEquals(3, i_4_6().size());
assertEquals(4, i_6_9().size());
}
@Test
public void sizeOfIntersectionWorksOnNonIntersectingIntervals() {
assertEquals(0, i_0_2().sizeOfIntersectionWith(i_3_5()));
assertEquals(0, i_0_2().sizeOfIntersectionWith(i_4_6()));
assertEquals(0, i_0_2().sizeOfIntersectionWith(i_6_9()));
}
@Test
public void sizeOfIntersectionWorksOnIntersectingIntervals() {
assertEquals(3, i_0_2().sizeOfIntersectionWith(i_0_9()));
assertEquals(3, i_0_9().sizeOfIntersectionWith(i_0_2()));
assertEquals(1, i_4_6().sizeOfIntersectionWith(i_6_9()));
}
@Test
public void boundingUnionWorksOnKnownIntervals() {
assertEquals(0, i_0_2().boundingUnion(i_6_9()).includedFrom());
assertEquals(9, i_0_2().boundingUnion(i_6_9()).includedTo());
assertEquals(0, i_6_9().boundingUnion(i_0_2()).includedFrom());
assertEquals(9, i_6_9().boundingUnion(i_0_2()).includedTo());
assertEquals(0, i_0_9().boundingUnion(i_0_9()).includedFrom());
assertEquals(9, i_0_9().boundingUnion(i_0_9()).includedTo());
}
@Test
public void isUnionableWithWorksOnKnownIntervals() {
// Intersecting intervals
assertTrue(i_0_9().isUnionableWith(i_0_9()));
assertTrue(i_0_9().isUnionableWith(i_3_5()));
assertTrue(i_3_5().isUnionableWith(i_3_5()));
assertTrue(i_3_5().isUnionableWith(i_0_9()));
assertTrue(i_3_5().isUnionableWith(i_4_6()));
assertTrue(i_4_6().isUnionableWith(i_4_6()));
assertTrue(i_4_6().isUnionableWith(i_3_5()));
// Contiguous intervals
assertTrue(i_3_5().isUnionableWith(i_6_9()));
assertTrue(i_6_9().isUnionableWith(i_3_5()));
}
@Test(expected = IllegalArgumentException.class)
public void unionFailsOnNonUnionableIntervals() {
i_0_2().union(i_6_9());
}
@Test
public void unionWorksWhenOneIntervalContainsTheOther() {
assertEquals(i_0_9(), i_0_9().union(i_3_5()));
assertEquals(i_0_9(), i_3_5().union(i_0_9()));
}
@Test
public void unionWorksWithASingleInterval() {
assertEquals(i_3_5(), i_3_5().union(i_3_5()).union(i_3_5()));
}
@Test
public void unionWorksWhenOneIntervalIsContiguousWithTheOther() {
Interval1D i = i_0_2().union(i_6_9().union(i_3_5()));
assertEquals(i_0_9(), i);
}
@Test
public void equalsIsStructural() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
int a = rng.nextInt(), b = rng.nextInt();
Interval1D int1 = new Interval1D(min(a, b), max(a, b));
Interval1D int2 = new Interval1D(min(a, b), max(a, b));
Interval1D int3 = new Interval1D(min(a, b) + 1, max(a, b) + 1);
assertTrue(int1.equals(int2));
assertFalse(int1.equals(int3));
}
}
@Test
public void hashCodeAndEqualsAreCompatible() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
int a = rng.nextInt(), b = rng.nextInt();
int c = rng.nextInt(), d = rng.nextInt();
Interval1D int1 = new Interval1D(min(a, b), max(a, b));
Interval1D int2 = new Interval1D(min(c, d), max(c, d));
Interval1D int3 = new Interval1D(min(c, d), max(c, d));
assertTrue(hashCodeIsCompatibleWithEquals(int1, int2));
assertTrue(hashCodeIsCompatibleWithEquals(int2, int3));
}
}
}

View File

@ -0,0 +1,227 @@
package ch.epfl.alpano;
import static org.junit.Assert.*;
import java.util.HashMap;
import org.junit.Test;
public class Interval2DTest {
@Test(expected= NullPointerException.class)
public void testInterval2D() {
new Interval2D(null, null);
}
@Test
public void testContains() {
HashMap<int[][], int[]> testValues = new HashMap<int[][], int[]>();
testValues.put(new int[][] { {0,4}, {0,4} }, new int[] {2,4});
testValues.put(new int[][] { {0,0}, {0,0} }, new int[] {0,0});
for (int[][] values : testValues.keySet()){
assertEquals ( true ,
new Interval2D(
new Interval1D(values[0][0], values[0][1]),
new Interval1D(values[1][0], values[1][1])).contains(testValues.get(values)[0],
testValues.get(values)[1])
);
}
testValues = new HashMap<int[][], int[]>();
testValues.put(new int[][] { {0,4}, {0,4} }, new int[] {-1,-1});
testValues.put(new int[][] { {0,0}, {0,0} }, new int[] {0,1});
for (int[][] values : testValues.keySet()){
assertEquals ( false,
new Interval2D(
new Interval1D(values[0][0], values[0][1]),
new Interval1D(values[1][0], values[1][1])).contains(testValues.get(values)[0],
testValues.get(values)[1])
);
}
}
@Test
public void testSize() {
HashMap<int[][], Integer> testValues = new HashMap<int[][], Integer>();
testValues.put(new int[][] { {0,4}, {0,4} }, 25);
testValues.put(new int[][] { {0,0}, {0,0} }, 1);
for (int[][] values : testValues.keySet()){
assertEquals ( (int)testValues.get(values),
new Interval2D(
new Interval1D(values[0][0], values[0][1]),
new Interval1D(values[1][0], values[1][1])).size()
);
}
}
@Test
public void testSizeOfIntersectionWith() {
HashMap<int[][][], Integer> testValues = new HashMap<int[][][], Integer>();
testValues.put( new int[][][] { {{0,5}, {0,5}} , {{4,6},{4,6}} } , 4);
testValues.put( new int[][][] { {{0,0}, {0,0}} , {{-40,40},{-40,40}} } , 1);
testValues.put( new int[][][] { {{0,5}, {0,5}} , {{10,15},{10,15}} } , 0);
for (int[][][] values : testValues.keySet()){
assertEquals ( (int)testValues.get(values),
new Interval2D(
new Interval1D(values[0][0][0], values[0][0][1]),
new Interval1D(values[0][1][0], values[0][1][1])).sizeOfIntersectionWith(
new Interval2D(
new Interval1D(values[1][0][0], values[1][0][1]),
new Interval1D(values[1][1][0], values[1][1][1])))
);
};
}
@Test
public void testBoundingUnion() {
HashMap<int[][][], int[][]> testValues = new HashMap<int[][][], int[][]>();
testValues.put( new int[][][] {{{0,5}, {0,5}} , {{4,6},{4,6}} } , new int[][] {{0,6},{0,6}} );
testValues.put( new int[][][] {{{0,0}, {0,0}} , {{4,6},{4,6}} } , new int[][] {{0,6},{0,6}} );
for (int[][][] values : testValues.keySet()){
assertEquals (true,
new Interval2D(
new Interval1D(values[0][0][0], values[0][0][1]),
new Interval1D(values[0][1][0], values[0][1][1])).boundingUnion(
new Interval2D(
new Interval1D(values[1][0][0], values[1][0][1]),
new Interval1D(values[1][1][0], values[1][1][1]))).equals(
new Interval2D(
new Interval1D(testValues.get(values)[0][0], testValues.get(values)[0][1]),
new Interval1D(testValues.get(values)[1][0], testValues.get(values)[1][1]))
));
};
}
@Test
public void testIsUnionableWith() {
HashMap<int[][][], Boolean> testValues = new HashMap<int[][][], Boolean>();
testValues.put( new int[][][] {{{0,5}, {0,5}} , {{4,6},{4,6}} } ,false);
testValues.put( new int[][][] {{{0,0}, {0,0}} , {{1,6},{1,6}} } , false );
for (int[][][] values : testValues.keySet()){
assertEquals ( true,
testValues.get(values) == new Interval2D(
new Interval1D(values[0][0][0], values[0][0][1]),
new Interval1D(values[0][1][0], values[0][1][1])).isUnionableWith(
new Interval2D(
new Interval1D(values[1][0][0], values[1][0][1]),
new Interval1D(values[1][1][0], values[1][1][1]))));
};
}
@Test(expected = IllegalArgumentException.class)
public void testUnion() {
HashMap<int[][][], int[][]> testValues = new HashMap<int[][][], int[][]>();
testValues.put( new int[][][] {{{0,5}, {0,5}} , {{4,6},{4,6}} } , new int[][] {{4,5},{4,5}} );
testValues.put( new int[][][] {{{-100,-100}, {0,0}} , {{0,0},{100,100}} } , new int[][] {{0,0},{0,0}} );
testValues.put( new int[][][] {{{-100,100}, {0,0}} , {{0,0},{-100,100}} } , new int[][] {{0,0},{0,0}} );
for (int[][][] values : testValues.keySet()){
assertEquals (false,
new Interval2D(
new Interval1D(values[0][0][0], values[0][0][1]),
new Interval1D(values[0][1][0], values[0][1][1])).union(
new Interval2D(
new Interval1D(values[1][0][0], values[1][0][1]),
new Interval1D(values[1][1][0], values[1][1][1]))).equals(
new Interval2D(
new Interval1D(testValues.get(values)[0][0], testValues.get(values)[0][1]),
new Interval1D(testValues.get(values)[1][0], testValues.get(values)[1][1]))
));
};
new Interval2D(
new Interval1D(0, 0),
new Interval1D(0, 0)).union(
new Interval2D(
new Interval1D(1, 1),
new Interval1D(1, 1)));
}
@Test
public void testEqualsObject() {
HashMap<int[][][], Boolean> testValues = new HashMap<int[][][], Boolean>();
testValues.put( new int[][][] {{{0,5}, {0,5}} , {{0,5},{0,5}} } ,true );
testValues.put( new int[][][] {{{0,5}, {0,5}} , {{0,5},{0,6}} } , false );
for (int[][][] values : testValues.keySet()){
assertEquals ( testValues.get(values) , new Interval2D(
new Interval1D(values[0][0][0], values[0][0][1]),
new Interval1D(values[0][1][0], values[0][1][1])).equals(
new Interval2D(
new Interval1D(values[1][0][0], values[1][0][1]),
new Interval1D(values[1][1][0], values[1][1][1]))) );
};
}
@Test
public void testToString() {
HashMap<int[][], String> testValues = new HashMap<int[][], String>();
testValues.put(new int[][] { {0,4}, {0,4} }, "[0..4]x[0..4]");
for (int[][] values : testValues.keySet()){
assertEquals (testValues.get(values),
new Interval2D(
new Interval1D(values[0][0], values[0][1]),
new Interval1D(values[1][0], values[1][1])).toString()
);
}
}
}

View File

@ -0,0 +1,209 @@
package ch.epfl.alpano;
import static ch.epfl.test.ObjectTest.hashCodeIsCompatibleWithEquals;
import static ch.epfl.test.TestRandomizer.RANDOM_ITERATIONS;
import static ch.epfl.test.TestRandomizer.newRandom;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Random;
import org.junit.Test;
public class Interval2DTestP {
private static Interval2D newInterval2D(int x1, int x2, int y1, int y2) {
return new Interval2D(new Interval1D(x1, x2), new Interval1D(y1, y2));
}
private static Interval2D i_0_10_0_10() {
return newInterval2D(0, 10, 0, 10);
}
private static Interval2D i_0_9_0_11() {
return newInterval2D(0, 9, 0, 11);
}
private static Interval2D i_0_10_11_20() {
return newInterval2D(0, 10, 11, 20);
}
private static Interval2D i_0_10_0_20() {
return newInterval2D(0, 10, 0, 20);
}
private static Interval2D i_11_20_0_10() {
return newInterval2D(11, 20, 0, 10);
}
private static Interval2D i_0_20_0_10() {
return newInterval2D(0, 20, 0, 10);
}
private static Interval2D i_2_2_2_2() {
return newInterval2D(2, 2, 2, 2);
}
@Test(expected = NullPointerException.class)
public void constructorFailsOnInvalidInterval() {
new Interval2D(null, null);
}
@Test
public void containsWorksOnKnownIntervals() {
Interval2D i = i_2_2_2_2();
for (int x = 1; x <= 3; ++x) {
for (int y = 1; y <= 3; ++y) {
assertEquals(x == 2 && y == 2, i.contains(x, y));
}
}
}
@Test
public void sizeWorksOnKnownIntervals() {
assertEquals(1, i_2_2_2_2().size());
assertEquals(21 * 11, i_0_20_0_10().size());
assertEquals(10 * 11, i_11_20_0_10().size());
}
@Test
public void sizeOfIntersectionWorksOnNonIntersectingIntervals() {
assertEquals(0, i_2_2_2_2().sizeOfIntersectionWith(i_11_20_0_10()));
assertEquals(0, i_11_20_0_10().sizeOfIntersectionWith(i_2_2_2_2()));
}
@Test
public void sizeOfIntersectionWorksOnIntersectingIntervals() {
assertEquals(1, i_2_2_2_2().sizeOfIntersectionWith(i_2_2_2_2()));
assertEquals(21 * 11, i_0_20_0_10().sizeOfIntersectionWith(i_0_20_0_10()));
assertEquals(1, i_2_2_2_2().sizeOfIntersectionWith(i_0_20_0_10()));
assertEquals(1, i_0_20_0_10().sizeOfIntersectionWith(i_2_2_2_2()));
assertEquals(10 * 11, i_0_10_0_10().sizeOfIntersectionWith(i_0_9_0_11()));
}
@Test
public void boudingUnionWorksOnKnownIntervals() {
assertEquals(i_2_2_2_2(), i_2_2_2_2().boundingUnion(i_2_2_2_2()));
Interval2D i1 = i_0_10_0_10().boundingUnion(i_0_9_0_11());
assertEquals(0, i1.iX().includedFrom());
assertEquals(10, i1.iX().includedTo());
assertEquals(0, i1.iY().includedFrom());
assertEquals(11, i1.iY().includedTo());
Interval2D i2 = i_2_2_2_2().boundingUnion(i_11_20_0_10());
assertEquals(2, i2.iX().includedFrom());
assertEquals(20, i2.iX().includedTo());
assertEquals(0, i2.iY().includedFrom());
assertEquals(10, i2.iY().includedTo());
}
@Test
public void isUnionableWorksOnKnownUnionableIntervals() {
assertTrue(i_0_10_0_10().isUnionableWith(i_0_10_0_10()));
assertTrue(i_0_10_0_10().isUnionableWith(i_0_10_11_20()));
assertTrue(i_0_10_11_20().isUnionableWith(i_0_10_0_10()));
assertTrue(i_0_10_0_10().isUnionableWith(i_11_20_0_10()));
assertTrue(i_11_20_0_10().isUnionableWith(i_0_10_0_10()));
assertTrue(i_0_10_0_10().isUnionableWith(i_2_2_2_2()));
assertTrue(i_2_2_2_2().isUnionableWith(i_0_10_0_10()));
}
@Test
public void isUnionableWorksOnKnownNonUnionableIntervals() {
assertFalse(i_2_2_2_2().isUnionableWith(i_11_20_0_10()));
assertFalse(i_11_20_0_10().isUnionableWith(i_2_2_2_2()));
assertFalse(i_0_9_0_11().isUnionableWith(i_0_10_0_10()));
assertFalse(i_0_10_0_10().isUnionableWith(i_0_9_0_11()));
}
@Test
public void isUnionableWithIsReflexive() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
Interval2D interval = nextInterval(rng, 500, 1000);
assertTrue(interval.isUnionableWith(interval));
}
}
@Test
public void isUnionableWithIsSymmetric() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
Interval2D i1 = nextInterval(rng, 5, 10);
Interval2D i2 = nextInterval(rng, 5, 10);
assertTrue(! i1.isUnionableWith(i2) || i2.isUnionableWith(i1));
}
}
@Test(expected = IllegalArgumentException.class)
public void unionFailsOnNonUnionableIntervals() {
i_2_2_2_2().union(i_11_20_0_10());
}
@Test
public void unionWorksOnASingleInterval() {
assertEquals(i_0_10_0_10(), i_0_10_0_10().union(i_0_10_0_10().union(i_0_10_0_10())));
}
@Test
public void unionWorksOnKnownIntervals() {
assertEquals(i_0_10_0_10(), i_0_10_0_10().union(i_2_2_2_2()));
assertEquals(i_0_10_0_10(), i_2_2_2_2().union(i_0_10_0_10()));
assertEquals(i_0_10_0_20(), i_0_10_0_10().union(i_0_10_11_20()));
assertEquals(i_0_10_0_20(), i_0_10_11_20().union(i_0_10_0_10()));
assertEquals(i_0_20_0_10(), i_0_10_0_10().union(i_11_20_0_10()));
assertEquals(i_0_20_0_10(), i_11_20_0_10().union(i_0_10_0_10()));
}
@Test
public void unionIsCommutative() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
Interval2D i1 = nextInterval(rng, 5, 10);
Interval2D i2 = nextInterval(rng, 5, 10);
if (i1.isUnionableWith(i2))
assertEquals(i1.union(i2), i2.union(i1));
}
}
@Test
public void equalsIsStructural() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
int x1 = rng.nextInt(1000) - 500;
int x2 = x1 + rng.nextInt(1000);
int y1 = rng.nextInt(1000) - 500;
int y2 = y1 + rng.nextInt(1000);
Interval2D int1 = newInterval2D(x1, x2, y1, y2);
Interval2D int2 = newInterval2D(x1, x2, y1, y2);
Interval2D int3 = newInterval2D(x1, x2, y1, y2 + 1);
assertTrue(int1.equals(int2));
assertTrue(int2.equals(int1));
assertFalse(int1.equals(int3));
assertFalse(int3.equals(int1));
}
}
@Test
public void hashCodeAndEqualsAreCompatible() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
int a = rng.nextInt(1000) - 500, b = a + rng.nextInt(20);
int c = rng.nextInt(1000) - 500, d = c + rng.nextInt(20);
Interval2D i1 = newInterval2D(a, b, c, d);
Interval2D i1b = newInterval2D(a, b, c, d);
Interval2D i2 = newInterval2D(a, b, c, d + 1);
assertTrue(hashCodeIsCompatibleWithEquals(i1, i1b));
assertTrue(hashCodeIsCompatibleWithEquals(i1, i2));
}
}
private Interval2D nextInterval(Random rng, int maxOffset, int maxSize) {
int offsetX = rng.nextInt(maxOffset), offsetY = rng.nextInt(maxOffset);
int sizeX = rng.nextInt(maxSize), sizeY = rng.nextInt(maxSize);
return newInterval2D(-offsetX, sizeX - offsetX, -offsetY, sizeY - offsetY);
}
}

View File

@ -0,0 +1,217 @@
package ch.epfl.alpano;
import static ch.epfl.alpano.Math2.angularDistance;
import static ch.epfl.alpano.Math2.bilerp;
import static ch.epfl.alpano.Math2.firstIntervalContainingRoot;
import static ch.epfl.alpano.Math2.floorMod;
import static ch.epfl.alpano.Math2.haversin;
import static ch.epfl.alpano.Math2.improveRoot;
import static ch.epfl.alpano.Math2.lerp;
import static ch.epfl.alpano.Math2.sq;
import static ch.epfl.test.TestRandomizer.RANDOM_ITERATIONS;
import static ch.epfl.test.TestRandomizer.newRandom;
import static java.lang.Math.PI;
import static java.lang.Math.cos;
import static java.lang.Math.floor;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.sin;
import static java.lang.Math.toRadians;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Random;
import java.util.function.DoubleUnaryOperator;
import org.junit.Test;
public class Math2Test {
@Test
public void sqSquaresRandomValues() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double x = rng.nextDouble() * 1_000d - 500d;
assertEquals(x * x, sq(x), 1e-10);
}
}
@Test
public void floorModWorksOnRandomValues() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double n = rng.nextDouble() * 1_000d - 500d;
double d = 0;
while (d == 0)
d = rng.nextDouble() * 1_000d - 500d;
double q = (int)floor(n / d);
double r = floorMod(n, d);
assertEquals(n, q * d + r, 1e-10);
}
}
@Test
public void haversinWorksOnRandomAngles() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double a = nextAngle(rng);
double h = (1d - cos(a)) / 2d;
assertEquals(h, haversin(a), 1e-10);
}
}
@Test
public void angularDistanceWorksOnKnownAngles() {
double data[] = {
0, 45, 45,
45, 0, -45,
0, 179, 179,
0, 181, -179,
181, 359, 178,
181, 2, -179
};
for (int i = 0; i < data.length; i += 3) {
double a1 = toRadians(data[i]);
double a2 = toRadians(data[i + 1]);
double expectedD = toRadians(data[i + 2]);
assertEquals(expectedD, angularDistance(a1, a2), 1e-10);
}
}
@Test
public void angularDistanceIsInExpectedRange() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double a1 = nextAngle(rng);
double a2 = nextAngle(rng);
double d = angularDistance(a1, a2);
assertTrue(-PI <= d && d < PI);
}
}
@Test
public void angularDistanceIsSymmetric() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double a1 = nextAngle(rng);
double a2 = nextAngle(rng);
assertEquals(0, angularDistance(a1, a2) + angularDistance(a2, a1), 1e-10);
}
}
@Test
public void lerpIsFirstValueAtStart() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double v1 = (rng.nextDouble() - 0.5) * 1000d;
double v2 = (rng.nextDouble() - 0.5) * 1000d;
assertEquals(v1, lerp(v1, v2, 0), 1e-10);
}
}
@Test
public void lerpIsAverageValueAtMiddle() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double v1 = (rng.nextDouble() - 0.5) * 1000d;
double v2 = (rng.nextDouble() - 0.5) * 1000d;
assertEquals((v1 + v2) / 2d, lerp(v1, v2, 0.5), 1e-10);
}
}
@Test
public void lerpIsSecondValueAtEnd() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double v1 = (rng.nextDouble() - 0.5) * 1000d;
double v2 = (rng.nextDouble() - 0.5) * 1000d;
assertEquals(v2, lerp(v1, v2, 1), 1e-10);
}
}
@Test
public void lerpIsInExpectedRange() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double v1 = (rng.nextDouble() - 0.5) * 1000d;
double v2 = (rng.nextDouble() - 0.5) * 1000d;
double p = rng.nextDouble();
double v = lerp(v1, v2, p);
assertTrue(min(v1, v2) <= v && v <= max(v1, v2));
}
}
@Test
public void bilerpIsInExpectedRange() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double v1 = (rng.nextDouble() - 0.5) * 1000d;
double v2 = (rng.nextDouble() - 0.5) * 1000d;
double v3 = (rng.nextDouble() - 0.5) * 1000d;
double v4 = (rng.nextDouble() - 0.5) * 1000d;
double x = rng.nextDouble(), y = rng.nextDouble();
double v = bilerp(v1, v2, v3, v4, x, y);
assertTrue(min(min(v1, v2), min(v3, v4)) <= v
&& v <= max(max(v1, v2), max(v3, v4)));
}
}
@Test
public void bilerpIsCorrectInCorners() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double v0 = rng.nextDouble(), v1 = rng.nextDouble();
double v2 = rng.nextDouble(), v3 = rng.nextDouble();
assertEquals(v0, bilerp(v0, v1, v2, v3, 0, 0), 1e-10);
assertEquals(v1, bilerp(v1, v1, v2, v3, 1, 0), 1e-10);
assertEquals(v2, bilerp(v2, v1, v2, v3, 0, 1), 1e-10);
assertEquals(v3, bilerp(v3, v1, v2, v3, 1, 1), 1e-10);
}
}
@Test
public void bilerpLerpsAlongSides() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double v0 = rng.nextDouble(), v1 = rng.nextDouble();
double v2 = rng.nextDouble(), v3 = rng.nextDouble();
assertEquals((v0 + v1)/2d, bilerp(v0, v1, v2, v3, 0.5, 0), 1e-10);
assertEquals((v0 + v2)/2d, bilerp(v0, v1, v2, v3, 0, 0.5), 1e-10);
assertEquals((v2 + v3)/2d, bilerp(v0, v1, v2, v3, 0.5, 1), 1e-10);
assertEquals((v1 + v3)/2d, bilerp(v0, v1, v2, v3, 1, 0.5), 1e-10);
}
}
@Test
public void firstIntervalContainingRootWorksOnSin() {
double i1 = firstIntervalContainingRoot(new Sin(), -1d, 1d, 0.1 + 1e-11);
assertEquals(-0.1, i1, 1e-10);
double i2 = firstIntervalContainingRoot(new Sin(), 1, 4, 1);
assertEquals(3, i2, 0);
}
@Test(expected = IllegalArgumentException.class)
public void improveRootFailsWhenIntervalDoesNotContainRoot() {
improveRoot(new Sin(), 1, 2, 1e-10);
}
@Test
public void improveRootWorksOnSin() {
double pi = improveRoot(new Sin(), 3.1, 3.2, 1e-10);
assertEquals(PI, pi, 1e-10);
double mPi = improveRoot(new Sin(), -4, -3.1, 1e-10);
assertEquals(-PI, mPi, 1e-10);
}
private static double nextAngle(Random rng) {
return rng.nextDouble() * 2d * PI;
}
}
class Sin implements DoubleUnaryOperator {
@Override
public double applyAsDouble(double x) {
return sin(x);
}
}

View File

@ -0,0 +1,45 @@
package ch.epfl.alpano;
import static org.junit.Assert.*;
import org.junit.Test;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.ElevationProfile;
public class PanoramaCompTest {
@Test(expected = NullPointerException.class)
public void throwsNullPointerUponConstruction(){
new PanoramaComputer(null);
}
@Test
public void givesTrivialZero(){
double zero =PanoramaComputer.rayToGroundDistance(
new ElevationProfile( new ContinuousElevationModel(new TestDemForPanoramaComp(new Interval2D(new Interval1D(-100, 100),
new Interval1D(-100, 100)))),
new GeoPoint(0,0),2.5,100), 0.0, 0.0).applyAsDouble(
1);
assertEquals(0, zero,0.01);
}
@Test(expected = IllegalArgumentException.class)
public void throwsIllegalArgumentExceptionOnNegValues(){
double zero =PanoramaComputer.rayToGroundDistance(
new ElevationProfile( new ContinuousElevationModel(new TestDemForPanoramaComp(new Interval2D(new Interval1D(-100, 100),
new Interval1D(-100, 100)))),
new GeoPoint(0,0),0,100), 0.0, 1.0).applyAsDouble(
-50);
assertEquals(0, zero,0);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,95 @@
package ch.epfl.alpano;
import static org.junit.Assert.*;
import org.junit.Test;
public class PanoramaParametersTest {
private GeoPoint vp = new GeoPoint(Math.toRadians(6.8087),Math.toRadians(47.0085));
private PanoramaParameters pp = new PanoramaParameters(vp, 1380, Math.toRadians(162),
Math.toRadians(27), 300, 2500, 800);
@Test
public void testAzimuthForX() {
assertEquals(pp.azimuthForX(1),Math.toRadians(148.5108043217287),0.000001);
}
@Test
public void testXForAzimuth() {
assertEquals(pp.xForAzimuth(pp.azimuthForX(1)),1,0.000000001);
}
@Test(expected = IllegalArgumentException.class)
public void testXForAzimuthFails() {
pp.xForAzimuth(Math.toRadians(27-81.5));
}
@Test
public void testAltitudeForY() {
assertEquals(pp.altitudeForY(0),Math.toRadians(4.316326530612245),0.00000001);
}
@Test
public void testYForAltitude() {
assertEquals(pp.yForAltitude(pp.altitudeForY(1)),1,0.000000001);
}
@Test
public void testIsValidSampleIndexFails1() {
assertEquals(pp.isValidSampleIndex(2500, 800),false);
}
@Test
public void testIsValidSampleIndexFails2() {
assertEquals(pp.isValidSampleIndex(-1, -1),false);
}
@Test
public void testLinearSampleIndexSucces() {
assertEquals(pp.linearSampleIndex(0, 0),0);
assertEquals(pp.linearSampleIndex(2500-1, 800-1),(2500*800)-1);
}
@Test
public void testObserverPosition() {
assertEquals(pp.observerPosition(),vp);
}
@Test
public void testObserverElevation() {
assertEquals(pp.observerElevation(),1380,0);
}
@Test
public void testCenterAzimuth() {
assertEquals(pp.centerAzimuth(),Math.toRadians(162),0.00000001);
}
@Test
public void testHorizontalFieldOfView() {
assertEquals(pp.horizontalFieldOfView(),Math.toRadians(27),0.0000001);
}
@Test
public void testVerticalFieldOfView() {
assertEquals(pp.verticalFieldOfView(),Math.toRadians(8.63265306122),0.000000001);
}
@Test
public void testMaxDistance() {
assertEquals(pp.maxDistance(),300,0);
}
@Test
public void testWidth() {
assertEquals(pp.width(),2500,0);
}
@Test
public void testHeight() {
assertEquals(pp.height(),800,0);
}
}

View File

@ -0,0 +1,167 @@
package ch.epfl.alpano;
import static ch.epfl.test.TestRandomizer.RANDOM_ITERATIONS;
import static ch.epfl.test.TestRandomizer.newRandom;
import static java.lang.Math.PI;
import static java.lang.Math.floorMod;
import static java.lang.Math.nextUp;
import static java.lang.Math.toRadians;
import static org.junit.Assert.assertEquals;
import java.util.Random;
import org.junit.Test;
public class PanoramaParametersTestP {
// Default (and valid) arguments for constructor
private static GeoPoint O_POS() { return new GeoPoint(toRadians(4), toRadians(4)); }
private static int O_EL = 1000;
private static double C_AZ = toRadians(180);
private static double H_FOV = toRadians(60);
private static int MAX_D = 1000;
private static int W = 100, H = 100;
@Test(expected = NullPointerException.class)
public void constructorFailsWithNullObserverPosition() {
new PanoramaParameters(null, O_EL, C_AZ, H_FOV, MAX_D, W, H);
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithNonCanonicalAzimuth() {
new PanoramaParameters(O_POS(), O_EL, 42d, H_FOV, MAX_D, W, H);
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithZeroFieldOfView() {
new PanoramaParameters(O_POS(), O_EL, C_AZ, 0, MAX_D, W, H);
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithTooLargeOfView() {
new PanoramaParameters(O_POS(), O_EL, C_AZ, nextUp(2d * PI), MAX_D, W, H);
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithZeroWidth() {
new PanoramaParameters(O_POS(), O_EL, C_AZ, H_FOV, MAX_D, 0, H);
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithZeroHeight() {
new PanoramaParameters(O_POS(), O_EL, C_AZ, H_FOV, MAX_D, W, 0);
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithZeroMaxDistance() {
new PanoramaParameters(O_POS(), O_EL, C_AZ, H_FOV, 0, W, H);
}
@Test
public void verticalFieldOfViewIsCorrect() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, C_AZ, toRadians(60), MAX_D, 601, 201);
assertEquals(p.verticalFieldOfView(), toRadians(20), 1e-10);
}
@Test(expected = IllegalArgumentException.class)
public void azimuthForXFailsForNegativeX() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, C_AZ, H_FOV, MAX_D, W, H);
p.azimuthForX(-1);
}
@Test(expected = IllegalArgumentException.class)
public void azimuthForXFailsForTooBigX() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, C_AZ, H_FOV, MAX_D, W, H);
p.azimuthForX(W + 1);
}
@Test
public void azimuthForXWorksForFullCircle() {
int centralAzDeg = 90;
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, toRadians(centralAzDeg), Math2.PI2, MAX_D, 361, 201);
for (int azDeg = 0; azDeg < 360; ++azDeg) {
double expectedAz = toRadians(floorMod(azDeg - centralAzDeg, 360));
double actualAz = p.azimuthForX(azDeg);
assertEquals(expectedAz, actualAz, 1e-10);
}
}
@Test(expected = IllegalArgumentException.class)
public void xForAzimuthFailsForTooSmallAzimuth() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, toRadians(10), toRadians(40), MAX_D, W, H);
p.xForAzimuth(toRadians(349.99));
}
@Test(expected = IllegalArgumentException.class)
public void xForAzimuthFailsForTooBigAzimuth() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, toRadians(10), toRadians(40), MAX_D, W, H);
p.xForAzimuth(toRadians(50.01));
}
@Test(expected = IllegalArgumentException.class)
public void altitudeForYFailsForNegativeY() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, C_AZ, H_FOV, MAX_D, W, H);
p.altitudeForY(-1);
}
@Test(expected = IllegalArgumentException.class)
public void altitueForYFailsForTooBigY() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, C_AZ, H_FOV, MAX_D, W, H);
p.altitudeForY(H + 1);
}
@Test
public void altitudeForYWorks() {
int height = 201;
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, C_AZ, toRadians(60), MAX_D, 601, height);
double halfVerticalFOV = toRadians(20) / 2d;
double delta = toRadians(0.1);
for (int y = 0; y < height; ++y) {
assertEquals(halfVerticalFOV - y * delta, p.altitudeForY(y), 1e-9);
}
}
@Test(expected = IllegalArgumentException.class)
public void yForAltitudeFailsForTooSmallAltitude() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, C_AZ, toRadians(60), MAX_D, 601, 201);
p.yForAltitude(toRadians(-10.01));
}
@Test(expected = IllegalArgumentException.class)
public void yForAltitudeFailsForTooBigAltitude() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, C_AZ, toRadians(60), MAX_D, 601, 201);
p.yForAltitude(toRadians(10.01));
}
@Test
public void azimuthForXAndXForAzimuthAreInverse() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, C_AZ, toRadians(60), MAX_D, 601, 201);
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
int x = 1 + rng.nextInt(600);
assertEquals(x, p.xForAzimuth(p.azimuthForX(x)), 1e-10);
}
}
@Test
public void altitudeForYAndYForAltitudeAreInverse() {
PanoramaParameters p =
new PanoramaParameters(O_POS(), O_EL, C_AZ, toRadians(60), MAX_D, 601, 201);
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
int x = 1 + rng.nextInt(200);
assertEquals(x, p.yForAltitude(p.altitudeForY(x)), 1e-10);
}
}
}

View File

@ -0,0 +1,104 @@
package ch.epfl.alpano;
import static ch.epfl.test.TestRandomizer.newRandom;
import static java.lang.Math.toRadians;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import java.util.Random;
import org.junit.Test;
public class PanoramaTestP{
private static PanoramaParameters PARAMS() {
return new PanoramaParameters(
new GeoPoint(toRadians(46), toRadians(6)),
1000,
toRadians(180),
toRadians(60),
100_000,
9,
7);
}
@Test(expected = NullPointerException.class)
public void builderFailsWithNullParameters() {
new Panorama.Builder(null);
}
@Test
public void builderConstructorCorrectlyInitializesSamples() {
Panorama p = new Panorama.Builder(PARAMS()).build();
assertEquals(Float.POSITIVE_INFINITY, p.distanceAt(0, 0), 0);
assertEquals(0, p.longitudeAt(0, 0), 0);
assertEquals(0, p.latitudeAt(0, 0), 0);
assertEquals(0, p.elevationAt(0, 0), 0);
assertEquals(0, p.slopeAt(0, 0), 0);
}
@Test(expected = IndexOutOfBoundsException.class)
public void setDistanceAtFailsWithInvalidIndex() {
Panorama.Builder b = new Panorama.Builder(PARAMS());
b.setDistanceAt(10, 0, 1);
}
@Test
public void setLongitudeAtReturnsThis() {
Panorama.Builder b = new Panorama.Builder(PARAMS());
assertSame(b, b.setLongitudeAt(0, 0, 1));
}
@Test(expected = IllegalStateException.class)
public void setSlopeAtFailsAfterBuild() {
Panorama.Builder b = new Panorama.Builder(PARAMS());
b.build();
b.setSlopeAt(0, 0, 0);
}
@Test(expected = IllegalStateException.class)
public void buildFailsAfterBuild() {
Panorama.Builder b = new Panorama.Builder(PARAMS());
b.build();
b.build();
}
@Test
public void parametersReturnsParameters() {
PanoramaParameters ps = PARAMS();
Panorama p = new Panorama.Builder(ps).build();
assertSame(ps, p.parameters());
}
@Test
public void builderSettersWork() {
PanoramaParameters ps = PARAMS();
float[] values = new float[ps.width() * ps.height()];
Random rng = newRandom();
for (int i = 0; i < values.length; ++i)
values[i] = rng.nextFloat() + 0.5f;
Panorama.Builder b = new Panorama.Builder(ps);
for (int x = 0; x < ps.width(); ++x) {
for (int y = 0; y < ps.height(); ++y) {
float v = values[y + x * ps.height()];
b.setDistanceAt(x, y, v)
.setElevationAt(x, y, v)
.setLatitudeAt(x, y, v)
.setLongitudeAt(x, y, v)
.setSlopeAt(x, y, v);
}
}
Panorama p = b.build();
for (int x = 0; x < ps.width(); ++x) {
for (int y = 0; y < ps.height(); ++y) {
float v = values[y + x * ps.height()];
assertEquals(v, p.distanceAt(x, y), 0);
assertEquals(v, p.elevationAt(x, y), 0);
assertEquals(v, p.latitudeAt(x, y), 0);
assertEquals(v, p.longitudeAt(x, y), 0);
assertEquals(v, p.slopeAt(x, y), 0);
}
}
}
}

View File

@ -0,0 +1,47 @@
package ch.epfl.alpano;
import static ch.epfl.alpano.Preconditions.*;
import org.junit.Test;
public class PreconditionsTest {
////////// checkArgument (1 argument)
@Test
public void checkArgument1SucceedsForTrue() {
checkArgument(true);
}
@Test(expected = IllegalArgumentException.class)
public void checkArgument1ThrowsForFalse() {
checkArgument(false);
}
////////// checkArgument (2 arguments)
@Test
public void checkArgument2SucceedsForTrue() {
checkArgument(true, "");
}
@Test(expected = IllegalArgumentException.class)
public void checkArgument2ThrowsForFalse() {
checkArgument(false, "");
}
////////// checkNonNul (1 arguments)
@Test
public void checkNonNulSuccess() {
checkNonNul(1, "");
}
@Test(expected = NullPointerException.class)
public void checkNonNulThrowsForZero() {
checkNonNul(0, "");
}
////////// checkNonNul (1 arguments)
}

View File

@ -0,0 +1,26 @@
package ch.epfl.alpano;
import ch.epfl.alpano.dem.DiscreteElevationModel;
public class TestDemForPanoramaComp implements DiscreteElevationModel {
private final Interval2D extent;
public TestDemForPanoramaComp(Interval2D extent) {
this.extent = extent;
}
@Override
public void close() throws Exception { }
@Override
public Interval2D extent() { return extent; }
@Override
public double elevationSample(int x, int y) {
return x*x-y*y;
}
}

View File

@ -0,0 +1,117 @@
package ch.epfl.alpano.dem;
import static ch.epfl.test.TestRandomizer.RANDOM_ITERATIONS;
import static ch.epfl.test.TestRandomizer.newRandom;
import static java.lang.Math.toRadians;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Random;
import org.junit.Test;
import ch.epfl.alpano.Interval1D;
import ch.epfl.alpano.Interval2D;
public class CompositeDiscreteElevationModelTest {
private final static Interval2D ext1 = new Interval2D(
new Interval1D(-100_000, 100_000),
new Interval1D(0, 100_000));
private final static Interval2D ext2 = new Interval2D(
new Interval1D(-100_000, 100_000),
new Interval1D(100_001, 200_000));
private final static Interval2D ext12 = new Interval2D(
new Interval1D(-100_000, 100_000),
new Interval1D(0, 200_000));
private final static Interval2D ext3 = new Interval2D(
new Interval1D(0, 99_999),
new Interval1D(0, 100_001));
@Test
public void samplesPerRadiansHasCorrectValue() {
assertEquals(206264.80624709636, DiscreteElevationModel.SAMPLES_PER_RADIAN, 1e-8);
}
@Test
public void sampleIndexWorksOnRandomValues() {
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
int arcSeconds = rng.nextInt(2_000_000) - 1_000_000;
double angle = toRadians(arcSeconds / 3_600d);
assertEquals(arcSeconds, DiscreteElevationModel.sampleIndex(angle), 1e-5);
}
}
@SuppressWarnings("resource")
@Test(expected = IllegalArgumentException.class)
public void unionFailsIfExtentsNotUnionable() {
ConstantElevationDEM dem1 = new ConstantElevationDEM(ext1, 0);
ConstantElevationDEM dem2 = new ConstantElevationDEM(ext3, 0);
dem1.union(dem2);
}
@SuppressWarnings("resource")
@Test
public void extentOfUnionIsUnionOfExtent() {
ConstantElevationDEM dem1 = new ConstantElevationDEM(ext1, 0);
ConstantElevationDEM dem2 = new ConstantElevationDEM(ext2, 0);
DiscreteElevationModel dem12 = dem1.union(dem2);
assertEquals(ext12, dem12.extent());
}
@SuppressWarnings("resource")
@Test(expected = IllegalArgumentException.class)
public void elevationSampleFailsWhenOutsideOfExtent() {
ConstantElevationDEM dem1 = new ConstantElevationDEM(ext1, 0);
ConstantElevationDEM dem2 = new ConstantElevationDEM(ext2, 0);
DiscreteElevationModel dem12 = dem1.union(dem2);
dem12.elevationSample(0, 200_001);
}
@SuppressWarnings("resource")
@Test
public void elevationSampleWorksOnBothSubDEMs() {
ConstantElevationDEM dem1 = new ConstantElevationDEM(ext1, 1);
ConstantElevationDEM dem2 = new ConstantElevationDEM(ext2, 2);
DiscreteElevationModel dem12 = dem1.union(dem2);
assertEquals(1, dem12.elevationSample(-100_000, 0), 0);
assertEquals(1, dem12.elevationSample(100_000, 0), 0);
assertEquals(1, dem12.elevationSample(100_000, 100_000), 0);
assertEquals(1, dem12.elevationSample(-100_000, 100_000), 0);
assertEquals(2, dem12.elevationSample(-100_000, 100_001), 0);
assertEquals(2, dem12.elevationSample(100_000, 100_001), 0);
assertEquals(2, dem12.elevationSample(100_000, 200_000), 0);
assertEquals(2, dem12.elevationSample(-100_000, 200_000), 0);
}
@SuppressWarnings("resource")
@Test
public void closeClosesBothSubDEMs() throws Exception {
ConstantElevationDEM dem1 = new ConstantElevationDEM(ext1, 0);
ConstantElevationDEM dem2 = new ConstantElevationDEM(ext2, 0);
DiscreteElevationModel dem12 = dem1.union(dem2);
dem12.close();
assertTrue(dem1.isClosed);
assertTrue(dem2.isClosed);
}
}
class ConstantElevationDEM implements DiscreteElevationModel {
private final Interval2D extent;
private final double elevation;
boolean isClosed = false;
public ConstantElevationDEM(Interval2D extent, double elevation) {
this.extent = extent;
this.elevation = elevation;
}
@Override
public void close() throws Exception { isClosed = true; }
@Override
public Interval2D extent() { return extent; }
@Override
public double elevationSample(int x, int y) { return elevation; }
}

View File

@ -0,0 +1,170 @@
package ch.epfl.alpano.dem;
import static ch.epfl.test.TestRandomizer.RANDOM_ITERATIONS;
import static ch.epfl.test.TestRandomizer.newRandom;
import static java.lang.Math.toDegrees;
import static java.lang.Math.toRadians;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Random;
import org.junit.Test;
import ch.epfl.alpano.GeoPoint;
import ch.epfl.alpano.Interval1D;
import ch.epfl.alpano.Interval2D;
public class ContinuousElevationModelTest {
private final static Interval2D EXT_100_100 = new Interval2D(
new Interval1D(0, 100),
new Interval1D(0, 100));
private final static Interval2D EXT_13_13 = new Interval2D(
new Interval1D(0, 13),
new Interval1D(0, 13));
@Test(expected = NullPointerException.class)
public void constructorFailsWithNullDEM() {
new ContinuousElevationModel(null);
}
@Test
public void elevationAtReturns0OutsideOfExtent() {
DiscreteElevationModel dDEM = new ConstantElevationDEM(EXT_100_100, 1000);
ContinuousElevationModel cDEM = new ContinuousElevationModel(dDEM);
assertEquals(0, cDEM.elevationAt(pointForSampleIndex(101, 0)), 0);
}
@Test
public void elevationAtReturnsCorrectElevationInsideExtent() {
double elevation = 1000;
DiscreteElevationModel dDEM = new ConstantElevationDEM(EXT_100_100, elevation);
ContinuousElevationModel cDEM = new ContinuousElevationModel(dDEM);
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double x = rng.nextDouble() * 100d, y = rng.nextDouble() * 100d;
assertEquals(elevation, cDEM.elevationAt(pointForSampleIndex(x, y)), 1e-10);
}
}
@Test
public void elevationAtInterpolatesJustOutsideExtent() {
DiscreteElevationModel dDEM = new ConstantElevationDEM(EXT_100_100, 1000);
ContinuousElevationModel cDEM = new ContinuousElevationModel(dDEM);
assertEquals(500, cDEM.elevationAt(pointForSampleIndex(100.5, 10)), 1e-10);
}
@Test
public void elevationAtReturnsCorrectInterpolatedElevation() {
DiscreteElevationModel dDEM = new ConstantSlopeDEM(EXT_100_100);
ContinuousElevationModel cDEM = new ContinuousElevationModel(dDEM);
Random rng = new Random();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double x = rng.nextDouble() * 100;
double y = rng.nextDouble() * 100;
assertEquals((x + y) * ConstantSlopeDEM.INTER_SAMPLE_DISTANCE, cDEM.elevationAt(pointForSampleIndex(x, y)), 1e-6);
}
}
@Test
public void elevationAtStaysWithinBoundsOnRandomTerrain() {
int maxElevation = 1000;
DiscreteElevationModel dDEM = new RandomElevationDEM(EXT_13_13, maxElevation);
ContinuousElevationModel cDEM = new ContinuousElevationModel(dDEM);
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double x = rng.nextDouble() * dDEM.extent().iX().size();
double y = rng.nextDouble() * dDEM.extent().iY().size();
double e = cDEM.elevationAt(pointForSampleIndex(x, y));
assertTrue(0 <= e && e <= maxElevation);
}
}
@Test
public void slopeAtReturnsCorrectInterpolatedSlope() {
DiscreteElevationModel dDEM = new ConstantSlopeDEM(EXT_100_100);
ContinuousElevationModel cDEM = new ContinuousElevationModel(dDEM);
Random rng = new Random();
double expectedSlope = Math.acos(1 / Math.sqrt(3));
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double x = 5 + rng.nextDouble() * 90;
double y = 5 + rng.nextDouble() * 90;
assertEquals(expectedSlope, cDEM.slopeAt(pointForSampleIndex(x, y)), 1e-4);
}
}
@Test
public void slopeAtStaysWithinBoundsOnRandomTerrain() {
int maxElevation = 1000;
DiscreteElevationModel dDEM = new RandomElevationDEM(EXT_13_13, maxElevation);
ContinuousElevationModel cDEM = new ContinuousElevationModel(dDEM);
Random rng = newRandom();
for (int i = 0; i < RANDOM_ITERATIONS; ++i) {
double x = rng.nextDouble() * dDEM.extent().iX().size();
double y = rng.nextDouble() * dDEM.extent().iY().size();
double e = toDegrees(cDEM.slopeAt(pointForSampleIndex(x, y)));
assertTrue(0 <= e && e < 90);
}
}
private static GeoPoint pointForSampleIndex(double x, double y) {
return new GeoPoint(toRadians(x / 3600d), toRadians(y / 3600d));
}
}
class RandomElevationDEM implements DiscreteElevationModel {
private final Interval2D extent;
private final double[][] elevations;
public RandomElevationDEM(Interval2D extent, int maxElevation) {
this.extent = extent;
this.elevations = randomElevations(extent.iX().size(), extent.iY().size(), maxElevation);
}
private static double[][] randomElevations(int width, int height, int maxElevation) {
Random rng = newRandom();
double[][] es = new double[width][height];
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
es[x][y] = rng.nextInt(maxElevation + 1);
}
}
return es;
}
@Override
public Interval2D extent() {
return extent;
}
@Override
public double elevationSample(int x, int y) {
return elevations[x][y];
}
@Override
public void close() throws Exception { }
}
class ConstantSlopeDEM implements DiscreteElevationModel {
public final static double INTER_SAMPLE_DISTANCE =
2d * Math.PI * 6_371_000d / (3600d * 360d);
private final Interval2D extent;
public ConstantSlopeDEM(Interval2D extent) {
this.extent = extent;
}
@Override
public Interval2D extent() { return extent; }
@Override
public double elevationSample(int x, int y) {
return (x + y) * INTER_SAMPLE_DISTANCE;
}
@Override
public void close() throws Exception {}
}

View File

@ -0,0 +1,70 @@
package ch.epfl.alpano.dem;
import static java.lang.Math.toRadians;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import javax.imageio.ImageIO;
import ch.epfl.alpano.GeoPoint;
final class DrawElevationProfile {
final static File HGT_FILE = new File("HGT"+File.separatorChar+"N46E006.hgt");
final static double MAX_ELEVATION = 1_500;
final static int LENGTH = 111_000;
final static double AZIMUTH = toRadians(27.97);
final static double LONGITUDE = toRadians(6.15432);
final static double LATITUDE = toRadians(46.20562);
final static int WIDTH = 800, HEIGHT = 100;
public static void main(String[] as) throws Exception {
DiscreteElevationModel dDEM =
new HgtDiscreteElevationModel(HGT_FILE);
ContinuousElevationModel cDEM =
new ContinuousElevationModel(dDEM);
GeoPoint o =
new GeoPoint(LONGITUDE, LATITUDE);
ElevationProfile p1 =
new ElevationProfile(cDEM, o, AZIMUTH, (int)(LENGTH/3));
GeoPoint gP1 = p1.positionAt((int)LENGTH/3);
ElevationProfile p2 =
new ElevationProfile(cDEM, p1.positionAt((int)LENGTH/3), AZIMUTH+toRadians(6.0),2*((int)LENGTH/3));
GeoPoint gP2 = p2.positionAt(2*((int)LENGTH/3));
ElevationProfile p3 = new ElevationProfile (cDEM,p2.positionAt(2*((int)LENGTH/3)), AZIMUTH, LENGTH);
ArrayList<GeoPoint> trekkingSpots = new ArrayList<GeoPoint>(Arrays.asList(o,gP1,gP2));
ArrayList<ElevationProfile> profiles= new ArrayList<ElevationProfile>();
for (int index = 0; index < trekkingSpots.size()-1; index++){
GeoPoint current= trekkingSpots.get(index);
GeoPoint next = trekkingSpots.get(index+1);
ElevationProfile evP= new ElevationProfile(cDEM, current,current.azimuthTo(next), current.distanceTo(next));
profiles.add(evP);
}
CompositElevationProfile p = new CompositElevationProfile(profiles);
int BLACK = 0x00_00_00, WHITE = 0xFF_FF_FF;
BufferedImage i =
new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < WIDTH; ++x) {
double pX = x * (double) LENGTH / (WIDTH - 1);
double pY = p.elevationAt(pX);
int yL = (int)((pY / MAX_ELEVATION) * (HEIGHT - 1));
for (int y = 0; y < HEIGHT; ++y) {
int color = y < yL ? BLACK : WHITE;
i.setRGB(x, HEIGHT - 1 - y, color);
}
}
dDEM.close();
ImageIO.write(i, "png", new File("tests/ch/epfl/alpano/dem/profile.png"));
}
}

View File

@ -0,0 +1,83 @@
package ch.epfl.alpano.dem;
import static java.lang.Math.max;
import static java.lang.Math.min;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import ch.epfl.alpano.GeoPoint;
import static java.lang.Math.toRadians;
final class DrawHgtDEM {
final static File HGT_FILE = new File("HGT"+File.separatorChar+"N46E006.hgt");
final static double ORIGIN_LON = toRadians(6.25);
final static double ORIGIN_LAT = toRadians(46.25);
final static double WIDTH = toRadians(0.5);
final static int IMAGE_SIZE = 300;
final static double MIN_ELEVATION = 200;
final static double MAX_ELEVATION = 1_500;
public static void main(String[] as) throws Exception {
DiscreteElevationModel dDEM =
new HgtDiscreteElevationModel(HGT_FILE);
ContinuousElevationModel cDEM =
new ContinuousElevationModel(dDEM);
double step = WIDTH / (IMAGE_SIZE - 1);
BufferedImage i = new BufferedImage(IMAGE_SIZE,
IMAGE_SIZE,
BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < IMAGE_SIZE; ++x) {
double lon = ORIGIN_LON + x * step;
for (int y = 0; y < IMAGE_SIZE; ++y) {
double lat = ORIGIN_LAT + y * step;
GeoPoint p = new GeoPoint(lon, lat);
double el =
(cDEM.elevationAt(p) - MIN_ELEVATION)
/ (MAX_ELEVATION - MIN_ELEVATION);
i.setRGB(x, IMAGE_SIZE - 1 - y, gray(el));
}
}
dDEM.close();
ImageIO.write(i, "png", new File("tests/ch/epfl/alpano/dem/dem.png"));
}
public static void drawDem(ContinuousElevationModel cDem){
double step = WIDTH / (IMAGE_SIZE - 1);
BufferedImage i = new BufferedImage(IMAGE_SIZE,
IMAGE_SIZE,
BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < IMAGE_SIZE; ++x) {
double lon = ORIGIN_LON + x * step;
for (int y = 0; y < IMAGE_SIZE; ++y) {
double lat = ORIGIN_LAT + y * step;
GeoPoint p = new GeoPoint(lon, lat);
double el =
(cDem.elevationAt(p) - MIN_ELEVATION)
/ (MAX_ELEVATION - MIN_ELEVATION);
i.setRGB(x, IMAGE_SIZE - 1 - y, gray(el));
}
}
try {
ImageIO.write(i, "png", new File("tests/ch/epfl/alpano/dem/dem.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
private static int gray(double v) {
double clampedV = max(0, min(v, 1));
int gray = (int) (255.9999 * clampedV);
return (gray << 16) | (gray << 8) | gray;
}
}

View File

@ -0,0 +1,50 @@
package ch.epfl.alpano.dem;
import static java.lang.Math.max;
import static java.lang.Math.min;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import ch.epfl.alpano.GeoPoint;
import static java.lang.Math.toRadians;
final class DrawHgtDEM2 {
final static double ORIGIN_LON = toRadians(6.0);
final static double ORIGIN_LAT = toRadians(45.0);
final static double WIDTH = toRadians(0.1);
final static int IMAGE_SIZE = 1200;
final static double MIN_ELEVATION = 00;
final static double MAX_ELEVATION = 500;
public static void main(String[] as) throws Exception {
DiscreteElevationModel dDEM = new HgtDiscreteElevationModel(null);
ContinuousElevationModel cDEM =
new ContinuousElevationModel(dDEM);
double step = WIDTH / (IMAGE_SIZE - 1);
BufferedImage i = new BufferedImage(IMAGE_SIZE,
IMAGE_SIZE, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < IMAGE_SIZE; ++x) {
double lon = ORIGIN_LON + x * step;
for (int y = 0; y < IMAGE_SIZE; ++y) {
double lat = ORIGIN_LAT + y * step;
GeoPoint p = new GeoPoint(lon, lat);
double el =
(cDEM.elevationAt(p) - MIN_ELEVATION)
/ (MAX_ELEVATION - MIN_ELEVATION);
i.setRGB(x, IMAGE_SIZE - 1 - y, gray(el));
}
}
dDEM.close();
ImageIO.write(i, "png", new File("tests/ch/epfl/alpano/dem/dem2.png"));
}
private static int gray(double v) {
double clampedV = max(0, min(v, 1));
int gray = (int) (255.9999 * clampedV);
return (gray << 16) | (gray << 8) | gray;
}
}

View File

@ -0,0 +1,49 @@
package ch.epfl.alpano.dem;
import static java.lang.Math.toRadians;
import static org.junit.Assert.*;
import java.io.File;
import org.junit.Test;
import ch.epfl.alpano.GeoPoint;
public class ElevationProfileTest {
final static File HGT_FILE = new File("HGT"+File.separatorChar+"N46E006.hgt");
final static double AZIMUTH = toRadians(45.0);
final static double LONGITUDE = toRadians(6.0);
final static double LATITUDE = toRadians(46.0);
final static int LENGTH = 111_000;
@Test
public void testElevationProfile() {
DiscreteElevationModel dDEM =
new HgtDiscreteElevationModel(HGT_FILE);
ContinuousElevationModel cDEM =
new ContinuousElevationModel(dDEM);
GeoPoint o =
new GeoPoint(LONGITUDE, LATITUDE);
ElevationProfile p =
new ElevationProfile(cDEM, o, AZIMUTH, LENGTH);
assertEquals(toRadians(6.09385),p.positionAt(10240).longitude(),0.00001);
assertEquals(toRadians(46.06508),p.positionAt(10240).latitude(),0.00001);
}
/*
@Test
public void testElevationAt() {
fail("Not yet implemented");
}
@Test
public void testPositionAt() {
fail("Not yet implemented");
}
@Test
public void testSlopeAt() {
fail("Not yet implemented");
}
*/
}

View File

@ -0,0 +1,134 @@
package ch.epfl.alpano.dem;
import static java.lang.Math.PI;
import static java.lang.Math.toRadians;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import ch.epfl.alpano.GeoPoint;
import ch.epfl.alpano.Interval1D;
import ch.epfl.alpano.Interval2D;
public class ElevationProfileTestP {
@Test(expected = NullPointerException.class)
public void constructorFailsWhenElevationModelIsNull() {
new ElevationProfile(null, new GeoPoint(0,0), 0, 100);
}
@Test(expected = NullPointerException.class)
public void constructorFailsWhenOriginIsNull() {
new ElevationProfile(newConstantSlopeDEM(), null, 0, 100);
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWhenAzimuthIsNotCanonical() {
new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(0,0), 6.3, 100);
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWhenLengthIsZero() {
new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(0,0), 0, 0);
}
@Test(expected = IllegalArgumentException.class)
public void elevationAtFailsWhenXIsTooBig() {
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(0,0), 0, 100);
p.elevationAt(101);
}
@Test
public void elevationAtWorksOnConstantSlopeDEMGoingNorth() {
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(0,0), 0, 100_000);
for (int i = 0; i < 100; ++i) {
double x = 100d * i;
assertEquals(x, p.elevationAt(x), 1e-5);
}
}
@Test
public void elevationAtWorksOnConstantSlopeDEMGoingSouth() {
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(0,0), PI, 100_000);
for (int i = 0; i < 100; ++i) {
double x = 100d * i;
assertEquals(-x, p.elevationAt(x), 1e-5);
}
}
@Test
public void elevationAtWorksOnConstantSlopeDEMGoingEast() {
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(0,0), PI/2d, 100_000);
for (int i = 0; i < 100; ++i) {
double x = 100d * i;
assertEquals(x, p.elevationAt(x), 1e-5);
}
}
@Test
public void elevationAtWorksOnConstantSlopeDEMGoingWest() {
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(0,0), 3d*PI/2d, 100_000);
for (int i = 0; i < 100; ++i) {
double x = 100d * i;
assertEquals(-x, p.elevationAt(x), 1e-5);
}
}
@Test(expected = IllegalArgumentException.class)
public void positionAtFailsWhenXIsTooBig() {
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(0,0), 0, 100);
p.positionAt(101);
}
@Test
public void positionAtProducesConstantLongitudeWhenGoingNorth() {
double lon = toRadians(3);
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(lon,toRadians(40)), 0, 100_000);
for (int i = 0; i < 100; ++i) {
double x = 500d * i;
assertEquals(lon, p.positionAt(x).longitude(), 1e-5);
}
}
@Test
public void positionAtProducesConstantLongitudeWhenGoingSouth() {
double lon = toRadians(3);
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(lon,toRadians(40)), PI, 100_000);
for (int i = 0; i < 100; ++i) {
double x = 500d * i;
assertEquals(lon, p.positionAt(x).longitude(), 1e-5);
}
}
@Test
public void positionAtProducesConstantLatitudeWhenGoingEast() {
double lat = toRadians(40);
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(toRadians(3),lat), PI/2d, 100_000);
for (int i = 0; i < 100; ++i) {
double x = 500d * i;
assertEquals(lat, p.positionAt(x).latitude(), 1e-4);
}
}
@Test
public void positionAtProducesConstantLatitudeWhenGoingWest() {
double lat = toRadians(40);
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(toRadians(3),lat), 3d*PI/2d, 100_000);
for (int i = 0; i < 100; ++i) {
double x = 500d * i;
assertEquals(lat, p.positionAt(x).latitude(), 1e-4);
}
}
@Test(expected = IllegalArgumentException.class)
public void slopeAtFailsWhenXIsNegative() {
ElevationProfile p = new ElevationProfile(newConstantSlopeDEM(), new GeoPoint(0,0), 0, 100);
p.positionAt(-1);
}
private static ContinuousElevationModel newConstantSlopeDEM() {
Interval2D extent = new Interval2D(
new Interval1D(-10_000, 10_000),
new Interval1D(-10_000, 10_000));
return new ContinuousElevationModel(new ConstantSlopeDEM(extent));
}
}

View File

@ -0,0 +1,77 @@
package ch.epfl.alpano.dem;
import static org.junit.Assert.*;
import static ch.epfl.alpano.Preconditions.checkArgument;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.Test;
public class HgtDiscreteElevationModelTest {
@Test
public void testHgtDiscreteElevationModel() {
String[] files = {"N45E006.hgt","N45E007.hgt","N45E008.hgt","N45E009.hgt",
"N45E010.hgt","N45E011.hgt","N46E006.hgt","N46E007.hgt",
"N46E008.hgt","N46E009.hgt","N46E010.hgt","N46E011.hgt",
"N47E006.hgt","N47E007.hgt","N47E008.hgt","N47E009.hgt",
"N47E010.hgt","N47E011.hgt","N00E000.hgt","S89W120"};
File file;
for(String txt: files){
file = new File("HGT"+File.separatorChar+txt);
if(file.exists())
new HgtDiscreteElevationModel(file);
}
assertTrue(true);
}
@Test(expected=IllegalArgumentException.class)
public void testHgtDiscreteElevationModelFails() {
String[] files = {"N45E180.hgt","N45E007.hgt",};
File file;
for(String txt: files){
file = new File("HGT"+File.separatorChar+txt);
new HgtDiscreteElevationModel(file);
}
}
@Test
public void checkFileNameSuccess() {
assertTrue(checkFileName("S00E000.hgt")!=null);
assertTrue(checkFileName("N00E000.hgt")!=null);
assertTrue(checkFileName("S00W129.hgt")!=null);
assertTrue(checkFileName("N00W000.hgt")!=null);
assertTrue(checkFileName("S69E139.hgt")!=null);
assertTrue(checkFileName("S90W180.hgt")!=null);
assertTrue(checkFileName("N90E180.hgt")!=null);
assertTrue(checkFileName("S90W180.hgt")!=null);
}
@Test(expected=IllegalArgumentException.class)
public void checkFileNameFails() {
checkFileName("E46E006.hgt");
checkFileName("N4gE006.hgt");
checkFileName("N46E0g6.hgt");
checkFileName("N46E006lhgt");
checkFileName("N46E006.hGT");
checkFileName("N4gE006.hgt");
checkFileName("N46E0g6.hgt");
checkFileName("N46E006phgt");
checkFileName("Q99E006.hGT");
}
private static final Matcher checkFileName(String txt){
final Pattern P = Pattern.compile("([NS])(\\d{2})([EW])(\\d{3})\\.hgt");
Matcher m = P.matcher(txt);
checkArgument((txt.length()==11 && m.find()));
int lat = Integer.parseInt(m.group(2));
int lon = Integer.parseInt(m.group(4));
checkArgument(!(lat>90 || lon>180 ));
return m;
}
}

View File

@ -0,0 +1,154 @@
package ch.epfl.alpano.dem;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import ch.epfl.alpano.Interval1D;
import ch.epfl.alpano.Interval2D;
public class HgtDiscreteElevationModelTestP {
private final static long HGT_FILE_SIZE = 3601L * 3601L * 2L;
private static Path FAKE_HGT_DIR, FAKE_HGT_FILE;
@BeforeClass
public static void createFakeHgtFiles() throws IOException {
Path fakeHgtDir = Files.createTempDirectory("hgt");
Path fakeHgtFile = fakeHgtDir.resolve("empty.hgt");
try (FileChannel c = FileChannel.open(fakeHgtFile, CREATE_NEW, READ, WRITE)) {
// make sure the empty hgt file has the right size
c.map(MapMode.READ_WRITE, 0, HGT_FILE_SIZE).asShortBuffer();
}
FAKE_HGT_FILE = fakeHgtFile;
FAKE_HGT_DIR = fakeHgtDir;
}
@AfterClass
public static void deleteFakeHgtFiles() throws IOException {
Files.walkFileTree(FAKE_HGT_DIR, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (exc != null)
throw exc;
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithTooShortName() throws Exception {
createHgtDemWithFileNamed("N47E010.hg");
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithInvalidLatitudeLetter() throws Exception {
createHgtDemWithFileNamed("N4xE010.hgt");
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithInvalidLongitudeLetter() throws Exception {
createHgtDemWithFileNamed("N47x010.hgt");
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithInexistantFile() throws Exception {
Path p = FAKE_HGT_DIR.resolve("N40E010.hgt");
try (DiscreteElevationModel d = new HgtDiscreteElevationModel(p.toFile())) {}
}
@Test(expected = IllegalArgumentException.class)
public void constructorFailsWithEmptyFile() throws Exception {
File f = FAKE_HGT_DIR.resolve("N41E010.hgt").toFile();
try (FileOutputStream s = new FileOutputStream(f)) {
s.write(0);
}
try (DiscreteElevationModel d = new HgtDiscreteElevationModel(f)) {}
}
@Test
public void constructorWorksInEcuador() throws Exception {
createHgtDemWithFileNamed("S03W078.hgt");
}
@Test
public void extentMatchesFileName() throws Exception {
int[] lons = new int[] { 1, 7 };
int[] lats = new int[] { 1, 47 };
for (int lon: lons) {
for (int lat: lats) {
Interval2D expectedExtent = new Interval2D(
new Interval1D(lon * 3600, (lon + 1) * 3600),
new Interval1D(lat * 3600, (lat + 1) * 3600));
String hgtFileName = String.format("N%02dE%03d.hgt", lat, lon);
Path p = copyEmptyHgtFileAs(hgtFileName);
try (HgtDiscreteElevationModel dem = new HgtDiscreteElevationModel(p.toFile())) {
assertEquals(expectedExtent, dem.extent());
}
}
}
}
@Test(expected = IllegalArgumentException.class)
public void elevationSampleFailsForIndexNotInExtent() throws Exception {
String hgtFileName = "N02E002.hgt";
Path p = copyEmptyHgtFileAs(hgtFileName);
try (HgtDiscreteElevationModel dem = new HgtDiscreteElevationModel(p.toFile())) {
dem.elevationSample(10, 10);
}
}
@Test
public void elevationSampleIsCorrectInFourCorners() throws Exception {
Path p = FAKE_HGT_DIR.resolve("N01E001.hgt");
try (FileChannel c = FileChannel.open(p, CREATE_NEW, READ, WRITE)) {
ShortBuffer b = c.map(MapMode.READ_WRITE, 0, HGT_FILE_SIZE).asShortBuffer();
b.put(0, (short)1);
b.put(3600, (short) 2);
b.put(3601 * 3600, (short) 3);
b.put(3601 * 3601 - 1, (short) 4);
}
try (HgtDiscreteElevationModel dem = new HgtDiscreteElevationModel(p.toFile())) {
assertEquals(0, dem.elevationSample(4000, 4000), 1e-10);
assertEquals(1, dem.elevationSample(3600, 7200), 1e-10);
assertEquals(2, dem.elevationSample(7200, 7200), 1e-10);
assertEquals(3, dem.elevationSample(3600, 3600), 1e-10);
assertEquals(4, dem.elevationSample(7200, 3600), 1e-10);
}
}
private static void createHgtDemWithFileNamed(String hgtFileName) throws Exception {
Path p = copyEmptyHgtFileAs(hgtFileName);
try (DiscreteElevationModel d = new HgtDiscreteElevationModel(p.toFile())) {}
}
private static Path copyEmptyHgtFileAs(String hgtFileName) throws IOException {
return Files.copy(FAKE_HGT_FILE, FAKE_HGT_DIR.resolve(hgtFileName), REPLACE_EXISTING);
}
}

View File

@ -0,0 +1,26 @@
package ch.epfl.alpano.dem;
import java.io.File;
import java.io.IOException;
import java.io.FileInputStream;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel.MapMode;
public class MemMapExample {
public static void main(String[] args)
throws IOException {
File f = new File("HGT"+File.separatorChar+"N46E007.hgt");
long l = f.length();
try (FileInputStream s = new FileInputStream(f)) {
ShortBuffer b = s.getChannel()
.map(MapMode.READ_ONLY, 0, l)
.asShortBuffer();
for (int i = 0; i <= 5; ++i)
System.out.println(b.get(i));
System.out.println("-----------------");
for (int i = 12967195; i < 12967201; ++i)
System.out.println(b.get(i));
}
}
}

View File

@ -0,0 +1,57 @@
package ch.epfl.alpano.dem;
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
import static java.lang.Math.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import ch.epfl.alpano.*;
import javax.imageio.ImageIO;
public final class TestWavyDEM {
@SuppressWarnings("resource")
public static void main(String[] args)
throws IOException {
DiscreteElevationModel dDEM1 =
new WavyDEM(new Interval2D(new Interval1D(0, 50),
new Interval1D(0, 100)));
DiscreteElevationModel dDEM2 =
new WavyDEM(new Interval2D(new Interval1D(50, 100),
new Interval1D(0, 100)));
DiscreteElevationModel dDEM =
dDEM1.union(dDEM2);
ContinuousElevationModel cDEM =
new ContinuousElevationModel(dDEM);
int size = 300;
double scale = (100d / 3600d) / (size - 1);
BufferedImage elI =
new BufferedImage(size, size, TYPE_INT_RGB);
BufferedImage slI =
new BufferedImage(size, size, TYPE_INT_RGB);
for (int x = 0; x < size; ++x) {
for (int y = 0; y < size; ++y) {
GeoPoint p = new GeoPoint(toRadians(x * scale),
toRadians(y * scale));
double el = cDEM.elevationAt(p);
elI.setRGB(x, y, gray(el / 1000d));
double sl = cDEM.slopeAt(p);
slI.setRGB(x, y, gray(sl / (PI / 2d)));
}
}
ImageIO.write(elI, "png", new File("tests/ch/epfl/alpano/dem/elevation.png"));
ImageIO.write(slI, "png", new File("tests/ch/epfl/alpano/dem/slope.png"));
}
private static int gray(double v) {
double clampedV = max(0, min(v, 1));
int gray = (int) (255.9999 * clampedV);
return (gray << 16) | (gray << 8) | gray;
}
}

View File

@ -0,0 +1,28 @@
package ch.epfl.alpano.dem;
import ch.epfl.alpano.Interval2D;
import static java.lang.Math.PI;
import static java.lang.Math.sin;
import static java.lang.Math.cos;
final class WavyDEM implements DiscreteElevationModel {
private final static double PERIOD = 100, HEIGHT = 1000;
private final Interval2D extent;
public WavyDEM(Interval2D extent) {
this.extent = extent;
}
@Override
public void close() throws Exception { }
@Override
public Interval2D extent() { return extent; }
@Override
public double elevationSample(int x, int y) {
double x1 = PI * 2d * x / PERIOD;
double y1 = PI * 2d * y / PERIOD;
return (1 + sin(x1) * cos(y1)) / 2d * HEIGHT;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

View File

@ -0,0 +1,31 @@
package ch.epfl.alpano.gui;
import static ch.epfl.alpano.gui.PredefinedPanoramas.*;
import ch.epfl.alpano.gui.PanoramaParametersBean;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.stage.Stage;
public final class BeansUse extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
PanoramaParametersBean bean =
new PanoramaParametersBean(NIESEN.get());
ObjectProperty<Integer> prop =
bean.ObserverLatitudeProperty();
prop.addListener((o, oV, nV) ->
System.out.printf(" %d -> %d (%s)%n", oV, nV, o));
System.out.println("set to 1");
prop.set(1);
System.out.println("set to 2");
prop.set(2);
Platform.exit();
}
}

View File

@ -0,0 +1,28 @@
package ch.epfl.alpano.gui;
import static org.junit.Assert.*;
import org.junit.Test;
public class FixedPointStringConverterTest {
@Test
public void testFixedPointStringConverter() {
//fail("Not yet implemented");
}
@Test
public void testFromStringString() {
FixedPointStringConverter f = new FixedPointStringConverter(4);
assertEquals(123457, (int)f.fromString("12.3456789"));
FixedPointStringConverter f1 = new FixedPointStringConverter(2);
assertEquals("1011", f1.toString(101100));
}
@Test
public void testToStringInteger() {
FixedPointStringConverter f = new FixedPointStringConverter(1);
assertEquals("67.8", f.toString(678));
}
}

View File

@ -0,0 +1,28 @@
package ch.epfl.alpano.gui;
import static org.junit.Assert.*;
import org.junit.Test;
public class LabeledListStringConverterTest {
@Test
public void testLabeledListStringConverter() {
}
@Test
public void testFromStringString() {
LabeledListStringConverter c = new LabeledListStringConverter("none","2x","4x");
System.out.println(c.fromString("2x"));
//assertEquals(1,c.fromString("2x"));
}
@Test
public void testToStringInteger() {
LabeledListStringConverter c = new LabeledListStringConverter("none","2x","4x");
assertEquals("none",c.toString(0));
}
}

View File

@ -0,0 +1,50 @@
package ch.epfl.alpano.gui;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.HgtDiscreteElevationModel;
import ch.epfl.alpano.summit.GazetteerParser;
import ch.epfl.alpano.summit.Summit;
import javafx.scene.Node;
import javafx.scene.text.Text;
public class LabelizerTest {
@Test
public void testLabels() {
try {
List<Summit> listOfSummit = GazetteerParser.readSummitsFrom(new File("HGT/alps.txt"));
HgtDiscreteElevationModel dDem = new HgtDiscreteElevationModel(new File ("HGT/N46E007.hgt"));
ContinuousElevationModel cDem = new ContinuousElevationModel(dDem);
Labelizer l = new Labelizer(cDem, listOfSummit);
List<Node> n= l.labels(PredefinedPanoramas.NIESEN.get().panoramaComputeParameters());
int count =0;
for (Node node : n){
System.out.println(node);
if (node instanceof Text){
++count;
//System.out.println("node");
System.out.println(node.toString());
}
}
System.out.println(count);
} catch (IOException e) {
e.printStackTrace();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,41 @@
package ch.epfl.alpano.sigcheck;
import ch.epfl.alpano.GeoPoint;
import ch.epfl.alpano.Interval1D;
import ch.epfl.alpano.Interval2D;
final class SignatureChecks_02 {
private SignatureChecks_02() {}
String checkGeoPoint() {
double lon = 0, lat = 0;
GeoPoint p = new GeoPoint(lon, lat);
lon += p.longitude() + p.latitude();
double d = p.distanceTo(p);
double a = p.azimuthTo(p);
return String.valueOf(d) + a;
}
String checkInterval1D() {
int a = 0;
Interval1D i = new Interval1D(a, a);
a = i.includedFrom() + i.includedTo() + i.size() + i.sizeOfIntersectionWith(i);
boolean b = i.contains(a)
| i.isUnionableWith(i);
i = i.union(i.boundingUnion(i));
return i.toString() + b;
}
String checkInterval2D() {
int a = 0;
Interval1D i1 = null;
Interval2D i2 = new Interval2D(i1, i1);
i1 = i2.iX();
i1 = i2.iY();
a = i2.size() + i2.sizeOfIntersectionWith(i2);
boolean b = i2.contains(a, a)
| i2.isUnionableWith(i2);
i2 = i2.union(i2.boundingUnion(i2));
return i2.toString() + b;
}
}

View File

@ -0,0 +1,29 @@
package ch.epfl.alpano.sigcheck;
import ch.epfl.alpano.GeoPoint;
import ch.epfl.alpano.Interval2D;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.DiscreteElevationModel;
final class SignatureChecks_03 {
private SignatureChecks_03() {}
String checkDiscElevationModel(DiscreteElevationModel d) throws Exception {
double a = DiscreteElevationModel.SAMPLES_PER_DEGREE * DiscreteElevationModel.SAMPLES_PER_RADIAN;
a = DiscreteElevationModel.sampleIndex(a);
Interval2D e = d.extent();
int v = 0;
a = d.elevationSample(v, v);
d = d.union(d);
d.close();
return d.toString() + e;
}
String checkContElevationModel() {
DiscreteElevationModel md = null;
ContinuousElevationModel m = new ContinuousElevationModel(md);
GeoPoint p = null;
double e = m.elevationAt(p) + m.slopeAt(p);
return String.valueOf(e);
}
}

View File

@ -0,0 +1,30 @@
package ch.epfl.alpano.sigcheck;
import java.io.File;
import ch.epfl.alpano.GeoPoint;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.DiscreteElevationModel;
import ch.epfl.alpano.dem.ElevationProfile;
import ch.epfl.alpano.dem.HgtDiscreteElevationModel;
final class SignatureChecks_04 {
private SignatureChecks_04() {}
void checkHgtDiscreteElevationModel() {
File f = null;
DiscreteElevationModel m = new HgtDiscreteElevationModel(f);
System.out.println(m);
}
void checkElevationProfile() {
ContinuousElevationModel dem = null;
GeoPoint o = null;
double d = 0;
ElevationProfile p = new ElevationProfile(dem, o, d, d);
d = p.elevationAt(d);
o = p.positionAt(d);
d = p.slopeAt(d);
}
}

View File

@ -0,0 +1,46 @@
package ch.epfl.alpano.sigcheck;
import java.io.File;
import java.io.IOException;
import java.util.List;
import ch.epfl.alpano.GeoPoint;
import ch.epfl.alpano.PanoramaParameters;
import ch.epfl.alpano.summit.GazetteerParser;
import ch.epfl.alpano.summit.Summit;
final class SignatureChecks_05 {
private SignatureChecks_05() {}
void checkSummit() {
GeoPoint p = null;
int e = 0;
Summit s = new Summit("", p, e);
p = s.position();
e = s.elevation();
System.out.println(s.name());
}
void checkGazetteerParser() throws IOException {
File f = null;
List<Summit> s = GazetteerParser.readSummitsFrom(f);
System.out.println(s);
}
void checkPanoramaParameters() {
GeoPoint p = null;
int i = 0;
double d = 0d;
PanoramaParameters pp = new PanoramaParameters(p, i, d, d, i, i, i);
p = pp.observerPosition();
i = pp.observerElevation();
d = pp.centerAzimuth();
d = pp.horizontalFieldOfView();
d = pp.verticalFieldOfView();
i = pp.width();
i = pp.height();
i = pp.maxDistance();
d = pp.xForAzimuth(pp.azimuthForX(d));
d = pp.yForAltitude(pp.altitudeForY(d));
}
}

View File

@ -0,0 +1,49 @@
package ch.epfl.alpano.sigcheck;
import java.util.function.DoubleUnaryOperator;
import ch.epfl.alpano.Panorama;
import ch.epfl.alpano.PanoramaComputer;
import ch.epfl.alpano.PanoramaParameters;
import ch.epfl.alpano.dem.ContinuousElevationModel;
import ch.epfl.alpano.dem.ElevationProfile;
final class SignatureChecks_06 {
private SignatureChecks_06() {}
void checkPanorama(Panorama p) {
PanoramaParameters pp = p.parameters();
int x = 0;
float d = p.distanceAt(x, x);
d = p.distanceAt(x, x, d);
d = p.longitudeAt(x, x);
d = p.latitudeAt(x, x);
d = p.elevationAt(x, x);
d = p.slopeAt(x, x);
checkPanoramaBuilder(pp);
}
void checkPanoramaBuilder(PanoramaParameters pp) {
Panorama.Builder b = new Panorama.Builder(pp);
int x = 0;
float d = 0;
b.setDistanceAt(x, x, d);
b.setLongitudeAt(x, x, d);
b.setLatitudeAt(x, x, d);
b.setElevationAt(x, x, d);
b.setSlopeAt(x, x, d);
Panorama p = b.build();
checkPanorama(p);
}
void checkPanoramaComputer(PanoramaParameters pp) {
ContinuousElevationModel d = null;
PanoramaComputer pc = new PanoramaComputer(d);
Panorama p = pc.computePanorama(pp);
checkPanorama(p);
double y = 0;
ElevationProfile pr = null;
DoubleUnaryOperator o = PanoramaComputer.rayToGroundDistance(pr, y, y);
System.out.println(o);
}
}

Some files were not shown because too many files have changed in this diff Show More