/*
* Copyright (C) 2009 Christian Gawron
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Christian Gawron
* Create date: 03-Jul-2009
*/
package uk.me.parabola.mkgmap.reader.dem;
import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.MappedByteBuffer;
import java.util.Vector;
import java.util.Locale;
import com.sun.media.jai.codec.*;
import javax.media.jai.*;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.Raster;
import java.awt.image.DataBuffer;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.ExitException;
import uk.me.parabola.imgfmt.FormatException;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.mkgmap.general.MapDetails;
import uk.me.parabola.mkgmap.general.MapPoint;
import uk.me.parabola.mkgmap.general.MapLine;
import uk.me.parabola.mkgmap.reader.osm.OsmConverter;
import uk.me.parabola.mkgmap.reader.osm.Style;
import uk.me.parabola.mkgmap.reader.osm.Way;
import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
import uk.me.parabola.mkgmap.osmstyle.StyledConverter;
import uk.me.parabola.mkgmap.osmstyle.eval.SyntaxException;
import uk.me.parabola.util.EnhancedProperties;
import static java.nio.channels.FileChannel.MapMode.READ_ONLY;
/**
* Create contour lines using an algorithm similar to that described in
* An Adaptive Grid Contouring Algorithm by Downing and Zoraster.
*/
public abstract class DEM
{
final static double epsilon = 1e-9;
final static double delta = 1.5;
final static int maxPoints=200000;
final static double minDist = 15;
final static double maxDist = 21;
final static double step = 0.01;
final static double semiMajorAxis = 6378137.0;
final static double inverseFlattening = 298.257223563;
static int M=1200;
static int N=M;
static double res = 1.0/N;
static int id = -1;
short values[] = null;
int lat;
int lon;
abstract double ele(int x, int y);
abstract void read(int minLon, int minLat, int maxLon, int maxLat);
abstract void serializeCopyRight(Writer out) throws IOException;
public static void createContours(MapDetails mapper, EnhancedProperties config)
{
Area bounds = mapper.getBounds();
double minLat = Utils.toDegrees(bounds.getMinLat());
double minLon = Utils.toDegrees(bounds.getMinLong());
double maxLat = Utils.toDegrees(bounds.getMaxLat());
double maxLon = Utils.toDegrees(bounds.getMaxLong());
System.out.printf("bounds: %f %f %f %f\n", minLat, minLon, maxLat, maxLon);
DEM data;
String demType = config.getProperty("dem-type", "CGIAR");
String dataPath;
if (demType.equals("ASTER")) {
dataPath = config.getProperty("dem-path", "ASTER");
data = new ASTERGeoTiff(dataPath, minLat, minLon, maxLat, maxLon);
}
else if (demType.equals("CGIAR")) {
dataPath = config.getProperty("dem-path", "CGIAR");
data = new CGIARGeoTiff(dataPath, minLat, minLon, maxLat, maxLon);
}
else {
dataPath = config.getProperty("dem-path", "SRTM");
data = new HGTDEM(dataPath, minLat, minLon, maxLat, maxLon);
}
Isolines lines = data.new Isolines(data, minLat, minLon, maxLat, maxLon);
int increment = config.getProperty("dem-increment", 10);
double minHeight = lines.getMinHeight();
double maxHeight = lines.getMaxHeight();
int maxLevels = config.getProperty("dem-maxlevels", 100);
while ((maxHeight-minHeight)/increment > maxLevels)
increment *= 2;
String loc = config.getProperty("style-file");
if (loc == null)
loc = config.getProperty("map-features");
String name = config.getProperty("style");
if (loc == null && name == null)
name = "default";
OsmConverter converter;
try {
Style style = new StyleImpl(loc, name);
style.applyOptionOverride(config);
converter = new StyledConverter(style, mapper, config);
} catch (SyntaxException e) {
System.err.println("Error in style: " + e.getMessage());
throw new ExitException("Could not open style " + name);
} catch (FileNotFoundException e) {
String name1 = (name != null)? name: loc;
throw new ExitException("Could not open style " + name1);
}
for (int level=0; level lat+1 || maxLon > lon+1)
throw new RuntimeException("Area too large (must not span more than one SRTM file)");
String northSouth = lat < 0 ? "S" : "N";
String eastWest = lon > 0 ? "E" : "W";
String fileName = String.format("%s/%s%02d%s%03d.hgt", dataPath,
northSouth, lat < 0 ? -lat : lat,
eastWest, lon < 0 ? -lon : lon);
try {
FileInputStream is = new FileInputStream(fileName);
buffer = is.getChannel().map(READ_ONLY, 0, 2*(M+1)*(M+1));
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void read(int minLon, int minLat, int maxLon, int maxLat)
{
}
public double ele(int x, int y)
{
return buffer.getShort(2*((M-y)*(M+1)+x))+delta;
}
public void serializeCopyRight(Writer out) throws IOException
{
out.write(" \n");
out.write(" Contour lines generated from DEM data by NASA\n");
out.write(" \n");
}
}
public static class CGIARGeoTiff extends DEM
{
Raster raster;
String fileName;
int minLat, minLon, maxLat, maxLon;
PlanarImage image;
public CGIARGeoTiff(String dataPath, double minLat, double minLon, double maxLat, double maxLon)
{
this.lat = ((int) (minLat/5))*5;
this.lon = ((int) (minLon/5))*5;
if (maxLat > lat+5 || maxLon > lon+5)
throw new RuntimeException("Area too large (must not span more than one CGIAR GeoTIFF)");
int tileX, tileY;
tileX = (180 + lon)/5 + 1;
tileY = (60 - lat)/5;
this.fileName = String.format("%s/srtm_%02d_%02d.tif", dataPath, tileX, tileY);
init();
}
public void serializeCopyRight(Writer out) throws IOException
{
out.write(" \n");
out.write(" Contour lines generated from improved SRTM data by CIAT-CSI (see http://srtm.csi.cgiar.org)\n");
out.write(" \n");
}
public void read(int minLon, int minLat, int maxLon, int maxLat)
{
this.minLon = minLon;
this.minLat = minLat;
this.maxLon = maxLon;
this.maxLat = maxLat;
raster = image.getData(new java.awt.Rectangle(minLon, 6000-maxLat-1, maxLon-minLon+1, maxLat-minLat+1));
System.out.printf("read: %d %d %d %d\n", minLon, 6000-maxLat-1, maxLon-minLon+1, maxLat-minLat+1);
}
void init()
{
System.out.printf("CGIAR GeoTIFF: %s\n", fileName);
N = 6000;
M = 6000;
res = 5.0/M;
try {
SeekableStream s = new FileSeekableStream(fileName);
ParameterBlock pb = new ParameterBlock();
pb.add(s);
TIFFDecodeParam param = new TIFFDecodeParam();
pb.add(param);
RenderedOp op = JAI.create("tiff", pb);
image = op.createInstance();
System.out.printf("Image: %d %d %d %d\n", image.getWidth(), image.getHeight(),
image.getNumXTiles(), image.getNumYTiles());
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public double ele(int x, int y)
{
try {
int elevation = raster.getPixel(x, 6000-y-1, (int[])null)[0];
return elevation+delta;
}
catch (ArrayIndexOutOfBoundsException ex) {
System.out.printf("ele: (%d, %d) (%d, %d, %d, %d) %s\n",
x, 6000-y-1,
raster.getMinX(), raster.getMinY(),
raster.getWidth(), raster.getHeight(), ex.toString());
throw ex;
}
}
}
public static class ASTERGeoTiff extends DEM
{
Raster raster;
String fileName;
int minLat, minLon, maxLat, maxLon;
PlanarImage image;
public ASTERGeoTiff(String dataPath, double minLat, double minLon, double maxLat, double maxLon)
{
this.lat = (int) minLat;
this.lon = (int) minLon;
if (maxLat > lat+1 || maxLon > lon+1)
throw new RuntimeException("Area too large (must not span more than one ASTER GeoTIFF)");
String northSouth = lat < 0 ? "S" : "N";
String eastWest = lon > 0 ? "E" : "W";
fileName = String.format("%s/ASTGTM_%s%02d%s%03d_dem.tif", dataPath,
northSouth, lat < 0 ? -lat : lat,
eastWest, lon < 0 ? -lon : lon);
init();
}
public void serializeCopyRight(Writer out) throws IOException
{
out.write(" \n");
out.write(" Contour lines generated from DGM data by ASTER (see https://wist.echo.nasa.gov/~wist/api/imswelcome)\n");
out.write(" \n");
}
public void read(int minLon, int minLat, int maxLon, int maxLat)
{
this.minLon = minLon;
this.minLat = minLat;
this.maxLon = maxLon;
this.maxLat = maxLat;
raster = image.getData(new java.awt.Rectangle(minLon, 3601-maxLat-1, maxLon-minLon+1, maxLat-minLat+1));
System.out.printf("read: %d %d %d %d\n", minLon, 3601-maxLat-1, maxLon-minLon+1, maxLat-minLat+1);
}
void init()
{
System.out.printf("ASTER GeoTIFF: %s\n", fileName);
N = 3600;
M = 3600;
res = 1.0/M;
try {
SeekableStream s = new FileSeekableStream(fileName);
ParameterBlock pb = new ParameterBlock();
pb.add(s);
TIFFDecodeParam param = new TIFFDecodeParam();
pb.add(param);
RenderedOp op = JAI.create("tiff", pb);
image = op.createInstance();
System.out.printf("Image: %d %d %d %d\n", image.getWidth(), image.getHeight(),
image.getNumXTiles(), image.getNumYTiles());
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public double ele(int x, int y)
{
try {
int elevation = raster.getPixel(x, 3601-y-1, (int[])null)[0];
return elevation+delta;
}
catch (ArrayIndexOutOfBoundsException ex) {
System.out.printf("ele: (%d, %d) (%d, %d, %d, %d) %s\n",
x, 3601-y-1,
raster.getMinX(), raster.getMinY(),
raster.getWidth(), raster.getHeight(), ex.toString());
throw ex;
}
}
}
private static void debug(String format, Object ... args)
{
//System.out.printf(format + "\n", args);
}
int lastXi = -1;
int lastYi = -1;
final static int bcInv[][]= {
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
{ -3, 0, 0, 3, 0, 0, 0, 0, -2, 0, 0, -1, 0, 0, 0, 0},
{ 2, 0, 0, -2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0},
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
{ 0, 0, 0, 0, -3, 0, 0, 3, 0, 0, 0, 0, -2, 0, 0, -1},
{ 0, 0, 0, 0, 2, 0, 0, -2, 0, 0, 0, 0, 1, 0, 0, 1},
{ -3, 3, 0, 0, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2, -1, 0, 0},
{ 9, -9, 9, -9, 6, 3, -3, -6, 6, -6, -3, 3, 4, 2, 1, 2},
{ -6, 6, -6, 6, -4, -2, 2, 4, -3, 3, 3, -3, -2, -1, -1, -2},
{ 2, -2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 1, 1, 0, 0},
{ -6, 6, -6, 6, -3, -3, 3, 3, -4, 4, 2, -2, -2, -2, -1, -1},
{ 4, -4, 4, -4, 2, 2, -2, -2, 2, -2, -2, 2, 1, 1, 1, 1}
};
static int lastId=1000000000;
static double lastX = 0;
static double lastY = 0;
static int edge[] = new int[2];
static int off0[][] = {{ 0, 0},
{ 0, 0},
{ 0, 1},
{ 1, 1}};
static int off1[][] = {{ 1, 0},
{ 0, 1},
{ 1, 1},
{ 1, 0}};
static int brd[] = { 1, 2, 4, 8 };
static int inv[] = { 4, 8, 1, 2 };
static int rev[] = { 2, 3, 0, 1 };
static int mov[][] = {{ 0, -1},
{-1, 0},
{ 0, 1},
{ 1, 0}};
double bc[][] = new double[4][4];
double bc_y[] = new double[4];
double bc_y1[] = new double[4];
double bc_y2[] = new double[4];
double bc_y12[] = new double[4];
double bc_Coeff[] = new double[16];
double bc_x[] = new double[16];
private void recalculateCoefficients(int xi, int yi)
{
double v00, vp0, v0p, vpp;
double vm0, v0m, vpm, vmp, vmm, vmP, vPm;
double vP0, v0P, vPp, vpP, vPP;
v00 = ele(xi, yi);
v0p = ele(xi, yi+1);
vpp = ele(xi+1, yi+1);
vp0 = ele(xi+1, yi);
vm0 = ele(xi-1, yi);
v0m = ele(xi, yi-1);
vmp = ele(xi-1, yi+1);
vpm = ele(xi+1, yi-1);
vmm = ele(xi-1, yi-1);
vmP = ele(xi+2, yi-1);
vPm = ele(xi-1, yi+2);
vP0 = ele(xi+2, yi);
v0P = ele(xi, yi+2);
vPp = ele(xi+2, yi+1);
vpP = ele(xi+1, yi+2);
vPP = ele(xi+2, yi+2);
bc_y[0] = v00;
bc_y[1] = vp0;
bc_y[2] = vpp;
bc_y[3] = v0p;
bc_y1[0] = (vp0-vm0)/2;
bc_y1[1] = (vP0-v00)/2;
bc_y1[2] = (vPp-v0p)/2;
bc_y1[3] = (vpp-vmp)/2;
bc_y2[0] = (v0p-v0m)/2;
bc_y2[1] = (vpp-vpm)/2;
bc_y2[2] = (vpP-vp0)/2;
bc_y2[3] = (v0P-v00)/2;
bc_y12[0] = (vpp - vpm - vmp + vmm) / 4;
bc_y12[0] = (vPp - vPm - v0p + v0m) / 4;
bc_y12[2] = (vPP - vP0 - v0P + v00) / 4;
bc_y12[0] = (vpP - vp0 - vmP + vm0) / 4;
int j, i;
double s;
for (i=0; i<4; i++)
{
bc_x[i]=bc_y[i];
bc_x[i+4]=bc_y1[i];
bc_x[i+8]=bc_y2[i];
bc_x[i+12]=bc_y12[i];
}
for (i=0; i<16; i++)
{
s = 0;
for (int k=0; k<16; k++) s += bcInv[i][k]*bc_x[k];
bc_Coeff[i] = s;
}
int l = 0;
for (i=0; i<4; i++)
for (j=0; j<4; j++)
bc[i][j] = bc_Coeff[l++];
}
public double gradient(double lat, double lon, double[] grad)
{
grad[0] = 0;
grad[1] = 0;
double x = (lon-this.lon)/res;
double y = (lat-this.lat)/res;
int xi = (int) x;
int yi = (int) y;
if (lastXi != xi || lastYi != yi)
{
debug("new Cell for interpolation: %d %d", xi, yi);
recalculateCoefficients(xi, yi);
lastXi = xi;
lastYi = yi;
}
double t = x - xi;
double u = y - yi;
if (xi < 0 || xi > N+1 || yi < 0 || yi > N+1)
throw new IndexOutOfBoundsException(String.format("(%f, %f)->(%d, %d)", lat, lon, xi, yi));
double val = 0;
for (int i=3; i>=0; i--)
{
val = t*val + ((bc[i][3]*u + bc[i][2])*u + bc[i][1])*u + bc[i][0];
grad[0] = u*grad[0] + (3*bc[3][i]*t + 2*bc[2][i])*t + bc[1][i];
grad[1] = t*grad[1] + (3*bc[i][3]*t + 2*bc[i][2])*t + bc[i][1];
}
return val;
}
public double elevation(double lat, double lon)
{
double x = (lon-this.lon)/res;
double y = (lat-this.lat)/res;
int xi = (int) x;
int yi = (int) y;
if (lastXi != xi || lastYi != yi)
{
debug("new Cell for interpolation: %d %d", xi, yi);
recalculateCoefficients(xi, yi);
lastXi = xi;
lastYi = yi;
}
double t = x - xi;
double u = y - yi;
if (xi < 0 || xi > N+1 || yi < 0 || yi > N+1)
throw new IndexOutOfBoundsException(String.format("(%f, %f)->(%d, %d)", lat, lon, xi, yi));
double val = 0;
for (int i=3; i>=0; i--)
{
val = t*val + ((bc[i][3]*u + bc[i][2])*u + bc[i][1])*u + bc[i][0];
}
return val;
}
public double elevation(int x, int y)
{
if (x < 0 || x > N || y < 0 || y > N)
throw new IndexOutOfBoundsException(String.format("elevation: %d %d", x, y));
return ele(x, y);
}
class Isolines
{
DEM data;
int minX;
int maxX;
int minY;
int maxY;
double min;
double max;
Vector isolines = new Vector();
class Isoline
{
int id;
Vector points;
double level;
boolean isClosed;
private Isoline(double level)
{
this.level = level;
isClosed = false;
id = lastId++;
points = new Vector();
}
private class Edge implements Brent.Function
{
double x0, y0, x1, y1;
Edge(double x0, double y0, double x1, double y1)
{
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
}
public double eval(double d)
{
double f = data.elevation(x0 + d * (x1-x0), y0 + d * (y1-y0)) - level;
//System.out.printf("evalEdge: %f %f\n", d, f);
return f;
}
}
private class FN implements Brent.Function
{
double x0, y0;
double dx, dy;
public void setParameter(double x0, double y0, double dx, double dy)
{
this.x0 = x0;
this.y0 = y0;
this.dx = dx;
this.dy = dy;
}
public double eval(double t)
{
double f = data.elevation(y0+t*dy, x0+t*dx) - level;
return f;
}
}
private FN fn = new FN();
double grad[] = new double[2];
double px[] = new double[4];
double py[] = new double[4];
int edges[] = new int[4];
boolean addCell(Position p, int direction)
{
debug("addCell: %f %d %d %d %d", level, p.ix, p.iy, p.edge, direction);
int c = 0;
for (int k=0; k<4; k++)
{
if (k == p.edge)
continue;
int x0 = p.ix + off0[k][0];
int y0 = p.iy + off0[k][1];
int x1 = p.ix + off1[k][0];
int y1 = p.iy + off1[k][1];
double l0 = elevation(x0, y0) - level;
double l1 = elevation(x1, y1) - level;
if (Math.abs(l1) < epsilon || l0*l1 < 0)
{
edges[c] = k;
Brent.Function f = new Edge(data.lat + y0 * DEM.res, data.lon + x0 * DEM.res, data.lat + y1 * DEM.res, data.lon + x1 * DEM.res);
double f0 = elevation(x0, y0) - level;
double f1 = elevation(x1, y1) - level;
double delta;
if (Math.abs(1) < epsilon)
{
delta = 1;
}
else if (Math.abs(f0) < epsilon)
throw new RuntimeException("implementation error!");
else
delta = Brent.zero(f, epsilon, 1-epsilon);
px[c] = data.lon + (x0+delta*(x1-x0))*DEM.res;
py[c] = data.lat + (y0+delta*(y1-y0))*DEM.res;
c++;
}
}
if (c == 1)
{
p.edge = edges[0];
double px0 = p.x;
double py0 = p.y;
p.x = px[0];
p.y = py[0];
double px1 = p.x;
double py1 = p.y;
double xMin = data.lon + p.ix * DEM.res;
double xMax = xMin + DEM.res;
double yMin = data.lat + p.iy * DEM.res;
double yMax = yMin + DEM.res;
refineAdaptively(xMin, yMin, xMax, yMax, px0, py0, px1, py1, direction, maxDist);
addPoint(p.x, p.y, direction);
p.moveCell();
return true;
}
else
{
debug("addCellByStepping: %d", c);
return addCellByStepping(p, direction, c, edges, px, py);
}
}
private void refineAdaptively(double xMin, double yMin, double xMax, double yMax,
double x0, double y0, double x1, double y1,
int direction, double maxDist)
{
double dist = orthodromicDistance(x0, y0, x1, y1);
if (dist > maxDist)
{
double dx = x1-x0;
double dy = y1-y0;
double xm, ym, f0, f1, t0, t1, t;
Brent.Function f;
xm = x0 + 0.5*dx;
ym = y0 + 0.5*dy;
double n = Math.sqrt(dx*dx+dy*dy);
fn.setParameter(xm, ym, -dy/n, dx/n);
f = fn;
t0 = -0.05*res;
t1 = 0.05*res;
f0 = f.eval(t0);
f1 = f.eval(t1);
int count = 0;
while (f0 * f1 > 0 && count++ < 20) {
if ((count & 1) > 0)
t0 -= 0.05*res;
else
t1 += 0.05*res;
f0 = f.eval(t0);
f1 = f.eval(t1);
debug("refine: %f %f %f %f", t0, t1, f0, f1);
}
if (f0 * f1 < 0)
{
t = Brent.zero(f, t0, t1);
xm -= t*dy;
ym += t*dx;
}
else
{
debug("refine failed: %f %f %f %f", t0, t1, f0, f1);
if (false) throw new RuntimeException(String.format("refine failed: %f %f %f %f %f %f %f %f", xMin, yMin, xMax, yMax, x0, y0, x1, y1));
return;
}
if (xm > xMin && xm < xMax && ym > yMin && ym < yMax)
refineAdaptively(xMin, yMin, xMax, yMax, x0, y0, xm, ym, direction, maxDist*1.1);
addPoint(xm, ym, direction);
if (xm > xMin && xm < xMax && ym > yMin && ym < yMax)
refineAdaptively(xMin, yMin, xMax, yMax, xm, ym, x1, y1, direction, maxDist*1.1);
}
}
boolean addCellByStepping(Position p, int direction, int numEdges, int[] edges, double[] px, double[] py)
{
debug("addCellByStepping: %f %d %d %d %d", level, p.ix, p.iy, p.edge, direction);
double xMin = data.lon + p.ix * DEM.res;
double xMax = xMin + DEM.res;
double yMin = data.lat + p.iy * DEM.res;
double yMax = yMin + DEM.res;
double dt, t0, t1;
double f0, f1;
boolean edgeHit = false;
double h[] = new double[4];
int dir;
double n2 = Math.sqrt(1.0/(grad[0]*grad[0] + grad[1]*grad[1]));
double dx;
double dy;
int count=0;
int iMin = -1;
double md = 5000;
for (int i=0; i minDist)
{
if (direction > 0)
points.add(0, new Coord(y, x));
else
points.add(points.size(), new Coord(y, x));
lastX = x;
lastY = y;
}
}
private void close()
{
points.add(points.size(), points.get(0));
isClosed = true;
}
private void addMove(int x0, int y0, int x1, int y1, int direction)
{
double x;
double y;
if (x0 < minX || x0 >= maxX || y0 < minY || y0 >= maxY)
return;
double l0 = data.elevation(x0, y0);
double l1 = data.elevation(x1, y1);
debug("addMove: %d %d %d %d %.2f %.2f %d", x0, y0, x1, y1, l0, l1, direction);
if ((l0 < level && l1 < level) || (l0 > level && l1 > level))
throw new RuntimeException("implementation error");
if (l0 == level)
{
x = data.lon + x0 * DEM.res;
y = data.lat + y0 * DEM.res;
}
else
{
double delta = (l0-level)/(l0-l1);
x = data.lon + (x0 + delta * (x1-x0)) * DEM.res;
y = data.lat + (y0 + delta * (y1-y0)) * DEM.res;
}
double dist = orthodromicDistance(x, y, lastX, lastY);
debug("levels: %d %d %f, %d %d %f: %f %f %f", x0, y0, l0, x1, y1, l1, x, y, dist);
if (dist > 1)
{
if (direction > 0)
points.add(0, new Coord(y, x));
else
points.add(points.size(), new Coord(y, x));
lastX = x;
lastY = y;
}
}
}
public Isolines(DEM data, int minX, int maxX, int minY, int maxY)
{
this.data = data;
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
init();
}
public Isolines(DEM data, double minLat, double minLon, double maxLat, double maxLon)
{
System.out.printf("init: %f %f %f %f\n", minLat, minLon, maxLat, maxLon);
this.data = data;
this.minX = (int) ((minLon-data.lon)/data.res);
this.minY = (int) ((minLat-data.lat)/data.res);
this.maxX = (int) ((maxLon-data.lon)/data.res);
this.maxY = (int) ((maxLat-data.lat)/data.res);
init();
}
private void init()
{
System.out.printf("init: %d %d %d %d\n", minX, minY, maxX, maxY);
data.read(minX-2, minY-2, maxX+2, maxY+2);
// we need some overlap for bicubic interpolation
max = -1000;
min = 10000;
for (int i=minX; i max) max = data.elevation(i, j);
}
debug("min: %f, max: %f\n", min, max);
}
double getMinHeight()
{
return min;
}
double getMaxHeight()
{
return max;
}
public void addLevels(int start, int increment)
{
for (int level=start; level max)
return;
System.out.printf("addLevel: %f\n", level);
java.util.Arrays.fill(visited, (byte) 0);
for (int y=minY; y= level)
{
if (data.elevation(x+1, y) < level) { v |= 1; }
if (data.elevation(x, y+1) < level) { v |= 2; }
}
else
{
if (data.elevation(x+1, y) > level) { v |= 1; }
if (data.elevation(x, y+1) > level) { v |= 2; }
}
int k=-1;
if ((v&1) > 0 && (visited[y*(N+1)+x]&1) == 0)
{
k=0;
}
else if ((v&2) > 0 && (visited[y*(N+1)+x]&2) == 0)
{
k=1;
}
if (k>=0)
{
int x0 = x + off0[k][0];
int y0 = y + off0[k][1];
int x1 = x + off1[k][0];
int y1 = y + off1[k][1];
try {
Brent.Function f = new Edge(data.lat + y0 * DEM.res, data.lon + x0 * DEM.res,
data.lat + y1 * DEM.res, data.lon + x1 * DEM.res,
level);
double f0 = elevation(x0, y0) - level;
double f1 = elevation(x1, y1) - level;
double delta;
if (Math.abs(f0) < epsilon)
{
delta = 0;
}
else if (Math.abs(f1) < epsilon)
continue;
else
delta = Brent.zero(f, 0, 1-epsilon);
Position p = new Position(x, y, data.lon + (x0+delta*(x1-x0))*DEM.res, data.lat + (y0+delta*(y1-y0))*DEM.res, k);
p.markEdge();
isolines.add(traceByStepping(level, p));
}
catch (RuntimeException ex)
{
debug("error: %s", ex.toString());
ex.printStackTrace();
return;
}
}
}
}
}
private Isoline traceByStepping(double level, Position p)
{
debug("traceByStepping: starting contour %f %d %d %f %f %d", level, p.ix, p.iy, p.x, p.y, p.edge);
int direction = 1;
int n = 0;
Position startP = new Position(p);
boolean foundEnd = false;
Isoline line = new Isoline(level);
while (true)
{
debug("traceByStepping: %f %d %d %f %f %d", level, p.ix, p.iy, p.x, p.y, p.edge);
visited[p.iy*(N+1)+p.ix] |= brd[p.edge];
if (n>0 && p.ix == startP.ix && p.iy == startP.iy && orthodromicDistance(p.x, p.y, startP.x, startP.y) < 5)
{
debug("closed curve!");
line.close();
break;
}
else if (p.ix < minX || p.iy < minY || p.ix >= maxX || p.iy >= maxY)
{
if (foundEnd) // did we already reach one end?
{
debug("second border reached!");
break;
}
else
{
debug("border reached!");
foundEnd = true;
n = 0;
direction *= -1;
p = new Position(startP);
p.moveCell();
continue;
}
}
n++;
if (!line.addCell(p, direction) || line.points.size() > maxPoints)
{
debug("ending contour");
isolines.add(line);
return line;
}
}
return line;
}
}
/**
* Returns the orthodromic distance between two geographic coordinates in WGS84 datum.
* The orthodromic distance is the shortest distance between two points
* on a sphere's surface. The orthodromic path is always on a great circle.
*
* @param x1 Longitude of first point (in degrees).
* @param y1 Latitude of first point (in degrees).
* @param x2 Longitude of second point (in degrees).
* @param y2 Latitude of second point (in degrees).
* @return The orthodromic distance (in meters).
*
*/
public static double orthodromicDistance(double x1, double y1, double x2, double y2)
{
x1 = Math.toRadians(x1);
y1 = Math.toRadians(y1);
x2 = Math.toRadians(x2);
y2 = Math.toRadians(y2);
final int MAX_ITERATIONS = 100;
final double EPS = 5.0E-14;
final double F = 1 / inverseFlattening;
final double R = 1 - F;
double tu1 = R * Math.sin(y1) / Math.cos(y1);
double tu2 = R * Math.sin(y2) / Math.cos(y2);
double cu1 = 1 / Math.sqrt(tu1 * tu1 + 1);
double cu2 = 1 / Math.sqrt(tu2 * tu2 + 1);
double su1 = cu1 * tu1;
double s = cu1 * cu2;
double baz = s * tu2;
double faz = baz * tu1;
double x = x2 - x1;
for (int i = 0; i < MAX_ITERATIONS; i++)
{
final double sx = Math.sin(x);
final double cx = Math.cos(x);
tu1 = cu2 * sx;
tu2 = baz - su1 * cu2 * cx;
final double sy = Math.sqrt(tu1*tu1 + tu2*tu2);;
final double cy = s * cx + faz;
final double y = Math.atan2(sy, cy);
final double SA = s * sx / sy;
final double c2a = 1 - SA * SA;
double cz = faz + faz;
if (c2a > 0) {
cz = -cz / c2a + cy;
}
double e = cz * cz * 2 - 1;
double c = ((-3 * c2a + 4) * F + 4) * c2a * F / 16;
double d = x;
x = ((e * cy * c + cz) * sy * c + y) * SA;
x = (1 - c) * x * F + x2 - x1;
if (Math.abs(d - x) <= EPS) {
x = Math.sqrt((1 / (R * R) - 1) * c2a + 1) + 1;
x = (x - 2) / x;
c = 1 - x;
c = (x * x / 4 + 1) / c;
d = (0.375 * x * x - 1) * x;
x = e * cy;
s = 1 - 2 * e;
s = ((((sy * sy * 4 - 3) * s * cz * d / 6 - x) * d / 4 + cz) * sy * d + y) * c * R * semiMajorAxis;
return s;
}
}
final double LEPS = 1.0E-10;
if (Math.abs(x1 - x2) <= LEPS && Math.abs(y1 - y2) <= LEPS) {
return 0;
}
if (Math.abs(y1) <= LEPS && Math.abs(y2) <= LEPS) {
return Math.abs(x1 - x2) * semiMajorAxis;
}
throw new ArithmeticException("no convergence");
}
}