changeset 7733:228be10e6165

Merge slt-simplify-cross-sections branch into default.
author Tom Gottfried <tom@intevation.de>
date Wed, 22 Jan 2014 12:54:18 +0100
parents 772c22b0ea12 (current diff) e1b831fe435a (diff)
children 46273d890da5
files
diffstat 4 files changed, 204 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/backend/src/main/java/org/dive4elements/river/importer/Config.java	Mon Jan 20 14:54:35 2014 +0100
+++ b/backend/src/main/java/org/dive4elements/river/importer/Config.java	Wed Jan 22 12:54:18 2014 +0100
@@ -100,6 +100,9 @@
     public static final String SKIP_SQ_RELATION =
         "flys.backend.importer.skip.sq.relation";
 
+    public static final Double CROSS_SECTION_SIMPLIFICATION_EPSILON =
+        getDouble("flys.backend.importer.cross.section.simplification.epsilon");
+
 
     public static final Config INSTANCE = new Config();
 
@@ -113,6 +116,21 @@
             : Boolean.getBoolean(SKIP_DEFAULT);
     }
 
+    public static final Double getDouble(String key) {
+        try {
+            String value = System.getProperty(key);
+            return value != null
+                ? Double.valueOf(value)
+                : null;
+        } catch (NumberFormatException nfe) {
+            return null;
+        }
+    }
+
+    public Double getCrossSectionSimplificationEpsilon() {
+        return CROSS_SECTION_SIMPLIFICATION_EPSILON;
+    }
+
     public boolean dryRun() {
         return getFlag(DRY_RUN);
     }
--- a/backend/src/main/java/org/dive4elements/river/importer/ImportRiver.java	Mon Jan 20 14:54:35 2014 +0100
+++ b/backend/src/main/java/org/dive4elements/river/importer/ImportRiver.java	Wed Jan 22 12:54:18 2014 +0100
@@ -39,23 +39,29 @@
 import org.dive4elements.river.model.River;
 import org.dive4elements.river.model.Unit;
 
+import org.dive4elements.river.utils.DouglasPeuker;
+
 import java.io.File;
 import java.io.IOException;
 
+import java.sql.SQLException;
+
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Iterator;
 
 import org.apache.log4j.Logger;
 
 import org.hibernate.Query;
 import org.hibernate.Session;
 
+import org.hibernate.exception.ConstraintViolationException;
+
 
 /** Import all river-related data (files) that can be found. */
 public class ImportRiver
@@ -183,24 +189,24 @@
     protected River peer;
 
 
-    /** Callback-implementation for CrossSectionParsers:
-     * Accept files with different md5(?)sums than what has already been parsed,
-     * on successfull parse, add data. */
-    class ImportRiverCrossSectionParserCallback implements CrossSectionParser.Callback {
-        Set<HashedFile> files = new HashSet<HashedFile>();
-        String type;
+    /** Callback-implementation for CrossSectionParsers. */
+    private class ImportRiverCrossSectionParserCallback
+    implements    CrossSectionParser.Callback {
 
+        private Set<HashedFile> files = new HashSet<HashedFile>();
+        private String          type;
 
         /**
          * Create new Callback, given type which is used for logging
          * purposes only.
          */
-        public ImportRiverCrossSectionParserCallback (String type) {
+        public ImportRiverCrossSectionParserCallback(String type) {
             this.type = type;
         }
 
 
         /** Accept file if not duplicate. */
+        @Override
         public boolean accept(File file) {
             HashedFile hf = new HashedFile(file);
             boolean success = files.add(hf);
@@ -212,14 +218,60 @@
 
 
         /** Add crosssection. */
+        @Override
         public void parsed(CrossSectionParser parser) {
            log.debug("callback from " + type + " parser");
 
-           addCrossSections(parser);
+            String  description = parser.getDescription();
+            Integer year        = parser.getYear();
+            ImportTimeInterval ti = year != null
+                ? new ImportTimeInterval(yearToDate(year))
+                : null;
+
+            Map<Double, List<XY>> data = parser.getData();
+
+            List<ImportCrossSectionLine> lines =
+                new ArrayList<ImportCrossSectionLine>(data.size());
+
+            Double simplificationEpsilon =
+                Config.INSTANCE.getCrossSectionSimplificationEpsilon();
+
+            long numReadPoints      = 0L;
+            long numRemainingPoints = 0L;
+
+            for (Map.Entry<Double, List<XY>> entry: data.entrySet()) {
+                Double   km     = entry.getKey();
+                List<XY> points = entry.getValue();
+                numReadPoints += points.size();
+                if (simplificationEpsilon != null) {
+                    points = DouglasPeuker.simplify(points, simplificationEpsilon);
+                }
+                numRemainingPoints += points.size();
+                lines.add(new ImportCrossSectionLine(km, points));
+            }
+
+            ImportRiver.this.addCrossSections(description, ti, lines);
+
+            double percent = numReadPoints > 0L
+                ? ((double)numRemainingPoints/numReadPoints)*100d
+                : 0d;
+
+            log.info(String.format(
+                "Number of points in cross section: %d / %d (%.2f%%)",
+                numReadPoints, numRemainingPoints, percent));
         }
     } // ImportRiverCrossSectionParserCallback
 
 
+    private void addCrossSections(
+        String                       description,
+        ImportTimeInterval           ti,
+        List<ImportCrossSectionLine> lines
+    ) {
+        crossSections.add(new ImportCrossSection(this, description, ti, lines));
+    }
+
+
     public ImportRiver() {
         hyks                      = new ArrayList<ImportHYK>();
         crossSections             = new ArrayList<ImportCrossSection>();
@@ -1052,30 +1104,6 @@
     }
 
 
-    /** Add cross sections with description, years and lines to
-     * store. */
-    private void addCrossSections(CrossSectionParser parser) {
-        String  description = parser.getDescription();
-        Integer year        = parser.getYear();
-        ImportTimeInterval ti = year != null
-            ? new ImportTimeInterval(yearToDate(year))
-            : null;
-
-        Map<Double, List<XY>> data = parser.getData();
-
-        List<ImportCrossSectionLine> lines =
-            new ArrayList<ImportCrossSectionLine>(data.size());
-
-        for (Map.Entry<Double, List<XY>> entry: data.entrySet()) {
-            Double   km     = entry.getKey();
-            List<XY> points = entry.getValue();
-            lines.add(new ImportCrossSectionLine(km, points));
-        }
-
-        crossSections.add(new ImportCrossSection(
-            ImportRiver.this, description, ti, lines));
-    }
-
     /** Create a W80 Parser and parse w80 files found. */
     public void parseW80s() {
         if (Config.INSTANCE.skipW80s()) {
@@ -1134,6 +1162,7 @@
 
         ImportRiverCrossSectionParserCallback da50Callback =
             new ImportRiverCrossSectionParserCallback("da50");
+
         parser.parseDA50s(riverDir, da50Callback);
     }
 
@@ -1155,6 +1184,7 @@
 
         ImportRiverCrossSectionParserCallback da66Callback =
             new ImportRiverCrossSectionParserCallback("da66");
+
         parser.parseDA66s(riverDir, da66Callback);
     }
 
--- a/backend/src/main/java/org/dive4elements/river/importer/XY.java	Mon Jan 20 14:54:35 2014 +0100
+++ b/backend/src/main/java/org/dive4elements/river/importer/XY.java	Wed Jan 22 12:54:18 2014 +0100
@@ -22,6 +22,10 @@
     public XY() {
     }
 
+    public XY(XY other) {
+        this(other.x, other.y, other.index);
+    }
+
     public XY(double x, double y, int index) {
         this.x     = x;
         this.y     = y;
@@ -60,5 +64,43 @@
     public void setIndex(int index) {
         this.index = index;
     }
+
+    public double dot(double ox, double oy) {
+        return x*ox + y*oy;
+    }
+
+    public double dot(XY other) {
+        return dot(other.x, other.y);
+    }
+
+    public XY sub(XY other) {
+        x -= other.x;
+        y -= other.y;
+        return this;
+    }
+
+    public XY ortho() {
+        double z = x;
+        x = y;
+        y = -z;
+        return this;
+    }
+
+    public XY normalize() {
+        double len = dot(this);
+
+        if (len > 1e-6) {
+            len = 1d/Math.sqrt(len);
+            x *= len;
+            y *= len;
+        }
+
+        return this;
+    }
+
+    // x*nx + y*ny + d = 0 <=> d = -x*nx -y*ny
+    public double lineOffset(XY p) {
+         return -x*p.x -y*p.y;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/backend/src/main/java/org/dive4elements/river/utils/DouglasPeuker.java	Wed Jan 22 12:54:18 2014 +0100
@@ -0,0 +1,81 @@
+package org.dive4elements.river.utils;
+
+import org.dive4elements.river.importer.XY; // TODO: Move to a more common package.
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class DouglasPeuker
+{
+    public static final double EPSILON = 1e-4;
+
+    private DouglasPeuker() {
+    }
+
+    public static List<XY> simplify(List<XY> input) {
+        return simplify(input, EPSILON);
+    }
+
+    public static List<XY> simplify(List<XY> input, double epsilon) {
+
+        int N = input.size();
+
+        if (N < 3) {
+            return new ArrayList<XY>(input);
+        }
+
+        List<XY> simplified = recursiveSimplify(input, 0, N-1, epsilon);
+
+        List<XY> output = new ArrayList<XY>(simplified.size()+2);
+        output.add(input.get(0));
+        output.addAll(simplified);
+        output.add(input.get(N-1));
+
+        return output;
+    }
+
+    private static List recursiveSimplify(
+        List<XY> input,
+        int      start,
+        int      end,
+        double   epsilon
+    ) {
+        XY a = input.get(start);
+        XY b = input.get(end);
+
+        // Normal of hesse normal form.
+        XY n = new XY(b).sub(a).ortho().normalize();
+
+        // distance offset of the hesse normal form.
+        double d = n.lineOffset(a);
+
+        double maxDist = -Double.MAX_VALUE;
+        int maxIdx = -1;
+
+        for (int i = start+1; i < end; ++i) {
+            double dist = Math.abs(n.dot(input.get(i)) + d);
+            if (dist > maxDist) {
+                maxDist = dist;
+                maxIdx  = i;
+            }
+        }
+
+        if (maxDist < epsilon) {
+            // All points between a and b can be ignored.
+            return Collections.<XY>emptyList();
+        }
+
+        // Split by input[maxIdx].
+        List<XY> before = recursiveSimplify(input, start, maxIdx, epsilon);
+        List<XY> after  = recursiveSimplify(input, maxIdx, end, epsilon);
+
+        List<XY> output = new ArrayList<XY>(before.size()+1+after.size());
+        output.addAll(before);
+        output.add(input.get(maxIdx));
+        output.addAll(after);
+
+        return output;
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org