view flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java @ 643:a9bde508824a

flys/issue82: Only successful interpolations are named. flys-artifacts/trunk@2027 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 30 May 2011 09:19:57 +0000
parents 2dbbb5be30a1
children 433f67a076aa
line wrap: on
line source
package de.intevation.flys.artifacts;

import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Vector;
import java.util.ArrayList;

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

import org.apache.log4j.Logger;

import de.intevation.artifacts.ArtifactNamespaceContext;
import de.intevation.artifacts.CallContext;

import de.intevation.artifactdatabase.ProtocolUtils;
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.common.utils.XMLUtils;

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

import de.intevation.flys.artifacts.states.DefaultState;
import de.intevation.flys.artifacts.context.FLYSContext;
import de.intevation.flys.artifacts.math.BackJumpCorrector;
import de.intevation.flys.artifacts.model.MainValuesFactory;
import de.intevation.flys.artifacts.model.WQCKms;
import de.intevation.flys.artifacts.model.WQDay;
import de.intevation.flys.artifacts.model.WQKms;
import de.intevation.flys.artifacts.model.WstValueTable;
import de.intevation.flys.artifacts.model.WstValueTable.QPosition;
import de.intevation.flys.artifacts.model.WstValueTableFactory;

import de.intevation.flys.artifacts.math.LinearRemap;

import gnu.trove.TDoubleArrayList;

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

    /** 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 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 = null;
        if (context instanceof FLYSContext) {
            flysContext = (FLYSContext) context;
        }
        else {
            flysContext = (FLYSContext) context.globalContext();
        }

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

        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 void appendOutputModes(
        Document    doc,
        Element     outs,
        CallContext context,
        String      uuid)
    {
        Vector<String> stateIds = getPreviousStateIds();

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

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

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

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

            ProtocolUtils.appendOutputModes(creator, outs, list);
        }

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

                    ProtocolUtils.appendOutputModes(creator, outs, list);
                }
            }
        }
        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)
    {
        Vector<String> stateIds = getPreviousStateIds();

        FLYSContext flysContext = 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 WQKms[] getWaterlevelData()
    throws NullPointerException
    {
        logger.debug("WINFOArtifact.getWaterlevelData");

        River river = getRiver();
        if (river == null) {
            throw new NullPointerException("No river selected.");
        }

        double[] kms = getKms();
        if (kms == null) {
            throw new NullPointerException("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);
        }

        WstValueTable wst = WstValueTableFactory.getTable(river);
        if (wst == null) {
            throw new NullPointerException("No Wst found for selected river.");
        }

        HashSet<Integer> failed = new HashSet<Integer>();

        WQKms[] results = computeWaterlevelData(
            kms, qs, wst, river.getKmUp(), failed);

        // TODO Introduce a caching mechanism here!

        setWaterlevelNames(
            results, qSel ? qs : ws, qSel ? "Q" : "W", failed);

        return results;
    }


    /**
     * Sets the name for waterlevels where each WQKms in <i>r</i> represents a
     * column.
     *
     * @param r The waterlevel columns.
     * @param v The input values of the computations.
     * @param wq The WQ mode - can be one of "W" or "Q".
     */
    public static void setWaterlevelNames(
        WQKms[]  r, 
        double[] v, 
        String   wq, 
        Set      failed
    ) {
        int pos = 0;
        for (int i = 0; i < v.length; i++) {
            if (!failed.contains(i)) {
                r[pos++].setName(wq + "=" + v[i]);
            }
        }
    }


    /**
     * 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 WQKms[] computeWaterlevelData(
        double[]      kms,
        double[]      qs,
        WstValueTable wst,
        boolean       up,
        Set<Integer>  failed
    ) {
        logger.info("WINFOArtifact.computeWaterlevelData");

        WQKms[] wqkms = new WQKms[qs.length];

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

        int referenceIndex = up ? 0 : kms.length-1; 

        for (int i = 0; i < qs.length; i++) {
            double [] oqs = new double[kms.length];
            double [] ows = new double[kms.length];
            WstValueTable.QPosition qPosition =
                wst.interpolate(qs[i], referenceIndex, kms, ows, oqs);
            if (qPosition != null) {
                results.add(new WQKms(kms, oqs, ows));
            }
            else {
                logger.warn("interpolation failed for q = " + qs[i]);
                failed.add(i);
            }
        }

        return results.toArray(new WQKms[results.size()]);
    }


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

        River r = getRiver();

        if (r == null) {
            throw new NullPointerException("Cannot determine river.");
        }

        Gauge g = getGauge();

        if (g == null) {
            throw new NullPointerException("Cannot determine gauge.");
        }

        double[] locations = getLocations();

        if (locations == null) {
            throw new NullPointerException("Cannot determine location.");
        }

        WstValueTable wst = WstValueTableFactory.getTable(r);
        if (wst == null) {
            throw new NullPointerException("No Wst found for selected river.");
        }

        // TODO Introduce a caching mechanism here!

        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 WQDay 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];

        double[] interpolatedW = new double[qs.length];
        interpolatedW          = wst.interpolateW(location, qs, interpolatedW);

        WQDay wqday = new WQDay(qs.length);

        for (int i = 0; i < days.length; i++) {
            wqday.add(days[i], interpolatedW[i], qs[i]);
        }

        return wqday;
    }


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

        River r = getRiver();

        if (r == null) {
            throw new NullPointerException("Cannot determine river.");
        }

        double[] locations = getLocations();

        if (locations == null) {
            throw new NullPointerException("Cannot determine location.");
        }

        WstValueTable wst = WstValueTableFactory.getTable(r);
        if (wst == null) {
            throw new NullPointerException("No Wst found for selected river.");
        }

        WQKms wqkms = computeDischargeCurveData(wst, locations[0]);

        // TODO Introduce a caching mechanism here!

        setComputedDischargeCurveNames(wqkms, locations[0]);

        return wqkms;
    }


    /**
     * Sets the name of the computed discharge curve data.
     *
     * @param wqkms The computed WQKms object.
     * @param l The location used for the computation.
     */
    public static void setComputedDischargeCurveNames(WQKms wqkms, double l) {
        wqkms.setName(Double.toString(l));
    }


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

        double[][] wqs = wst.interpolateWQ(location);

        if (wqs == null) {
            logger.error("Cannot compute discharge curve data.");
            return null;
        }

        double[] ws = wqs[0];
        double[] qs = wqs[1];

        WQKms wqkms = new WQKms(ws.length);

        for (int i = 0; i < ws.length; i++) {
            wqkms.add(ws[i], qs[i], location);
        }

        return wqkms;
    }

    private static final double [] getBounds(double [][] segments) {
        boolean down = true;
        double min =  Double.MAX_VALUE;
        double max = -Double.MAX_VALUE;

        for (double [] segment: segments) {
            if (down = segment[0] > segment[1]) {
                if (segment[1] < min) min = segment[1];
                if (segment[0] > max) max = segment[0];
            }
            else {
                if (segment[0] < min) min = segment[0];
                if (segment[1] > max) max = segment[1];
            }
        }

        return down
            ?  new double [] { max, min }
            :  new double [] { min, max };

    }

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

        logger.debug("WINFOArtifact.getDischargeLongitudinalSectionData");

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

        boolean kmUp = river.getKmUp();

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

        double [][] segments = getRanges();

        if (logger.isDebugEnabled()) {
            logger.debug("segments ----------------- enter");
            for (double [] segment: segments) {
                logger.debug("  " + joinDoubles(segment));
            }
            logger.debug("segments ----------------- leave");
        }

        if (segments.length < 1) {
            logger.warn("no segments given");
            return new WQKms[0];
        }

        if (segments.length == 1) {
            // fall back to normal "Wasserstand/Wasserspiegellage" calculation
            double [] qs = toQs(segments[0]);
            if (qs == null) {
                logger.warn("no qs given");
                return new WQKms[0];
            }
            if (qs.length == 1) {
                double [] kms = getKms(segments[0]);
                HashSet<Integer> failed = new HashSet<Integer>();
                WQKms [] results = computeWaterlevelData
                    (kms, qs, wst, kmUp, failed);
                setWaterlevelNames(
                    results, qs, isQ() ? "Q" : "W", failed);
                return results;
            }
        }

        // more than one segment

        double [] boundKms;

        if (segments.length == 2) {
            boundKms = getBounds(segments);
        }
        else {
            TDoubleArrayList bounds = new TDoubleArrayList();

            bounds.add(Math.min(segments[0][0], segments[0][1]));

            for (int i = 1; i < segments.length-1; ++i) {
                double [] segment = segments[i];

                Gauge gauge = river.determineGauge(segment[0], segment[1]);

                if (gauge == null) {
                    logger.warn("no gauge found between " + 
                        segment[0] + " and " + segment[1]);
                    bounds.add(0.5*(segment[0] + segment[1]));
                }
                else {
                    bounds.add(gauge.getStation().doubleValue());
                }
            }

            bounds.add(Math.max(
                segments[segments.length-1][0],
                segments[segments.length-1][1]));

            boundKms = bounds.toNativeArray();
        }

        if (logger.isDebugEnabled()) {
            logger.debug("bound kms: " + joinDoubles(boundKms));
        }

        double [][] iqs = null;

        for (int i = 0; i < segments.length; ++i) {
            double [] iqsi = toQs(segments[i]);
            if (iqsi == null) {
                logger.warn("iqsi == null");
                return new WQKms[0];
            }

            if (iqs == null) {
                iqs = new double[iqsi.length][boundKms.length];
            }
            else if (iqs.length != iqsi.length) {
                logger.warn("iqsi.logger != iqs.length: "
                    + iqsi.length + " " + iqsi.length);
                return new WQKms[0];
            }

            if (logger.isDebugEnabled()) {
                logger.debug("segments qs[ " + i + "]: " + joinDoubles(iqsi));
            }

            for (int j = 0; j < iqs.length; ++j) {
                iqs[j][i] = iqsi[j];
            }
        }

        if (logger.isDebugEnabled()) {
            for (int i = 0; i < iqs.length; ++i) {
                logger.debug("iqs[" + i + "]: " + joinDoubles(iqs[i]));
            }
        }

        double [] boundWs = new double[boundKms.length];
        double [] boundQs = new double[boundKms.length];

        double [] okms = getKms(new double [] {
            boundKms[0], boundKms[boundKms.length-1] });

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

        int referenceIndex = kmUp ?  0 : boundKms.length-1;

        HashSet<Integer> failed = new HashSet<Integer>();

        for (int i = 0; i < iqs.length; ++i) {
            double [] iqsi = iqs[i];

            QPosition qPosition = wst.interpolate(
                iqsi[0],
                referenceIndex,
                boundKms, boundWs, boundQs);

            if (qPosition == null) {
                logger.warn("interpolation failed for " + iqsi[i]);
                failed.add(i);
                continue;
            }

            LinearRemap remap = new LinearRemap();

            for (int j = 1; j < boundKms.length; ++j) {
                remap.add(
                    boundKms[j-1], boundKms[j],
                    boundQs[j-1],  iqsi[j-1],
                    boundQs[j],    iqsi[j]);
            }

            double [] oqs = new double[okms.length];
            double [] ows = new double[okms.length];

            wst.interpolate(okms, ows, oqs, qPosition, remap);

            BackJumpCorrector bjc = new BackJumpCorrector();
            if (bjc.doCorrection(okms, ows)) {
                logger.debug("Discharge longitudinal section has backjumps.");
                results.add(new WQCKms(okms, oqs, ows, bjc.getCorrected()));
            }
            else {
                results.add(new WQKms(okms, oqs, ows));
            }
        }

        WQKms [] wqkms = results.toArray(new WQKms[results.size()]);

        setDischargeLongitudinalSectionNames(
            wqkms, iqs, isQ() ? "Q" : "W", failed);

        return wqkms;
    }

    protected static String joinDoubles(double [] x) {
        if (x == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < x.length; ++i) {
            if (i > 0) sb.append(", ");
            sb.append(x[i]);
        }
        return sb.toString();
    }

    protected double [] toQs(double [] range) {
        double [] qs = getQs(range);
        if (qs == null) {
            logger.debug("Determine Q values based on a set of W values.");
            double [] ws = getWs(range);
            qs = getQsForWs(ws);
        }
        return qs;
    }


    /**
     * Sets the name for discharge longitudinal section curves where each WQKms
     * in <i>r</i> represents a column.
     */
    public static void setDischargeLongitudinalSectionNames(
        WQKms  []    wqkms,
        double [][]  iqs,
        String       wq,
        Set<Integer> failed
    ) {
        logger.debug("WINFOArtifact.setDischargeLongitudinalSectionNames");

        // TODO: I18N

        int pos = 0;

        for (int j = 0; j < iqs.length; ++j) {
            if (failed.contains(j)) {
                continue;
            }
            StringBuilder sb = new StringBuilder(wq)
                .append(" benutzerdefiniert (");

            double [] iqsi = iqs[j];
            for (int i = 0; i < iqsi.length; i++) {
                if (i > 0) {
                    sb.append("; ");
                }
                sb.append(iqsi[i]);
            }
            sb.append(")");

            wqkms[pos++].setName(sb.toString());
        }
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org