changeset 5083:7bbee0cfc171 slt-simplify-cross-sections

Added experimental Douglas Peuker simplification of cross sections.
author Sascha L. Teichmann <teichmann@intevation.de>
date Sun, 24 Feb 2013 17:29:52 +0100
parents fb4d87274f28
children ca45dd039b54
files flys-backend/src/main/java/de/intevation/flys/importer/Config.java flys-backend/src/main/java/de/intevation/flys/importer/ImportRiver.java flys-backend/src/main/java/de/intevation/flys/importer/XY.java flys-backend/src/main/java/de/intevation/flys/utils/DouglasPeuker.java
diffstat 4 files changed, 183 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/flys-backend/src/main/java/de/intevation/flys/importer/Config.java	Sun Feb 24 13:03:44 2013 +0100
+++ b/flys-backend/src/main/java/de/intevation/flys/importer/Config.java	Sun Feb 24 17:29:52 2013 +0100
@@ -86,6 +86,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();
 
@@ -99,6 +102,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/flys-backend/src/main/java/de/intevation/flys/importer/ImportRiver.java	Sun Feb 24 13:03:44 2013 +0100
+++ b/flys-backend/src/main/java/de/intevation/flys/importer/ImportRiver.java	Sun Feb 24 17:29:52 2013 +0100
@@ -1,23 +1,9 @@
 package de.intevation.flys.importer;
 
-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.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.log4j.Logger;
-import org.hibernate.Query;
-import org.hibernate.Session;
-import org.hibernate.exception.ConstraintViolationException;
+import de.intevation.artifacts.common.utils.FileTools.HashedFile;
 
 import de.intevation.artifacts.common.utils.FileTools;
-import de.intevation.artifacts.common.utils.FileTools.HashedFile;
+
 import de.intevation.flys.importer.parsers.AnnotationClassifier;
 import de.intevation.flys.importer.parsers.AnnotationsParser;
 import de.intevation.flys.importer.parsers.BedHeightEpochParser;
@@ -39,9 +25,32 @@
 import de.intevation.flys.importer.parsers.WaterlevelDifferencesParser;
 import de.intevation.flys.importer.parsers.WaterlevelParser;
 import de.intevation.flys.importer.parsers.WstParser;
+
 import de.intevation.flys.model.River;
 import de.intevation.flys.model.Unit;
 
+import de.intevation.flys.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.List;
+import java.util.Map;
+import java.util.Set;
+
+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
@@ -159,9 +168,11 @@
 
 
     /** Callback-implementation for CrossSectionParsers. */
-    class ImportRiverCrossSectionParserCallback implements CrossSectionParser.Callback {
-        Set<HashedFile> files = new HashSet<HashedFile>();
-        String type;
+    private class ImportRiverCrossSectionParserCallback
+    implements    CrossSectionParser.Callback {
+
+        private Set<HashedFile> files = new HashSet<HashedFile>();
+        private String type;
 
 
         /**
@@ -185,7 +196,7 @@
 
 
         /** Add crosssection. */
-        public void    parsed(CrossSectionParser parser) {
+        public void parsed(CrossSectionParser parser) {
            log.debug("callback from " + type + " parser");
 
            addCrossSections(parser);
@@ -941,14 +952,20 @@
         List<ImportCrossSectionLine> lines =
             new ArrayList<ImportCrossSectionLine>(data.size());
 
+        Double simplificationEpsilon =
+            Config.INSTANCE.getCrossSectionSimplificationEpsilon();
+
         for (Map.Entry<Double, List<XY>> entry: data.entrySet()) {
             Double   km     = entry.getKey();
             List<XY> points = entry.getValue();
+            if (simplificationEpsilon != null) {
+                points = DouglasPeuker.simplify(points, simplificationEpsilon);
+            }
             lines.add(new ImportCrossSectionLine(km, points));
         }
 
         crossSections.add(new ImportCrossSection(
-            ImportRiver.this, description, ti, lines));
+            this, description, ti, lines));
     }
 
     /** Create a W80 Parser and parse w80 files found. */
@@ -1080,7 +1097,10 @@
     }
 
     public void storeCrossSections() {
-        if (!Config.INSTANCE.skipPRFs() || !Config.INSTANCE.skipDA66s() || !Config.INSTANCE.skipDA50s() || !Config.INSTANCE.skipW80s()) {
+        if (!Config.INSTANCE.skipPRFs()
+        ||  !Config.INSTANCE.skipDA66s()
+        ||  !Config.INSTANCE.skipDA50s()
+        ||  !Config.INSTANCE.skipW80s()) {
             log.info("store cross sections");
             getPeer();
             for (ImportCrossSection crossSection: crossSections) {
--- a/flys-backend/src/main/java/de/intevation/flys/importer/XY.java	Sun Feb 24 13:03:44 2013 +0100
+++ b/flys-backend/src/main/java/de/intevation/flys/importer/XY.java	Sun Feb 24 17:29:52 2013 +0100
@@ -14,6 +14,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;
@@ -52,5 +56,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/flys-backend/src/main/java/de/intevation/flys/utils/DouglasPeuker.java	Sun Feb 24 17:29:52 2013 +0100
@@ -0,0 +1,81 @@
+package de.intevation.flys.utils;
+
+import de.intevation.flys.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