Mercurial > dive4elements > river
diff artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/GaugeMainValueFinder.java @ 9202:b4402594213b
More work on calculations and output for S-Info flood duration workflow (chart types 1 and 2)
author | mschaefer |
---|---|
date | Mon, 02 Jul 2018 07:33:53 +0200 |
parents | |
children | b38be7ea53e2 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/GaugeMainValueFinder.java Mon Jul 02 07:33:53 2018 +0200 @@ -0,0 +1,239 @@ +/** 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.common; + +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.TreeMap; + +import org.dive4elements.river.artifacts.model.Calculation; +import org.dive4elements.river.model.Gauge; +import org.dive4elements.river.model.MainValue; +import org.dive4elements.river.model.MainValueType.MainValueTypeKey; + +/** + * Loading the main values of a gauge to find relative positions of a value and build a corresponding zone name + * + * @author Matthias Schäfer + * + */ +public final class GaugeMainValueFinder { + + /***** FIELDS *****/ + + // private static Logger log = Logger.getLogger(GaugeMainValueNameFinder.class); + + private final Gauge gauge; + + private Calculation problems; + + private final NavigableMap<Double, MainValue> mainValues; + + private final MainValueTypeKey keyType; + + private final String approxPrefix = "ca."; // "\u2248" geht wohl nicht + + private Entry<Double, MainValue> foundCeiling; + + private Entry<Double, MainValue> foundFloor; + + private double foundRelativeDistance; + + + /***** CONSTRUCTORS *****/ + + private GaugeMainValueFinder(final MainValueTypeKey keyType, final Gauge gauge, final Calculation problems) { + this.gauge = gauge; + this.problems = problems; + this.keyType = keyType; + this.mainValues = new TreeMap<>(); + for (final MainValue mainValue : MainValue.getValuesOfGaugeAndType(gauge, keyType)) + this.mainValues.put(Double.valueOf(mainValue.getValue().doubleValue()), mainValue); + if (this.mainValues.isEmpty() && (this.problems != null)) { + this.problems.addProblem("gauge_main_values.missing", gauge.getName()); + // Report only once + this.problems = null; + } + } + + + /***** METHODS *****/ + + /** + * Loads the the main values table of a type and a gauge (GAUGE.sta) + * + * @return The main values finder of a type and a gauge, or null + */ + public static GaugeMainValueFinder loadValues(final MainValueTypeKey type, final Gauge gauge, final Calculation problems) { + return new GaugeMainValueFinder(type, gauge, problems); + } + + /** + * If this provider may return valid data at all. + */ + public boolean isValid() { + return (this.mainValues != null); + } + + /** + * Searches the main value zone for a value, and returns a textual description of the zone + * (name for an exact match, circa expression for +/- 10% match, less-than/between/greater-than expression otherwise) + */ + public String findZoneName(final double value) { + if (!this.findValue(value)) + return ""; + + // Clearly below or just (max. 10%) below lowest named value + if (this.foundFloor == null) { + if (Double.isInfinite(this.foundRelativeDistance)) + return "<" + this.foundCeiling.getValue().getMainValue().getName(); + else + return this.approxPrefix + this.foundCeiling.getValue().getMainValue().getName(); + } + + // Clearly above or just (max. 10%) above highest named value + if (this.foundCeiling == null) { + if (Double.isInfinite(this.foundRelativeDistance)) + return ">" + this.foundFloor.getValue().getMainValue().getName(); + else + return this.approxPrefix + this.foundFloor.getValue().getMainValue().getName(); + } + + // Exact match + if (this.mainValues.containsKey(Double.valueOf(value))) + return this.mainValues.get(Double.valueOf(value)).getMainValue().getName(); + + // Near (10%) one of the borders of a zone interval, or clearly within a zone + if (this.foundRelativeDistance <= 0.001) + return this.foundFloor.getValue().getMainValue().getName(); + else if (this.foundRelativeDistance <= 0.1) + return this.approxPrefix + this.foundFloor.getValue().getMainValue().getName(); + else if (this.foundRelativeDistance >= 0.9) + return this.approxPrefix + this.foundCeiling.getValue().getMainValue().getName(); + else + return this.foundFloor.getValue().getMainValue().getName() + "-" + this.foundCeiling.getValue().getMainValue().getName(); + } + + /** + * Searches the main value zone for a value, and returns the zone name for an exact match, the nomatchReturn otherwise + */ + public String findExactZoneName(final double value, final String noMatchReturn) { + this.findValue(value); + if ((this.foundFloor != null) && (this.foundFloor.getKey() == this.foundCeiling.getKey())) + return this.foundFloor.getValue().getMainValue().getName(); + else + return noMatchReturn; + } + + /** + * Searches the interval of a main value and its relative distance from the lower value + */ + public boolean findValue(final double value) { + this.foundFloor = null; + this.foundCeiling = null; + this.foundRelativeDistance = Double.NaN; + if (!this.isValid()) + return false; + if (Double.isNaN(value)) + return false; + + // Clearly below or just (max. 10%) below lowest named value + this.foundFloor = this.mainValues.floorEntry(Double.valueOf(value)); + if (this.foundFloor == null) { + this.foundCeiling = this.mainValues.firstEntry(); + if (value >= this.mainValues.firstKey().doubleValue() * 0.9) { + this.foundRelativeDistance = 0.9; + return true; + } + else { + this.foundRelativeDistance = Double.NEGATIVE_INFINITY; + return false; + } + } + + // Clearly above or just (max. 10%) above highest named value + this.foundCeiling = this.mainValues.ceilingEntry(Double.valueOf(value)); + if (this.foundCeiling == null) { + if (value <= this.mainValues.lastKey().doubleValue() * 1.1) { + this.foundRelativeDistance = 0.1; + return true; + } + else { + this.foundRelativeDistance = Double.POSITIVE_INFINITY; + return false; + } + } + + // Exact match or within an interval + if (this.foundCeiling.getKey() == this.foundFloor.getKey()) + this.foundRelativeDistance = 0.0; + else + this.foundRelativeDistance = (value - this.foundFloor.getKey().doubleValue()) + / (this.foundCeiling.getKey().doubleValue() - this.foundFloor.getKey().doubleValue()); + return true; + } + + /** + * Floor value of the last findValue + */ + public MainValue getFoundFloorValue() { + if (this.foundFloor != null) + return this.foundFloor.getValue(); + else + return null; + } + + /** + * Ceiling value of the last findValue + */ + public MainValue getFoundCeilingValue() { + if (this.foundCeiling != null) + return this.foundCeiling.getValue(); + else + return null; + } + + /** + * Relative distance of the last findValue + */ + public double getFoundRelativeDistance() { + return this.getFoundRelativeDistance(); + } + + /** + * Searches a pair of zone names and return the a value within the interval by a relative distance, or NaN + */ + public double findValue(final String floorZone, final String ceilingZone, final double relativeDistance) { + this.foundFloor = null; + this.foundCeiling = null; + this.foundRelativeDistance = relativeDistance; + for (final Entry<Double, MainValue> mainValue : this.mainValues.entrySet()) { + if (mainValue.getValue().getMainValue().getName().equalsIgnoreCase(floorZone)) { + this.foundFloor = mainValue; + break; + } + } + if (this.foundFloor == null) + return Double.NaN; + if (floorZone.equalsIgnoreCase(ceilingZone)) + return this.foundFloor.getKey().doubleValue(); + for (final Entry<Double, MainValue> mainValue : this.mainValues.entrySet()) { + if (mainValue.getValue().getMainValue().getName().equalsIgnoreCase(ceilingZone)) { + this.foundCeiling = mainValue; + break; + } + } + if (this.foundCeiling == null) + return Double.NaN; + else + return (this.foundCeiling.getKey().doubleValue() - this.foundFloor.getKey().doubleValue()) * this.foundRelativeDistance + + this.foundFloor.getKey().doubleValue(); + } +} \ No newline at end of file