diff flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java @ 462:ebf049a1eb53

merged flys-artifacts/2.3.1
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:11 +0200
parents af1b64ec7250
children 929137ee8154
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java	Fri Sep 28 12:14:11 2012 +0200
@@ -0,0 +1,707 @@
+package de.intevation.flys.artifacts;
+
+import java.util.List;
+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());
+
+        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) {
+                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);
+            state = (DefaultState) fillState(state);
+
+            ui.appendChild(state.describeStatic(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.");
+        }
+
+        WQKms[] results = computeWaterlevelData(kms, qs, wst);
+
+        // TODO Introduce a caching mechanism here!
+
+        setWaterlevelNames(results, qSel ? qs : ws, qSel ? "Q" : "W");
+
+        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) {
+        for (int i = 0; i < v.length; i++) {
+            r[i].setName(wq + "=" + Double.toString(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)
+    {
+        logger.info("WINFOArtifact.computeWaterlevelData");
+
+        WQKms[] wqkms = new WQKms[qs.length];
+
+        ArrayList<WQKms> results = new ArrayList<WQKms>();
+
+        for (int i = 0; i < qs.length; i++) {
+            double [] oqs = new double[kms.length];
+            double [] ows = new double[kms.length];
+            int referenceIndex = 0; // TODO: Make depend on the flow direction
+            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]);
+            }
+        }
+
+        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;
+    }
+
+    /**
+     * 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];
+        }
+
+        WstValueTable wst = WstValueTableFactory.getTable(river);
+        if (wst == null) {
+            logger.error("No wst found for selected river.");
+            return new WQKms[0];
+        }
+
+        double [][] segments = getSplittedDistance();
+
+        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]);
+                return computeWaterlevelData(kms, qs, wst);
+            }
+        }
+
+        // more than one segment
+
+        double [] boundKms;
+
+        if (segments.length == 2) {
+            boundKms = new double [] { segments[0][0], segments[1][1] };
+        }
+        else {
+            TDoubleArrayList bounds = new TDoubleArrayList();
+
+            bounds.add(segments[0][0]);
+
+            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(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>();
+
+        for (int i = 0; i < iqs.length; ++i) {
+            double [] iqsi = iqs[i];
+
+            QPosition qPosition = wst.interpolate(
+                iqsi[0], 0, boundKms, boundWs, boundQs);
+
+            if (qPosition == null) {
+                logger.warn("interpolation failed for " + iqsi[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");
+
+        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
+    ) {
+        logger.debug("WINFOArtifact.setDischargeLongitudinalSectionNames");
+
+        // TODO: I18N
+
+        for (int j = 0; j < wqkms.length; ++j) {
+            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[j].setName(sb.toString());
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org