ingo@1115: /*
ingo@1115: * Copyright (c) 2010 by Intevation GmbH
ingo@1115: *
ingo@1115: * This program is free software under the LGPL (>=v2.1)
ingo@1115: * Read the file LGPL.txt coming with the software for details
ingo@1115: * or visit http://www.gnu.org/licenses/ if it does not exist.
ingo@1115: */
ingo@1115:
sascha@434: package de.intevation.gnv.math;
sascha@434:
sascha@434: import com.vividsolutions.jts.geom.Coordinate;
sascha@434: import com.vividsolutions.jts.geom.Envelope;
sascha@434:
sascha@515: import com.vividsolutions.jts.index.strtree.STRtree;
sascha@515:
sascha@515: import java.awt.Dimension;
sascha@515:
sascha@515: import java.io.Serializable;
sascha@515:
sascha@515: import java.util.Arrays;
sascha@515: import java.util.List;
sascha@434:
sascha@434: import org.apache.log4j.Logger;
sascha@434:
sascha@445: /**
sascha@808: * Interpolates parameter values along a given line string from surface
sascha@808: * to the sea ground to generate "Profilschnitte".
sascha@808: *
sascha@780: * @author Sascha L. Teichmann
sascha@445: */
sascha@434: public class Interpolation3D
sascha@445: implements Serializable
sascha@434: {
sascha@434: private static Logger log = Logger.getLogger(Interpolation3D.class);
sascha@434:
sascha@808: /**
sascha@808: * The default width of the interpolation: {@value}
sascha@808: */
sascha@434: public static final int DEFAULT_WIDTH = 1024;
sascha@808:
sascha@808: /**
sascha@808: * The default height of the interpolation: {@value}
sascha@808: */
sascha@434: public static final int DEFAULT_HEIGHT = 768;
sascha@434:
sascha@808: /**
sascha@808: * Epsilon for numerical stability.
sascha@808: */
sascha@515: public static final double EPS = 1e-6d;
sascha@515:
sascha@808: /**
sascha@808: * The width of the interpolation.
sascha@808: */
sascha@434: protected int width;
sascha@808:
sascha@808: /**
sascha@808: * The height of the interpolation.
sascha@808: */
sascha@434: protected int height;
sascha@434:
sascha@808: /**
sascha@808: * The cell width of the interpolation in world units.
sascha@808: */
sascha@521: protected double cellWidth;
sascha@808:
sascha@808: /**
sascha@808: * The cell height of the interpolation in world units.
sascha@808: */
sascha@521: protected double cellHeight;
sascha@521:
sascha@808: /**
ingo@837: * The coordinates of the interpolation.
ingo@837: */
ingo@837: protected Coordinate[] coordinates;
ingo@837:
ingo@837: /**
sascha@808: * The generated raster.
sascha@808: */
sascha@434: protected double [] raster;
sascha@808:
sascha@808: /**
sascha@808: * The sea ground depth along the line string.
sascha@808: */
sascha@434: protected double [] depths;
sascha@434:
sascha@808: /**
sascha@808: * Default constructor.
sascha@808: */
sascha@434: public Interpolation3D() {
sascha@434: this(DEFAULT_WIDTH, DEFAULT_HEIGHT);
sascha@434: }
sascha@434:
sascha@808: /**
sascha@808: * Constructor to create a Interpolation3D with a given size.
sascha@808: * @param size The size of the interpolation.
sascha@808: */
sascha@445: public Interpolation3D(Dimension size) {
sascha@445: this(size.width, size.height);
sascha@445: }
sascha@445:
sascha@808: /**
sascha@808: * Constructor to create a Interpolation3D with a given size.
sascha@808: * @param width The width of the interpolation.
sascha@808: * @param height the height of the interpolation.
sascha@808: */
sascha@434: public Interpolation3D(int width, int height) {
sascha@434: this.width = width;
sascha@434: this.height = height;
sascha@434: }
sascha@434:
sascha@808: /**
sascha@808: * Returns the raster height of the interpolation.
sascha@808: * @return The raster height of the interpolation.
sascha@808: */
sascha@434: public int getHeight() {
sascha@434: return height;
sascha@434: }
sascha@434:
sascha@808: /**
sascha@808: * Returns the raster width of the interpolation.
sascha@808: * @return The raster width of the interpolation.
sascha@808: */
sascha@434: public int getWidth() {
sascha@434: return width;
sascha@434: }
sascha@434:
sascha@808: /**
sascha@808: * Returns the cell width of the interpolation in world units.
sascha@808: * @return The cell width of the interpolation in world units.
sascha@808: */
sascha@521: public double getCellWidth() {
sascha@521: return cellWidth;
sascha@521: }
sascha@521:
sascha@808: /**
sascha@808: * Returns the cell height of the interpolation in world units.
sascha@808: * @return The cell height of the interpolation in world units.
sascha@808: */
sascha@521: public double getCellHeight() {
sascha@521: return cellHeight;
sascha@521: }
sascha@521:
sascha@808: /**
ingo@837: * Returns the coordinates used for the interpolation.
ingo@837: * @return the coordinates used for the interpolation.
ingo@837: */
ingo@837: public Coordinate[] getCoordinates() {
ingo@837: return coordinates;
ingo@837: }
ingo@837:
ingo@837: /**
sascha@808: * Returns the generated raster.
sascha@808: * @return The generated raster.
sascha@808: */
sascha@434: public double [] getRaster() {
sascha@434: return raster;
sascha@434: }
sascha@434:
sascha@808: /**
sascha@808: * Returns the depths along the line string path.
sascha@808: * @return The depth along the line string path.
sascha@808: */
sascha@434: public double [] getDepths() {
sascha@434: return depths;
sascha@434: }
sascha@434:
sascha@808: /**
sascha@808: * Returns the deepest depth along the line string path.
sascha@808: * @return The deepest depth along the line string path.
sascha@808: */
sascha@445: public double getMaxDepth() {
sascha@445: double maxDepth = Double.MAX_VALUE;
sascha@445: for (int i = depths!=null?depths.length-1:0; i >= 0; --i) {
sascha@446: double d = depths[i];
sascha@446: if (!Double.isNaN(d) && d < maxDepth) {
sascha@446: maxDepth = d;
sascha@445: }
sascha@445: }
sascha@445: return maxDepth;
sascha@445: }
sascha@445:
sascha@808: /**
sascha@808: * Interpolates parameters along a given line string path from the surface
sascha@808: * to the sea ground.
sascha@808: * @param path The line string path.
sascha@808: * @param points The sample points.
sascha@808: * @param from Start point in scalar terms of the line string.
sascha@808: * @param to End point in scalar terms of the line string.
sascha@808: * @param metrics The used metric.
sascha@808: * @param xyDepth The callback to query the depth at a given point.
sascha@808: * @return true if the interpolation succeeds, else false.
sascha@808: */
sascha@434: public boolean interpolate(
sascha@434: List extends Coordinate> path,
sascha@434: List extends XYColumn> points,
sascha@434: double from,
sascha@434: double to,
sascha@434: Metrics metrics,
sascha@434: XYDepth xyDepth
sascha@434: ) {
sascha@434: boolean debug = log.isDebugEnabled();
sascha@434:
sascha@434: int N = path.size();
sascha@434: int M = points.size();
sascha@434:
sascha@434: if (debug) {
sascha@434: log.debug("Size of path: " + N);
sascha@434: log.debug("Size of points: " + M);
sascha@434: }
sascha@434:
sascha@434: if (M < 1 || N < 2) { // nothing to do
sascha@434: return false;
sascha@434: }
sascha@434:
sascha@518: List cells = GridCell.pointsToGridCells(
sascha@528: points, Interpolation2D.relevantArea(path, points));
sascha@434:
sascha@515: if (cells.isEmpty()) {
sascha@515: log.warn("no cells found");
sascha@515: return false;
sascha@434: }
sascha@434:
sascha@434: // put into spatial index to speed up finding neighbors.
sascha@515: STRtree spatialIndex = new STRtree();
sascha@434:
sascha@515: for (GridCell cell: cells) {
sascha@515: spatialIndex.insert(cell.getEnvelope(), cell);
sascha@434: }
sascha@434:
sascha@434: LinearToMap linearToMap = new LinearToMap(
sascha@434: path, from, to, metrics);
sascha@434:
sascha@434: double [] depths = new double[width];
sascha@434: Arrays.fill(depths, Double.NaN);
sascha@434:
ingo@837: Coordinate[] coordinates = new Coordinate[width];
ingo@837:
sascha@434: double cellWidth = (to - from)/width;
sascha@434:
sascha@434: double maxDepth = Double.MAX_VALUE;
sascha@434:
sascha@434: int i = 0;
sascha@434: Coordinate center = new Coordinate();
sascha@434: for (double p = cellWidth*0.5; i < depths.length; ++i, p += cellWidth) {
sascha@434: if (linearToMap.locate(p, center)) {
ingo@837: coordinates[i] = (Coordinate) center.clone();
sascha@434: double depth = xyDepth.depth(center);
sascha@434: depths[i] = depth;
sascha@434: if (depth < maxDepth) {
sascha@434: maxDepth = depth;
sascha@434: }
sascha@434: }
sascha@434: }
sascha@434:
sascha@434: if (maxDepth == Double.MAX_VALUE) {
sascha@434: log.warn("no depth found -> no interpolation");
sascha@434: return false;
sascha@434: }
sascha@434:
sascha@434: double cellHeight = Math.abs(maxDepth)/height;
sascha@434:
sascha@434: if (debug) {
sascha@445: log.debug("max depth found: " + maxDepth);
sascha@434: log.debug("cell size: " + cellWidth + " x " + cellHeight);
sascha@434: }
sascha@434:
sascha@434: double [] raster = new double[width*height];
sascha@434: Arrays.fill(raster, Double.NaN);
sascha@434:
sascha@515: Envelope queryBuffer = new Envelope();
sascha@515: GridCell.CellFinder finder = new GridCell.CellFinder();
sascha@434:
sascha@434: i = 0;
sascha@434: for (double p = cellWidth*0.5; i < depths.length; ++i, p += cellWidth) {
sascha@434: double depth = depths[i];
sascha@521: if (Double.isNaN(depth) || depth >= 0d) {
sascha@434: continue;
sascha@434: }
sascha@434: linearToMap.locate(p, center);
sascha@434:
sascha@434: queryBuffer.init(
sascha@778: center.x - EPS, center.x + EPS,
sascha@515: center.y - EPS, center.y + EPS);
sascha@434:
sascha@515: finder.prepare(center);
sascha@515: spatialIndex.query(queryBuffer, finder);
sascha@434:
sascha@515: GridCell found = finder.found;
sascha@515:
sascha@515: if (found == null) {
sascha@515: continue;
sascha@434: }
sascha@434:
sascha@515: XYColumn n0 = (XYColumn)found.p1;
sascha@515: XYColumn n1 = (XYColumn)found.p2;
sascha@515: XYColumn n2 = (XYColumn)found.p3;
sascha@515: XYColumn n3 = (XYColumn)found.p4;
sascha@434:
sascha@515: if (n0.prepare(xyDepth)
sascha@515: && n1.prepare(xyDepth)
sascha@515: && n2.prepare(xyDepth)
sascha@515: && n3.prepare(xyDepth)
sascha@434: ) {
sascha@434: double y1 = Interpolation2D.interpolate(
sascha@515: n0.x, n0.y,
sascha@515: n1.x, n1.y,
sascha@434: center.x);
sascha@434: double y2 = Interpolation2D.interpolate(
sascha@515: n2.x, n2.y,
sascha@515: n3.x, n3.y,
sascha@434: center.x);
sascha@521: double z = -cellHeight*0.5;
sascha@521: for (int j = i;
sascha@453: j < raster.length && z >= depth;
sascha@453: z -= cellHeight, j += width) {
sascha@434:
sascha@515: double v0 = n0.value(z);
sascha@515: double v1 = n1.value(z);
sascha@515: double v2 = n2.value(z);
sascha@515: double v3 = n3.value(z);
sascha@434:
sascha@434: double z1 = Interpolation2D.interpolate(
sascha@515: n0.x, v0,
sascha@515: n1.x, v1,
sascha@434: center.x);
sascha@434: double z2 = Interpolation2D.interpolate(
sascha@515: n2.x, v2,
sascha@515: n3.x, v3,
sascha@434: center.x);
sascha@434: double value = Interpolation2D.interpolate(
sascha@434: y1, z1,
sascha@434: y2, z2,
sascha@434: center.y);
sascha@434: raster[j] = value;
sascha@434: }
sascha@778: // XXX: Adjusted depth to create no gap
sascha@521: // between last value and ground.
sascha@778: depths[i] = z+0.5d*cellHeight;
sascha@434: } // down the x/y column
sascha@434: } // all along the track
sascha@434:
ingo@837: this.coordinates = coordinates;
ingo@837: this.depths = depths;
ingo@837: this.raster = raster;
ingo@837: this.cellWidth = cellWidth;
ingo@837: this.cellHeight = cellHeight;
sascha@434:
sascha@434: return true;
sascha@434: }
sascha@434: }
sascha@798: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :