diff gnv-artifacts/src/main/java/de/intevation/gnv/math/Interpolation2D.java @ 875:5e9efdda6894

merged gnv-artifacts/1.0
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:13:56 +0200
parents 05bf8534a35a
children f953c9a559d8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gnv-artifacts/src/main/java/de/intevation/gnv/math/Interpolation2D.java	Fri Sep 28 12:13:56 2012 +0200
@@ -0,0 +1,337 @@
+package de.intevation.gnv.math;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+
+import com.vividsolutions.jts.index.strtree.STRtree;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Interpolates along a given line string. This used to generate
+ * "horizontale Schnittprofile".
+ *
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
+ */
+public final class Interpolation2D
+{
+    private static Logger log = Logger.getLogger(Interpolation2D.class);
+
+    /**
+     * If the number of input points are over this threshold
+     * some culling strategies are applied to reduce the amount of points.
+     */
+    public static final int CULL_POINT_THRESHOLD = Integer.getInteger(
+        "gnv.interpolation2d.cull.point.threshold", 1000);
+
+    /**
+     * Numerical stability epsilon.
+     */
+    public static final double EPS = 1e-6d;
+
+    /**
+     * Callback to send the interpolated values back to the
+     * caller without building large temporary strcutures.
+     */
+    public interface Consumer {
+        /**
+         * Sends an interpolated point back to the called.
+         * The interpolated parameter is stored in the z component.
+         * @param point The interpolated point.
+         * @param success true if interpolation at the point
+         * succeed, else false.
+         */
+        void interpolated(Coordinate point, boolean success);
+    } // interface Consumer
+
+    private Interpolation2D() {
+    }
+
+    /**
+     * Calculates the relevant area for a given line string.
+     * @param path The line string.
+     * @param points The sample points.
+     * @return The relevant area.
+     */
+    public static final Envelope relevantArea(
+        List<? extends Coordinate> path,
+        List<? extends Coordinate> points
+    ) {
+        return relevantArea(path, points, CULL_POINT_THRESHOLD);
+    }
+
+    /**
+     * Calculates the relevant area for a given bounding box.
+     * @param pathBBox The bounding box.
+     * @param points The sample points.
+     * @return The relevant area.
+     */
+    public static final Envelope relevantArea(
+        Envelope                   pathBBox,
+        List<? extends Coordinate> points
+    ) {
+        return relevantArea(pathBBox, points, CULL_POINT_THRESHOLD);
+    }
+
+    /**
+     * Calculates the relevant area for a given bounding box.
+     * @param pathBBox The bounding box.
+     * @param points The sample points.
+     * @param threshold If the number of sample points
+     * are below this threshold no relevant area is calculated.
+     * @return The relevant area.
+     */
+    public static final Envelope relevantArea(
+        Envelope                   pathBBox,
+        List<? extends Coordinate> points,
+        int                        threshold
+    ) {
+        return points.size() < threshold
+            ? null
+            : relevantArea(
+                pathBBox,
+                pointsBoundingBox(points));
+    }
+
+    /**
+     * Calculates the relevant area for a given line string.
+     * @param path The line string.
+     * @param points The sample points.
+     * @param threshold If the number of sample points
+     * are below this threshold no relevant area is calculated.
+     * @return The relevant area.
+     */
+    public static final Envelope relevantArea(
+        List<? extends Coordinate> path,
+        List<? extends Coordinate> points,
+        int                        threshold
+    ) {
+        return points.size() < threshold || path.isEmpty()
+            ? null
+            : relevantArea(
+                pointsBoundingBox(path),
+                pointsBoundingBox(points));
+    }
+
+    /**
+     * Heuristic function to define a 'relevant area'.
+     * @param pathBBox The bounding box of the line line string.
+     * @param pointsBBox The bounding box of the sample points.
+     * @return The relevant area.
+     */
+    public static final Envelope relevantArea(
+        Envelope pathBBox,
+        Envelope pointsBBox
+    ) {
+        double pathArea   = pathBBox.getWidth()*pathBBox.getHeight();
+        double pointsArea = pointsBBox.getWidth()*pointsBBox.getHeight();
+
+        if (pathArea > 0.8d*pointsArea) { return null; }
+
+        double nArea = 1.44d * pathArea;
+        if (nArea < 0.1d*pointsArea) nArea = 0.1d*pointsArea;
+        double w = pathBBox.getWidth();
+        double h = pathBBox.getHeight();
+        double [] d = solveQuadratic(1d, w+h, pathArea - nArea);
+
+        if (d == null) { return null; }
+
+        double extra = pos(d);
+
+        pathBBox.expandBy(extra);
+
+        return pathBBox;
+    }
+
+    /**
+     * Solves quadratic equation a*x*x + b*x + c = 0.
+     * @param a a-coefficent.
+     * @param b b-coefficent
+     * @param c c-coefficent.
+     * @return the solution of the equation, or null if not solvable.
+     */
+    public static final double [] solveQuadratic(
+        double a, double b, double c
+    ) {
+        double d = b*b - 4d*a*c;
+        if (d < 0d) { return null; }
+
+        d = Math.sqrt(d);
+        a = 1d/(2d*a);
+        b = -b;
+
+        return new double [] { a*(b + d), a*(b - d) };
+    }
+
+    /**
+     * Return the element of a two element array which
+     * is greater or equal zero.
+     * @param x The two values.
+     * @return The value which is greater or equal zero.
+     */
+    public static final double pos(double [] x) {
+        return x[0] >= 0 ? x[0] : x[1];
+    }
+
+
+    /**
+     * Calculates the bounding box of a given line string.
+     * @param path The line string.
+     * @return The bounding box.
+     */
+    public static Envelope pointsBoundingBox(
+        List<? extends Coordinate> path
+    ) {
+        int N = path.size();
+        Envelope area = new Envelope(path.get(N-1));
+
+        for (int i = N-2; i >= 0; --i) {
+            area.expandToInclude(path.get(i));
+        }
+
+        return area;
+    }
+
+    /**
+     * Interpolates linearly a number of coordinates and parameter values along
+     * a given line string path. The results are issued to a consumer.
+     * @param path The line string path.
+     * @param points The sample points.
+     * @param from Start point as a scalar value linear
+     * referenced on the line string.
+     * @param to End point of  as a scalar value linear
+     * referenced on the line string.
+     * @param steps Number of points to be interpolated.
+     * @param metrics The used metric.
+     * @param consumer The callback to retrieve the result points.
+     */
+    public static void interpolate(
+        List<? extends Coordinate> path,
+        List<? extends Point2d>    points,
+        double                     from,
+        double                     to,
+        int                        steps,
+        Metrics                    metrics,
+        Consumer                   consumer
+    ) {
+        boolean debug = log.isDebugEnabled();
+
+        int N = path.size();
+        int M = points.size();
+
+        if (debug) {
+            log.debug("Size of path: " + N);
+            log.debug("Size of points: " + M);
+        }
+
+        if (M < 1 || N < 2) { // nothing to do
+            return;
+        }
+
+        List<GridCell> cells = GridCell.pointsToGridCells(
+            points, relevantArea(path, points));
+
+        if (cells.isEmpty()) {
+            log.warn("no cells found");
+            return;
+        }
+
+        // put into spatial index to speed up finding neighbors.
+        STRtree spatialIndex = new STRtree();
+
+        for (GridCell cell: cells) {
+            spatialIndex.insert(cell.getEnvelope(), cell);
+        }
+
+        LinearToMap linearToMap = new LinearToMap(
+            path, from, to, metrics);
+
+        double dP = (to - from)/steps;
+
+        Coordinate          center      = new Coordinate();
+        Envelope            queryBuffer = new Envelope();
+        GridCell.CellFinder finder      = new GridCell.CellFinder();
+
+        int missedInterpolations = 0;
+        int interpolations       = 0;
+
+        for (double p = from; p <= to; p += dP) {
+            if (!linearToMap.locate(p, center)) {
+                continue;
+            }
+            queryBuffer.init(
+                center.x - EPS, center.x + EPS,
+                center.y - EPS, center.y + EPS);
+
+            finder.prepare(center);
+            spatialIndex.query(queryBuffer, finder);
+
+            GridCell found = finder.found;
+
+            if (found == null) {
+                consumer.interpolated(center, false);
+                ++missedInterpolations;
+                continue;
+            }
+
+            Point2d n0 = found.p1;
+            Point2d n1 = found.p2;
+            Point2d n2 = found.p3;
+            Point2d n3 = found.p4;
+
+            double z1 = interpolate(
+                n0.x, n0.z,
+                n1.x, n1.z,
+                center.x);
+            double z2 = interpolate(
+                n2.x, n2.z,
+                n3.x, n3.z,
+                center.x);
+            double y1 = interpolate(
+                n0.x, n0.y,
+                n1.x, n1.y,
+                center.x);
+            double y2 = interpolate(
+                n2.x, n2.y,
+                n3.x, n3.y,
+                center.x);
+            center.z = interpolate(
+                y1, z1,
+                y2, z2,
+                center.y);
+            consumer.interpolated(center, true);
+            ++interpolations;
+        }
+
+        if (debug) {
+            log.debug("interpolations: " +
+                interpolations + " / " + missedInterpolations);
+        }
+    }
+
+    /**
+     * Linear interpolate a value between (x1, y1) and (x2, y2) at
+     * a given x-value.
+     * @param x1 x component of first point.
+     * @param y1 y component of first point.
+     * @param x2 x component of second point.
+     * @param y2 y component of second point.
+     * @param x The x value.
+     * @return The intepolated result.
+     */
+    public static final double interpolate(
+        double x1, double y1,
+        double x2, double y2,
+        double x
+    ) {
+        if (x2 == x1) {
+            return (y1 + y2)*0.5d;
+        }
+        double m = (y2-y1)/(x2-x1);
+        double b = y1 - m*x1;
+        return m*x + b;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org