# HG changeset patch # User Tom Gottfried # Date 1390391658 -3600 # Node ID 228be10e6165cb507bd20e3bd71671dad61c8967 # Parent 772c22b0ea12188a28cfbf807b3ad4070f6ff564# Parent e1b831fe435a7b2730bfb02f8c63e1b56568acdc Merge slt-simplify-cross-sections branch into default. diff -r 772c22b0ea12 -r 228be10e6165 backend/src/main/java/org/dive4elements/river/importer/Config.java --- 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); } diff -r 772c22b0ea12 -r 228be10e6165 backend/src/main/java/org/dive4elements/river/importer/ImportRiver.java --- 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 files = new HashSet(); - String type; + /** Callback-implementation for CrossSectionParsers. */ + private class ImportRiverCrossSectionParserCallback + implements CrossSectionParser.Callback { + private Set files = new HashSet(); + 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> data = parser.getData(); + + List lines = + new ArrayList(data.size()); + + Double simplificationEpsilon = + Config.INSTANCE.getCrossSectionSimplificationEpsilon(); + + long numReadPoints = 0L; + long numRemainingPoints = 0L; + + for (Map.Entry> entry: data.entrySet()) { + Double km = entry.getKey(); + List 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 lines + ) { + crossSections.add(new ImportCrossSection(this, description, ti, lines)); + } + + public ImportRiver() { hyks = new ArrayList(); crossSections = new ArrayList(); @@ -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> data = parser.getData(); - - List lines = - new ArrayList(data.size()); - - for (Map.Entry> entry: data.entrySet()) { - Double km = entry.getKey(); - List 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); } diff -r 772c22b0ea12 -r 228be10e6165 backend/src/main/java/org/dive4elements/river/importer/XY.java --- 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 : diff -r 772c22b0ea12 -r 228be10e6165 backend/src/main/java/org/dive4elements/river/utils/DouglasPeuker.java --- /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 simplify(List input) { + return simplify(input, EPSILON); + } + + public static List simplify(List input, double epsilon) { + + int N = input.size(); + + if (N < 3) { + return new ArrayList(input); + } + + List simplified = recursiveSimplify(input, 0, N-1, epsilon); + + List output = new ArrayList(simplified.size()+2); + output.add(input.get(0)); + output.addAll(simplified); + output.add(input.get(N-1)); + + return output; + } + + private static List recursiveSimplify( + List 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.emptyList(); + } + + // Split by input[maxIdx]. + List before = recursiveSimplify(input, start, maxIdx, epsilon); + List after = recursiveSimplify(input, maxIdx, end, epsilon); + + List output = new ArrayList(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 :