view flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java @ 2089:0da8874bd378

Added initial state to map artifact to be able to advance and step back. The map artifact overrides describe() to have the complete UI information in the describe response document. flys-artifacts/trunk@3613 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Raimund Renkert <raimund.renkert@intevation.de>
date Fri, 06 Jan 2012 12:02:10 +0000
parents d13be39cfd1d
children 16e74c5e636f
line wrap: on
line source
package de.intevation.flys.artifacts;

import java.awt.geom.Point2D;

import de.intevation.artifactdatabase.ProtocolUtils;

import de.intevation.artifactdatabase.data.StateData;

import de.intevation.artifactdatabase.state.Facet;
import de.intevation.artifactdatabase.state.Output;
import de.intevation.artifactdatabase.state.State;
import de.intevation.artifactdatabase.state.StateEngine;

import de.intevation.artifactdatabase.transition.TransitionEngine;

import de.intevation.artifacts.CallContext;
import de.intevation.artifacts.Message;

import de.intevation.artifacts.common.ArtifactNamespaceContext;

import de.intevation.artifacts.common.utils.XMLUtils;
import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;

import de.intevation.flys.artifacts.context.FLYSContext;

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.Calculation;
import de.intevation.flys.artifacts.model.CalculationResult;
import de.intevation.flys.artifacts.model.CrossSectionFactory;
import de.intevation.flys.artifacts.model.DischargeTables;
import de.intevation.flys.artifacts.model.FacetTypes;
import de.intevation.flys.artifacts.model.MainValuesFactory;
import de.intevation.flys.artifacts.model.Segment;
import de.intevation.flys.artifacts.model.WQKms;
import de.intevation.flys.artifacts.model.WstValueTable;
import de.intevation.flys.artifacts.model.WstValueTableFactory;

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

import de.intevation.flys.geom.Lines;

import de.intevation.flys.model.Gauge;
import de.intevation.flys.model.River;
import de.intevation.flys.model.CrossSection;
import de.intevation.flys.model.CrossSectionLine;

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

import gnu.trove.TDoubleArrayList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import de.intevation.flys.artifacts.model.CalculationMessage;

/**
 * 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;


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


    /**
     * This method returns a description of this artifact.
     *
     * @param data Some data.
     * @param context The CallContext.
     *
     * @return the description of this artifact.
     */
    public Document describe(Document data, CallContext context) {
        logger.debug("Describe: the current state is: " + getCurrentStateId());

        if (logger.isDebugEnabled()) {
            dumpArtifact();
        }

        FLYSContext flysContext = FLYSUtils.getFlysContext(context);

        StateEngine stateEngine = (StateEngine) flysContext.get(
            FLYSContext.STATE_ENGINE_KEY);

        TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
            FLYSContext.TRANSITION_ENGINE_KEY);

        List<State> reachable = transitionEngine.getReachableStates(
            this, getCurrentState(context), stateEngine);

        Document description            = XMLUtils.newDocument();
        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
            description,
            ArtifactNamespaceContext.NAMESPACE_URI,
            ArtifactNamespaceContext.NAMESPACE_PREFIX);

        Element root = ProtocolUtils.createRootNode(creator);
        description.appendChild(root);

        State current = getCurrentState(context);

        ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
        ProtocolUtils.appendState(creator, root, current);
        ProtocolUtils.appendReachableStates(creator, root, reachable);

        appendBackgroundActivity(creator, root, context);

        Element name = ProtocolUtils.createArtNode(
            creator, "name",
            new String[] { "value" },
            new String[] { getName() });

        Element ui = ProtocolUtils.createArtNode(
            creator, "ui", null, null);

        Element staticUI  = ProtocolUtils.createArtNode(
            creator, "static", null, null);

        Element outs = ProtocolUtils.createArtNode(
            creator, "outputmodes", null, null);
        appendOutputModes(description, outs, context, identifier());

        appendStaticUI(description, staticUI, context, identifier());

        Element dynamic = current.describe(
            this,
            description,
            root,
            context,
            identifier());

        if (dynamic != null) {
            ui.appendChild(dynamic);
        }

        ui.appendChild(staticUI);

        root.appendChild(name);
        root.appendChild(ui);
        root.appendChild(outs);

        return description;
    }


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


    protected static void appendBackgroundActivity(
        ElementCreator cr,
        Element        root,
        CallContext    context
    ) {
        Element inBackground = cr.create("background-processing");
        root.appendChild(inBackground);

        cr.addAttr(
            inBackground,
            "value",
            String.valueOf(context.isInBackground()),
            true);

        LinkedList<Message> messages = context.getBackgroundMessages();

        if (messages == null) {
            return;
        }

        CalculationMessage  message  = (CalculationMessage) messages.getLast();
        cr.addAttr(
            inBackground,
            "steps",
            String.valueOf(message.getSteps()),
            true);

        cr.addAttr(
            inBackground,
            "currentStep",
            String.valueOf(message.getCurrentStep()),
            true);

        inBackground.setTextContent(message.getMessage());
    }


    /**
     * Append output mode nodes to a document.
     */
    protected void appendOutputModes(
        Document    doc,
        Element     outs,
        CallContext context,
        String      uuid)
    {
        List<String> stateIds = getPreviousStateIds();

        XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
            doc,
            ArtifactNamespaceContext.NAMESPACE_URI,
            ArtifactNamespaceContext.NAMESPACE_PREFIX);

        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
        StateEngine engine      = (StateEngine) flysContext.get(
            FLYSContext.STATE_ENGINE_KEY);

        for (String stateId: stateIds) {
            logger.debug("Append output modes for state: " + stateId);
            DefaultState state = (DefaultState) engine.getState(stateId);

            List<Output> list = state.getOutputs();
            if (list == null || list.size() == 0) {
                logger.debug("-> No output modes for this state.");
                continue;
            }

            List<Facet> fs = facets.get(stateId);

            if (fs == null || fs.size() == 0) {
                logger.debug("No facets for previous state found.");
                continue;
            }

            logger.debug("Found " + fs.size() + " facets in previous states.");

            List<Output> generated = generateOutputs(list, fs);

            ProtocolUtils.appendOutputModes(doc, outs, generated);
        }

        try {
            DefaultState cur = (DefaultState) getCurrentState(context);
            if (cur.validate(this)) {
                List<Output> list = cur.getOutputs();
                if (list != null && list.size() > 0) {
                    logger.debug(
                        "Append output modes for current state: " + cur.getID());

                    List<Facet> fs = facets.get(cur.getID());

                    if (fs != null && fs.size() > 0) {
                        List<Output> generated = generateOutputs(list, fs);

                        logger.debug("Found " + fs.size() + " current facets.");
                        if (!generated.isEmpty()) {
                            ProtocolUtils.appendOutputModes(
                                doc, outs, generated);
                        }
                    }
                    else {
                        logger.debug("No facets found for the current state.");
                    }
                }
            }
        }
        catch (IllegalArgumentException iae) {
            // state is not valid, so we do not append its outputs.
        }
    }


    /**
     * This method appends the static data - that has already been inserted by
     * the user - to the static node of the DESCRIBE document.
     *
     * @param doc The document.
     * @param ui The root node.
     * @param context The CallContext.
     * @param uuid The identifier of the artifact.
     */
    protected void appendStaticUI(
        Document    doc,
        Node        ui,
        CallContext context,
        String uuid)
    {
        List<String> stateIds = getPreviousStateIds();

        FLYSContext flysContext = FLYSUtils.getFlysContext(context);
        StateEngine engine      = (StateEngine) flysContext.get(
            FLYSContext.STATE_ENGINE_KEY);

        for (String stateId: stateIds) {
            logger.debug("Append static data for state: " + stateId);
            DefaultState state = (DefaultState) engine.getState(stateId);

            ui.appendChild(state.describeStatic(this, doc, ui, context, uuid));
        }
    }


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

        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;

        if (qs == null) {
            logger.debug("Determine Q values based on a set of W values.");
            qSel = false;
            ws   = getWs();
            qs   = getQsForWs(ws);
            if (qs == null) {
                return error(new WQKms[0], "conversion ws to qs failed.");
            }
        }

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


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

        double refKm;

        if (isFreeQ()) {
            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 " + range[0]);
            }

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

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

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


    /**
     * Computes the data of a waterlevel computation based on the interpolation
     * in WstValueTable.
     *
     * @param kms The kilometer values.
     * @param qa 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
    ) {
        logger.info("WINFOArtifact.computeWaterlevelData");

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

        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, "Cannot determine river.");
        }

        Gauge g = getGauge();

        if (g == null) {
           return error(null, "Cannot determine gauge.");
        }

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

        if (locations == null) {
            return error(null, "Cannot determine location.");
        }

        WstValueTable wst = WstValueTableFactory.getTable(r);
        if (wst == null) {
            return error(null, "No Wst found for selected 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 = MainValuesFactory.getDurationCurveData(gauge);

        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 used to create discharge curves.
     *
     */
    public CalculationResult getDischargeCurveData() {

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

        double [] distance = FLYSUtils.getKmRange(this);

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

        List<Gauge> gauges = river.determineGauges(distance[0], distance[1]);

        if (gauges.isEmpty()) {
            return error(new WQKms[0], "no gauges found");
        }

        String [] names = new String[gauges.size()];

        for (int i = 0; i < names.length; ++i) {
            names[i] = gauges.get(i).getName();
        }

        DischargeTables dt = new DischargeTables(river.getName(), names);

        Map<String, double [][]> map = dt.getValues(100d);

        ArrayList<WQKms> res = new ArrayList<WQKms>();

        for (Gauge gauge: gauges) {
            String name = gauge.getName();
            double [][] values = map.get(name);
            if (values == null) {
                continue;
            }
            double [] kms = new double[values[0].length];
            Arrays.fill(kms, gauge.getStation().doubleValue());
            res.add(new WQKms(kms, values[0], values[1], name));
        }

        return new CalculationResult(
            res.toArray(new WQKms[res.size()]),
            new Calculation());
    }


    /**
     * 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], "Cannot determine river.");
        }

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

        if (locations == null) {
            return error(new WQKms[0], "Cannot determine location.");
        }

        WstValueTable wst = WstValueTableFactory.getTable(r);
        if (wst == null) {
            return error(new WQKms[0], "No Wst found for selected 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.
     * @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);
    }

    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 found for selected 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], "Cannot figure out range.");
        }

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

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


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


    /**
     * Get List of all cross-sections for current river.
     *
     * @return List of CrossSections for current river, null in case of error.
     */
    protected List<CrossSection> getCrossSections() {
        River river = FLYSUtils.getRiver(this);
        if (river == null) {
            logger.warn("No river in WINFO found");
            return null;
        }
        return CrossSectionFactory.getCrossSections(river);
    }


    /**
     * Get points of line describing the surface of water at cross section.
     *
     * @return an array holding coordinates of points of surface of water (
     *         in the form {{x1, x2} {y1, y2}} ).
     */
    public double [][] getWaterLines(int idx, CrossSectionLine csl) {
        logger.debug("getWaterLines(" + idx + ")");

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

        // 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.");
        }

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

        // Find index of km.
        double wishKM = csl.getKm().doubleValue();
        int old_idx = 0;

        if (triple.size() == 0) {
            logger.warn("Calculation of 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_w = triple.getW(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_w = triple.getW(i);
            old_dist_wish = diff;
        }
        return Lines.createWaterLines(points, last_w);
    }


    /**
     * Get name of cross sections.
     * @return list of names of cross-sections.
     */
    public List<String> getCrossSectionNames() {
        logger.debug("getCrossSectionNames");
        List<String> names = new ArrayList<String>();

        for (CrossSection section: getCrossSections()) {
            names.add(section.getDescription());
        }

        return names;
    }


    /**
     * 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) {

        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 (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() + "'");
        }

        DischargeTables dt = new DischargeTables(r.getName(), g.getName());
        Map<String, double [][]>  tmp = dt.getValues();

        double[][] values = tmp.get(g.getName());
        double[]   qs     = new double[ws.length];

        for (int i = 0; i < ws.length; i++) {
            qs[i] = dt.getQForW(values, ws[i]);
            if (debug) {
                logger.debug("w: " + ws[i] + " -> q: " + qs[i]);
            }
        }

        return qs;
    }


    /**
     * 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);
    }


    /**
     * Returns the gauges that match the selected kilometer range.
     *
     * @return the gauges based on the selected kilometer range.
     */
    public List<Gauge> getGauges() {

        River river = FLYSUtils.getRiver(this);
        if (river == null) {
            return null;
        }

        double [] dist = FLYSUtils.getKmRange(this);
        if (dist == null) {
            return null;
        }

        return river.determineGauges(dist[0], dist[1]);
    }


    /**
     * 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_mode");
        StateData dSelection = getData("wq_selection");

        String mode = dMode != null ? (String) dMode.getValue() : "";
        String sel  = dSelection != null ? (String)dSelection.getValue() : null;

        if (mode.equals("Q")) {
            if (sel != null && sel.equals("single")) {
                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_mode");
        return mode != null && mode.getValue().equals("Q");
    }


    /**
     * 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() {
        StateData mode  = getData("wq_free");
        String    value = (mode != null) ? (String) mode.getValue() : null;

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

        if (value == null) {
            return false;
        }

        return 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_mode");
        StateData dValues = getData("wq_values");

        String mode = (dMode != null) ? (String) dMode.getValue() : "";

        if (mode.equals("Q")) {
            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) {
        StateData dMode   = getData("wq_mode");
        StateData dValues = getData("wq_values");

        String mode = (dMode != null) ? (String) dMode.getValue() : "";

        if (mode.equals("W")) {
            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 dMode   = getData("wq_mode");
        StateData dSingle = getData("wq_single");

        String mode = (dMode != null) ? (String) dMode.getValue() : "";

        if (mode.equals("W")) {
            if (dSingle != null) {
                return getSingleWQValues();
            }
            else {
                return getWQTriple();
            }
        }
        else {
            logger.warn("You try to get Qs, but W 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();
    }


    /**
     * Determines Facets initial disposition regarding activity (think of
     * selection in Client ThemeList GUI). This will be checked one time
     * when the facet enters a collections describe document.
     *
     * @param facetName name of the facet.
     * @param index     index of the facet.
     * @return 0 if not active
     */
    @Override
    public int getInitialFacetActivity(String outputName, String facetName, int index) {
        String [] inactives = new String[] {
                                            LONGITUDINAL_Q,
                                            DURATION_Q
                                           };

        logger.debug("WINFOArtifact.active?: "
            + outputName
            + "/"
            + facetName);

        if (facetName.equals(COMPUTED_DISCHARGE_MAINVALUES_Q) ||
             facetName.equals(COMPUTED_DISCHARGE_MAINVALUES_W)
             && outputName.equals("computed_discharge_curve"))
            {
                return 0;
            }
        return Arrays.asList(inactives).contains(facetName)
               ? 0
               : 1;
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org