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 path, sascha@434: List 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 :