changeset 593:b248531fa20b

Added experimental support for extrapolation in "Horizontalschnitte" gnv-artifacts/trunk@648 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sun, 31 Jan 2010 21:50:15 +0000
parents 3c939c95c477
children 5b9b74c08bbb
files gnv-artifacts/ChangeLog gnv-artifacts/doc/conf/conf.xml gnv-artifacts/src/main/java/de/intevation/gnv/artifacts/context/GNVArtifactContext.java gnv-artifacts/src/main/java/de/intevation/gnv/artifacts/context/GNVArtifactContextFactory.java gnv-artifacts/src/main/java/de/intevation/gnv/math/AreaInterpolation.java gnv-artifacts/src/main/java/de/intevation/gnv/math/GridCell.java gnv-artifacts/src/main/java/de/intevation/gnv/math/Point2d.java gnv-artifacts/src/main/java/de/intevation/gnv/state/profile/horizontalcrosssection/HorizontalCrossSectionMeshOutputState.java
diffstat 8 files changed, 363 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/gnv-artifacts/ChangeLog	Fri Jan 29 13:28:28 2010 +0000
+++ b/gnv-artifacts/ChangeLog	Sun Jan 31 21:50:15 2010 +0000
@@ -1,3 +1,37 @@
+2010-01-31  Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/gnv/artifacts/context/GNVArtifactContext.java,
+	  src/main/java/de/intevation/gnv/artifacts/context/GNVArtifactContextFactory.java,
+	  doc/conf/conf.xml: Added configuration for extrapolation in "Horizontalschnitte".
+
+	  Use gnv/horizontal-cross-section/extrapolation/@rounds with 
+	  integer rounds > 0 to turn extrapolation on (default: 0).
+	  Rounds is a number of successive point extrapolations which means that 
+	  the grid is successively filled with missing points based on prior rounds. 
+	  The larger 'rounds' get more gaps are filled synthetic generated points.
+
+	  Set this to 2 to get good results for the model data FIS.
+
+	* src/main/java/de/intevation/gnv/state/profile/horizontalcrosssection/HorizontalCrossSectionMeshOutputState.java:
+	  Foward configuration to area interpolation.
+
+	* src/main/java/de/intevation/gnv/math/AreaInterpolation.java: 
+	  Foward configuration to GridCell.
+
+	* src/main/java/de/intevation/gnv/math/Point2d.java: Added method to extrapolate
+	  point along a line spanned by two points.
+	  Calculate Inverse Distance Weighting (IDW) for a given set of points on
+	  z components. Added method to check if set of points are near a given
+	  point.
+
+	* src/main/java/de/intevation/gnv/math/GridCell.java: Before building the
+	  i/j cells the grid is filled with synthetic generated points. The 
+	  position is estimated from the neighboring points. The parameter values
+	  are calculated by IDW. Some care is taken to avoid invalid grid topologies.
+
+	  TODO: Implement this for the "Profillschnitt" too to keep the inner
+	  symmetry.
+
 2010-01-29  Tim Englich  <tim.englich@intevation.de>
 
 	* src/test/java/de/intevation/gnv/artifacts/MeshVerticalProfileTestCase.java (testArtifact),
@@ -252,7 +286,7 @@
 	* src/main/java/de/intevation/gnv/chart/VerticalCrossSectionChart.java:
 	  Handle the seabad polygon color.
 
-2010-01-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>fill-color
+2010-01-24	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
 
 	* contrib/palette2qgis.xsl: Cosmetic cleanups.
 
--- a/gnv-artifacts/doc/conf/conf.xml	Fri Jan 29 13:28:28 2010 +0000
+++ b/gnv-artifacts/doc/conf/conf.xml	Sun Jan 31 21:50:15 2010 +0000
@@ -421,6 +421,7 @@
         <horizontal-cross-section>
             <!-- This section configures the HorizontalCrossSection ("Horizontalschnitt") -->
             <samples number="1024"/>
+            <extrapolation rounds="0"/>
             <ground interpolation="bilinear" />
             <result-shapefile-directory path="${artifacts.config.dir}/../shapefiles/"/>
         </horizontal-cross-section>
--- a/gnv-artifacts/src/main/java/de/intevation/gnv/artifacts/context/GNVArtifactContext.java	Fri Jan 29 13:28:28 2010 +0000
+++ b/gnv-artifacts/src/main/java/de/intevation/gnv/artifacts/context/GNVArtifactContext.java	Sun Jan 31 21:50:15 2010 +0000
@@ -36,6 +36,9 @@
     public static final String HORIZONTAL_CROSS_SECTION_SAMPLES_KEY =
         "gnv.horizontal.cross.section.samples";
 
+    public static final String HORIZONTAL_CROSS_SECTION_EXTRAPOLATION_ROUNDS_KEY =
+        "gnv.horizontal.cross.section.extrapolation.rounds";
+
     public static final String
         HORIZONTAL_CROSS_SECTION_RESULT_SHAPEFILE_PATH_KEY =
         "gnv.horizontal.cross.section.result.shapefile";
@@ -50,6 +53,9 @@
     public static final Integer 
         DEFAULT_HORIZONTAL_CROSS_SECTION_SAMPLES = Integer.valueOf(1024);
 
+    public static final Integer 
+        DEFAULT_HORIZONTAL_CROSS_SECTION_EXTRAPOLATION_ROUNDS = Integer.valueOf(0);
+
     public static final String HORIZONTAL_CROSS_SECTION_GROUND_INTERPOLATION_KEY =
         "gnv.horizontal.cross.section.ground.interpolation";
 
--- a/gnv-artifacts/src/main/java/de/intevation/gnv/artifacts/context/GNVArtifactContextFactory.java	Fri Jan 29 13:28:28 2010 +0000
+++ b/gnv-artifacts/src/main/java/de/intevation/gnv/artifacts/context/GNVArtifactContextFactory.java	Sun Jan 31 21:50:15 2010 +0000
@@ -1,6 +1,3 @@
-/**
- *
- */
 package de.intevation.gnv.artifacts.context;
 
 import java.awt.Color;
@@ -81,6 +78,9 @@
     public final static String HORIZONTAL_CROSS_SECTION_SAMPLES =
         "/artifact-database/gnv/horizontal-cross-section/samples/@number";
     
+    public final static String HORIZONTAL_CROSS_SECTION_EXTRAPOLATION_ROUNDS =
+        "/artifact-database/gnv/horizontal-cross-section/extrapolation/@rounds";
+    
     public final static String HORIZONTAL_CROSS_SECTION_RESULT_SHAPEFILE_PATH =
         "/artifact-database/gnv/horizontal-cross-section/result-shapefile-directory/@path";
     
@@ -364,10 +364,47 @@
         log.info("configuration of horizontal cross section");
 
         configureHorizontalCrossSectionSamples(config, context);
+        configureHorizontalCrossSectionExtrapolation(config, context);
         configureHorizontalCrossSectionResultShapeFilePath(config, context);
         configureHorizontalCrossSectionGroundInterpolation(config, context);
     }
 
+    protected void configureHorizontalCrossSectionExtrapolation(
+        Document           config,
+        GNVArtifactContext context
+    )
+    {
+        log.info(
+            "configuration of horizontal cross section extrapolation");
+
+        String extrapolationRoundsValue = Config.getStringXPath(
+            config,
+            HORIZONTAL_CROSS_SECTION_EXTRAPOLATION_ROUNDS);
+
+        Integer extrapolationRounds =
+            GNVArtifactContext.DEFAULT_HORIZONTAL_CROSS_SECTION_EXTRAPOLATION_ROUNDS;
+
+        if (extrapolationRoundsValue != null
+        && (extrapolationRoundsValue = extrapolationRoundsValue.trim()).length() > 0
+        ) {
+            try {
+                extrapolationRounds =
+                    Integer.valueOf(extrapolationRoundsValue);
+            }
+            catch (NumberFormatException nfe) {
+                log.error(
+                    "'" + extrapolationRoundsValue + "' is not a valid integer");
+            }
+        }
+
+        log.info("extrapolation rounds: " + extrapolationRounds);
+
+        context.put(
+            GNVArtifactContext
+                .HORIZONTAL_CROSS_SECTION_EXTRAPOLATION_ROUNDS_KEY,
+            extrapolationRounds);
+    }
+
     protected void configureHorizontalCrossSectionGroundInterpolation(
         Document           config,
         GNVArtifactContext context
--- a/gnv-artifacts/src/main/java/de/intevation/gnv/math/AreaInterpolation.java	Fri Jan 29 13:28:28 2010 +0000
+++ b/gnv-artifacts/src/main/java/de/intevation/gnv/math/AreaInterpolation.java	Sun Jan 31 21:50:15 2010 +0000
@@ -49,7 +49,8 @@
         List<? extends Point2d> points,
         Envelope                boundingBox,
         Dimension               samples,
-        XYDepth                 depth
+        XYDepth                 depth,
+        int                     extrapolationRounds
     ) {
         boolean debug = log.isDebugEnabled();
 
@@ -62,7 +63,8 @@
             points,
             Interpolation2D.relevantArea(
                 boundingBox,
-                points));
+                points),
+            extrapolationRounds);
 
         if (cells.isEmpty()) {
             log.warn("no cells to interpolate");
--- a/gnv-artifacts/src/main/java/de/intevation/gnv/math/GridCell.java	Fri Jan 29 13:28:28 2010 +0000
+++ b/gnv-artifacts/src/main/java/de/intevation/gnv/math/GridCell.java	Sun Jan 31 21:50:15 2010 +0000
@@ -1,5 +1,7 @@
 package de.intevation.gnv.math;
 
+import com.vividsolutions.jts.algorithm.CGAlgorithms;
+
 import com.vividsolutions.jts.geom.Coordinate;
 import com.vividsolutions.jts.geom.Envelope;
 import com.vividsolutions.jts.geom.Geometry;
@@ -73,8 +75,21 @@
     }
 
     protected void createPolygon() {
+        Coordinate [] coords = new Coordinate [] { p1, p2, p3, p4, p1 };
+        if (!CGAlgorithms.isCCW(coords)) {
+            for (int i = 0, j = coords.length-1; i < j; ++i, --j) {
+                Coordinate c = coords[i];
+                coords[i] = coords[j];
+                coords[j] = c;
+            }
+        }
         LinearRing shell = GEOMETRY_FACTORY
-            .createLinearRing(new Coordinate [] { p1, p2, p3, p4, p1 });
+            .createLinearRing(coords);
+
+        if (!shell.isValid()) {
+            log.warn("linear ring is not valid");
+        }
+
         polygon = GEOMETRY_FACTORY.createPolygon(shell, null);
     }
 
@@ -96,6 +111,111 @@
         List<? extends Point2d> points,
         Envelope                relevantArea
     ) {
+        return pointsToGridCells(points, relevantArea, 0);
+    }
+
+    private static final int NEIGHBORS [][] = {
+        { -1, -1 }, // 0
+        { -1,  0 }, // 1
+        { -1, +1 }, // 2
+        {  0, +1 }, // 3
+        { +1, +1 }, // 4
+        { +1,  0 }, // 5
+        { +1, -1 }, // 6
+        {  0, -1 }  // 7
+    };
+
+    public static int extrapolate(
+        HashMap<Integer, HashMap<Integer, Point2d>> rows,
+        int minI, int maxI,
+        int minJ, int maxJ,
+        int rounds,
+        Envelope relevantArea
+    ) {
+        Point2d [] neighbors = new Point2d[NEIGHBORS.length];
+        Point2d [] positions = new Point2d[NEIGHBORS.length];
+
+        int total = 0;
+
+        ArrayList<ArrayList<IJKey>> prio =
+            new ArrayList<ArrayList<IJKey>>(NEIGHBORS.length);
+
+        for (int i = 0; i < NEIGHBORS.length; ++i) {
+            prio.add(new ArrayList<IJKey>());
+        }
+
+        while (rounds-- > 0) {
+            for (int i = minI; i <= maxI; ++i) {
+                for (int j = minJ; j <= maxJ; ++j) {
+                    Point2d p = get(rows, i, j);
+                    if (p != null) {
+                        continue;
+                    }
+
+                    int count = 0;
+
+                    for (int k = 0; k < neighbors.length; ++k) {
+                        neighbors[k] = positions[k] = null;
+                        int dij [] = NEIGHBORS[k];
+                        Point2d n1 = get(rows, i+  dij[0], j+  dij[1]);
+                        Point2d n2 = get(rows, i+2*dij[0], j+2*dij[1]);
+                        if (n1 != null && n2 != null) {
+                            ++count;
+                        }
+                    }
+
+                    if (count > 0) {
+                        prio.get(count-1).add(new IJKey(i, j));
+                    }
+                } // for all columns
+            } // for all rows
+
+            int N = 0;
+
+            for (int l = NEIGHBORS.length-1; l >= 0; --l) {
+                ArrayList<IJKey> list = prio.get(l);
+                for (IJKey ij: list) {
+                    int i = ij.i;
+                    int j = ij.j;
+                    for (int k = 0; k < neighbors.length; ++k) {
+                        neighbors[k] = positions[k] = null;
+                        int dij [] = NEIGHBORS[k];
+                        Point2d n1 = get(rows, i+  dij[0], j+  dij[1]);
+                        Point2d n2 = get(rows, i+2*dij[0], j+2*dij[1]);
+                        if (n1 != null && n2 != null) {
+                            neighbors[k] = n1;
+                            positions[k] = n1.extrapolate(-1d, n2);
+                        }
+                    }
+
+                    Point2d avg = Point2d.average(positions);
+
+                    if (avg != null && avg.near(positions)
+                    && (relevantArea == null || relevantArea.contains(avg.x, avg.y))) {
+                        avg.i = i;
+                        avg.j = j;
+                        avg.inverseDistanceWeighting(neighbors);
+                        put(rows, avg, i, j);
+                    }
+                }
+                N += list.size();
+                list.clear();
+            }
+
+            if (N == 0) {
+                break;
+            }
+            total += N;
+        } // for all rounds
+
+        return total;
+    }
+
+    public static List<GridCell> pointsToGridCells(
+        List<? extends Point2d> points,
+        Envelope                relevantArea,
+        int                     extrapolationRounds
+    ) {
         int minI = Integer.MAX_VALUE;
         int maxI = Integer.MIN_VALUE;
         int minJ = Integer.MAX_VALUE;
@@ -129,25 +249,36 @@
 
         ArrayList<GridCell> cells = new ArrayList<GridCell>(points.size());
 
+        int extrapolated = extrapolate(
+            rows,
+            minI, maxI,
+            minJ, maxJ,
+            extrapolationRounds,
+            relevantArea);
+
         for (int i = minI + 1; i <= maxI; ++i) {
             HashMap<Integer, Point2d> row1 = rows.get(i-1);
             HashMap<Integer, Point2d> row2 = rows.get(i);
-            if (row1 != null && row2 != null) {
-                for (int j = minJ + 1; j < maxJ; ++j) {
-                    Point2d p1 = row1.get(j-1);
-                    Point2d p2 = row1.get(j);
-                    Point2d p3 = row2.get(j);
-                    Point2d p4 = row2.get(j-1);
 
-                    if (p1 != null && p2 != null &&  p3 != null && p4 != null) {
-                        cells.add(new GridCell(p1, p2, p3, p4));
-                    }
-                } // for all columns in row
+            if (row1 == null || row2 == null) {
+                continue;
+            }
+
+            for (int j = minJ + 1; j < maxJ; ++j) {
+                Point2d p1 = row1.get(j-1);
+                Point2d p2 = row1.get(j);
+                Point2d p3 = row2.get(j);
+                Point2d p4 = row2.get(j-1);
+
+                if (p1 != null && p2 != null && p3 != null && p4 != null) {
+                    cells.add(new GridCell(p1, p2, p3, p4));
+                }
             }
         } // for all rows
 
         if (log.isDebugEnabled()) {
             log.debug("culled points: " + culled);
+            log.debug("extrapolated points: " + extrapolated);
             log.debug("min/max i: " + minI + " / " + maxI);
             log.debug("min/max j: " + minJ + " / " + maxJ);
             log.debug("cells found: " + cells.size());
@@ -155,5 +286,26 @@
 
         return cells;
     }
+
+    private static Point2d get(
+        HashMap<Integer, HashMap<Integer, Point2d>> rows,
+        int i, int j
+    ) {
+        HashMap<Integer, Point2d> row = rows.get(i);
+        return row != null ? row.get(j) : null;
+    }
+
+    private static void put(
+        HashMap<Integer, HashMap<Integer, Point2d>> rows,
+        Point2d point,
+        int i, int j
+    ) {
+        Integer I = Integer.valueOf(i);
+        HashMap<Integer, Point2d> row = rows.get(I);
+        if (row == null) {
+            rows.put(I, row = new HashMap<Integer, Point2d>());
+        }
+        row.put(j, point);
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/gnv-artifacts/src/main/java/de/intevation/gnv/math/Point2d.java	Fri Jan 29 13:28:28 2010 +0000
+++ b/gnv-artifacts/src/main/java/de/intevation/gnv/math/Point2d.java	Sun Jan 31 21:50:15 2010 +0000
@@ -1,16 +1,20 @@
 package de.intevation.gnv.math;
 
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+
 import java.util.Comparator;
 
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.Coordinate;
+import org.apache.log4j.Logger;
 
 /**
- *  @author Sascha L. Teichmann
+ *  @author Sascha L. Teichmann (sascha.teichmann@intevation.de)
  */
 public class Point2d
 extends      Coordinate
 {
+    private static Logger log = Logger.getLogger(Point2d.class);
+
     public static final double EPSILON = 1e-3d;
 
     public static final Comparator X_COMPARATOR = new Comparator() {
@@ -59,6 +63,10 @@
     public Point2d() {
     }
 
+    public Point2d(double x, double y) {
+        super(x, y);
+    }
+
     public Point2d(double x, double y, int i, int j) {
         super(x, y);
         this.i = i;
@@ -97,5 +105,95 @@
     public boolean hasJGap(Point2d other) {
         return Math.abs(j - other.j) > 1;
     }
+
+    public Point2d extrapolate(double t, Point2d other) {
+        if (other == null) {
+            return null;
+        }
+        Point2d p = newPoint();
+        p.x = t*(other.x - x) + x;
+        p.y = t*(other.y - y) + y;
+        return p;
+    }
+
+    public Point2d newPoint() {
+        return new Point2d(0d, 0d);
+    }
+
+    public Point2d newPoint(double x, double y) {
+        return new Point2d(x, y);
+    }
+
+    public void inverseDistanceWeighting(Point2d [] ps) {
+
+        double sum = 0d;
+
+        double [] d = new double[ps.length];
+
+        for (int i = 0; i < ps.length; ++i) {
+            Point2d p = ps[i];
+            if (p != null) {
+                double di = distance(p);
+                if (di < 1e-5d) { z = p.z; return; }
+                di = 1d/di;
+                d[i] = di;
+                sum += di;
+            }
+        }
+
+        if (sum == 0d) {
+            return;
+        }
+
+        double v = 0d;
+
+        for (int i = 0; i < ps.length; ++i) {
+            Point2d p = ps[i];
+            if (p != null) {
+                v += p.z*d[i];
+            }
+        }
+        z = v/sum;
+    }
+
+    public static Point2d average(Point2d [] ps) {
+
+        Point2d p = null;
+        int count = 0;
+
+        for (int i = 0; i < ps.length; ++i) {
+            Point2d t = ps[i];
+            if (t != null) {
+                ++count;
+                if (p == null) {
+                    p = t.newPoint(t.x, t.y);
+                }
+                else {
+                    p.x += t.x;
+                    p.y += t.y;
+                }
+            }
+        }
+
+        if (p != null) {
+            double s = 1d/count;
+            p.x *= s;
+            p.y *= s;
+        }
+
+        return p;
+    }
+
+    public boolean near(Point2d [] ps) {
+
+        for (int i = 0; i < ps.length; ++i) {
+            Point2d p = ps[i];
+            if (p != null && distance(p) > EPSILON) {
+                return false;
+            }
+        }
+
+        return true;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8:
--- a/gnv-artifacts/src/main/java/de/intevation/gnv/state/profile/horizontalcrosssection/HorizontalCrossSectionMeshOutputState.java	Fri Jan 29 13:28:28 2010 +0000
+++ b/gnv-artifacts/src/main/java/de/intevation/gnv/state/profile/horizontalcrosssection/HorizontalCrossSectionMeshOutputState.java	Sun Jan 31 21:50:15 2010 +0000
@@ -522,12 +522,14 @@
 
         int numSamples          = numSamples(callContext);
         int groundInterpolation = getGroundInterpolation(callContext);
+        int extrapolationRounds = extrapolationRounds(callContext);
 
         if (!interpolation.interpolate(
             input.getPoints(), 
             boundingBox,
             new Dimension(numSamples, numSamples),
-            new QueriedXYDepth(groundInterpolation)
+            new QueriedXYDepth(groundInterpolation),
+            extrapolationRounds
         )) {
             log.error("interpolation failed");
             return null;
@@ -636,6 +638,16 @@
             : GNVArtifactContext.DEFAULT_HORIZONTAL_CROSS_SECTION_SAMPLES;
     }
 
+    private static int extrapolationRounds(CallContext callContext) {
+        GNVArtifactContext context =
+            (GNVArtifactContext)callContext.globalContext();
+        Integer extrapolationRounds = (Integer)context.get(
+            GNVArtifactContext.HORIZONTAL_CROSS_SECTION_EXTRAPOLATION_ROUNDS_KEY);
+        return extrapolationRounds != null
+            ? extrapolationRounds.intValue()
+            : GNVArtifactContext.DEFAULT_HORIZONTAL_CROSS_SECTION_EXTRAPOLATION_ROUNDS;
+    }
+
     private static File shapefileDirectory(CallContext callContext) {
         GNVArtifactContext context =
             (GNVArtifactContext)callContext.globalContext();

http://dive4elements.wald.intevation.org