view artifacts/src/main/java/org/dive4elements/river/artifacts/WINFOArtifact.java @ 9638:6c1ebf2220f5

# 19b (check for usages of WaterlevelExporter.getWforGaugeAndQ) -> cleanup: removing calc.extreme.curve
author dnt_bjoernsen <d.tironi@bjoernsen.de>
date Thu, 31 Oct 2019 17:37:53 +0100
parents b380a5693514
children
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * 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;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Map;

import org.apache.log4j.Logger;
import org.dive4elements.artifactdatabase.data.StateData;
import org.dive4elements.artifactdatabase.state.Facet;
import org.dive4elements.artifactdatabase.state.FacetActivity;
import org.dive4elements.artifacts.Artifact;
import org.dive4elements.artifacts.CallContext;
import org.dive4elements.artifacts.CallMeta;
import org.dive4elements.artifacts.common.utils.StringUtils;
import org.dive4elements.river.artifacts.access.Calculation4Access;
import org.dive4elements.river.artifacts.access.ComputationRangeAccess;
import org.dive4elements.river.artifacts.access.RangeAccess;
import org.dive4elements.river.artifacts.access.RiverAccess;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.Calculation1;
import org.dive4elements.river.artifacts.model.Calculation2;
import org.dive4elements.river.artifacts.model.Calculation3;
import org.dive4elements.river.artifacts.model.Calculation4;
import org.dive4elements.river.artifacts.model.Calculation5;
import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.DischargeTables;
import org.dive4elements.river.artifacts.model.FacetTypes;
import org.dive4elements.river.artifacts.model.WQCKms;
import org.dive4elements.river.artifacts.model.WQKms;
import org.dive4elements.river.artifacts.model.WW;
import org.dive4elements.river.artifacts.model.WstValueTable;
import org.dive4elements.river.artifacts.model.WstValueTableFactory;
import org.dive4elements.river.artifacts.model.extreme.ExtremeResult;
import org.dive4elements.river.artifacts.states.DefaultState.ComputeType;
import org.dive4elements.river.model.DischargeTable;
import org.dive4elements.river.model.Gauge;
import org.dive4elements.river.model.River;
import org.dive4elements.river.utils.DoubleUtil;
import org.dive4elements.river.utils.RiverUtils;

import gnu.trove.TDoubleArrayList;

/**
 * The default WINFO artifact.
 *
 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
 */
public class WINFOArtifact extends D4EArtifact implements FacetTypes, WaterLineArtifact {

    /** The log for this class. */
    private static Logger log = Logger.getLogger(WINFOArtifact.class);

    /** The name of the artifact. */
    public static final String ARTIFACT_NAME = "winfo";

    /** XPath */
    public static final String XPATH_STATIC_UI = "/art:result/art:ui/art:static";

    /**
     * The default number of steps between the start end end of a selected Q
     * range.
     */
    public static final int DEFAULT_Q_STEPS = 30;

    private static final String[] INACTIVES = new String[] { LONGITUDINAL_Q, DURATION_Q, STATIC_WQKMS_Q };

    static {
        // TODO: Move to configuration.
        FacetActivity.Registry.getInstance().register(ARTIFACT_NAME, new FacetActivity() {
            @Override
            public Boolean isInitialActive(final Artifact artifact, final Facet facet, final String outputName) {
                final String fname = facet.getName();
                if ((fname.equals(MAINVALUES_Q) || fname.equals(MAINVALUES_W)) && outputName.equals("computed_discharge_curve")) {
                    return Boolean.FALSE;
                }
                return !StringUtils.contains(fname, INACTIVES);
            }
        });
    }

    /**
     * The default constructor.
     */
    public WINFOArtifact() {
    }

    /**
     * Returns the name of the concrete artifact.
     *
     * @return the name of the concrete artifact.
     */
    @Override
    public String getName() {
        return ARTIFACT_NAME;
    }

    protected static boolean reportGeneratedWs(final Calculation report, final double[] ws) {
        if (ws == null || ws.length < 2) {
            return false;
        }

        double lastW = ws[0];
        boolean alreadyReported = false;

        for (int i = 1; i < ws.length; ++i) {
            if (Math.abs(lastW - ws[i]) < 1e-5) {
                if (!alreadyReported) {
                    alreadyReported = true;
                    report.addProblem("more.than.one.q.for.w", ws[i]);
                }
            } else {
                alreadyReported = false;
            }
            lastW = ws[i];
        }

        return true;
    }

    //
    // METHODS FOR RETRIEVING COMPUTED DATA FOR DIFFERENT CHART TYPES
    //
    //
    /**
     * Returns the data that is computed by a waterlevel computation.
     *
     * @return an array of data triples that consist of W, Q and Kms.
     */
    public CalculationResult getWaterlevelData() {
        return this.getWaterlevelData(null);
    }

    private CalculationResult getDischargeLongitudinalSectionData(final CallMeta meta) {
        // TODO: This caluclation should be cached as it is quite expensive.
        return new Calculation4(new Calculation4Access(this)).calculate(meta);
    }

    /**
     * Returns the data that is computed by a waterlevel computation.
     *
     * @return an array of data triples that consist of W, Q and Kms.
     */
    public CalculationResult getWaterlevelData(final CallContext context) {
        log.debug("WINFOArtifact.getWaterlevelData");

        final String calculationMode = getDataAsString("calculation_mode");

        // If this WINFO-Artifact has a calculation trait.
        if (calculationMode != null) {
            if (calculationMode.equals("calc.discharge.longitudinal.section"))
                return getDischargeLongitudinalSectionData(context.getMeta());

            if (calculationMode.equals("calc.w.differences"))
                return (CalculationResult) this.compute(context, ComputeType.ADVANCE, true);

            log.warn("Unhandled calculation_mode " + calculationMode);
        }

        // Otherwise get it from parameterization.
        // TODO: wrong comment: now always a waterlevle computation is executed; actually there is a calc_mode for that, why
        // dont check?
        return computeWaterlevelData();
    }

    /** Execu5tes the calculation of 'waterlevel', fetches all input data from this artifact */
    private CalculationResult computeWaterlevelData() {
        final double[] kms = new ComputationRangeAccess(this).getKms();
        if (kms == null)
            return error(new WQKms[0], "no.kms.selected");

        return computeWaterlevelData(kms);
    }

    /**
     * Execu5tes the calculation of 'waterlevel'.
     * Allows to override the stations for which the calculation is done. All other inputs are fetched from this artifact.
     */
    public final CalculationResult computeWaterlevelData(final double kms[]) {

        final River river = new RiverAccess(this).getRiver();
        if (river == null)
            return error(new WQKms[0], "no.river.selected");

        double[] qs = getQs();
        double[] ws = null;

        final Calculation report = new Calculation();

        if (qs == null) {
            log.debug("Determine Q values based on a set of W values.");
            ws = getWs();
            final double[][] qws = getQsForWs(ws, report);
            if (qws == null || qws.length == 0) {
                return error(new WQKms[0], "converting.ws.to.qs.failed");
            }
            qs = qws[0];

            if (reportGeneratedWs(report, qws[1])) {
                ws = qws[1];
            }
        }

        final WstValueTable wst = WstValueTableFactory.getTable(river);
        if (wst == null) {
            return error(new WQKms[0], "no.wst.for.selected.river");
        }

        final RangeAccess rangeAccess = new RangeAccess(this);
        final double[] range = rangeAccess.getKmRange();
        if (range == null) {
            return error(new WQKms[0], "no.range.found");
        }

        double refKm;

        if (isFreeQ() || isFreeW()) {
            refKm = range[0];
            log.debug("'free' calculation (km " + refKm + ")");
        } else {
            final Gauge gauge = river.determineRefGauge(range, rangeAccess.isRange());

            if (gauge == null) {
                return error(new WQKms[0], "no.gauge.found.for.km", range[0]);
            }

            refKm = gauge.getStation().doubleValue();

            log.debug("reference gauge: " + gauge.getName() + " (km " + refKm + ")");
        }

        return computeWaterlevelData(kms, qs, ws, wst, refKm, report);
    }

    /**
     * Computes the data of a waterlevel computation based on the interpolation
     * in WstValueTable.
     *
     * @param kms
     *            The kilometer values.
     * @param qs
     *            The discharge values.
     * @param wst
     *            The WstValueTable used for the interpolation.
     *
     * @return an array of data triples that consist of W, Q and Kms.
     */
    private static CalculationResult computeWaterlevelData(final double[] kms, final double[] qs, final double[] ws, final WstValueTable wst,
            final double refKm, final Calculation report) {
        log.info("WINFOArtifact.computeWaterlevelData");

        final Calculation1 calc1 = new Calculation1(kms, qs, ws, refKm);

        if (report != null) {
            calc1.addProblems(report);
        }

        return calc1.calculate(wst);
    }

    /**
     * Returns the data that is computed by a duration curve computation.
     *
     * @return the data computed by a duration curve computation.
     */
    public CalculationResult getDurationCurveData() {
        log.debug("WINFOArtifact.getDurationCurveData");

        final RangeAccess rangeAccess = new RangeAccess(this);

        final River r = rangeAccess.getRiver();
        if (r == null) {
            return error(null, "no.river.selected");
        }

        final double[] locations = rangeAccess.getLocations();
        if (locations == null) {
            return error(null, "no.locations.selected");
        }

        final Gauge g = r.determineGaugeByPosition(locations[0]);
        if (g == null) {
            return error(null, "no.gauge.selected");
        }

        final WstValueTable wst = WstValueTableFactory.getTable(r);
        if (wst == null) {
            return error(null, "no.wst.for.river");
        }

        return computeDurationCurveData(g, wst, locations[0]);
    }

    /**
     * Computes the data used to create duration curves.
     *
     * @param gauge
     *            The selected gauge.
     * @param location
     *            The selected location.
     *
     * @return the computed data.
     */
    private static CalculationResult computeDurationCurveData(final Gauge gauge, final WstValueTable wst, final double location) {
        log.info("WINFOArtifact.computeDurationCurveData");

        final Object[] obj = gauge.fetchDurationCurveData();

        final int[] days = (int[]) obj[0];
        final double[] qs = (double[]) obj[1];

        final Calculation3 calculation = new Calculation3(location, days, qs, gauge.getStation().doubleValue());

        return calculation.calculate(wst);
    }

    /**
     * Returns the data that is computed by a discharge curve computation.
     *
     * @return the data computed by a discharge curve computation.
     */
    public CalculationResult getComputedDischargeCurveData() throws NullPointerException {
        log.debug("WINFOArtifact.getComputedDischargeCurveData");

        final River r = RiverUtils.getRiver(this);

        if (r == null) {
            return error(new WQKms[0], "no.river.selected");
        }

        final RangeAccess rangeAccess = new RangeAccess(this);
        final double[] locations = rangeAccess.getLocations();

        if (locations == null) {
            return error(new WQKms[0], "no.locations.selected");
        }

        final WstValueTable wst = WstValueTableFactory.getTable(r);
        if (wst == null) {
            return error(new WQKms[0], "no.wst.for.river");
        }

        return computeDischargeCurveData(wst, locations[0]);
    }

    /**
     * Computes the data used to create computed discharge curves.
     *
     * @param wst
     *            The WstValueTable that is used for the interpolation (river-
     *            bound).
     * @param location
     *            The location where the computation should be based on.
     *
     * @return an object that contains tuples of W/Q values at the specified
     *         location.
     */
    private static CalculationResult computeDischargeCurveData(final WstValueTable wst, final double location) {
        log.info("WINFOArtifact.computeDischargeCurveData");

        final Calculation2 calculation = new Calculation2(location);

        return calculation.calculate(wst);
    }

    /** Create CalculationResult with data and message. */
    protected static final CalculationResult error(final Object data, final String msg) {
        return new CalculationResult(data, new Calculation(msg));
    }

    /** Create CalculationResult with data and message with args. */
    protected static final CalculationResult error(final Object data, final String msg, final Object... args) {
        return new CalculationResult(data, new Calculation(msg, args));
    }

    /**
     * Returns the data that is computed by a reference curve computation.
     *
     * @return the data computed by a reference curve computation.
     */
    public CalculationResult getReferenceCurveData(final CallContext context) {

        final Double startKm = getReferenceStartKm();

        if (startKm == null) {
            return error(new WW[0], "no.reference.start.km");
        }

        final double[] endKms = getReferenceEndKms();

        if (endKms == null || endKms.length == 0) {
            return error(new WW[0], "no.reference.end.kms");
        }

        final Calculation5 calc5 = new Calculation5(startKm, endKms);

        final River r = RiverUtils.getRiver(this);
        if (r == null) {
            return error(new WW[0], "no.river.found");
        }

        final WstValueTable wst = WstValueTableFactory.getTable(r);
        if (wst == null) {
            return error(new WW[0], "no.wst.for.river");
        }

        final Map<Double, Double> kms2gaugeDatums = r.queryGaugeDatumsKMs();

        return calc5.calculate(wst, kms2gaugeDatums, context);
    }

    /** Get reference (start) km. */
    public Double getReferenceStartKm() {
        final StateData sd = getData("reference_startpoint");

        if (sd == null) {
            log.warn("no reference start given.");
            return null;
        }

        log.debug("Reference start km given: " + sd.getValue());

        String input = (String) sd.getValue();

        if (input == null || (input = input.trim()).length() == 0) {
            log.warn("reference start string is empty.");
            return null;
        }

        try {
            return Double.valueOf(input);
        }
        catch (final NumberFormatException nfe) {
            log.warn("reference start string is not numeric.");
        }

        return null;
    }

    /**
     * Get end kms for reference curve (null if none).
     */
    public double[] getReferenceEndKms() {
        final StateData sd = getData("reference_endpoint");

        if (sd == null) {
            log.warn("no reference end given.");
            return null;
        } else {
            log.debug("Reference end km : " + sd.getValue());
        }

        String input = (String) sd.getValue();

        if (input == null || (input = input.trim()).length() == 0) {
            log.warn("reference end string is empty.");
            return null;
        }

        final TDoubleArrayList endKms = new TDoubleArrayList();

        for (final String part : input.split("\\s+")) {
            try {
                final double km = Double.parseDouble(part);
                if (!endKms.contains(km)) {
                    endKms.add(km);
                }
            }
            catch (final NumberFormatException nfe) {
                log.warn("reference end string is not numeric.");
            }
        }

        return endKms.toNativeArray();
    }

    /**
     * Get corrected waterline against surface/profile.
     */
    private double waterLineC(final int idx, final double currentKm, final CallMeta meta) {

        final WQKms[] wqckms = (WQKms[]) getDischargeLongitudinalSectionData(meta).getData();

        // Find index of km.
        final double wishKM = currentKm;

        // Find W/C at km, linear naive approach.
        final WQCKms triple = (WQCKms) wqckms[idx - 1];

        if (triple.size() == 0) {
            log.warn("Calculation of c/waterline is empty.");
            return Double.NaN;
        }

        // Linear seach in WQKms for closest km.
        double old_dist_wish = Math.abs(wishKM - triple.getKm(0));
        double last_c = triple.getC(0);

        for (int i = 0, T = triple.size(); i < T; i++) {
            final double diff = Math.abs(wishKM - triple.getKm(i));
            if (diff > old_dist_wish) {
                break;
            }
            last_c = triple.getC(i);
            old_dist_wish = diff;
        }

        return last_c;
    }

    /**
     * Get points of line describing the surface of water at cross section.
     *
     * @param idx
     *            Index for getWaterlevelData.
     * @param csl
     *            The profile/surface to fill with water.
     * @param nextIgnored
     *            Ignored in this implementation of WaterLineArtifact.
     * @param prevIgnored
     *            Ignored in this implementation of WaterLineArtifact.
     *
     * @return an array holding coordinates of points of surface of water (
     *         in the form {{x1, x2} {y1, y2}} ).
     */
    @Override
    public double getWaterLevel(final ComputeType type, final String hash, final String stateId, final double currentKm, final Serializable waterLineIndex,
            final double nextKm, final double prevKm, final CallContext context) {

        final int idx = (int) waterLineIndex;

        // Need W at km
        final Object waterlevelResult = getWaterlevelData(context).getData();
        WQKms[] wqkms;

        if (waterlevelResult instanceof ExtremeResult) {
            wqkms = ((ExtremeResult) waterlevelResult).getWQKms();
        } else {
            wqkms = (WQKms[]) waterlevelResult;
        }

        if (wqkms.length == 0) {
            log.error("No WQKms found.");
            return Double.NaN;
        }

        if (wqkms.length <= idx) {
            log.error("getWaterLines() requested index (" + idx + " not found.");
            return waterLineC(idx, currentKm, context.getMeta());
        }

        // Find W at km, linear naive approach.
        final WQKms triple = wqkms[idx];

        // Find index of km.
        final double wishKM = currentKm;

        if (triple.size() == 0) {
            log.warn("Calculation of waterline is empty.");
            return Double.NaN;
        }

        // Early abort if we would need to extrapolate.
        final int T = triple.size();
        final double max_km = triple.getKm(T - 1), min_km = triple.getKm(0);
        if (wishKM < min_km || wishKM > max_km) {
            // TODO Does this have to be done in the other WaterlineArtifact
            // implementations, too?
            log.warn("Will not extrapolate waterlevels.");
            return Double.NaN;
        }

        // Linear seach in WQKms for closest km.
        double old_dist_wish = Math.abs(wishKM - triple.getKm(0));
        double last_w = triple.getW(0);

        for (int i = 0; i < T; i++) {
            final double diff = Math.abs(wishKM - triple.getKm(i));
            if (diff > old_dist_wish) {
                break;
            }
            last_w = triple.getW(i);
            old_dist_wish = diff;
        }

        return last_w;
    }

    /**
     * Returns the Qs for a number of Ws.
     *
     * @param ws
     *            An array of W values.
     *
     * @return an array of Q values.
     */
    private double[][] getQsForWs(final double[] ws, final Calculation report) {

        if (ws == null) {
            log.error("getQsForWs: ws == null");
            return null;
        }

        final boolean debug = log.isDebugEnabled();

        if (debug) {
            log.debug("D4EArtifact.getQsForWs");
        }

        final River r = RiverUtils.getRiver(this);
        if (r == null) {
            log.warn("no river found");
            return null;
        }

        final RangeAccess rangeAccess = new RangeAccess(this);
        final double[] range = rangeAccess.getKmRange();
        if (range == null) {
            log.warn("no ranges found");
            return null;
        }

        if (isFreeW()) {
            log.debug("Bezugslinienverfahren I: W auf freier Strecke");
            // The simple case of the "Bezugslinienverfahren"
            // "W auf freier Strecke".
            final WstValueTable wst = WstValueTableFactory.getTable(r);
            if (wst == null) {
                log.warn("no wst value table found");
                return null;
            }
            final double km = range[0];

            final TDoubleArrayList outQs = new TDoubleArrayList(ws.length);
            final TDoubleArrayList outWs = new TDoubleArrayList(ws.length);

            boolean generatedWs = false;

            for (final double w : ws) {
                if (debug) {
                    log.debug("getQsForWs: lookup Q for W: " + w);
                }
                // There could be more than one Q per W.
                final double[] qs = wst.findQsForW(km, w, report);
                for (final double element : qs) {
                    outWs.add(w);
                    outQs.add(element);
                }
                generatedWs |= qs.length != 1;
            }

            if (debug) {
                log.debug("getQsForWs: number of Qs: " + outQs.size());
            }

            return new double[][] { outQs.toNativeArray(), generatedWs ? outWs.toNativeArray() : null };
        }

        if (debug) {
            log.debug("range: " + Arrays.toString(range));
        }

        final Gauge g = rangeAccess.getRiver().determineRefGauge(range, rangeAccess.isRange());
        if (g == null) {
            log.warn("no gauge found for km: " + range[0]);
            return null;
        }

        if (debug) {
            log.debug("convert w->q with gauge '" + g.getName() + "'");
        }

        final DischargeTable dt = g.fetchMasterDischargeTable();

        if (dt == null) {
            log.warn("No master discharge table found for gauge '" + g.getName() + "'");
            return null;
        }

        final double[][] values = DischargeTables.loadDischargeTableValues(dt);

        final TDoubleArrayList wsOut = new TDoubleArrayList(ws.length);
        final TDoubleArrayList qsOut = new TDoubleArrayList(ws.length);

        boolean generatedWs = false;

        for (final double element : ws) {
            if (Double.isNaN(element)) {
                log.warn("W is NaN: ignored");
                continue;
            }
            final double[] qs = DischargeTables.getQsForW(values, element);

            if (qs.length == 0) {
                log.warn("No Qs found for W = " + element);
            } else {
                for (final double q : qs) {
                    wsOut.add(element);
                    qsOut.add(q);
                }
            }
            generatedWs |= qs.length != 1;
        }

        return new double[][] { qsOut.toNativeArray(), generatedWs ? wsOut.toNativeArray() : null };
    }

    /**
     * Returns the selected distance based on a given range (from, to).
     *
     * @param dFrom
     *            The StateData that contains the lower value.
     * @param dTo
     *            The StateData that contains the upper value.
     *
     * @return the selected distance.
     */
    protected double[] getDistanceByRange(final StateData dFrom, final StateData dTo) {
        final double from = Double.parseDouble((String) dFrom.getValue());
        final double to = Double.parseDouble((String) dTo.getValue());

        return new double[] { from, to };
    }

    /**
     * This method returns the Q values.
     *
     * @return the selected Q values or null, if no Q values are selected.
     */
    public double[] getQs() {
        final StateData dMode = getData("wq_isq");
        final StateData dSelection = getData("wq_isrange");

        final boolean isRange = dSelection != null ? Boolean.valueOf((String) dSelection.getValue()) : false;

        if (isQ()) {
            if (!isRange) {
                return getSingleWQValues();
            } else {
                return getWQTriple();
            }
        } else {
            log.warn("You try to get Qs, but W has been inserted.");
            return null;
        }
    }

    public boolean isQ() {
        final StateData mode = getData("wq_isq");
        final String value = (mode != null) ? (String) mode.getValue() : null;
        return value != null ? Boolean.valueOf(value) : false;
    }

    public boolean isW() {
        final StateData mode = getData("wq_isq");
        final String value = (mode != null) ? (String) mode.getValue() : null;
        return value != null ? !Boolean.valueOf(value) : false;
    }

    public boolean isFreeW() {
        if (!isW()) {
            return false;
        }
        final StateData mode = getData("wq_isfree");
        final String value = (mode != null) ? (String) mode.getValue() : null;

        return value != null ? Boolean.valueOf(value) : false;
    }

    /**
     * Returns true, if the parameter is set to compute data on a free range.
     * Otherwise it returns false, which tells the calculation that it is bound
     * to a gauge.
     *
     * @return true, if the calculation should compute on a free range otherwise
     *         false and the calculation is bound to a gauge.
     */
    public boolean isFreeQ() {
        if (!isQ()) {
            return false;
        }
        final StateData mode = getData("wq_isfree");
        final String value = (mode != null) ? (String) mode.getValue() : null;

        log.debug("isFreeQ: " + value);

        return value != null && Boolean.valueOf(value);
    }

    /**
     * Returns the Q values based on a specified kilometer range.
     *
     * @param range
     *            A 2dim array with lower and upper kilometer range.
     *
     * @return an array of Q values.
     */
    public double[] getQs(final double[] range) {
        final StateData dMode = getData("wq_isq");

        if (isQ()) {
            return getWQForDist(range);
        }

        log.warn("You try to get Qs, but Ws has been inserted.");
        return null;
    }

    /**
     * Returns the W values based on a specified kilometer range.
     *
     * @param range
     *            A 2dim array with lower and upper kilometer range.
     *
     * @return an array of W values.
     */
    public double[] getWs(final double[] range) {
        if (isW()) {
            return getWQForDist(range);
        }

        log.warn("You try to get Ws, but Qs has been inserted.");
        return null;
    }

    /**
     * This method returns the W values.
     *
     * @return the selected W values or null, if no W values are selected.
     */
    public double[] getWs() {
        if (isW()) {
            final StateData dSingle = getData("wq_single");
            if (dSingle != null) {
                return getSingleWQValues();
            } else {
                return getWQTriple();
            }
        } else {
            log.warn("You try to get Ws, but Q has been inserted.");
            return null;
        }
    }

    /**
     * This method returns the given W or Q values for a specific range
     * (inserted in the WQ input panel for discharge longitudinal sections).
     *
     * @param dist
     *            A 2dim array with lower und upper kilometer values.
     *
     * @return an array of W or Q values.
     */
    protected double[] getWQForDist(final double[] dist) {
        log.debug("Search wq values for range: " + dist[0] + " - " + dist[1]);
        final StateData data = getData("wq_values");

        if (data == null) {
            log.warn("Missing wq values!");
            return null;
        }

        final String dataString = (String) data.getValue();
        final String[] ranges = dataString.split(":");

        for (final String range : ranges) {
            final String[] parts = range.split(";");

            final double lower = Double.parseDouble(parts[0]);
            final double upper = Double.parseDouble(parts[1]);

            if (lower <= dist[0] && upper >= dist[1]) {
                final String[] values = parts[2].split(",");

                final int num = values.length;
                final double[] res = new double[num];

                for (int i = 0; i < num; i++) {
                    try {
                        res[i] = Double.parseDouble(values[i]);
                    }
                    catch (final NumberFormatException nfe) {
                        log.warn(nfe, nfe);
                    }
                }

                return res;
            }
        }

        log.warn("Specified range for WQ not found!");

        return null;
    }

    /**
     * This method returns an array of inserted WQ triples that consist of from,
     * to and the step width.
     *
     * @return an array of from, to and step width.
     */
    protected double[] getWQTriple() {
        final StateData dFrom = getData("wq_from");
        final StateData dTo = getData("wq_to");

        if (dFrom == null || dTo == null) {
            log.warn("Missing start or end value for range.");
            return null;
        }

        final double from = Double.parseDouble((String) dFrom.getValue());
        final double to = Double.parseDouble((String) dTo.getValue());

        final StateData dStep = getData("wq_step");

        if (dStep == null) {
            log.warn("No step width given. Cannot compute Qs.");
            return null;
        }

        double step = Double.parseDouble((String) dStep.getValue());

        // if no width is given, the DEFAULT_Q_STEPS is used to compute the step
        // width. Maybe, we should round the value to a number of digits.
        if (step == 0d) {
            final double diff = to - from;
            step = diff / DEFAULT_Q_STEPS;
        }

        return DoubleUtil.explode(from, to, step);
    }

    /**
     * Returns an array of inserted WQ double values stored as whitespace
     * separated list.
     *
     * @return an array of W or Q values.
     */
    protected double[] getSingleWQValues() {
        final StateData dSingle = getData("wq_single");

        if (dSingle == null) {
            log.warn("Cannot determine single WQ values. No data given.");
            return null;
        }

        final String tmp = (String) dSingle.getValue();
        final String[] strValues = tmp.split(" ");

        final TDoubleArrayList values = new TDoubleArrayList();

        for (final String strValue : strValues) {
            try {
                values.add(Double.parseDouble(strValue));
            }
            catch (final NumberFormatException nfe) {
                log.warn(nfe, nfe);
            }
        }

        values.sort();

        return values.toNativeArray();
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org