changeset 4116:4ffeccc5b5a1

Initial GUI and state for per-segment Q-input for extreme valua analysis.
author Felix Wolfsteller <felix.wolfsteller@intevation.de>
date Fri, 12 Oct 2012 15:07:01 +0200
parents 0cc2c3d89a9d
children 3e29c49a3ab8
files flys-artifacts/ChangeLog flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/extreme/ExtremeQInput.java flys-client/ChangeLog flys-client/src/main/java/de/intevation/flys/client/client/ui/QSegmentedInputPanel.java
diffstat 4 files changed, 742 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/flys-artifacts/ChangeLog	Fri Oct 12 09:27:18 2012 +0200
+++ b/flys-artifacts/ChangeLog	Fri Oct 12 15:07:01 2012 +0200
@@ -1,3 +1,9 @@
+2012-10-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/extreme/ExtremeQInput.java:
+	  New, initial Input state for Q input per segment in extreme analysis
+	  path.
+
 2012-10-12  Ingo Weinzierl <ingo@intevation.de>
 
 	* src/main/java/de/intevation/flys/artifacts/services/DischargeInfoService.java:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/states/extreme/ExtremeQInput.java	Fri Oct 12 15:07:01 2012 +0200
@@ -0,0 +1,245 @@
+package de.intevation.flys.artifacts.states.extreme;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Comparator;
+import java.util.Collections;
+
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Element;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.CallContext;
+
+import de.intevation.flys.artifacts.access.ExtremeAccess;
+
+import de.intevation.artifactdatabase.ProtocolUtils;
+import de.intevation.artifactdatabase.data.StateData;
+
+import de.intevation.artifacts.common.utils.XMLUtils;
+
+import de.intevation.flys.artifacts.model.RiverFactory;
+import de.intevation.flys.artifacts.model.WstValueTable;
+import de.intevation.flys.model.Gauge;
+import de.intevation.flys.artifacts.model.Range;
+import de.intevation.flys.model.River;
+import de.intevation.flys.model.Wst;
+
+import de.intevation.flys.artifacts.FLYSArtifact;
+
+import de.intevation.flys.artifacts.model.RangeWithValues;
+import de.intevation.flys.artifacts.states.DefaultState;
+import de.intevation.flys.artifacts.model.WstValueTableFactory;
+import de.intevation.flys.utils.FLYSUtils;
+
+
+/** TODO Subclass WQAdapted. */
+
+/**
+ * State to input Q data in segments for extreme value calculations..
+ * The data item ranges is expected to have this format <from1>;<to1>;<value1>:<from2>;<to2>;<value2>:...
+ * (;;;:;;;:;;;:...)
+ */
+public class ExtremeQInput extends DefaultState {
+
+    /** The logger used in this state.*/
+    private static Logger logger = Logger.getLogger(ExtremeQInput.class);
+
+
+    /** Trivial, empty constructor. */
+    public ExtremeQInput() {
+    }
+
+
+    /**
+     * Create one element for each 'segment' of the selected river that
+     * is within the given kilometer range (TODO). Each element is a tuple of
+     * (from;to) where <i>from</i> is the lower bounds of the segment or the
+     * lower kilometer range. <i>to</i> is the upper bounds of the segment or
+     * the upper kilometer range.
+     *
+     * @param cr The ElementCreator.
+     * @param artifact The FLYS artifact.
+     * @param name The name of the data item.
+     * @param context The CallContext.
+     *
+     * @return a list of elements that consist of tuples of the intersected
+     *         segments of the selected river.
+     */
+    @Override
+    protected Element[] createItems(
+        XMLUtils.ElementCreator cr,
+        Artifact    artifact,
+        String      name,
+        CallContext context)
+    {
+        logger.debug("ExtremeQInput.createItems: " + name);
+
+        FLYSArtifact flysArtifact = (FLYSArtifact) artifact;
+
+        ExtremeAccess access = new ExtremeAccess(flysArtifact);
+        River river = RiverFactory.getRiver(access.getRiver());
+        WstValueTable wstValueTable = WstValueTableFactory.getTable(river);
+
+        List<Range> ranges   = wstValueTable.findSegments(access.getFrom(),
+            access.getTo());
+
+        int num = ranges != null ? ranges.size() : 0;
+
+        if (num == 0) {
+            logger.warn("Selected distance matches no segments.");
+            return null;
+        }
+
+        List<Element> elements = new ArrayList<Element>();
+
+        for (Range range: ranges) {
+            elements.add(createItem(
+                cr, new String[] { range.getStart() + ";" + range.getEnd(), ""}, new double[] {0,100000}));
+        }
+
+        Element[] els = new Element[elements.size()];
+
+        return elements.toArray(els);
+    }
+
+
+    /** Create sub-item ('row') of data thing. */
+    protected Element createItem(
+        XMLUtils.ElementCreator cr,
+        Object   obj,
+        double[] q
+        )
+    {
+        Element item  = ProtocolUtils.createArtNode(cr, "item", null, null);
+        Element label = ProtocolUtils.createArtNode(cr, "label", null, null);
+        Element value = ProtocolUtils.createArtNode(cr, "value", null, null);
+
+        String[] arr = (String[]) obj;
+
+        label.setTextContent(arr[0]);
+        value.setTextContent(arr[1]);
+
+        item.appendChild(label);
+        item.appendChild(value);
+
+        if (q != null) {
+            Element qRange = createRangeElement(cr, q, "Q");
+            item.appendChild(qRange);
+        }
+
+        return item;
+    }
+
+
+    /**
+     * Create elements to set min and max values of segments q (just min makes
+     * sense for extremes.
+     */
+    protected Element createRangeElement(
+        XMLUtils.ElementCreator cr,
+        double[] mm,
+        String   type)
+    {
+        Element range = ProtocolUtils.createArtNode(
+            cr, "range",
+            new String[] {"type"},
+            new String[] {type});
+
+        Element min = ProtocolUtils.createArtNode(cr, "min", null, null);
+        min.setTextContent(String.valueOf(mm[0]));
+
+        Element max = ProtocolUtils.createArtNode(cr, "max", null, null);
+        max.setTextContent(String.valueOf(mm[1]));
+
+        range.appendChild(min);
+        range.appendChild(max);
+
+        return range;
+    }
+
+
+    @Override
+    protected String getUIProvider() {
+        return "q_segmented_panel";
+    }
+
+
+    /** Validate given data (return true). */
+    @Override
+    public boolean validate(Artifact artifact)
+    throws IllegalArgumentException
+    {
+        logger.debug("ExtremeQInput.validate");
+
+        FLYSArtifact flys = (FLYSArtifact) artifact;
+        logger.debug("ExtremeQInput: " + getData(flys, "ranges"));
+
+        /*
+        // TODO sort out what has to be validated (prevent negative values?).
+        RangeWithValues[] rwvs = extractInput(getData(flys, "ranges"));
+
+        if (rwvs == null) {
+            throw new IllegalArgumentException("error_missing_wq_data");
+        }
+
+        List<Gauge> gauges = FLYSUtils.getGauges(flys);
+        River        river = FLYSUtils.getRiver(flys);
+        Wst            wst = WstFactory.getWst(river);
+
+        for (Gauge gauge: gauges) {
+            Range range  = gauge.getRange();
+            double lower = range.getA().doubleValue();
+            double upper = range.getB().doubleValue();
+
+            for (RangeWithValues rwv: rwvs) {
+                if (lower <= rwv.getStart() && upper >= rwv.getEnd()) {
+                    compareQsWithGauge(wst, gauge, rwv.getValues());
+                }
+            }
+        }
+        */
+
+        return true;
+    }
+
+
+    /** Form RangeWithValue-Array from state data. */
+    protected RangeWithValues[] extractInput(StateData data) {
+        if (data == null) {
+            return null;
+        }
+
+        String dataString = (String) data.getValue();
+        String[]   ranges = dataString.split(":");
+
+        List<RangeWithValues> rwv = new ArrayList<RangeWithValues>();
+
+        for (String range: ranges) {
+            String[] parts = range.split(";");
+
+            double lower = Double.parseDouble(parts[0]);
+            double upper = Double.parseDouble(parts[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);
+                }
+            }
+
+            rwv.add(new RangeWithValues(lower, upper, res));
+        }
+
+        return rwv.toArray(new RangeWithValues[rwv.size()]);
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :
--- a/flys-client/ChangeLog	Fri Oct 12 09:27:18 2012 +0200
+++ b/flys-client/ChangeLog	Fri Oct 12 15:07:01 2012 +0200
@@ -1,3 +1,8 @@
+2012-10-12	Felix Wolfsteller	<felix.wolfsteller@intevation.de>
+
+	* flys-client/src/main/java/de/intevation/flys/client/client/ui/QSegmentedInputPanel.java:
+	  New, initial GUI for Q input per segment.
+
 2012-10-12  Ingo Weinzierl <ingo@intevation.de>
 
 	* src/main/java/de/intevation/flys/client/client/ui/GaugeTimeRangePanel.java:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-client/src/main/java/de/intevation/flys/client/client/ui/QSegmentedInputPanel.java	Fri Oct 12 15:07:01 2012 +0200
@@ -0,0 +1,486 @@
+package de.intevation.flys.client.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.i18n.client.NumberFormat;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import com.smartgwt.client.types.TitleOrientation;
+import com.smartgwt.client.types.VerticalAlignment;
+import com.smartgwt.client.util.SC;
+import com.smartgwt.client.widgets.Canvas;
+import com.smartgwt.client.widgets.Label;
+import com.smartgwt.client.widgets.form.DynamicForm;
+import com.smartgwt.client.widgets.form.fields.RadioGroupItem;
+import com.smartgwt.client.widgets.form.fields.events.BlurHandler;
+import com.smartgwt.client.widgets.form.fields.events.BlurEvent;
+import com.smartgwt.client.widgets.form.fields.events.ChangeHandler;
+import com.smartgwt.client.widgets.form.fields.events.ChangeEvent;
+import com.smartgwt.client.widgets.layout.HLayout;
+import com.smartgwt.client.widgets.layout.VLayout;
+import com.smartgwt.client.widgets.tab.TabSet;
+import com.smartgwt.client.widgets.tab.Tab;
+
+import de.intevation.flys.client.shared.model.Data;
+import de.intevation.flys.client.shared.model.DataItem;
+import de.intevation.flys.client.shared.model.DataList;
+import de.intevation.flys.client.shared.model.DefaultData;
+import de.intevation.flys.client.shared.model.DefaultDataItem;
+import de.intevation.flys.client.shared.model.WQDataItem;
+import de.intevation.flys.client.shared.model.WQInfoObject;
+import de.intevation.flys.client.shared.model.WQInfoRecord;
+import de.intevation.flys.client.shared.model.ArtifactDescription;
+
+
+import de.intevation.flys.client.client.FLYSConstants;
+import de.intevation.flys.client.client.Config;
+import de.intevation.flys.client.client.services.WQInfoService;
+import de.intevation.flys.client.client.services.WQInfoServiceAsync;
+import de.intevation.flys.client.client.ui.wq.WTable;
+import de.intevation.flys.client.client.ui.wq.QDTable;
+
+
+/**
+ * This UIProvider creates a widget to enter Q values per segment.
+ */
+public class QSegmentedInputPanel
+extends      AbstractUIProvider
+implements   ChangeHandler, BlurHandler
+{
+    public static final String FIELD_WQ_MODE = "wq_isq";
+    public static final String FIELD_WQ_Q    = "Q";
+
+    public static final String GAUGE_SEPARATOR = ":";
+
+    public static final String GAUGE_PART_SEPARATOR = ";";
+
+    public static final String VALUE_SEPARATOR = ",";
+
+    public static final int ROW_HEIGHT = 20;
+
+    /** The constant field name for choosing single values or range.*/
+    public static final String FIELD_MODE = "mode";
+
+    /** The constant field value for range input mode.*/
+    public static final String FIELD_MODE_RANGE = "range";
+
+    protected WQInfoServiceAsync wqInfoService =
+        GWT.create(WQInfoService.class);
+
+    /** The message class that provides i18n strings.*/
+    protected FLYSConstants MSG = GWT.create(FLYSConstants.class);
+
+    /** Stores the input panels related to their keys.*/
+    protected Map<String, DoubleArrayPanel> wqranges;
+
+    /** Stores the min/max values for each q range.*/
+    protected Map<String, double[]> qranges;
+
+    protected QDTable qdTable;
+
+    protected WTable wTable;
+
+    protected TabSet tabs;
+
+
+    public QSegmentedInputPanel() {
+        wqranges = new HashMap<String, DoubleArrayPanel>();
+        qranges  = new HashMap<String, double[]>();
+        qdTable  = new QDTable();
+        wTable   = new WTable();
+    }
+
+
+    /** Create main UI Canvas. */
+    public Canvas create(DataList data) {
+        initHelperPanel();
+
+        Canvas submit = getNextButton();
+        Canvas widget = createWidget(data);
+        Label  label  = new Label(MSG.wqadaptedTitle());
+
+        label.setHeight(25);
+
+        VLayout layout = new VLayout();
+        layout.setMembersMargin(10);
+        layout.setWidth(350);
+
+        layout.addMember(label);
+        layout.addMember(widget);
+        layout.addMember(submit);
+
+        return layout;
+    }
+
+
+    protected void initHelperPanel() {
+        tabs = new TabSet();
+        tabs.setWidth100();
+        tabs.setHeight100();
+
+        // TODO i18n
+        Tab qTab = new Tab("Q / D");
+
+        qTab.setPane(qdTable);
+        qdTable.hideIconFields();
+
+        tabs.addTab(qTab, 1);
+
+        helperContainer.addMember(tabs);
+
+        // TODO Q only would suffice.
+        fetchWQData();
+    }
+
+
+    /** Create display for passive mode. */
+    public Canvas createOld(DataList dataList) {
+        List<Data> all = dataList.getAll();
+        Data    wqData = getData(all, "ranges");
+
+        Canvas back = getBackButton(dataList.getState());
+
+        HLayout valLayout  = new HLayout();
+        VLayout vlayout    = new VLayout();
+        Label wqLabel      = new Label(dataList.getLabel());
+
+        wqLabel.setValign(VerticalAlignment.TOP);
+
+        wqLabel.setWidth(200);
+        wqLabel.setHeight(25);
+
+        valLayout.addMember(wqLabel);
+        valLayout.addMember(createOldWQValues(wqData));
+        valLayout.addMember(back);
+
+        vlayout.addMember(valLayout);
+
+        return vlayout;
+    }
+
+
+    /** Create canvas showing previously entered values. */
+    protected Canvas createOldWQValues(Data wqData) {
+        VLayout layout = new VLayout();
+
+        //TODO: Sort by first field, numerically.
+
+        DataItem item  = wqData.getItems()[0];
+        String   value = item.getStringValue();
+
+        String[] gauges = value.split(GAUGE_SEPARATOR);
+
+        for (String gauge: gauges) {
+            HLayout h = new HLayout();
+
+            String[] parts  = gauge.split(GAUGE_PART_SEPARATOR);
+            String[] values = parts[2].split(VALUE_SEPARATOR);
+
+            Label l = new Label(parts[0] + " - " + parts[1] + ": ");
+
+            StringBuilder sb = new StringBuilder();
+            boolean    first = true;
+
+            for (String v: values) {
+                if (!first) {
+                    sb.append(", ");
+                }
+
+                sb.append(v);
+
+                first = false;
+            }
+
+            Label v = new Label(sb.toString());
+
+            l.setWidth(65);
+            v.setWidth(65);
+
+            h.addMember(l);
+            h.addMember(v);
+
+            layout.addMember(h);
+        }
+
+        return layout;
+    }
+
+
+    protected Canvas createWidget(DataList dataList) {
+        VLayout layout = new VLayout();
+
+        Canvas list = createList(dataList);
+
+        DataItem[] items = getWQItems(dataList);
+        int listHeight   = ROW_HEIGHT * items.length;
+
+        layout.addMember(list);
+
+        layout.setHeight(25 + listHeight);
+        layout.setWidth(350);
+
+        return layout;
+    }
+
+
+    @Override
+    public List<String> validate() {
+        List<String> errors = new ArrayList<String>();
+        NumberFormat nf     = NumberFormat.getDecimalFormat();
+
+        Iterator<String> iter = wqranges.keySet().iterator();
+
+        while (iter.hasNext()) {
+            List<String> tmpErrors = new ArrayList<String>();
+
+            String           key = iter.next();
+            DoubleArrayPanel dap = wqranges.get(key);
+
+            if (!dap.validateForm()) {
+                errors.add(MSG.error_invalid_double_value());
+                return errors;
+            }
+
+            double[] mm = qranges.get(key);
+            if (mm == null) {
+                SC.warn(MSG.error_read_minmax_values());
+                continue;
+            }
+
+            double[] values = dap.getInputValues();
+            // might geht npe here if one field not filled
+            double[] good   = new double[values.length];
+
+            int idx = 0;
+
+            for (double value: values) {
+                if (value < mm[0] || value > mm[1]) {
+                    String tmp = MSG.error_validate_range();
+                    tmp = tmp.replace("$1", nf.format(value));
+                    tmp = tmp.replace("$2", nf.format(mm[0]));
+                    tmp = tmp.replace("$3", nf.format(mm[1]));
+                    tmpErrors.add(tmp);
+                }
+                else {
+                    good[idx++] = value;
+                }
+            }
+
+            double[] justGood = new double[idx];
+            for (int i = 0; i < justGood.length; i++) {
+                justGood[i] = good[i];
+            }
+
+            if (!tmpErrors.isEmpty()) {
+                dap.setValues(justGood);
+
+                errors.addAll(tmpErrors);
+            }
+        }
+
+        return errors;
+    }
+
+
+    protected Canvas createList(DataList dataList) {
+        VLayout layout = new VLayout();
+
+        DataItem[] items = getWQItems(dataList);
+
+        for (DataItem item: items) {
+            String title = item.getLabel();
+
+            DoubleArrayPanel dap = new DoubleArrayPanel(
+                createLineTitle(title), null, this, TitleOrientation.LEFT);
+
+            wqranges.put(title, dap);
+
+            if (item instanceof WQDataItem) {
+                WQDataItem wq = (WQDataItem) item;
+                double[] mmQ = wq.getQRange();
+                double[] mmW = wq.getWRange();
+
+                qranges.put(title, mmQ);
+            }
+
+            layout.addMember(dap);
+        }
+
+        layout.setHeight(items.length * ROW_HEIGHT);
+
+        return layout;
+    }
+
+
+    protected DataItem[] getWQItems(DataList dataList) {
+        List<Data> data = dataList.getAll();
+
+        for (Data d: data) {
+            String name = d.getLabel();
+
+            // TODO to be gone
+            if (name.equals(FIELD_WQ_MODE)) {
+                continue;
+            }
+
+            return d.getItems();
+        }
+
+        return null;
+    }
+
+
+
+    public String createLineTitle(String key) {
+        String[] splitted = key.split(";");
+
+        return splitted[0] + " - " + splitted[1];
+    }
+
+
+    public Data[] getData() {
+        Data values = getWQValues();
+
+        return new Data[] { values };
+    }
+
+
+    protected Data getWQValues() {
+        String wqvalue = null;
+
+        Iterator<String> iter = wqranges.keySet().iterator();
+        while (iter.hasNext()) {
+            String           key = iter.next();
+            DoubleArrayPanel dap = wqranges.get(key);
+
+            double[] values = dap.getInputValues();
+            if (wqvalue == null) {
+                wqvalue = createValueString(key, values);
+            }
+            else {
+                wqvalue += GAUGE_SEPARATOR + createValueString(key, values);
+            }
+        }
+
+        // TODO probably ranges
+        DataItem valueItem = new DefaultDataItem(
+            "ranges", "ranges", wqvalue);
+        Data values = new DefaultData(
+            "ranges", null, null, new DataItem[] { valueItem });
+
+        return values;
+    }
+
+
+    protected String createValueString(String key, double[] values) {
+        StringBuilder sb = new StringBuilder();
+
+        boolean first = true;
+
+        for (double value: values) {
+            if (!first) {
+                sb.append(",");
+            }
+
+            sb.append(Double.toString(value));
+
+            first = false;
+        }
+
+        return key + ";" + sb.toString();
+    }
+
+
+    public void onChange(ChangeEvent event) {
+        // TODO IMPLEMENT ME
+    }
+
+
+    public void onBlur(BlurEvent event) {
+        DoubleArrayPanel dap = (DoubleArrayPanel) event.getForm();
+        dap.validateForm(event.getItem());
+    }
+
+
+    protected void fetchWQData() {
+        Config config    = Config.getInstance();
+        String locale    = config.getLocale ();
+
+        ArtifactDescription adescr = artifact.getArtifactDescription();
+        DataList[] data = adescr.getOldData();
+
+        double[]  mm = getMinMaxKM(data);
+        String river = getRiverName(data);
+
+        wqInfoService.getWQInfo(locale, river, mm[0], mm[0],
+            new AsyncCallback<WQInfoObject[]>() {
+                public void onFailure(Throwable caught) {
+                    GWT.log("Could not recieve wq informations.");
+                    SC.warn(caught.getMessage());
+                }
+
+                public void onSuccess(WQInfoObject[] wqi) {
+                    int num = wqi != null ? wqi.length :0;
+                    GWT.log("Recieved " + num + " wq informations.");
+
+                    if (num == 0) {
+                        return;
+                    }
+
+                    addWQInfo(wqi);
+
+                }
+            }
+        );
+    }
+
+
+    protected void addWQInfo (WQInfoObject[] wqi) {
+        for(WQInfoObject wi: wqi) {
+            WQInfoRecord rec = new WQInfoRecord(wi);
+
+            if (wi.getType().equals("W")) {
+                wTable.addData(rec);
+            }
+            else {
+                qdTable.addData(rec);
+            }
+        }
+    }
+
+
+    /**
+     * Determines the min and max kilometer value selected in a former state. A
+     * bit silly, but we need to run over each value of the "old data" to find
+     * such values because it is not available here.
+     *
+     * @param data The DataList which contains the whole data inserted for the
+     * current artifact.
+     *
+     * @return a double array with [min, max].
+     */
+    protected double[] getMinMaxKM(DataList[] data) {
+        ArtifactDescription adesc = artifact.getArtifactDescription();
+        return adesc.getKMRange();
+    }
+
+
+    /**
+     * Returns the name of the selected river.
+     *
+     * @param data The DataList with all data.
+     *
+     * @return the name of the current river.
+     */
+    protected String getRiverName(DataList[] data) {
+        ArtifactDescription adesc = artifact.getArtifactDescription();
+        return adesc.getRiver();
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org