ingo@105: package de.intevation.flys.artifacts;
ingo@105: 
ingo@109: import java.util.List;
ingo@134: import java.util.Vector;
sascha@449: import java.util.ArrayList;
ingo@109: 
ingo@105: import org.w3c.dom.Document;
ingo@110: import org.w3c.dom.Element;
ingo@124: import org.w3c.dom.Node;
ingo@105: 
ingo@105: import org.apache.log4j.Logger;
ingo@105: 
ingo@110: import de.intevation.artifacts.ArtifactNamespaceContext;
ingo@105: import de.intevation.artifacts.CallContext;
ingo@105: 
ingo@110: import de.intevation.artifactdatabase.ProtocolUtils;
ingo@144: import de.intevation.artifactdatabase.state.Output;
ingo@110: import de.intevation.artifactdatabase.state.State;
ingo@110: import de.intevation.artifactdatabase.state.StateEngine;
ingo@112: import de.intevation.artifactdatabase.transition.TransitionEngine;
ingo@110: 
ingo@109: import de.intevation.artifacts.common.utils.XMLUtils;
ingo@109: 
ingo@385: import de.intevation.flys.model.Gauge;
ingo@362: import de.intevation.flys.model.River;
ingo@362: 
ingo@134: import de.intevation.flys.artifacts.states.DefaultState;
ingo@109: import de.intevation.flys.artifacts.context.FLYSContext;
ingo@402: import de.intevation.flys.artifacts.math.BackJumpCorrector;
ingo@385: import de.intevation.flys.artifacts.model.MainValuesFactory;
ingo@402: import de.intevation.flys.artifacts.model.WQCKms;
ingo@385: import de.intevation.flys.artifacts.model.WQDay;
ingo@362: import de.intevation.flys.artifacts.model.WQKms;
ingo@362: import de.intevation.flys.artifacts.model.WstValueTable;
sascha@451: import de.intevation.flys.artifacts.model.WstValueTable.QPosition;
sascha@443: import de.intevation.flys.artifacts.model.WstValueTableFactory;
ingo@105: 
sascha@451: import de.intevation.flys.artifacts.math.LinearRemap;
sascha@451: 
sascha@451: import gnu.trove.TDoubleArrayList;
ingo@105: 
ingo@105: /**
ingo@105:  * The default WINFO artifact.
ingo@105:  *
ingo@105:  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
ingo@105:  */
ingo@119: public class WINFOArtifact extends FLYSArtifact {
ingo@105: 
ingo@105:     /** The logger for this class */
ingo@105:     private static Logger logger = Logger.getLogger(WINFOArtifact.class);
ingo@105: 
ingo@105: 
ingo@121:     /** The name of the artifact.*/
ingo@121:     public static final String ARTIFACT_NAME = "winfo";
ingo@121: 
ingo@124:     /** XPath */
ingo@124:     public static final String XPATH_STATIC_UI ="/art:result/art:ui/art:static";
ingo@124: 
ingo@121: 
ingo@105:     /**
ingo@105:      * The default constructor.
ingo@105:      */
ingo@105:     public WINFOArtifact() {
ingo@105:     }
ingo@105: 
ingo@105: 
ingo@105:     /**
ingo@105:      * This method returns a description of this artifact.
ingo@105:      *
ingo@105:      * @param data Some data.
ingo@128:      * @param context The CallContext.
ingo@105:      *
ingo@105:      * @return the description of this artifact.
ingo@105:      */
ingo@105:     public Document describe(Document data, CallContext context) {
ingo@119:         logger.debug("Describe: the current state is: " + getCurrentStateId());
ingo@105: 
ingo@112:         FLYSContext flysContext = null;
ingo@112:         if (context instanceof FLYSContext) {
ingo@112:             flysContext = (FLYSContext) context;
ingo@112:         }
ingo@112:         else {
ingo@112:             flysContext = (FLYSContext) context.globalContext();
ingo@112:         }
ingo@110: 
ingo@112:         StateEngine stateEngine = (StateEngine) flysContext.get(
ingo@112:             FLYSContext.STATE_ENGINE_KEY);
ingo@112: 
ingo@112:         TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
ingo@112:             FLYSContext.TRANSITION_ENGINE_KEY);
ingo@112: 
ingo@112:         List<State> reachable = transitionEngine.getReachableStates(
ingo@355:             this, getCurrentState(context), stateEngine);
ingo@112: 
ingo@112:         Document description            = XMLUtils.newDocument();
ingo@110:         XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
ingo@110:             description,
ingo@110:             ArtifactNamespaceContext.NAMESPACE_URI,
ingo@110:             ArtifactNamespaceContext.NAMESPACE_PREFIX);
ingo@110: 
ingo@110:         Element root = ProtocolUtils.createRootNode(creator);
ingo@115:         description.appendChild(root);
ingo@115: 
ingo@119:         State current = getCurrentState(context);
ingo@119: 
ingo@110:         ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
ingo@119:         ProtocolUtils.appendState(creator, root, current);
ingo@112:         ProtocolUtils.appendReachableStates(creator, root, reachable);
ingo@110: 
ingo@303:         Element name = ProtocolUtils.createArtNode(
ingo@303:             creator, "name",
ingo@303:             new String[] { "value" },
ingo@303:             new String[] { getName() });
ingo@303: 
ingo@127:         Element ui = ProtocolUtils.createArtNode(
ingo@127:             creator, "ui", null, null);
ingo@110: 
ingo@127:         Element staticUI  = ProtocolUtils.createArtNode(
ingo@127:             creator, "static", null, null);
ingo@127: 
ingo@144:         Element outs = ProtocolUtils.createArtNode(
ingo@144:             creator, "outputmodes", null, null);
ingo@144:         appendOutputModes(description, outs, context, identifier());
ingo@144: 
ingo@134:         appendStaticUI(description, staticUI, context, identifier());
ingo@128: 
ingo@127:         Element dynamic = current.describe(
ingo@306:             this,
ingo@124:             description,
ingo@127:             root,
ingo@127:             context,
ingo@127:             identifier());
ingo@127: 
ingo@144:         if (dynamic != null) {
ingo@144:             ui.appendChild(dynamic);
ingo@144:         }
ingo@144: 
ingo@128:         ui.appendChild(staticUI);
ingo@144: 
ingo@303:         root.appendChild(name);
ingo@127:         root.appendChild(ui);
ingo@144:         root.appendChild(outs);
ingo@124: 
ingo@110:         return description;
ingo@105:     }
ingo@121: 
ingo@121: 
ingo@121:     /**
ingo@121:      * Returns the name of the concrete artifact.
ingo@121:      *
ingo@121:      * @return the name of the concrete artifact.
ingo@121:      */
ingo@121:     public String getName() {
ingo@121:         return ARTIFACT_NAME;
ingo@121:     }
ingo@124: 
ingo@124: 
ingo@144:     protected void appendOutputModes(
ingo@144:         Document    doc,
ingo@144:         Element     outs,
ingo@144:         CallContext context,
ingo@144:         String      uuid)
ingo@144:     {
ingo@144:         Vector<String> stateIds = getPreviousStateIds();
ingo@144: 
ingo@144:         XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
ingo@144:             doc,
ingo@144:             ArtifactNamespaceContext.NAMESPACE_URI,
ingo@144:             ArtifactNamespaceContext.NAMESPACE_PREFIX);
ingo@144: 
ingo@144:         FLYSContext flysContext = getFlysContext(context);
ingo@144:         StateEngine engine      = (StateEngine) flysContext.get(
ingo@144:             FLYSContext.STATE_ENGINE_KEY);
ingo@144: 
ingo@144:         for (String stateId: stateIds) {
ingo@144:             logger.debug("Append output modes for state: " + stateId);
ingo@144:             State state = engine.getState(stateId);
ingo@144: 
ingo@144:             List<Output> list = state.getOutputs();
ingo@144:             if (list == null || list.size() == 0) {
ingo@144:                 continue;
ingo@144:             }
ingo@144: 
ingo@144:             ProtocolUtils.appendOutputModes(creator, outs, list);
ingo@144:         }
ingo@144: 
ingo@355:         try {
ingo@355:             DefaultState cur = (DefaultState) getCurrentState(context);
ingo@355:             if (cur.validate(this, context)) {
ingo@355:                 List<Output> list = cur.getOutputs();
ingo@355:                 if (list != null && list.size() > 0) {
ingo@355:                     logger.debug(
ingo@355:                         "Append output modes for state: " + cur.getID());
ingo@355: 
ingo@355:                     ProtocolUtils.appendOutputModes(creator, outs, list);
ingo@355:                 }
ingo@355:             }
ingo@355:         }
ingo@355:         catch (IllegalArgumentException iae) {
ingo@355:             // state is not valid, so we do not append its outputs.
ingo@355:         }
ingo@144:     }
ingo@144: 
ingo@144: 
ingo@124:     /**
ingo@124:      * This method appends the static data - that has already been inserted by
ingo@124:      * the user - to the static node of the DESCRIBE document.
ingo@124:      *
ingo@134:      * @param doc The document.
ingo@134:      * @param ui The root node.
ingo@134:      * @param context The CallContext.
ingo@134:      * @param uuid The identifier of the artifact.
ingo@124:      */
ingo@128:     protected void appendStaticUI(
ingo@134:         Document    doc,
ingo@134:         Node        ui,
ingo@134:         CallContext context,
ingo@134:         String uuid)
ingo@128:     {
ingo@134:         Vector<String> stateIds = getPreviousStateIds();
ingo@124: 
ingo@134:         FLYSContext flysContext = getFlysContext(context);
ingo@134:         StateEngine engine      = (StateEngine) flysContext.get(
ingo@134:             FLYSContext.STATE_ENGINE_KEY);
ingo@124: 
ingo@134:         for (String stateId: stateIds) {
ingo@134:             logger.debug("Append static data for state: " + stateId);
ingo@134:             DefaultState state = (DefaultState) engine.getState(stateId);
ingo@302:             state = (DefaultState) fillState(state);
ingo@302: 
ingo@134:             ui.appendChild(state.describeStatic(doc, ui, context, uuid));
ingo@134:         }
ingo@362:     }
ingo@124: 
ingo@362: 
ingo@362:     //
ingo@362:     // METHODS FOR RETRIEVING COMPUTED DATA FOR DIFFERENT CHART TYPES
ingo@362:     //
ingo@362: 
ingo@362:     /**
ingo@362:      * Returns the data that is computed by a waterlevel computation.
ingo@362:      *
ingo@362:      * @return an array of data triples that consist of W, Q and Kms.
ingo@362:      */
ingo@362:     public WQKms[] getWaterlevelData()
ingo@362:     throws NullPointerException
ingo@362:     {
ingo@362:         logger.debug("WINFOArtifact.getWaterlevelData");
ingo@362: 
ingo@362:         River river = getRiver();
ingo@362:         if (river == null) {
ingo@362:             throw new NullPointerException("No river selected.");
ingo@362:         }
ingo@362: 
ingo@362:         double[] kms = getKms();
ingo@362:         if (kms == null) {
ingo@362:             throw new NullPointerException("No Kms selected.");
ingo@362:         }
ingo@362: 
ingo@447:         double[] qs   = getQs();
ingo@447:         double[] ws   = null;
ingo@447:         boolean  qSel = true;
ingo@447: 
ingo@362:         if (qs == null) {
ingo@377:             logger.debug("Determine Q values based on a set of W values.");
ingo@447:             qSel = false;
ingo@447:             ws   = getWs();
ingo@447:             qs   = getQsForWs(ws);
ingo@362:         }
ingo@362: 
sascha@443:         WstValueTable wst = WstValueTableFactory.getTable(river);
ingo@362:         if (wst == null) {
ingo@362:             throw new NullPointerException("No Wst found for selected river.");
ingo@362:         }
ingo@362: 
ingo@447:         WQKms[] results = computeWaterlevelData(kms, qs, wst);
ingo@447: 
ingo@362:         // TODO Introduce a caching mechanism here!
ingo@362: 
ingo@447:         setWaterlevelNames(results, qSel ? qs : ws, qSel ? "Q" : "W");
ingo@447: 
ingo@447:         return results;
ingo@447:     }
ingo@447: 
ingo@447: 
ingo@447:     /**
ingo@447:      * Sets the name for waterlevels where each WQKms in <i>r</i> represents a
ingo@447:      * column.
ingo@447:      *
ingo@447:      * @param r The waterlevel columns.
ingo@447:      * @param v The input values of the computations.
ingo@447:      * @param wq The WQ mode - can be one of "W" or "Q".
ingo@447:      */
ingo@447:     public static void setWaterlevelNames(WQKms[] r, double[] v, String wq) {
ingo@447:         for (int i = 0; i < v.length; i++) {
ingo@447:             r[i].setName(wq + "=" + Double.toString(v[i]));
ingo@447:         }
ingo@362:     }
ingo@362: 
ingo@362: 
ingo@362:     /**
ingo@362:      * Computes the data of a waterlevel computation based on the interpolation
ingo@362:      * in WstValueTable.
ingo@362:      *
ingo@362:      * @param kms The kilometer values.
ingo@362:      * @param qa The discharge values.
ingo@362:      * @param wst The WstValueTable used for the interpolation.
ingo@362:      *
ingo@362:      * @return an array of data triples that consist of W, Q and Kms.
ingo@362:      */
ingo@362:     public static WQKms[] computeWaterlevelData(
ingo@362:         double[]      kms,
ingo@362:         double[]      qs,
ingo@362:         WstValueTable wst)
ingo@362:     {
ingo@362:         logger.info("WINFOArtifact.computeWaterlevelData");
ingo@362: 
ingo@362:         WQKms[] wqkms = new WQKms[qs.length];
sascha@380: 
sascha@449:         ArrayList<WQKms> results = new ArrayList<WQKms>();
ingo@362: 
sascha@449:         for (int i = 0; i < qs.length; i++) {
sascha@449:             double [] oqs = new double[kms.length];
sascha@449:             double [] ows = new double[kms.length];
sascha@449:             int referenceIndex = 0; // TODO: Make depend on the flow direction
sascha@449:             WstValueTable.QPosition qPosition =
sascha@449:                 wst.interpolate(qs[i], referenceIndex, kms, ows, oqs);
sascha@449:             if (qPosition != null) {
sascha@449:                 results.add(new WQKms(kms, oqs, ows));
sascha@449:             }
sascha@449:             else {
sascha@449:                 logger.warn("interpolation failed for q = " + qs[i]);
ingo@362:             }
ingo@362:         }
ingo@362: 
sascha@449:         return results.toArray(new WQKms[results.size()]);
ingo@124:     }
ingo@385: 
ingo@385: 
ingo@385:     /**
ingo@385:      * Returns the data that is computed by a duration curve computation.
ingo@385:      *
ingo@385:      * @return the data computed by a duration curve computation.
ingo@385:      */
ingo@385:     public WQDay getDurationCurveData()
ingo@385:     throws NullPointerException
ingo@385:     {
ingo@385:         logger.debug("WINFOArtifact.getDurationCurveData");
ingo@385: 
ingo@385:         River r = getRiver();
ingo@385: 
ingo@385:         if (r == null) {
ingo@385:             throw new NullPointerException("Cannot determine river.");
ingo@385:         }
ingo@385: 
ingo@385:         Gauge g = getGauge();
ingo@385: 
ingo@385:         if (g == null) {
ingo@385:             throw new NullPointerException("Cannot determine gauge.");
ingo@385:         }
ingo@385: 
ingo@385:         double[] locations = getLocations();
ingo@385: 
ingo@385:         if (locations == null) {
ingo@385:             throw new NullPointerException("Cannot determine location.");
ingo@385:         }
ingo@385: 
sascha@443:         WstValueTable wst = WstValueTableFactory.getTable(r);
ingo@385:         if (wst == null) {
ingo@385:             throw new NullPointerException("No Wst found for selected river.");
ingo@385:         }
ingo@385: 
ingo@385:         // TODO Introduce a caching mechanism here!
ingo@385: 
ingo@385:         return computeDurationCurveData(g, wst, locations[0]);
ingo@385:     }
ingo@385: 
ingo@385: 
ingo@385:     /**
ingo@385:      * Computes the data used to create duration curves.
ingo@385:      *
ingo@385:      * @param gauge The selected gauge.
ingo@385:      * @param location The selected location.
ingo@385:      *
ingo@385:      * @return the computed data.
ingo@385:      */
ingo@385:     public static WQDay computeDurationCurveData(
ingo@385:         Gauge           gauge,
ingo@385:         WstValueTable   wst,
ingo@385:         double          location)
ingo@385:     {
ingo@385:         logger.info("WINFOArtifact.computeDurationCurveData");
ingo@385: 
ingo@385:         Object[] obj = MainValuesFactory.getDurationCurveData(gauge);
ingo@385: 
ingo@385:         int[]    days = (int[]) obj[0];
ingo@385:         double[] qs   = (double[]) obj[1];
ingo@385: 
ingo@385:         double[] interpolatedW = new double[qs.length];
ingo@385:         interpolatedW          = wst.interpolateW(location, qs, interpolatedW);
ingo@385: 
ingo@385:         WQDay wqday = new WQDay(qs.length);
ingo@385: 
ingo@385:         for (int i = 0; i < days.length; i++) {
ingo@385:             wqday.add(days[i], interpolatedW[i], qs[i]);
ingo@385:         }
ingo@385: 
ingo@385:         return wqday;
ingo@385:     }
ingo@393: 
ingo@393: 
ingo@393:     /**
ingo@393:      * Returns the data that is computed by a discharge curve computation.
ingo@393:      *
ingo@393:      * @return the data computed by a discharge curve computation.
ingo@393:      */
ingo@393:     public WQKms getComputedDischargeCurveData()
ingo@393:     throws NullPointerException
ingo@393:     {
ingo@393:         logger.debug("WINFOArtifact.getComputedDischargeCurveData");
ingo@393: 
ingo@393:         River r = getRiver();
ingo@393: 
ingo@393:         if (r == null) {
ingo@393:             throw new NullPointerException("Cannot determine river.");
ingo@393:         }
ingo@393: 
ingo@393:         double[] locations = getLocations();
ingo@393: 
ingo@393:         if (locations == null) {
ingo@393:             throw new NullPointerException("Cannot determine location.");
ingo@393:         }
ingo@393: 
sascha@443:         WstValueTable wst = WstValueTableFactory.getTable(r);
ingo@393:         if (wst == null) {
ingo@393:             throw new NullPointerException("No Wst found for selected river.");
ingo@393:         }
ingo@393: 
ingo@456:         WQKms wqkms = computeDischargeCurveData(wst, locations[0]);
ingo@456: 
ingo@393:         // TODO Introduce a caching mechanism here!
ingo@393: 
ingo@456:         setComputedDischargeCurveNames(wqkms, locations[0]);
ingo@456: 
ingo@456:         return wqkms;
ingo@456:     }
ingo@456: 
ingo@456: 
ingo@456:     /**
ingo@456:      * Sets the name of the computed discharge curve data.
ingo@456:      *
ingo@456:      * @param wqkms The computed WQKms object.
ingo@456:      * @param l The location used for the computation.
ingo@456:      */
ingo@456:     public static void setComputedDischargeCurveNames(WQKms wqkms, double l) {
ingo@456:         wqkms.setName(Double.toString(l));
ingo@393:     }
ingo@393: 
ingo@393: 
ingo@393:     /**
ingo@393:      * Computes the data used to create computed discharge curves.
ingo@393:      *
ingo@393:      * @param wst The WstValueTable that is used for the interpolation.
ingo@393:      * @param location The location where the computation should be based on.
ingo@393:      *
ingo@393:      * @return an object that contains tuples of W/Q values at the specified
ingo@393:      * location.
ingo@393:      */
ingo@393:     public static WQKms computeDischargeCurveData(
ingo@393:         WstValueTable wst,
ingo@393:         double location)
ingo@393:     {
ingo@393:         logger.info("WINFOArtifact.computeDischargeCurveData");
ingo@393: 
ingo@393:         double[][] wqs = wst.interpolateWQ(location);
ingo@393: 
ingo@393:         if (wqs == null) {
ingo@393:             logger.error("Cannot compute discharge curve data.");
ingo@393:             return null;
ingo@393:         }
ingo@393: 
ingo@393:         double[] ws = wqs[0];
ingo@393:         double[] qs = wqs[1];
ingo@393: 
ingo@393:         WQKms wqkms = new WQKms(ws.length);
ingo@393: 
ingo@393:         for (int i = 0; i < ws.length; i++) {
ingo@393:             wqkms.add(ws[i], qs[i], location);
ingo@393:         }
ingo@393: 
ingo@393:         return wqkms;
ingo@393:     }
ingo@402: 
ingo@402:     /**
ingo@402:      * Returns the data computed by the discharge longitudinal section
ingo@402:      * computation.
ingo@402:      *
ingo@402:      * @return an array of WQKms object - one object for each given Q value.
ingo@402:      */
sascha@451:     public WQKms [] getDischargeLongitudinalSectionData() {
sascha@451: 
ingo@402:         logger.debug("WINFOArtifact.getDischargeLongitudinalSectionData");
ingo@402: 
ingo@402:         River river = getRiver();
ingo@402:         if (river == null) {
ingo@402:             logger.error("No river selected.");
sascha@451:             return new WQKms[0];
ingo@402:         }
ingo@402: 
sascha@443:         WstValueTable wst = WstValueTableFactory.getTable(river);
ingo@402:         if (wst == null) {
sascha@451:             logger.error("No wst found for selected river.");
sascha@451:             return new WQKms[0];
ingo@402:         }
ingo@402: 
sascha@451:         double [][] segments = getSplittedDistance();
ingo@402: 
sascha@451:         if (segments.length < 1) {
sascha@451:             logger.warn("no segments given");
sascha@451:             return new WQKms[0];
sascha@451:         }
ingo@450: 
sascha@451:         if (segments.length == 1) {
sascha@451:             // fall back to normal "Wasserstand/Wasserspiegellage" calculation
sascha@451:             double [] qs = toQs(segments[0]);
sascha@451:             if (qs == null) {
sascha@451:                 logger.warn("no qs given");
sascha@451:                 return new WQKms[0];
sascha@451:             }
sascha@451:             if (qs.length == 1) {
sascha@451:                 double [] kms = getKms(segments[0]);
sascha@451:                 return computeWaterlevelData(kms, qs, wst);
sascha@451:             }
sascha@451:         }
ingo@402: 
sascha@451:         // more than one segment
sascha@451: 
sascha@451:         double [] boundKms;
sascha@451: 
sascha@451:         if (segments.length == 2) {
sascha@451:             boundKms = new double [] { segments[0][0], segments[1][1] };
sascha@451:         }
sascha@451:         else {
sascha@451:             TDoubleArrayList bounds = new TDoubleArrayList();
sascha@451: 
sascha@451:             bounds.add(segments[0][0]);
sascha@451: 
sascha@451:             for (int i = 1; i < segments.length-1; ++i) {
sascha@451:                 double [] segment = segments[i];
sascha@451: 
sascha@451:                 Gauge gauge = river.determineGauge(segment[0], segment[1]);
sascha@451: 
sascha@451:                 if (gauge == null) {
sascha@451:                     logger.warn("no gauge found between " + 
sascha@451:                         segment[0] + " and " + segment[1]);
sascha@451:                     bounds.add(0.5*(segment[0] + segment[1]));
sascha@451:                 }
sascha@451:                 else {
sascha@451:                     bounds.add(gauge.getStation().doubleValue());
sascha@451:                 }
sascha@451:             }
sascha@451: 
sascha@451:             bounds.add(segments[segments.length-1][1]);
sascha@451:             boundKms = bounds.toNativeArray();
sascha@451:         }
sascha@451: 
sascha@451:         if (logger.isDebugEnabled()) {
sascha@451:             logger.debug("bound kms: " + joinDoubles(boundKms));
sascha@451:         }
sascha@451: 
sascha@451:         double [][] iqs = null;
sascha@451: 
sascha@451:         for (int i = 0; i < segments.length; ++i) {
sascha@451:             double [] iqsi = toQs(segments[i]);
sascha@451:             if (iqsi == null) {
sascha@451:                 logger.warn("iqsi == null");
sascha@451:                 return new WQKms[0];
sascha@451:             }
sascha@451: 
sascha@451:             if (iqs == null) {
sascha@451:                 iqs = new double[iqsi.length][boundKms.length];
sascha@451:             }
sascha@451:             else if (iqs.length != iqsi.length) {
sascha@451:                 logger.warn("iqsi.logger != iqs.length: "
sascha@451:                     + iqsi.length + " " + iqsi.length);
sascha@451:                 return new WQKms[0];
sascha@451:             }
sascha@451: 
sascha@451:             if (logger.isDebugEnabled()) {
sascha@451:                 logger.debug("segments qs[ " + i + "]: " + joinDoubles(iqsi));
sascha@451:             }
sascha@451: 
sascha@451:             for (int j = 0; j < iqs.length; ++j) {
sascha@451:                 iqs[j][i] = iqsi[j];
sascha@451:             }
sascha@451:         }
sascha@451: 
sascha@451:         if (logger.isDebugEnabled()) {
sascha@451:             for (int i = 0; i < iqs.length; ++i) {
sascha@451:                 logger.debug("iqs[" + i + "]: " + joinDoubles(iqs[i]));
sascha@451:             }
sascha@451:         }
sascha@451: 
sascha@451:         double [] boundWs = new double[boundKms.length];
sascha@451:         double [] boundQs = new double[boundKms.length];
sascha@451: 
sascha@452:         double [] okms = getKms(new double [] {
sascha@452:             boundKms[0], boundKms[boundKms.length-1] });
sascha@451: 
sascha@451:         ArrayList<WQKms> results = new ArrayList<WQKms>();
sascha@451: 
sascha@451:         for (int i = 0; i < iqs.length; ++i) {
sascha@451:             double [] iqsi = iqs[i];
sascha@451: 
sascha@451:             QPosition qPosition = wst.interpolate(
sascha@451:                 iqsi[0], 0, boundKms, boundWs, boundQs);
sascha@451: 
sascha@451:             if (qPosition == null) {
sascha@451:                 logger.warn("interpolation failed for " + iqsi[i]);
ingo@402:                 continue;
ingo@402:             }
ingo@402: 
sascha@451:             LinearRemap remap = new LinearRemap();
ingo@450: 
sascha@451:             for (int j = 1; j < boundKms.length; ++j) {
sascha@451:                 remap.add(
sascha@451:                     boundKms[j-1], boundKms[j],
sascha@451:                     boundQs[j-1],  iqsi[j-1],
sascha@451:                     boundQs[j],    iqsi[j]);
ingo@402:             }
ingo@402: 
sascha@451:             double [] oqs = new double[okms.length];
sascha@451:             double [] ows = new double[okms.length];
ingo@450: 
sascha@451:             wst.interpolate(okms, ows, oqs, qPosition, remap);
sascha@451: 
sascha@451:             BackJumpCorrector bjc = new BackJumpCorrector();
sascha@451:             if (bjc.doCorrection(okms, ows)) {
sascha@451:                 logger.debug("Discharge longitudinal section has backjumps.");
sascha@451:                 results.add(new WQCKms(okms, oqs, ows, bjc.getCorrected()));
sascha@451:             }
sascha@451:             else {
sascha@451:                 results.add(new WQKms(okms, oqs, ows));
sascha@451:             }
ingo@402:         }
ingo@402: 
sascha@455:         WQKms [] wqkms = results.toArray(new WQKms[results.size()]);
sascha@455: 
sascha@455:         setDischargeLongitudinalSectionNames(wqkms, iqs, isQ() ? "Q" : "W");
sascha@455: 
sascha@455:         return wqkms;
sascha@451:     }
ingo@402: 
sascha@451:     protected static String joinDoubles(double [] x) {
sascha@451:         if (x == null) {
sascha@452:             return "";
sascha@451:         }
sascha@451:         StringBuilder sb = new StringBuilder();
sascha@451:         for (int i = 0; i < x.length; ++i) {
sascha@451:             if (i > 0) sb.append(", ");
sascha@451:             sb.append(x[i]);
sascha@451:         }
sascha@451:         return sb.toString();
sascha@451:     }
sascha@451: 
sascha@451:     protected double [] toQs(double [] range) {
sascha@451:         double [] qs = getQs(range);
sascha@451:         if (qs == null) {
sascha@451:             logger.debug("Determine Q values based on a set of W values.");
sascha@451:             double [] ws = getWs(range);
sascha@451:             qs = getQsForWs(ws);
sascha@451:         }
sascha@451:         return qs;
ingo@402:     }
ingo@402: 
ingo@402: 
ingo@402:     /**
ingo@450:      * Sets the name for discharge longitudinal section curves where each WQKms
ingo@450:      * in <i>r</i> represents a column.
ingo@450:      */
ingo@450:     public static void setDischargeLongitudinalSectionNames(
sascha@455:         WQKms  []   wqkms,
sascha@455:         double [][] iqs,
sascha@455:         String      wq
sascha@455:     ) {
ingo@450:         logger.debug("WINFOArtifact.setDischargeLongitudinalSectionNames");
ingo@450: 
sascha@455:         // TODO: I18N
ingo@450: 
sascha@455:         for (int j = 0; j < wqkms.length; ++j) {
sascha@455:             StringBuilder sb = new StringBuilder(wq)
sascha@455:                 .append(" benutzerdefiniert (");
ingo@450: 
sascha@455:             double [] iqsi = iqs[j];
sascha@455:             for (int i = 0; i < iqsi.length; i++) {
sascha@455:                 if (i > 0) {
sascha@455:                     sb.append("; ");
sascha@455:                 }
sascha@455:                 sb.append(iqsi[i]);
sascha@455:             }
sascha@455:             sb.append(")");
ingo@450: 
sascha@455:             wqkms[j].setName(sb.toString());
sascha@455:         }
ingo@450:     }
ingo@105: }
ingo@105: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :