package org.codemap.internal;
import org.codemap.Location;
import org.codemap.MapAlgorithm;
import org.codemap.util.StopWatch;
/** Creates the digital elevation model of a map. A digital elevation model (DEM) is a raster of z-ordinates for each pixel.
*<p>
* For each location on the map, a hill is added to the digital elevation model. The elevation of a hill at position
* (x0,y0) with height (z) is defined as follows, f(x,y) = z * exp ^ (-0.5 * (dist * factor) ^ 2) where
* dist = sqrt((x - x0) ^ 2 + (y - y0) ^ 2) and factor = k / z. The constant (k) is chosen such that a hill of height
* 100.0 has a diameter of 41% of the map.
*<p>
* The algorithm has been optimized to run fast.
*<p>
* Not thread-safe.
*
* @author Adrian Kuhn
*
*/
public class DEMAlgorithm extends MapAlgorithm<float[][]> {
public static final int MAGIC_VALUE = 8*320; // TODO magic number!
private static final double THRESHOLD = 1.0;
private float[][] DEM;
private int radius;
@Override
public float[][] call() {
setup();
compute();
return DEM;
}
private void compute() {
StopWatch stopWatch = new StopWatch("DEM").start();
// TODO a map configuration on map should return locations on map
for (Location each: map.getDEMLocations()) {
elevateHill(each, computePie(each));
}
stopWatch.printStop();
}
private void elevateHill(Location each, float[][] pie) {
final int y0, x0, top, bottom, left, right;
y0 = each.py;
x0 = each.px;
top = y0 > radius ? 1 - radius : 0 - y0;
left = x0 > radius ? 1 - radius : 0 - x0;
bottom = y0 + radius < DEM.length ? radius : DEM.length - y0;
right = x0 + radius < DEM.length ? radius : DEM.length - x0;
for (int y = top; y < bottom; y++) {
int absy = Math.abs(y);
for (int x = left; x < right; x++) {
DEM[x+x0][y+y0] += pie
[Math.max(absy,Math.abs(x))]
[Math.min(absy,Math.abs(x))];
}
}
}
private float[][] computePie(Location each) {
float[][] pie = new float[DEM.length][];
double elevationFactor = each.getElevation();
double distFactor2 = -1.0
/ (elevationFactor * elevationFactor)
* (MAGIC_VALUE * MAGIC_VALUE)
/ (DEM.length * DEM.length) // bigger hill when there are more pixels
/ 2;
radius = computePieLoop(pie, elevationFactor, distFactor2);
return pie;
}
private int computePieLoop(float[][] pie, double elevationFactor, double distFactor2) {
// generate square numbers by summing up all the odd numbers
for (int n = 0, n2 = 0; n < pie.length; n2 += (++n)+n-1) {
pie[n] = new float[n+1];
for (int m = 0, dist2 = n2; m <= n; dist2 += (++m)+m-1) {
double elevation = elevationFactor * Math.exp(distFactor2 * (double) dist2);
if (elevation < THRESHOLD) {
if (m == 0) return n;
break;
}
pie[n][m] += elevation - THRESHOLD;
}
}
throw new Error("Should not happen, nach Adam Riese.");
}
private void setup() {
DEM = new float[map.getWidth()][map.getWidth()];
}
}