view flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java @ 4478:6153c50f78cf

WaterLineArtifact: Added callcontext-parameter to interfaces getWaterLine. Update all implementations. The change was done to be able to compute the extreme values during getWaterLine to access data needed in CrossSectionProfile Diagrams.
author Felix Wolfsteller <felix.wolfsteller@intevation.de>
date Tue, 13 Nov 2012 14:46:44 +0100
parents e1f81795401f
children 5fc7df736634
line wrap: on
line source
package de.intevation.flys.artifacts;

import de.intevation.artifactdatabase.data.StateData;

import de.intevation.artifactdatabase.state.Facet;
import de.intevation.artifactdatabase.state.FacetActivity;

import de.intevation.artifacts.Artifact;
import de.intevation.artifacts.CallContext;

import de.intevation.artifacts.common.utils.StringUtils;

import de.intevation.flys.artifacts.geom.Lines;

import de.intevation.flys.artifacts.model.Calculation1;
import de.intevation.flys.artifacts.model.Calculation2;
import de.intevation.flys.artifacts.model.Calculation3;
import de.intevation.flys.artifacts.model.Calculation4;
import de.intevation.flys.artifacts.model.Calculation5;
import de.intevation.flys.artifacts.model.Calculation6;
import de.intevation.flys.artifacts.model.Calculation;
import de.intevation.flys.artifacts.model.CalculationResult;
import de.intevation.flys.artifacts.model.DischargeTables;
import de.intevation.flys.artifacts.model.FacetTypes;
import de.intevation.flys.artifacts.model.Segment;
import de.intevation.flys.artifacts.model.WQCKms;
import de.intevation.flys.artifacts.model.WQKms;
import de.intevation.flys.artifacts.model.WW;
import de.intevation.flys.artifacts.model.WstValueTable;
import de.intevation.flys.artifacts.model.WstValueTableFactory;

import de.intevation.flys.artifacts.states.LocationDistanceSelect;

import de.intevation.flys.model.DischargeTable;
import de.intevation.flys.model.FastCrossSectionLine;
import de.intevation.flys.model.Gauge;
import de.intevation.flys.model.River;

import de.intevation.flys.utils.DoubleUtil;
import de.intevation.flys.utils.FLYSUtils;

import gnu.trove.TDoubleArrayList;

import java.awt.geom.Point2D;

import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;


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

    /** The logger for this class. */
    private static Logger logger = 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;

    /** The default step width between the start end end kilometer. */
    public static final double DEFAULT_KM_STEPS = 0.1;

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

    static {
        // TODO: Move to configuration.
        FacetActivity.Registry.getInstance().register(
            ARTIFACT_NAME,
            new FacetActivity() {
                @Override
                public Boolean isInitialActive(
                    Artifact artifact,
                    Facet    facet,
                    String   outputName
                ) {
                    String fname = facet.getName();
                    if ((fname.equals(COMPUTED_DISCHARGE_MAINVALUES_Q)
                        || fname.equals(COMPUTED_DISCHARGE_MAINVALUES_W)
                        || 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(
        Calculation report,
        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(CallContext context)
    {
        logger.debug("WINFOArtifact.getWaterlevelData");

        if (getDataAsString("calculation_mode")
            .equals("calc.discharge.longitudinal.section")
        ) {
            return getDischargeLongitudinalSectionData();
        }

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

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

        double[] qs   = getQs();
        double[] ws   = null;
        boolean  qSel = true;

        Calculation report = new Calculation();

        if (qs == null) {
            logger.debug("Determine Q values based on a set of W values.");
            qSel = false;
            ws   = getWs();
            double [][] qws = getQsForWs(ws);
            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];
            }
        }

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


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

        double refKm;

        if (isFreeQ() || isFreeW()) {
            refKm = range[0];
            logger.debug("'free' calculation (km " + refKm + ")");
        }
        else {
            Gauge gauge = river.determineGaugeByPosition(range[0]);
            if (gauge == null) {
                return error(
                    new WQKms[0], "no.gauge.found.for.km");
            }

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

            logger.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.
     */
    public static CalculationResult computeWaterlevelData(
        double []     kms,
        double []     qs,
        double []     ws,
        WstValueTable wst,
        double        refKm,
        Calculation   report
    ) {
        logger.info("WINFOArtifact.computeWaterlevelData");

        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() {
        logger.debug("WINFOArtifact.getDurationCurveData");

        River r = FLYSUtils.getRiver(this);

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

        Gauge g = getGauge();

        if (g == null) {
           return error(null, "no.gauge.selected");
        }

        double[] locations = FLYSUtils.getLocations(this);

        if (locations == null) {
            return error(null, "no.locations.selected");
        }

        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.
     */
    public static CalculationResult computeDurationCurveData(
        Gauge         gauge,
        WstValueTable wst,
        double        location)
    {
        logger.info("WINFOArtifact.computeDurationCurveData");

        Object[] obj = gauge.fetchDurationCurveData();

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

        Calculation3 calculation = new Calculation3(location, days, qs);

        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
    {
        logger.debug("WINFOArtifact.getComputedDischargeCurveData");

        River r = FLYSUtils.getRiver(this);

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

        double[] locations = FLYSUtils.getLocations(this);

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

        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.
     */
    public static CalculationResult computeDischargeCurveData(
        WstValueTable wst,
        double location)
    {
        logger.info("WINFOArtifact.computeDischargeCurveData");

        Calculation2 calculation = new Calculation2(location);

        return calculation.calculate(wst);
    }


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


    /**
     * Returns the data computed by the discharge longitudinal section
     * computation.
     *
     * @return an array of WQKms object - one object for each given Q value.
     */
    public CalculationResult getDischargeLongitudinalSectionData() {

        logger.debug("WINFOArtifact.getDischargeLongitudinalSectionData");

        River river = FLYSUtils.getRiver(this);
        if (river == null) {
            logger.debug("No river selected.");
            return error(new WQKms[0], "no.river.selected");
        }

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

        List<Segment> segments = getSegments();

        if (segments == null) {
            logger.debug("Cannot create segments.");
            return error(new WQKms[0], "cannot.create.segments");
        }

        double [] range = getFromToStep();

        if (range == null) {
            logger.debug("Cannot figure out range.");
            return error(new WQKms[0], "no.range.found");
        }

        Calculation4 calc4 = new Calculation4(segments, river, isQ());

        return calc4.calculate(table, range[0], range[1], range[2]);
    }


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

        Double startKm = getReferenceStartKm();

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

        double [] endKms = getReferenceEndKms();

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

        Calculation5 calc5 = new Calculation5(startKm, endKms);

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

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

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

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


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

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

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

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

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

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

        return null;
    }


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

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

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

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

        TDoubleArrayList endKms = new TDoubleArrayList();

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

        return endKms.toNativeArray();
    }


    public CalculationResult getHistoricalDischargeData() {
        Gauge  gauge        = FLYSUtils.getReferenceGauge(this);
        String rawTimerange = getDataAsString("year_range");
        String rawValues    = getDataAsString("historical_values");
        int    mode         = getDataAsInteger("historical_mode");

        long[]   timerange = FLYSUtils.longArrayFromString(rawTimerange);
        double[] values    = FLYSUtils.doubleArrayFromString(rawValues);

        Calendar start = new GregorianCalendar();
        start.setTimeInMillis(timerange[0]);
        Calendar end   = new GregorianCalendar();
        end.setTimeInMillis(timerange[1]);

        Calculation6 calc = new Calculation6(
            mode,
            new long[] { start.getTimeInMillis(), end.getTimeInMillis() },
            values);

        return calc.calculate(gauge);
    }


    public List<Segment> getSegments() {
        StateData wqValues = getData("wq_values");
        if (wqValues == null) {
            logger.warn("no wq_values given");
            return Collections.<Segment>emptyList();
        }
        String input = (String) wqValues.getValue();
        if (input == null || (input = input.trim()).length() == 0) {
            logger.warn("wq_values are empty");
            return Collections.<Segment>emptyList();
        }
        return Segment.parseSegments(input);
    }


    /**
     * Get corrected waterline against surface/profile.
     */
    public Lines.LineData waterLineC(int idx, FastCrossSectionLine csl) {
        List<Point2D> points = csl.getPoints();

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

        // Find index of km.
        double wishKM = csl.getKm();

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

        int old_idx = 0;

        if (triple.size() == 0) {
            logger.warn("Calculation of c/waterline is empty.");
            return Lines.createWaterLines(points, 0.0f);
        }

        // 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++) {
            double diff = Math.abs(wishKM - triple.getKm(i));
            if (diff > old_dist_wish) {
                break;
            }
            last_c = triple.getC(i);
            old_dist_wish = diff;
        }

        return Lines.createWaterLines(points, 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 Lines.LineData getWaterLines(int idx, FastCrossSectionLine csl,
        double nextIgnored, double prevIgnored) {
        logger.debug("getWaterLines(" + idx + ")");

        List<Point2D> points = csl.getPoints();

        // Need W at km
        WQKms [] wqkms = (WQKms[]) getWaterlevelData().getData();
        if (wqkms.length == 0) {
            logger.error("No WQKms found.");
            return Lines.createWaterLines(points, 0.0f);
        }

        if (wqkms.length <= idx) {
            logger.error("getWaterLines() requested index ("
                         + idx + " not found.");
            return waterLineC(idx, csl);
        }

        // Find index of km.
        double wishKM = csl.getKm();

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

        if (triple.size() == 0) {
            logger.warn("Calculation of waterline is empty.");
            return Lines.createWaterLines(points, 0.0f);
        }

        // Early abort if we would need to extrapolate.
        int T = triple.size();
        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?
            logger.warn("Will not extrapolate waterlevels.");
            return Lines.createWaterLines(points, 0.0f);
        }

        int old_idx = 0;

        // 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++) {
            double diff = Math.abs(wishKM - triple.getKm(i));
            if (diff > old_dist_wish) {
                break;
            }
            last_w = triple.getW(i);
            old_dist_wish = diff;
        }

        return Lines.createWaterLines(points, last_w);
    }


    /**
     * Returns the Qs for a number of Ws. This method makes use of
     * DischargeTables.getQForW().
     *
     * @param ws An array of W values.
     *
     * @return an array of Q values.
     */
    public double [][] getQsForWs(double[] ws) {

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

        boolean debug = logger.isDebugEnabled();

        if (debug) {
            logger.debug("FLYSArtifact.getQsForWs");
        }

        River r = FLYSUtils.getRiver(this);
        if (r == null) {
            logger.warn("no river found");
            return null;
        }

        double [] range = FLYSUtils.getKmRange(this);
        if (range == null) {
            logger.warn("no ranges found");
            return null;
        }

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

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

            boolean generatedWs = false;

            for (int i = 0; i < ws.length; ++i) {
                double w = ws[i];
                if (debug) {
                    logger.debug("getQsForWs: lookup Q for W: " + w);
                }
                // There could bemore than one Q per W.
                double [] qs = wst.findQsForW(km, w);
                for (int j = 0; j < qs.length; ++j) {
                    outWs.add(ws[i]);
                    outQs.add(qs[j]);
                }
                generatedWs |= qs.length != 1;
            }

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

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

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

        Gauge g = r.determineGaugeByPosition(range[0]);
        if (g == null) {
            logger.warn("no gauge found for km: " + range[0]);
            return null;
        }

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

        DischargeTable dt = g.fetchMasterDischargeTable();

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

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

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

        boolean generatedWs = false;

        for (int i = 0; i < ws.length; i++) {
            if (Double.isNaN(ws[i])) {
                logger.warn("W is NaN: ignored");
                continue;
            }
            double w = ws[i] / 100d;
            double [] qs = DischargeTables.getQsForW(values, w);

            if (qs.length == 0) {
                logger.warn("No Qs found for W = " + ws[i]);
            }
            else {
                for (double q: qs) {
                    wsOut.add(ws[i]);
                    qsOut.add(q * 100d);
                }
            }
            generatedWs |= qs.length != 1;
        }

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


    /**
     * Determines the selected mode of distance/range input.
     *
     * @return true, if the range mode is selected otherwise false.
     */
    public boolean isRange() {
        StateData mode = getData("ld_mode");

        if (mode == null) {
            logger.warn("No mode location/range chosen. Defaults to range.");
            return true;
        }

        String value = (String) mode.getValue();

        return value.equals("distance");
    }


    /**
     * 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(StateData dFrom, StateData dTo) {
        double from = Double.parseDouble((String) dFrom.getValue());
        double to   = Double.parseDouble((String) dTo.getValue());

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


    /**
     * Returns the selected Kms.
     *
     * @param distance An 2dim array with [lower, upper] values.
     *
     * @return the selected Kms.
     */
    public double[] getKms(double[] distance) {
        StateData dStep = getData("ld_step");

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

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

        // transform step from 'm' into 'km'
        step = step / 1000;

        if (step == 0d) {
            step = DEFAULT_KM_STEPS;
        }

        return DoubleUtil.explode(distance[0], distance[1], step);
    }


    /**
     * Returns the selected Kms.
     *
     * @return the selected kms.
     */
    public double[] getKms() {
        if (isRange()) {
            double[] distance = FLYSUtils.getKmRange(this);
            return getKms(distance);

        }
        else {
            return LocationDistanceSelect.getLocations(this);
        }
    }


    public double [] getFromToStep() {
        if (!isRange()) {
            return null;
        }
        double [] fromTo = FLYSUtils.getKmRange(this);

        if (fromTo == null) {
            return null;
        }

        StateData dStep = getData("ld_step");
        if (dStep == null) {
            return null;
        }

        double [] result = new double[3];
        result[0] = fromTo[0];
        result[1] = fromTo[1];

        try {
            String step = (String)dStep.getValue();
            result[2] = DoubleUtil.round(Double.parseDouble(step) / 1000d);
        }
        catch (NumberFormatException nfe) {
            return null;
        }

        return result;
    }


    /**
     * Returns the gauge based on the current distance and river.
     *
     * @return the gauge.
     */
    public Gauge getGauge() {
        return FLYSUtils.getGauge(this);
    }




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

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

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


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

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

    public boolean isFreeW() {
        if(!isW()) {
            return false;
        }
        StateData mode = getData("wq_isfree");
        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;
        }
        StateData mode  = getData("wq_isfree");
        String    value = (mode != null) ? (String) mode.getValue() : null;

        logger.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(double[] range) {
        StateData dMode   = getData("wq_isq");

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

        logger.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(double[] range) {
        if (isW()) {
            return getWQForDist(range);
        }

        logger.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() {
        StateData dSingle = getData("wq_single");

        if (isW()) {
            if (dSingle != null) {
                return getSingleWQValues();
            }
            else {
                return getWQTriple();
            }
        }
        else {
            logger.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(double[] dist) {
        logger.debug("Search wq values for range: " + dist[0] + " - " + dist[1]);
        StateData data = getData("wq_values");

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

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

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

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

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

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

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

                return res;
            }
        }

        logger.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() {
        StateData dFrom = getData("wq_from");
        StateData dTo   = getData("wq_to");

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

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

        StateData dStep = getData("wq_step");

        if (dStep == null) {
            logger.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) {
            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() {
        StateData dSingle = getData("wq_single");

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

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

        TDoubleArrayList values = new TDoubleArrayList();

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

        values.sort();

        return values.toNativeArray();
    }

    /**
     * Returns the WstValueTable of current river.
     */
    public WstValueTable getWstValueTable() {
        River r = FLYSUtils.getRiver(this);
        return WstValueTableFactory.getTable(r);
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org