view artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/GaugeMainValueFinder.java @ 9206:b38be7ea53e2

Fixed approx char restaurated, findValue condition for zone name corrected
author mschaefer
date Mon, 02 Jul 2018 19:02:24 +0200
parents b4402594213b
children d9fda7af24ca
line wrap: on
line source
/** 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 = "\u2248";

    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) {
        this.findValue(value);
        if (Double.isNaN(this.foundRelativeDistance))
            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();
    }
}

http://dive4elements.wald.intevation.org