ingo@119: package de.intevation.flys.artifacts; ingo@119: ingo@121: import java.util.HashMap; ingo@119: import java.util.List; ingo@121: import java.util.Map; ingo@122: import java.util.Set; ingo@122: import java.util.Vector; ingo@121: ingo@121: import javax.xml.xpath.XPathConstants; ingo@119: ingo@379: import gnu.trove.TDoubleArrayList; ingo@379: ingo@119: import org.apache.log4j.Logger; ingo@119: ingo@119: import org.w3c.dom.Document; ingo@121: import org.w3c.dom.Element; ingo@121: import org.w3c.dom.Node; ingo@121: import org.w3c.dom.NodeList; ingo@119: ingo@119: import de.intevation.artifacts.ArtifactFactory; ingo@119: import de.intevation.artifacts.CallContext; ingo@119: ingo@119: import de.intevation.artifacts.common.ArtifactNamespaceContext; ingo@119: import de.intevation.artifacts.common.utils.XMLUtils; ingo@119: ingo@119: import de.intevation.artifactdatabase.DefaultArtifact; ingo@121: import de.intevation.artifactdatabase.data.DefaultStateData; ingo@119: import de.intevation.artifactdatabase.data.StateData; ingo@119: import de.intevation.artifactdatabase.state.State; ingo@119: import de.intevation.artifactdatabase.state.StateEngine; ingo@122: import de.intevation.artifactdatabase.transition.TransitionEngine; ingo@119: ingo@317: import de.intevation.flys.model.Gauge; ingo@402: import de.intevation.flys.model.Range; ingo@317: import de.intevation.flys.model.River; ingo@317: ingo@119: import de.intevation.flys.artifacts.context.FLYSContext; ingo@377: import de.intevation.flys.artifacts.model.DischargeTables; ingo@317: import de.intevation.flys.artifacts.model.RiverFactory; ingo@322: import de.intevation.flys.artifacts.states.DefaultState; ingo@119: ingo@119: ingo@119: /** ingo@119: * The defaul FLYS artifact. ingo@119: * ingo@119: * @author Ingo Weinzierl ingo@119: */ ingo@119: public abstract class FLYSArtifact extends DefaultArtifact { ingo@119: ingo@119: /** The logger that is used in this artifact.*/ ingo@119: private static Logger logger = Logger.getLogger(FLYSArtifact.class); ingo@119: ingo@119: ingo@121: /** The XPath that points to the input data elements of the FEED document.*/ ingo@121: public static final String XPATH_FEED_INPUT = ingo@121: "/art:action/art:data/art:input"; ingo@119: ingo@122: /** The XPath that points to the name of the target state of ADVANCE.*/ ingo@122: public static final String XPATH_ADVANCE_TARGET = ingo@122: "/art:action/art:target/@art:name"; ingo@122: ingo@122: /** The constant string that shows that an operation was successful.*/ ingo@122: public static final String OPERATION_SUCCESSFUL = "SUCCESS"; ingo@122: ingo@122: /** The constant string that shows that an operation failed.*/ ingo@122: public static final String OPERATION_FAILED = "FAILURE"; ingo@122: ingo@362: /** The default number of steps between the start end end of a selected Q ingo@362: * range.*/ ingo@362: public static final int DEFAULT_Q_STEPS = 30; ingo@362: ingo@398: /** The default step width between the start end end kilometer.*/ ingo@398: public static final double DEFAULT_KM_STEPS = 0.1; ingo@398: ingo@119: ingo@119: /** The identifier of the current state. */ ingo@119: protected String currentStateId; ingo@119: ingo@122: /** The identifiers of previous states on a stack.*/ ingo@122: protected Vector previousStateIds; ingo@122: ingo@119: /** The name of the artifact.*/ ingo@119: protected String name; ingo@119: ingo@121: /** The data that have been inserted into this artifact.*/ ingo@121: protected Map data; ingo@121: ingo@121: ingo@121: /** ingo@121: * The default constructor that creates an empty FLYSArtifact. ingo@121: */ ingo@121: public FLYSArtifact() { ingo@122: data = new HashMap(); ingo@122: previousStateIds = new Vector(); ingo@121: } 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 abstract String getName(); ingo@121: ingo@121: ingo@121: /** ingo@121: * Returns the FLYSContext from context object. ingo@121: * ingo@121: * @param context The CallContext or the FLYSContext. ingo@121: * ingo@121: * @return the FLYSContext. ingo@121: */ ingo@121: protected FLYSContext getFlysContext(Object context) { ingo@121: return context instanceof FLYSContext ingo@121: ? (FLYSContext) context ingo@121: : (FLYSContext) ((CallContext) context).globalContext(); ingo@121: } ingo@121: ingo@119: ingo@119: /** ingo@119: * Initialize the artifact and insert new data if data contains ingo@119: * information necessary for this artifact. ingo@119: * ingo@119: * @param identifier The UUID. ingo@119: * @param factory The factory that is used to create this artifact. ingo@119: * @param context The CallContext. ingo@119: * @param data Some optional data. ingo@119: */ ingo@119: @Override ingo@119: public void setup( ingo@119: String identifier, ingo@119: ArtifactFactory factory, ingo@119: Object context, ingo@119: Document data) ingo@119: { ingo@119: logger.debug("Setup this artifact with the uuid: " + identifier); ingo@119: ingo@119: super.setup(identifier, factory, context, data); ingo@119: ingo@119: FLYSContext flysContext = (FLYSContext) context; ingo@119: StateEngine engine = (StateEngine) flysContext.get( ingo@119: FLYSContext.STATE_ENGINE_KEY); ingo@119: ingo@121: String name = getName(); ingo@121: ingo@121: logger.debug("Set initial state for artifact '" + name + "'"); ingo@119: List states = engine.getStates(name); ingo@119: ingo@119: setCurrentState(states.get(0)); ingo@119: } ingo@119: ingo@119: ingo@119: /** ingo@119: * Insert new data included in input into the current state. ingo@119: * ingo@119: * @param target XML document that contains new data. ingo@119: * @param context The CallContext. ingo@119: * ingo@119: * @return a document that contains a SUCCESS or FAILURE message. ingo@119: */ ingo@119: @Override ingo@119: public Document feed(Document target, CallContext context) { ingo@123: logger.info("FLYSArtifact.feed()"); ingo@123: ingo@121: Document doc = XMLUtils.newDocument(); ingo@119: ingo@121: XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator( ingo@121: doc, ingo@121: ArtifactNamespaceContext.NAMESPACE_URI, ingo@121: ArtifactNamespaceContext.NAMESPACE_PREFIX); ingo@119: ingo@121: Element result = creator.create("result"); ingo@121: doc.appendChild(result); ingo@119: ingo@121: try { ingo@322: saveData(target, XPATH_FEED_INPUT, context); ingo@123: return describe(target, context); ingo@121: } ingo@121: catch (IllegalArgumentException iae) { ingo@122: creator.addAttr(result, "type", OPERATION_FAILED, true); ingo@121: ingo@121: result.setTextContent(iae.getMessage()); ingo@121: } ingo@121: ingo@121: return doc; ingo@119: } ingo@119: ingo@119: ingo@119: /** ingo@122: * This method handles request for changing the current state of an ingo@122: * artifact. It is possible to step forward or backward. ingo@122: * ingo@122: * @param target The incoming ADVANCE document. ingo@122: * @param context The CallContext. ingo@122: * ingo@122: * @return a document that contains a SUCCESS or FAILURE message. ingo@122: */ ingo@122: public Document advance(Document target, CallContext context) { ingo@122: Document doc = XMLUtils.newDocument(); ingo@122: ingo@122: XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( ingo@122: doc, ingo@122: ArtifactNamespaceContext.NAMESPACE_URI, ingo@122: ArtifactNamespaceContext.NAMESPACE_PREFIX); ingo@122: ingo@122: Element result = ec.create("result"); ingo@122: ingo@122: String targetState = XMLUtils.xpathString( ingo@122: target, XPATH_ADVANCE_TARGET, ArtifactNamespaceContext.INSTANCE); ingo@122: ingo@140: logger.info("FLYSArtifact.advance() to '" + targetState + "'"); ingo@140: ingo@122: if (isStateReachable(targetState, context)) { ingo@140: logger.info("Advance: Step forward"); ingo@122: ingo@122: Vector prev = getPreviousStateIds(); ingo@122: prev.add(getCurrentStateId()); ingo@122: ingo@122: setCurrentStateId(targetState); ingo@122: ingo@123: return describe(target, context); ingo@122: } ingo@140: else if (isPreviousState(targetState, context)) { ingo@140: logger.info("Advance: Step back to"); ingo@122: ingo@140: Vector prevs = getPreviousStateIds(); ingo@140: int targetIdx = prevs.indexOf(targetState); ingo@140: int start = prevs.size() - 1; ingo@140: ingo@140: for (int i = start; i >= targetIdx; i--) { ingo@140: String prev = prevs.get(i); ingo@140: logger.debug("Remove state id '" + prev + "'"); ingo@140: prevs.remove(prev); ingo@140: } ingo@140: ingo@140: setCurrentStateId(targetState); ingo@140: ingo@140: return describe(target, context); ingo@140: } ingo@122: ingo@122: logger.warn("Advance: Cannot advance to '" + targetState + "'"); ingo@123: ec.addAttr(result, "type", OPERATION_FAILED, true); ingo@122: ingo@123: doc.appendChild(result); ingo@122: ingo@122: return doc; ingo@122: } ingo@122: ingo@122: ingo@122: /** ingo@119: * Returns the identifier of the current state. ingo@119: * ingo@119: * @return the identifier of the current state. ingo@119: */ ingo@119: protected String getCurrentStateId() { ingo@119: return currentStateId; ingo@119: } ingo@119: ingo@119: ingo@119: /** ingo@119: * Sets the identifier of the current state. ingo@119: * ingo@119: * @param id the identifier of a state. ingo@119: */ ingo@119: protected void setCurrentStateId(String id) { ingo@119: currentStateId = id; ingo@119: } ingo@119: ingo@119: ingo@119: /** ingo@119: * Set the current state of this artifact. NOTEWe don't store the ingo@119: * State object itself - which is not necessary - but its identifier. So ingo@119: * this method will just call the setCurrentStateId() method with the ingo@119: * identifier of state. ingo@119: * ingo@119: * @param state The new current state. ingo@119: */ ingo@119: protected void setCurrentState(State state) { ingo@119: setCurrentStateId(state.getID()); ingo@119: } ingo@119: ingo@119: ingo@119: /** ingo@119: * Returns the current state of the artifact. ingo@119: * ingo@119: * @return the current State of the artifact. ingo@119: */ ingo@119: protected State getCurrentState(Object context) { ingo@121: FLYSContext flysContext = getFlysContext(context); ingo@119: StateEngine engine = (StateEngine) flysContext.get( ingo@119: FLYSContext.STATE_ENGINE_KEY); ingo@119: ingo@119: return engine.getState(getCurrentStateId()); ingo@119: } ingo@119: ingo@119: ingo@119: /** ingo@122: * Returns the vector of previous state identifiers. ingo@122: * ingo@122: * @return the vector of previous state identifiers. ingo@122: */ ingo@122: protected Vector getPreviousStateIds() { ingo@122: return previousStateIds; ingo@122: } ingo@122: ingo@122: ingo@122: /** ingo@121: * Adds a new StateData item to the data pool of this artifact. ingo@121: * ingo@121: * @param name the name of the data object. ingo@121: * @param data the data object itself. ingo@121: */ ingo@121: protected void addData(String name, StateData data) { ingo@121: this.data.put(name, data); ingo@121: } ingo@121: ingo@121: ingo@121: /** ingo@122: * This method returns a specific StateData object that is stored in the ingo@122: * data pool of this artifact. ingo@122: * ingo@122: * @param name The name of the data object. ingo@122: * ingo@122: * @return the StateData object if existing, otherwise null. ingo@122: */ ingo@298: public StateData getData(String name) { ingo@122: return data.get(name); ingo@122: } ingo@122: ingo@122: ingo@122: /** ingo@121: * This method stores the data that is contained in the FEED document. ingo@119: * ingo@119: * @param feed The FEED document. ingo@119: * @param xpath The XPath that points to the data nodes. ingo@119: */ ingo@322: public void saveData(Document feed, String xpath, CallContext context) ingo@121: throws IllegalArgumentException ingo@121: { ingo@121: if (feed == null || xpath == null || xpath.length() == 0) { ingo@325: throw new IllegalArgumentException("error_feed_no_data"); ingo@121: } ingo@119: ingo@121: NodeList nodes = (NodeList) XMLUtils.xpath( ingo@121: feed, ingo@121: xpath, ingo@121: XPathConstants.NODESET, ingo@121: ArtifactNamespaceContext.INSTANCE); ingo@121: ingo@121: if (nodes == null || nodes.getLength() == 0) { ingo@325: throw new IllegalArgumentException("error_feed_no_data"); ingo@121: } ingo@121: ingo@121: int count = nodes.getLength(); ingo@121: logger.debug("Try to save " + count + " data items."); ingo@121: ingo@121: for (int i = 0; i < count; i++) { ingo@121: Node node = nodes.item(i); ingo@121: ingo@121: String name = XMLUtils.xpathString( ingo@121: node, "@art:name", ArtifactNamespaceContext.INSTANCE); ingo@121: String value = XMLUtils.xpathString( ingo@121: node, "@art:value", ArtifactNamespaceContext.INSTANCE); ingo@121: ingo@121: if (name != null && value != null) { ingo@121: logger.debug("Save data item for '" + name + "' : " + value); ingo@121: ingo@121: addData(name, new DefaultStateData(name, null, null, value)); ingo@121: } ingo@121: } ingo@322: ingo@322: State current = getCurrentState(context); ingo@322: DefaultState toValidate = (DefaultState) fillState(current); ingo@322: ingo@322: toValidate.validate(this, context); ingo@119: } ingo@122: ingo@122: ingo@122: /** ingo@122: * This method fills a state object with the data that have been inserted to ingo@122: * this artifact. This is necessary to use the isStateReachable() method, ingo@122: * because the Transitions need to know about the inserted data. ingo@122: * ingo@122: * @param state The state that needs to be filled with data. ingo@122: * ingo@122: * @return the filled state. ingo@122: */ ingo@122: protected State fillState(State state) { ingo@122: Map stateData = state.getData(); ingo@140: ingo@140: if (stateData == null) { ingo@140: return state; ingo@140: } ingo@140: ingo@122: Set keys = stateData.keySet(); ingo@122: ingo@122: for (String key: keys) { ingo@122: StateData tmp = getData(key); ingo@122: ingo@122: if (tmp != null) { ingo@122: StateData data = stateData.get(key); ingo@122: data.setValue(tmp.getValue()); ingo@122: } ingo@122: } ingo@122: ingo@122: return state; ingo@122: } ingo@122: ingo@122: ingo@122: /** ingo@122: * Determines if the state with the identifier stateId is reachable ingo@122: * from the current state. The determination itself takes place in the ingo@122: * TransitionEngine. ingo@122: * ingo@122: * @param stateId The identifier of a state. ingo@122: * @param context The context object. ingo@122: * ingo@122: * @return true, if the state specified by stateId is reacahble, ingo@122: * otherwise false. ingo@122: */ ingo@122: protected boolean isStateReachable(String stateId, Object context) { ingo@122: logger.debug("Determine if the state '" + stateId + "' is reachable."); ingo@122: ingo@122: FLYSContext flysContext = getFlysContext(context); ingo@122: ingo@122: State currentState = fillState(getCurrentState(context)); ingo@122: StateEngine sEngine = (StateEngine) flysContext.get( ingo@122: FLYSContext.STATE_ENGINE_KEY); ingo@122: ingo@122: TransitionEngine tEngine = (TransitionEngine) flysContext.get( ingo@122: FLYSContext.TRANSITION_ENGINE_KEY); ingo@122: ingo@355: return tEngine.isStateReachable(this, stateId, currentState, sEngine); ingo@122: } ingo@140: ingo@140: ingo@140: /** ingo@140: * Determines if the state with the identifier stateId is a previous ingo@140: * state of the current state. ingo@140: * ingo@140: * @param stateId The target state identifier. ingo@140: * @param context The context object. ingo@140: */ ingo@140: protected boolean isPreviousState(String stateId, Object context) { ingo@140: logger.debug("Determine if the state '" + stateId + "' is old."); ingo@140: ingo@140: Vector prevs = getPreviousStateIds(); ingo@140: if (prevs.contains(stateId)) { ingo@140: return true; ingo@140: } ingo@140: ingo@140: return false; ingo@140: } ingo@317: ingo@317: ingo@317: /** ingo@317: * Returns the selected River object based on the 'river' data that might ingo@317: * have been inserted by the user. ingo@317: * ingo@317: * @return the selected River or null if no river has been chosen yet. ingo@317: */ ingo@317: public River getRiver() { ingo@317: StateData dRiver = getData("river"); ingo@317: ingo@317: return dRiver != null ingo@317: ? RiverFactory.getRiver((String) dRiver.getValue()) ingo@317: : null; ingo@317: } ingo@317: ingo@317: ingo@317: /** ingo@317: * Returns the selected distance of points. ingo@317: * ingo@317: * @return the selected distance or points. ingo@317: */ ingo@317: public double[] getDistance() { ingo@383: StateData dFrom = getData("ld_from"); ingo@383: StateData dTo = getData("ld_to"); ingo@383: StateData dLocations = getData("ld_locations"); ingo@317: ingo@383: if (dFrom != null && dTo != null) { ingo@383: return getDistanceByRange(dFrom, dTo); ingo@383: } ingo@383: else if (dLocations != null) { ingo@383: double[] locations = getLocations(); ingo@383: return new double[] { locations[0], locations[locations.length-1] }; ingo@383: } ingo@383: ingo@383: logger.warn("No data found for distance determination!"); ingo@383: ingo@383: return null; ingo@383: } ingo@383: ingo@383: ingo@383: /** ingo@402: * This method returns the given distance ingo@402: * ingo@402: * @return an array with lower and upper kilometer range for each ingo@402: * intersected gauge. ingo@402: */ ingo@402: public double[][] getSplittedDistance() { ingo@402: double[] dist = getDistance(); ingo@402: List gauges = getGauges(); ingo@402: ingo@402: int num = gauges != null ? gauges.size() : 0; ingo@402: ingo@402: double[][] res = new double[num][2]; ingo@402: ingo@402: for (int i = 0; i < num; i++) { ingo@402: Range range = gauges.get(i).getRange(); ingo@402: ingo@402: double lower = range.getA().doubleValue(); ingo@402: double upper = range.getB().doubleValue(); ingo@402: ingo@402: res[i][0] = dist[0] < lower ? lower : dist[0]; ingo@402: res[i][1] = dist[1] > upper ? upper : dist[1]; ingo@402: } ingo@402: ingo@402: return res; ingo@402: } ingo@402: ingo@402: ingo@402: /** ingo@383: * Returns the selected locations based on a given array of locations. ingo@383: * ingo@383: * @param locations The StateData that contains the locations. ingo@383: * ingo@383: * @return the selected locations. ingo@383: */ ingo@383: public double[] getLocations() { ingo@383: StateData dLocations = getData("ld_locations"); ingo@383: String locationStr = dLocations != null ingo@383: ? (String) dLocations.getValue() ingo@383: : ""; ingo@383: ingo@383: if (locationStr == null || locationStr.length() == 0) { ingo@383: logger.warn("No valid location string found!"); ingo@383: return null; ingo@383: } ingo@383: ingo@383: String[] tmp = locationStr.split(" "); ingo@383: TDoubleArrayList locations = new TDoubleArrayList(); ingo@383: ingo@383: for (String l: tmp) { ingo@383: try { ingo@383: locations.add(Double.parseDouble(l)); ingo@383: } ingo@383: catch (NumberFormatException nfe) { ingo@383: logger.warn(nfe, nfe); ingo@383: } ingo@383: } ingo@383: ingo@383: locations.sort(); ingo@383: ingo@383: return locations.toNativeArray(); ingo@383: } ingo@383: ingo@383: ingo@383: /** ingo@383: * Returns the selected distance based on a given range (from, to). ingo@383: * ingo@383: * @param dFrom The StateData that contains the lower value. ingo@383: * @param dTo The StateData that contains the upper value. ingo@383: * ingo@383: * @return the selected distance. ingo@383: */ ingo@383: protected double[] getDistanceByRange(StateData dFrom, StateData dTo) { ingo@317: double from = Double.parseDouble((String) dFrom.getValue()); ingo@317: double to = Double.parseDouble((String) dTo.getValue()); ingo@317: ingo@317: return new double[] { from, to }; ingo@317: } ingo@317: ingo@317: ingo@317: /** ingo@362: * Returns the selected Kms. ingo@362: * ingo@402: * @param distance An 2dim array with [lower, upper] values. ingo@402: * ingo@362: * @return the selected Kms. ingo@362: */ ingo@402: public double[] getKms(double[] distance) { ingo@362: StateData dStep = getData("ld_step"); ingo@362: ingo@362: if (dStep == null) { ingo@362: logger.warn("No step width given. Cannot compute Kms."); ingo@362: return null; ingo@362: } ingo@362: ingo@402: double step = Double.parseDouble((String) dStep.getValue()); ingo@362: ingo@362: // transform step from 'm' into 'km' ingo@362: step = step / 1000; ingo@362: ingo@398: if (step == 0d) { ingo@398: step = DEFAULT_KM_STEPS; ingo@398: } ingo@398: ingo@362: return getExplodedValues(distance[0], distance[1], step); ingo@362: } ingo@362: ingo@362: ingo@362: /** ingo@402: * Returns the selected Kms. ingo@402: * ingo@402: * @return the selected kms. ingo@402: */ ingo@402: public double[] getKms() { ingo@402: double[] distance = getDistance(); ingo@402: return getKms(distance); ingo@402: } ingo@402: ingo@402: ingo@402: /** ingo@317: * Returns the gauge based on the current distance and river. ingo@317: * ingo@317: * @return the gauge. ingo@317: */ ingo@317: public Gauge getGauge() { ingo@317: River river = getRiver(); ingo@317: double[] dist = getDistance(); ingo@317: ingo@317: if (logger.isDebugEnabled()) { ingo@317: logger.debug("Determine gauge for:"); ingo@317: logger.debug("... river: " + river.getName()); ingo@317: logger.debug("... distance: " + dist[0] + " - " + dist[1]); ingo@317: } ingo@317: ingo@320: Gauge gauge = river.determineGauge(dist[0], dist[1]); ingo@317: ingo@320: String name = gauge != null ? gauge.getName() : "'n/a"; ingo@320: logger.debug("Found gauge: " + name); ingo@320: ingo@320: return gauge; ingo@317: } ingo@362: ingo@362: ingo@362: /** ingo@399: * Returns the gauges that match the selected kilometer range. ingo@399: * ingo@399: * @return the gauges based on the selected kilometer range. ingo@399: */ ingo@399: public List getGauges() { ingo@399: River river = getRiver(); ingo@399: double[] dist = getDistance(); ingo@399: ingo@399: return river.determineGauges(dist[0], dist[1]); ingo@399: } ingo@399: ingo@399: ingo@399: /** ingo@362: * This method returns the Q values. ingo@362: * ingo@362: * @return the selected Q values or null, if no Q values are selected. ingo@362: */ ingo@362: public double[] getQs() { ingo@379: StateData dMode = getData("wq_mode"); ingo@379: StateData dSingle = getData("wq_single"); ingo@377: ingo@377: String mode = dMode != null ? (String) dMode.getValue() : ""; ingo@377: ingo@377: if (mode.equals("Q")) { ingo@379: if (dSingle != null) { ingo@379: return getSingleWQValues(); ingo@379: } ingo@379: else { ingo@379: return getWQTriple(); ingo@379: } ingo@377: } ingo@377: else { ingo@377: logger.warn("You try to get Qs, but W has been inserted."); ingo@377: return null; ingo@377: } ingo@377: } ingo@377: ingo@377: ingo@377: /** ingo@402: * Returns the Q values based on a specified kilometer range. ingo@402: * ingo@402: * @param range A 2dim array with lower and upper kilometer range. ingo@402: * ingo@402: * @return an array of Q values. ingo@402: */ ingo@402: public double[] getQs(double[] range) { ingo@402: StateData dMode = getData("wq_mode"); ingo@402: StateData dValues = getData("wq_values"); ingo@402: ingo@402: String mode = dMode != null ? (String) dMode.getValue() : ""; ingo@402: ingo@402: if (mode.equals("Q")) { ingo@402: return getWQForDist(range); ingo@402: } ingo@402: ingo@402: logger.warn("You try to get Qs, but Ws has been inserted."); ingo@402: return null; ingo@402: } ingo@402: ingo@402: ingo@402: /** ingo@402: * Returns the W values based on a specified kilometer range. ingo@402: * ingo@402: * @param range A 2dim array with lower and upper kilometer range. ingo@402: * ingo@402: * @return an array of W values. ingo@402: */ ingo@402: public double[] getWs(double[] range) { ingo@402: StateData dMode = getData("wq_mode"); ingo@402: StateData dValues = getData("wq_values"); ingo@402: ingo@402: String mode = dMode != null ? (String) dMode.getValue() : ""; ingo@402: ingo@402: if (mode.equals("W")) { ingo@402: return getWQForDist(range); ingo@402: } ingo@402: ingo@402: logger.warn("You try to get Ws, but Qs has been inserted."); ingo@402: return null; ingo@402: } ingo@402: ingo@402: ingo@402: /** ingo@377: * This method returns the W values. ingo@377: * ingo@377: * @return the selected W values or null, if no W values are selected. ingo@377: */ ingo@377: public double[] getWs() { ingo@379: StateData dMode = getData("wq_mode"); ingo@379: StateData dSingle = getData("wq_single"); ingo@377: ingo@377: String mode = dMode != null ? (String) dMode.getValue() : ""; ingo@377: ingo@377: if (mode.equals("W")) { ingo@379: if (dSingle != null) { ingo@379: return getSingleWQValues(); ingo@379: } ingo@379: else { ingo@379: return getWQTriple(); ingo@379: } ingo@377: } ingo@377: else { ingo@377: logger.warn("You try to get Qs, but W has been inserted."); ingo@377: return null; ingo@377: } ingo@377: } ingo@377: ingo@377: ingo@377: /** ingo@377: * Returns the Qs for a number of Ws. This method makes use of ingo@377: * DischargeTables.getQForW(). ingo@377: * ingo@377: * @param ws An array of W values. ingo@377: * ingo@377: * @return an array of Q values. ingo@377: */ ingo@377: public double[] getQsForWs(double[] ws) { ingo@377: logger.debug("FLYSArtifact.getQsForWs"); ingo@377: ingo@377: River r = getRiver(); ingo@377: Gauge g = getGauge(); ingo@377: ingo@377: DischargeTables dt = new DischargeTables(r.getName(), g.getName()); ingo@377: Map tmp = dt.getValues(); ingo@377: ingo@377: double[][] values = tmp.get(g.getName()); ingo@377: double[] qs = new double[ws.length]; ingo@377: ingo@377: for (int i = 0; i < ws.length; i++) { ingo@377: qs[i] = dt.getQForW(values, ws[i]); ingo@377: } ingo@377: ingo@377: return qs; ingo@377: } ingo@377: ingo@377: ingo@377: /** ingo@402: * This method returns the given W or Q values for a specific range ingo@402: * (inserted in the WQ input panel for discharge longitudinal sections). ingo@402: * ingo@402: * @param dist A 2dim array with lower und upper kilometer values. ingo@402: * ingo@402: * @return an array of W or Q values. ingo@402: */ ingo@402: protected double[] getWQForDist(double[] dist) { ingo@402: logger.debug("Search wq values for range: " + dist[0] + " - " + dist[1]); ingo@402: StateData data = getData("wq_values"); ingo@402: ingo@402: if (data == null) { ingo@402: logger.warn("Missing wq values!"); ingo@402: return null; ingo@402: } ingo@402: ingo@402: String dataString = (String) data.getValue(); ingo@402: String[] ranges = dataString.split(":"); ingo@402: ingo@402: for (String range: ranges) { ingo@402: String[] parts = range.split(";"); ingo@402: ingo@402: double lower = Double.parseDouble(parts[0]); ingo@402: double upper = Double.parseDouble(parts[1]); ingo@402: ingo@402: if (lower <= dist[0] && upper >= dist[1]) { ingo@402: String[] values = parts[2].split(","); ingo@402: ingo@402: int num = values.length; ingo@402: double[] res = new double[num]; ingo@402: ingo@402: for (int i = 0; i < num; i++) { ingo@402: try { ingo@402: res[i] = Double.parseDouble(values[i]); ingo@402: } ingo@402: catch (NumberFormatException nfe) { ingo@402: logger.warn(nfe, nfe); ingo@402: } ingo@402: } ingo@402: ingo@402: return res; ingo@402: } ingo@402: } ingo@402: ingo@402: logger.warn("Specified range for WQ not found!"); ingo@402: ingo@402: return null; ingo@402: } ingo@402: ingo@402: ingo@402: /** ingo@377: * This method returns an array of inserted WQ triples that consist of from, ingo@377: * to and the step width. ingo@377: * ingo@377: * @return an array of from, to and step width. ingo@377: */ ingo@377: protected double[] getWQTriple() { ingo@362: StateData dFrom = getData("wq_from"); ingo@362: StateData dTo = getData("wq_to"); ingo@362: ingo@362: if (dFrom == null || dTo == null) { ingo@379: logger.warn("Missing start or end value for range."); ingo@362: return null; ingo@362: } ingo@362: ingo@362: double from = Double.parseDouble((String) dFrom.getValue()); ingo@362: double to = Double.parseDouble((String) dTo.getValue()); ingo@362: ingo@362: StateData dStep = getData("wq_step"); ingo@362: ingo@362: if (dStep == null) { ingo@362: logger.warn("No step width given. Cannot compute Qs."); ingo@362: return null; ingo@362: } ingo@362: ingo@362: double step = Double.parseDouble((String) dStep.getValue()); ingo@362: ingo@362: // if no width is given, the DEFAULT_Q_STEPS is used to compute the step ingo@362: // width. Maybe, we should round the value to a number of digits. ingo@362: if (step == 0d) { ingo@362: double diff = to - from; ingo@362: step = diff / DEFAULT_Q_STEPS; ingo@362: } ingo@362: ingo@362: return getExplodedValues(from, to, step); ingo@362: } ingo@362: ingo@362: ingo@362: /** ingo@379: * Returns an array of inserted WQ double values stored as whitespace ingo@379: * separated list. ingo@379: * ingo@379: * @return an array of W or Q values. ingo@379: */ ingo@379: protected double[] getSingleWQValues() { ingo@379: StateData dSingle = getData("wq_single"); ingo@379: ingo@379: if (dSingle == null) { ingo@379: logger.warn("Cannot determine single WQ values. No data given."); ingo@379: return null; ingo@379: } ingo@379: ingo@379: String tmp = (String) dSingle.getValue(); ingo@379: String[] strValues = tmp.split(" "); ingo@379: ingo@379: TDoubleArrayList values = new TDoubleArrayList(); ingo@379: ingo@379: for (String strValue: strValues) { ingo@379: try { ingo@379: values.add(Double.parseDouble(strValue)); ingo@379: } ingo@379: catch (NumberFormatException nfe) { ingo@379: logger.warn(nfe, nfe); ingo@379: } ingo@379: } ingo@379: ingo@379: values.sort(); ingo@379: ingo@379: return values.toNativeArray(); ingo@379: } ingo@379: ingo@379: ingo@379: /** ingo@362: * Returns an array of double values. The values contained in this array ingo@362: * begin with the value from and end with the value to. The ingo@362: * number of values in the result array depends on the step width. ingo@362: * ingo@362: * @param from The lower value. ingo@362: * @param to The upper value. ingo@362: * @param step The step width between two values in the result array. ingo@362: * ingo@362: * @return an array of double values. ingo@362: */ ingo@362: public double[] getExplodedValues(double from, double to, double step) { ingo@362: double lower = from; ingo@362: ingo@362: double diff = to - from; ingo@368: double tmp = diff / step; ingo@368: int num = (int) Math.ceil(tmp) ; ingo@362: ingo@362: double[] values = new double[num]; ingo@362: ingo@368: for (int idx = 0; idx < num; idx++) { ingo@368: values[idx] = lower; ingo@368: lower += step; ingo@362: } ingo@362: ingo@362: return values; ingo@362: } ingo@119: } ingo@119: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :