mschaefer@9202: /** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde mschaefer@9202: * Software engineering by mschaefer@9202: * Björnsen Beratende Ingenieure GmbH mschaefer@9202: * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt mschaefer@9202: * mschaefer@9202: * This file is Free Software under the GNU AGPL (>=v3) mschaefer@9202: * and comes with ABSOLUTELY NO WARRANTY! Check out the mschaefer@9202: * documentation coming with Dive4Elements River for details. mschaefer@9202: */ mschaefer@9202: package org.dive4elements.river.artifacts.sinfo.common; mschaefer@9202: mschaefer@9202: import java.util.Map.Entry; mschaefer@9202: import java.util.NavigableMap; mschaefer@9202: import java.util.TreeMap; mschaefer@9202: mschaefer@9202: import org.dive4elements.river.artifacts.model.Calculation; mschaefer@9202: import org.dive4elements.river.model.Gauge; mschaefer@9202: import org.dive4elements.river.model.MainValue; mschaefer@9202: import org.dive4elements.river.model.MainValueType.MainValueTypeKey; mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Loading the main values of a gauge to find relative positions of a value and build a corresponding zone name mschaefer@9202: * mschaefer@9202: * @author Matthias Schäfer mschaefer@9202: * mschaefer@9202: */ mschaefer@9202: public final class GaugeMainValueFinder { mschaefer@9202: mschaefer@9202: /***** FIELDS *****/ mschaefer@9202: mschaefer@9202: // private static Logger log = Logger.getLogger(GaugeMainValueNameFinder.class); mschaefer@9202: mschaefer@9202: private final Gauge gauge; mschaefer@9202: mschaefer@9202: private Calculation problems; mschaefer@9202: mschaefer@9202: private final NavigableMap mainValues; mschaefer@9202: mschaefer@9202: private final MainValueTypeKey keyType; mschaefer@9202: mschaefer@9202: private final String approxPrefix = "ca."; // "\u2248" geht wohl nicht mschaefer@9202: mschaefer@9202: private Entry foundCeiling; mschaefer@9202: mschaefer@9202: private Entry foundFloor; mschaefer@9202: mschaefer@9202: private double foundRelativeDistance; mschaefer@9202: mschaefer@9202: mschaefer@9202: /***** CONSTRUCTORS *****/ mschaefer@9202: mschaefer@9202: private GaugeMainValueFinder(final MainValueTypeKey keyType, final Gauge gauge, final Calculation problems) { mschaefer@9202: this.gauge = gauge; mschaefer@9202: this.problems = problems; mschaefer@9202: this.keyType = keyType; mschaefer@9202: this.mainValues = new TreeMap<>(); mschaefer@9202: for (final MainValue mainValue : MainValue.getValuesOfGaugeAndType(gauge, keyType)) mschaefer@9202: this.mainValues.put(Double.valueOf(mainValue.getValue().doubleValue()), mainValue); mschaefer@9202: if (this.mainValues.isEmpty() && (this.problems != null)) { mschaefer@9202: this.problems.addProblem("gauge_main_values.missing", gauge.getName()); mschaefer@9202: // Report only once mschaefer@9202: this.problems = null; mschaefer@9202: } mschaefer@9202: } mschaefer@9202: mschaefer@9202: mschaefer@9202: /***** METHODS *****/ mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Loads the the main values table of a type and a gauge (GAUGE.sta) mschaefer@9202: * mschaefer@9202: * @return The main values finder of a type and a gauge, or null mschaefer@9202: */ mschaefer@9202: public static GaugeMainValueFinder loadValues(final MainValueTypeKey type, final Gauge gauge, final Calculation problems) { mschaefer@9202: return new GaugeMainValueFinder(type, gauge, problems); mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * If this provider may return valid data at all. mschaefer@9202: */ mschaefer@9202: public boolean isValid() { mschaefer@9202: return (this.mainValues != null); mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Searches the main value zone for a value, and returns a textual description of the zone mschaefer@9202: * (name for an exact match, circa expression for +/- 10% match, less-than/between/greater-than expression otherwise) mschaefer@9202: */ mschaefer@9202: public String findZoneName(final double value) { mschaefer@9202: if (!this.findValue(value)) mschaefer@9202: return ""; mschaefer@9202: mschaefer@9202: // Clearly below or just (max. 10%) below lowest named value mschaefer@9202: if (this.foundFloor == null) { mschaefer@9202: if (Double.isInfinite(this.foundRelativeDistance)) mschaefer@9202: return "<" + this.foundCeiling.getValue().getMainValue().getName(); mschaefer@9202: else mschaefer@9202: return this.approxPrefix + this.foundCeiling.getValue().getMainValue().getName(); mschaefer@9202: } mschaefer@9202: mschaefer@9202: // Clearly above or just (max. 10%) above highest named value mschaefer@9202: if (this.foundCeiling == null) { mschaefer@9202: if (Double.isInfinite(this.foundRelativeDistance)) mschaefer@9202: return ">" + this.foundFloor.getValue().getMainValue().getName(); mschaefer@9202: else mschaefer@9202: return this.approxPrefix + this.foundFloor.getValue().getMainValue().getName(); mschaefer@9202: } mschaefer@9202: mschaefer@9202: // Exact match mschaefer@9202: if (this.mainValues.containsKey(Double.valueOf(value))) mschaefer@9202: return this.mainValues.get(Double.valueOf(value)).getMainValue().getName(); mschaefer@9202: mschaefer@9202: // Near (10%) one of the borders of a zone interval, or clearly within a zone mschaefer@9202: if (this.foundRelativeDistance <= 0.001) mschaefer@9202: return this.foundFloor.getValue().getMainValue().getName(); mschaefer@9202: else if (this.foundRelativeDistance <= 0.1) mschaefer@9202: return this.approxPrefix + this.foundFloor.getValue().getMainValue().getName(); mschaefer@9202: else if (this.foundRelativeDistance >= 0.9) mschaefer@9202: return this.approxPrefix + this.foundCeiling.getValue().getMainValue().getName(); mschaefer@9202: else mschaefer@9202: return this.foundFloor.getValue().getMainValue().getName() + "-" + this.foundCeiling.getValue().getMainValue().getName(); mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Searches the main value zone for a value, and returns the zone name for an exact match, the nomatchReturn otherwise mschaefer@9202: */ mschaefer@9202: public String findExactZoneName(final double value, final String noMatchReturn) { mschaefer@9202: this.findValue(value); mschaefer@9202: if ((this.foundFloor != null) && (this.foundFloor.getKey() == this.foundCeiling.getKey())) mschaefer@9202: return this.foundFloor.getValue().getMainValue().getName(); mschaefer@9202: else mschaefer@9202: return noMatchReturn; mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Searches the interval of a main value and its relative distance from the lower value mschaefer@9202: */ mschaefer@9202: public boolean findValue(final double value) { mschaefer@9202: this.foundFloor = null; mschaefer@9202: this.foundCeiling = null; mschaefer@9202: this.foundRelativeDistance = Double.NaN; mschaefer@9202: if (!this.isValid()) mschaefer@9202: return false; mschaefer@9202: if (Double.isNaN(value)) mschaefer@9202: return false; mschaefer@9202: mschaefer@9202: // Clearly below or just (max. 10%) below lowest named value mschaefer@9202: this.foundFloor = this.mainValues.floorEntry(Double.valueOf(value)); mschaefer@9202: if (this.foundFloor == null) { mschaefer@9202: this.foundCeiling = this.mainValues.firstEntry(); mschaefer@9202: if (value >= this.mainValues.firstKey().doubleValue() * 0.9) { mschaefer@9202: this.foundRelativeDistance = 0.9; mschaefer@9202: return true; mschaefer@9202: } mschaefer@9202: else { mschaefer@9202: this.foundRelativeDistance = Double.NEGATIVE_INFINITY; mschaefer@9202: return false; mschaefer@9202: } mschaefer@9202: } mschaefer@9202: mschaefer@9202: // Clearly above or just (max. 10%) above highest named value mschaefer@9202: this.foundCeiling = this.mainValues.ceilingEntry(Double.valueOf(value)); mschaefer@9202: if (this.foundCeiling == null) { mschaefer@9202: if (value <= this.mainValues.lastKey().doubleValue() * 1.1) { mschaefer@9202: this.foundRelativeDistance = 0.1; mschaefer@9202: return true; mschaefer@9202: } mschaefer@9202: else { mschaefer@9202: this.foundRelativeDistance = Double.POSITIVE_INFINITY; mschaefer@9202: return false; mschaefer@9202: } mschaefer@9202: } mschaefer@9202: mschaefer@9202: // Exact match or within an interval mschaefer@9202: if (this.foundCeiling.getKey() == this.foundFloor.getKey()) mschaefer@9202: this.foundRelativeDistance = 0.0; mschaefer@9202: else mschaefer@9202: this.foundRelativeDistance = (value - this.foundFloor.getKey().doubleValue()) mschaefer@9202: / (this.foundCeiling.getKey().doubleValue() - this.foundFloor.getKey().doubleValue()); mschaefer@9202: return true; mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Floor value of the last findValue mschaefer@9202: */ mschaefer@9202: public MainValue getFoundFloorValue() { mschaefer@9202: if (this.foundFloor != null) mschaefer@9202: return this.foundFloor.getValue(); mschaefer@9202: else mschaefer@9202: return null; mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Ceiling value of the last findValue mschaefer@9202: */ mschaefer@9202: public MainValue getFoundCeilingValue() { mschaefer@9202: if (this.foundCeiling != null) mschaefer@9202: return this.foundCeiling.getValue(); mschaefer@9202: else mschaefer@9202: return null; mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Relative distance of the last findValue mschaefer@9202: */ mschaefer@9202: public double getFoundRelativeDistance() { mschaefer@9202: return this.getFoundRelativeDistance(); mschaefer@9202: } mschaefer@9202: mschaefer@9202: /** mschaefer@9202: * Searches a pair of zone names and return the a value within the interval by a relative distance, or NaN mschaefer@9202: */ mschaefer@9202: public double findValue(final String floorZone, final String ceilingZone, final double relativeDistance) { mschaefer@9202: this.foundFloor = null; mschaefer@9202: this.foundCeiling = null; mschaefer@9202: this.foundRelativeDistance = relativeDistance; mschaefer@9202: for (final Entry mainValue : this.mainValues.entrySet()) { mschaefer@9202: if (mainValue.getValue().getMainValue().getName().equalsIgnoreCase(floorZone)) { mschaefer@9202: this.foundFloor = mainValue; mschaefer@9202: break; mschaefer@9202: } mschaefer@9202: } mschaefer@9202: if (this.foundFloor == null) mschaefer@9202: return Double.NaN; mschaefer@9202: if (floorZone.equalsIgnoreCase(ceilingZone)) mschaefer@9202: return this.foundFloor.getKey().doubleValue(); mschaefer@9202: for (final Entry mainValue : this.mainValues.entrySet()) { mschaefer@9202: if (mainValue.getValue().getMainValue().getName().equalsIgnoreCase(ceilingZone)) { mschaefer@9202: this.foundCeiling = mainValue; mschaefer@9202: break; mschaefer@9202: } mschaefer@9202: } mschaefer@9202: if (this.foundCeiling == null) mschaefer@9202: return Double.NaN; mschaefer@9202: else mschaefer@9202: return (this.foundCeiling.getKey().doubleValue() - this.foundFloor.getKey().doubleValue()) * this.foundRelativeDistance mschaefer@9202: + this.foundFloor.getKey().doubleValue(); mschaefer@9202: } mschaefer@9202: }