Mercurial > dive4elements > river
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 :