view flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java @ 2561:b3f6d49cdc80

Service to generate the data needed to build the "Fixerungen pro Fluss Uebersicht" flys-artifacts/trunk@4087 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sun, 19 Feb 2012 17:14:39 +0000
parents efb2038783f9
children 3f1cc396d253
line wrap: on
line source
package de.intevation.flys.artifacts;

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.ElementCreator;

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

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.Calculation5;
import de.intevation.flys.artifacts.model.Calculation6;
import de.intevation.flys.artifacts.model.Calculation;
import de.intevation.flys.artifacts.model.CalculationMessage;
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.MainValuesFactory;
import de.intevation.flys.artifacts.model.Segment;
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.DefaultState;
import de.intevation.flys.artifacts.states.LocationDistanceSelect;

import de.intevation.flys.geom.Lines;

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

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

import gnu.trove.TDoubleArrayList;

import java.awt.geom.Point2D;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
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;

/**
 * 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();
            double [][] qws = getQsForWs(ws);
            if (qws == null || qws.length == 0) {
                return error(new WQKms[0], "converting.ws.to.qs.failed");
            }
            qs = qws[0];

            if (qws[1] != null) { // If new ws where generated.
                // TODO: Inform user!
                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);
    }


    /**
     * 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, "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 = 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.selected");
        }

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

        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], "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.
     * @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.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. */
    protected 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;
    }


    protected 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");

        int[]    timerange = FLYSUtils.intArrayFromString(rawTimerange);
        double[] values    = FLYSUtils.doubleArrayFromString(rawValues);

        Calendar start = new GregorianCalendar(timerange[0], 0, 1);
        Calendar end   = new GregorianCalendar(timerange[1], 0, 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.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 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, FastCrossSectionLine csl) {
        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.");
        }

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

        // Find index of km.
        double wishKM = csl.getKm();
        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);
    }


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


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


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