# HG changeset patch # User gernotbelger # Date 1520617626 -3600 # Node ID 11bf13cf046360cc4944de4b3b70df540e2309e8 # Parent a9950a3a71e514e3aa376816eddc207384dacce6 Minor changes to tkh calculation. Loading default bed heights form config file. diff -r a9950a3a71e5 -r 11bf13cf0463 artifacts/doc/conf/sinfo_tkh_bedheights.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/artifacts/doc/conf/sinfo_tkh_bedheights.properties Fri Mar 09 18:47:06 2018 +0100 @@ -0,0 +1,9 @@ +# Konfigurationsdatei f\u00fcr die in der Berechnungsart SINFO-Transportk\u00f6rperh\u00f6hen zu verwendenden Sohlh\u00f6hen. +# Die Datei ist im Charset 'ISO-8859-1' zu encodieren. +# Jede Zeile entspricht: = , , ... +# Alle Daten beziehen sich auf die FLYS Datenbank (d4e Datenbank) +# der entspricht dem Feld 'name' der Tabelle 'rivers' +# die Sohlh\u00f6hen dem Feld 'description' aus der Tabelle 'bed_heights' +# Leerzeichen im Ge\u00e4ssernamen m\u00fcssen mit u0020 kodiert werden. +Beispielfluss=DGM-2004_Epoche-2-SOBEK +Beispiel\u0020fluss=DGM-2004_Epoche-2-SOBEK \ No newline at end of file diff -r a9950a3a71e5 -r 11bf13cf0463 artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/BedHeightsFinder.java --- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/BedHeightsFinder.java Wed Mar 07 17:36:04 2018 +0100 +++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/BedHeightsFinder.java Fri Mar 09 18:47:06 2018 +0100 @@ -11,7 +11,6 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map.Entry; import java.util.NavigableMap; @@ -19,11 +18,9 @@ import org.apache.commons.lang.math.DoubleRange; import org.dive4elements.river.artifacts.math.Linear; -import org.dive4elements.river.artifacts.model.Calculation; import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo; import org.dive4elements.river.model.BedHeight; import org.dive4elements.river.model.BedHeightValue; -import org.dive4elements.river.model.River; /** * Provides bed heigts for vcarious calculations. @@ -37,23 +34,14 @@ private final NavigableMap values; /** - * Create specific bed heights used in tkh-calculation - * - * @param problems + * Create bed height finders from a collection of bed heights. */ - public static Collection createTkhBedHeights(final River river, final Calculation problems, final DoubleRange range) { - // FIXME: determine relevant bed-heights by river: read from some configuration file - // '3' is already the right one for demo-model == '"DGM-2004_Epoche-2-SOBEK"' - final int bedheightId = 3; - - final Collection bedHeights = Collections.singletonList(BedHeight.getBedHeightById(bedheightId)); - - // TODO: check for overlapping ranges... and provide a warning message, else we get problems later - + public static Collection createTkhBedHeights(final DoubleRange range, final Collection bedHeights) { final List result = new ArrayList<>(bedHeights.size()); for (final BedHeight bedHeight : bedHeights) { - result.add(createBedHeights(bedHeight, range)); + final BedHeightsFinder finder = createBedHeights(bedHeight, range); + result.add(finder); } return result; diff -r a9950a3a71e5 -r 11bf13cf0463 artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/DefaultBedHeights.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/DefaultBedHeights.java Fri Mar 09 18:47:06 2018 +0100 @@ -0,0 +1,146 @@ +/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde + * Software engineering by + * Björnsen Beratende Ingenieure GmbH + * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt + * + * This file is Free Software under the GNU AGPL (>=v3) + * and comes with ABSOLUTELY NO WARRANTY! Check out the + * documentation coming with Dive4Elements River for details. + */ +package org.dive4elements.river.artifacts.sinfo.tkhstate; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.math.NumberRange; +import org.dive4elements.artifacts.common.utils.Config; +import org.dive4elements.river.artifacts.model.Calculation; +import org.dive4elements.river.model.BedHeight; +import org.dive4elements.river.model.Range; +import org.dive4elements.river.model.River; + +/** + * This class knows how to find the default bed heights defined for tkh calculation + * + * @author Gernot Belger + */ +final class DefaultBedHeights { + private static final String CONFIG_FILE = "sinfo_tkh_bedheights.properties"; + private final River river; + + public DefaultBedHeights(final River river) { + this.river = river; + } + + public List getBedHeights(final Calculation problems) { + final Collection bedHeightNames = loadBedHeightDefaultsForRiver(this.river, problems); + + final List defaultBedHeights = loadBedHeightsByName(this.river, bedHeightNames, problems); + + final List validBedHeights = new ArrayList<>(defaultBedHeights.size()); + + // REMARK: check for bad ranges because db schema allow for incomplete ranges, and ignore if this is the case + for (final BedHeight bedHeight : defaultBedHeights) { + + final Range range = bedHeight.getRange(); + + if (range.getA() == null || range.getB() == null) + problems.addProblem("sinfo.bedheightsfinder.badrange", bedHeight.getDescription()); + else + validBedHeights.add(bedHeight); + } + + /* check for overlapping ranges, N2-search, but we expect only have small numbers of bed heights */ + final List result = new ArrayList<>(defaultBedHeights.size()); + + for (int i = 0; i < defaultBedHeights.size(); i++) { + final BedHeight bedHeight = validBedHeights.get(i); + + final Range range = bedHeight.getRange(); + final NumberRange bedRange = new NumberRange(range.getA(), range.getB()); + + if (overlapsRange(bedRange, validBedHeights, i + 1)) { + problems.addProblem("sinfo.bedheightsfinder.overlappingrange", bedHeight.getDescription()); + } else + result.add(bedHeight); + } + + return result; + } + + private static Collection loadBedHeightDefaultsForRiver(final River river, final Calculation problems) { + final File configDir = Config.getConfigDirectory(); + final File configFile = new File(configDir, CONFIG_FILE); + + final Properties properties = new Properties(); + try (final InputStreamReader reader = new InputStreamReader(Files.newInputStream(configFile.toPath()), StandardCharsets.ISO_8859_1)) { + properties.load(reader); + + final String value = properties.getProperty(river.getName()); + final String[] split = StringUtils.split(StringUtils.trim(value), ','); + if (ArrayUtils.isEmpty(split)) { + problems.addProblem("sinfo.bedheightsfinder.configfile.missingriver", CONFIG_FILE, river.getName()); + return Collections.emptyList(); + } + + return Arrays.asList(split); + } + catch (final IOException e) { + e.printStackTrace(); + problems.addProblem("sinfo.bedheightsfinder.configfile.loaderror", CONFIG_FILE, e.getMessage()); + return Collections.emptyList(); + } + } + + private static List loadBedHeightsByName(final River shouldBeRiver, final Collection bedHeightNames, final Calculation problems) { + + final List bedHeights = new ArrayList<>(bedHeightNames.size()); + + for (final String name : bedHeightNames) { + try { + final BedHeight bedHeight = BedHeight.getBedHeightByDescription(name); + if (bedHeight == null) + problems.addProblem("sinfo.bedheightsfinder.missingdescription", name); + else { + final River river = bedHeight.getRiver(); + if (!shouldBeRiver.getId().equals(river.getId())) + problems.addProblem("sinfo.bedheightsfinder.wrongriver", name, shouldBeRiver.getName()); + else + bedHeights.add(bedHeight); + } + } + catch (final Exception e) { + e.printStackTrace(); + problems.addProblem("sinfo.bedheightsfinder.missingdescription", name); + } + } + + return bedHeights; + } + + private static boolean overlapsRange(final NumberRange bedRange, final List result, final int startIndex) { + + for (int i = startIndex; i < result.size(); i++) { + + final BedHeight compareBed = result.get(i); + final Range range = compareBed.getRange(); + final NumberRange compareRange = new NumberRange(range.getA(), range.getB()); + + if (compareRange.overlapsRange(bedRange)) + return true; + } + + return false; + } +} \ No newline at end of file diff -r a9950a3a71e5 -r 11bf13cf0463 artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java --- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java Wed Mar 07 17:36:04 2018 +0100 +++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/tkhstate/TkhCalculation.java Fri Mar 09 18:47:06 2018 +0100 @@ -11,9 +11,14 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import org.apache.commons.lang.math.DoubleRange; +import org.apache.commons.lang.math.NumberRange; import org.dive4elements.artifacts.CallContext; import org.dive4elements.river.artifacts.WINFOArtifact; import org.dive4elements.river.artifacts.model.Calculation; @@ -26,11 +31,13 @@ import org.dive4elements.river.artifacts.sinfo.tkhcalculation.DischargeValuesFinder; import org.dive4elements.river.artifacts.sinfo.tkhcalculation.Tkh; import org.dive4elements.river.artifacts.sinfo.tkhcalculation.TkhCalculator; +import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo; import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils; import org.dive4elements.river.artifacts.sinfo.util.RiverInfo; import org.dive4elements.river.artifacts.sinfo.util.WstInfo; import org.dive4elements.river.artifacts.states.WaterlevelData; import org.dive4elements.river.exports.WaterlevelDescriptionBuilder; +import org.dive4elements.river.model.BedHeight; import org.dive4elements.river.model.River; /** @@ -55,7 +62,8 @@ final Calculation problems = new Calculation(); /* find relevant bed-heights */ - final Collection bedHeights = BedHeightsFinder.createTkhBedHeights(river, problems, calcRange); + final List defaultBedHeights = new DefaultBedHeights(river).getBedHeights(problems); + final Collection bedHeights = BedHeightsFinder.createTkhBedHeights(calcRange, defaultBedHeights); /* misuse winfo-artifact to calculate waterlevels in the same way */ final WINFOArtifact winfo = new WinfoArtifactWrapper(sinfo); @@ -118,46 +126,77 @@ final WstInfo wstInfo = new WstInfo(wstLabel, wspYear, riverInfoProvider.getReferenceGauge()); + /* build tkh calculators per bedheight */ + final Map calculatorsByRanges = buildCalculators(calcRange, wkms, bedHeights, problems, riverInfoProvider, wstLabel); + if (calculatorsByRanges.isEmpty()) { + /* there should already be some problems, so just abort */ + return null; + } + final Collection rows = new ArrayList<>(); - /* - * for each separate bed height dataset we do the calculation and put everything into one result, bed heights must not - * overlap accordingly - */ + /* using wst-kms as basis, because we know that they are generated wst's with a fixed km-step */ + // FIXME: das führt dazu, das aktuell die Sohlhöhen beliebig linear interpolierrt werden. ist das immer richtig? z.b. + // bei großen abständen? + + final int size = wkms.size(); + for (int i = 0; i < size; i++) { + + final double station = wkms.getKm(i); + final double wst = wkms.getW(i); + + /* find the right calculator (i.e. bedheigh) depending on station, there should only be one maximal */ + final TkhCalculator tkhCalculator = findCalculator(calculatorsByRanges, station); + if (tkhCalculator == null) + continue; + + final Tkh tkh = tkhCalculator.getTkh(station, wst); + + final String description = descBuilder.getDesc(wkms); + final String gaugeLabel = riverInfoProvider.findGauge(station); + final String location = riverInfoProvider.getLocation(station); + + rows.add(new TkhResultRow(tkh, description, gaugeLabel, location)); + } + + return new TkhCalculationResult(wstLabel, wstInfo, true, rows); + } + + private TkhCalculator findCalculator(final Map calculators, final double station) { + + // REMAKR: linear search at this point, put we expect the number of bed heights to be very small (1-2 items) + final Set> x = calculators.entrySet(); + for (final Entry entry : x) { + final NumberRange range = entry.getKey(); + // FIXME: check if we need comparison with a tolerance + if (range.containsDouble(station)) + return entry.getValue(); + } + + return null; + } + + private Map buildCalculators(final DoubleRange calcRange, final WQKms wkms, final Collection bedHeights, + final Calculation problems, final RiverInfoProvider riverInfoProvider, final String wstLabel) { + final Map calculatorByRanges = new HashMap<>(); for (final BedHeightsFinder bedHeightsProvider : bedHeights) { + final BedHeightInfo info = bedHeightsProvider.getInfo(); + + final NumberRange range = new NumberRange(info.getFrom(), info.getTo()); + final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wkms); /* initialize tkh calculator */ final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(true, this.context, problems, wstLabel, riverInfoProvider.getRiver(), - calcRange, - dischargeProvider, bedHeightsProvider); - if (tkhCalculator == null) { - /* just abort, problems have already been updated by buildTkhCalculator() */ - return null; - } - - /* using wst-kms as basis, because we know that they are generated wst's with a fixed km-step */ - - // FIXME: das führt dazu, das aktuell die Sohlhöhen beliebig linear interpolierrt werden. ist das immer richtig? z.b. - // bei großen abständen? + calcRange, dischargeProvider, bedHeightsProvider); - final int size = wkms.size(); - for (int i = 0; i < size; i++) { - - final double station = wkms.getKm(i); - final double wst = wkms.getW(i); - - final Tkh tkh = tkhCalculator.getTkh(station, wst); - - final String description = descBuilder.getDesc(wkms); - final String gaugeLabel = riverInfoProvider.findGauge(station); - final String location = riverInfoProvider.getLocation(station); - - rows.add(new TkhResultRow(tkh, description, gaugeLabel, location)); + if (tkhCalculator != null) { + /* just ignore null ones, problems have already been updated by buildTkhCalculator() */ + calculatorByRanges.put(range, tkhCalculator); } } - return new TkhCalculationResult(wstLabel, wstInfo, true, rows); + return calculatorByRanges; } } \ No newline at end of file diff -r a9950a3a71e5 -r 11bf13cf0463 artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/BedHeightInfo.java --- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/BedHeightInfo.java Wed Mar 07 17:36:04 2018 +0100 +++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/util/BedHeightInfo.java Fri Mar 09 18:47:06 2018 +0100 @@ -10,6 +10,7 @@ package org.dive4elements.river.artifacts.sinfo.util; import java.io.Serializable; +import java.math.BigDecimal; import org.dive4elements.river.model.BedHeight; @@ -36,6 +37,9 @@ private final String oldElevationModelUnit; + private final BigDecimal from; + + private final BigDecimal to; public static BedHeightInfo from(final BedHeight bedHeight) { return new BedHeightInfo(bedHeight); @@ -49,6 +53,9 @@ this.locationSystem = bedHeight.getLocationSystem().getName(); this.curElevationModelUnit = bedHeight.getCurElevationModel().getUnit().getName(); this.oldElevationModelUnit = bedHeight.getCurElevationModel().getUnit().getName(); + + this.from = bedHeight.getRange().getA(); + this.to = bedHeight.getRange().getB(); } public Integer getYear() { @@ -78,4 +85,12 @@ public String getOldElevationModelUnit() { return this.oldElevationModelUnit; } -} + + public BigDecimal getFrom() { + return this.from; + } + + public BigDecimal getTo() { + return this.to; + } +} \ No newline at end of file diff -r a9950a3a71e5 -r 11bf13cf0463 artifacts/src/main/resources/messages.properties --- a/artifacts/src/main/resources/messages.properties Wed Mar 07 17:36:04 2018 +0100 +++ b/artifacts/src/main/resources/messages.properties Fri Mar 09 18:47:06 2018 +0100 @@ -779,6 +779,13 @@ sinfo_calc_flow_depth.warning.missingD50 = {0}: no D50 available, calculation of transport body height not possible sinfo_calc_flow_depth.warning.missingVelocity = {0}: no flow velocities available, calculation of transport body height not possible +sinfo.bedheightsfinder.badrange = Invalid range for bed heights {0}. +sinfo.bedheightsfinder.overlappingrange = Range of bed height {0} overlaps with other ranges. +sinfo.bedheightsfinder.missingdescription = No bed heights found with description = {0} +sinfo.bedheightsfinder.wrongriver = Bed heights {0} does not belong to river {1} +sinfo.bedheightsfinder.configfile.missingriver = River not defined in config file '{0}': {1} +sinfo.bedheightsfinder.configfile.loaderror = Failed to load config file '{0}': {1} + sinfo_calc_flow_depth_development=Flie\u00dftiefenentwicklung sinfo_calc_flow_depth_minmax=Minimale und Maximale Flie\u00dftiefe sinfo_calc_grounding=Grundber\u00fchrungen diff -r a9950a3a71e5 -r 11bf13cf0463 artifacts/src/main/resources/messages_de.properties --- a/artifacts/src/main/resources/messages_de.properties Wed Mar 07 17:36:04 2018 +0100 +++ b/artifacts/src/main/resources/messages_de.properties Fri Mar 09 18:47:06 2018 +0100 @@ -779,6 +779,13 @@ sinfo_calc_flow_depth.warning.missingD50 = {0}: kein D50 vorhanden, Transportk\u00f6rperh\u00f6henberechnung nicht m\u00f6glich sinfo_calc_flow_depth.warning.missingVelocity = {0}: keine Flie\u00dfgeschwindigkeiten vorhanden, Transportk\u00f6rperh\u00f6henberechnung nicht m\u00f6glich +sinfo.bedheightsfinder.badrange = Ung\u00fcltige -range- f\u00fcr Sohlh\u00f6hen {0}. +sinfo.bedheightsfinder.overlappingrange = -Range- der Sohlh\u00f6hen {0} \u00fcberlappt andere Sohlh\u00f6hen. +sinfo.bedheightsfinder.missingdescription = Sohlh\u00f6he mit -description- {0} nicht vorhanden +sinfo.bedheightsfinder.wrongriver = Sohlh\u00f6he {0} geh\u00f6rt nicht zum Gew\u00e4sser {1} +sinfo.bedheightsfinder.configfile.missingriver = Gew\u00e4sser {1} ist in Konfigurationsdatei {0} nicht definiert. +sinfo.bedheightsfinder.configfile.loaderror = Fehler beim Laden der Konfigurationsdatei '{0}': {1} + sinfo_calc_flow_depth_development=Flie\u00dftiefenentwicklung sinfo_calc_flow_depth_minmax=Minimale und Maximale Flie\u00dftiefe sinfo_calc_grounding=Grundber\u00fchrungen diff -r a9950a3a71e5 -r 11bf13cf0463 backend/src/main/java/org/dive4elements/river/model/BedHeight.java --- a/backend/src/main/java/org/dive4elements/river/model/BedHeight.java Wed Mar 07 17:36:04 2018 +0100 +++ b/backend/src/main/java/org/dive4elements/river/model/BedHeight.java Fri Mar 09 18:47:06 2018 +0100 @@ -262,5 +262,17 @@ return singles != null ? singles.get(0) : null; } + + public static BedHeight getBedHeightByDescription(final String description) { + + final Session session = SessionHolder.HOLDER.get(); + + final Query query = session.createQuery("from BedHeight where description=:description"); + query.setParameter("description", description); + + final List singles = query.list(); + + return singles != null ? singles.get(0) : null; + } } -// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : \ No newline at end of file