ingo@105: package de.intevation.flys.artifacts; ingo@105: felix@1122: import java.awt.geom.Point2D; felix@1122: sascha@1055: import de.intevation.artifactdatabase.ProtocolUtils; ingo@109: sascha@1055: import de.intevation.artifactdatabase.data.StateData; ingo@105: ingo@693: import de.intevation.artifactdatabase.state.Facet; ingo@144: import de.intevation.artifactdatabase.state.Output; ingo@110: import de.intevation.artifactdatabase.state.State; ingo@110: import de.intevation.artifactdatabase.state.StateEngine; sascha@1055: ingo@112: import de.intevation.artifactdatabase.transition.TransitionEngine; ingo@110: sascha@1055: import de.intevation.artifacts.CallContext; ingo@1157: import de.intevation.artifacts.Message; sascha@1055: sascha@1055: import de.intevation.artifacts.common.ArtifactNamespaceContext; sascha@1055: ingo@109: import de.intevation.artifacts.common.utils.XMLUtils; ingo@1145: import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator; ingo@109: sascha@1055: import de.intevation.flys.artifacts.context.FLYSContext; sascha@1055: sascha@1055: import de.intevation.flys.artifacts.model.Calculation1; sascha@1055: import de.intevation.flys.artifacts.model.Calculation2; sascha@1055: import de.intevation.flys.artifacts.model.Calculation3; sascha@1055: import de.intevation.flys.artifacts.model.Calculation4; sascha@1055: import de.intevation.flys.artifacts.model.Calculation; sascha@1055: import de.intevation.flys.artifacts.model.CalculationResult; felix@1116: import de.intevation.flys.artifacts.model.CrossSectionFactory; sascha@1055: import de.intevation.flys.artifacts.model.DischargeTables; sascha@1055: import de.intevation.flys.artifacts.model.MainValuesFactory; sascha@1055: import de.intevation.flys.artifacts.model.Segment; sascha@1055: import de.intevation.flys.artifacts.model.WQKms; sascha@1055: import de.intevation.flys.artifacts.model.WstValueTable; sascha@1055: import de.intevation.flys.artifacts.model.WstValueTableFactory; sascha@1055: sascha@1055: import de.intevation.flys.artifacts.states.DefaultState; sascha@1055: import de.intevation.flys.artifacts.states.LocationDistanceSelect; sascha@1055: ingo@385: import de.intevation.flys.model.Gauge; ingo@362: import de.intevation.flys.model.River; felix@1116: import de.intevation.flys.model.CrossSection; felix@1122: import de.intevation.flys.model.CrossSectionLine; felix@1122: import de.intevation.flys.model.CrossSectionPoint; ingo@362: sascha@1055: import de.intevation.flys.utils.DoubleUtil; ingo@1095: import de.intevation.flys.utils.FLYSUtils; sascha@655: sascha@1055: import gnu.trove.TDoubleArrayList; sascha@451: sascha@1055: import java.util.ArrayList; sascha@1055: import java.util.Arrays; sascha@1055: import java.util.Collections; ingo@1157: import java.util.LinkedList; sascha@1055: import java.util.List; sascha@1055: import java.util.Map; sascha@1055: sascha@1055: import org.apache.log4j.Logger; sascha@1055: sascha@1055: import org.w3c.dom.Document; sascha@1055: import org.w3c.dom.Element; sascha@1055: import org.w3c.dom.Node; ingo@105: felix@1122: import de.intevation.flys.artifacts.charts.CrossSectionApp; ingo@1157: import de.intevation.flys.artifacts.model.CalculationMessage; felix@1122: ingo@105: /** ingo@105: * The default WINFO artifact. ingo@105: * ingo@105: * @author Ingo Weinzierl ingo@105: */ ingo@119: public class WINFOArtifact extends FLYSArtifact { ingo@105: felix@1029: /** The logger for this class. */ ingo@105: private static Logger logger = Logger.getLogger(WINFOArtifact.class); ingo@105: felix@1029: /** 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: sascha@1055: /** The default number of steps between the start end end of a selected Q felix@1115: * range. */ sascha@1055: public static final int DEFAULT_Q_STEPS = 30; sascha@1055: felix@1115: /** The default step width between the start end end kilometer. */ sascha@1055: public static final double DEFAULT_KM_STEPS = 0.1; sascha@1055: 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@624: if (logger.isDebugEnabled()) { ingo@624: dumpArtifact(); ingo@624: } ingo@624: sascha@706: FLYSContext flysContext = getFlysContext(context); 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 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@1145: appendBackgroundActivity(creator, root, context); ingo@1145: 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@1145: protected void appendBackgroundActivity( ingo@1145: ElementCreator cr, ingo@1145: Element root, ingo@1145: CallContext context ingo@1145: ) { ingo@1145: Element inBackground = cr.create("background-processing"); ingo@1157: root.appendChild(inBackground); ingo@1145: ingo@1145: cr.addAttr( ingo@1145: inBackground, ingo@1145: "value", ingo@1145: String.valueOf(context.isInBackground()), ingo@1145: true); ingo@1145: ingo@1157: LinkedList messages = context.getBackgroundMessages(); ingo@1157: ingo@1157: if (messages == null) { ingo@1157: return; ingo@1157: } ingo@1157: ingo@1157: CalculationMessage message = (CalculationMessage) messages.getLast(); ingo@1157: cr.addAttr( ingo@1157: inBackground, ingo@1157: "steps", ingo@1157: String.valueOf(message.getSteps()), ingo@1157: true); ingo@1157: ingo@1157: cr.addAttr( ingo@1157: inBackground, ingo@1157: "currentStep", ingo@1157: String.valueOf(message.getCurrentStep()), ingo@1157: true); ingo@1157: ingo@1157: inBackground.setTextContent(message.getMessage()); ingo@1145: } ingo@1145: ingo@1145: ingo@144: protected void appendOutputModes( ingo@144: Document doc, ingo@144: Element outs, ingo@144: CallContext context, ingo@144: String uuid) ingo@144: { sascha@661: List 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@693: DefaultState state = (DefaultState) engine.getState(stateId); ingo@144: ingo@144: List list = state.getOutputs(); ingo@144: if (list == null || list.size() == 0) { ingo@624: logger.debug("-> No output modes for this state."); ingo@144: continue; ingo@144: } ingo@144: ingo@693: List fs = facets.get(stateId); ingo@693: if (fs == null || fs.size() == 0) { felix@1137: logger.debug("No facets for previous state found."); ingo@693: continue; ingo@693: } ingo@693: ingo@696: logger.debug("Found " + fs.size() + " facets in previous states."); ingo@693: ingo@693: List generated = generateOutputs(list, fs); ingo@693: ingo@943: ProtocolUtils.appendOutputModes(doc, outs, generated); ingo@144: } ingo@144: ingo@355: try { ingo@355: DefaultState cur = (DefaultState) getCurrentState(context); sascha@1050: if (cur.validate(this)) { ingo@355: List list = cur.getOutputs(); ingo@355: if (list != null && list.size() > 0) { ingo@355: logger.debug( felix@1137: "Append output modes for current state: " + cur.getID()); ingo@355: ingo@693: List fs = facets.get(cur.getID()); ingo@693: if (fs != null && fs.size() > 0) { ingo@693: List generated = generateOutputs(list, fs); ingo@693: ingo@696: logger.debug("Found " + fs.size() + " current facets."); sascha@714: if (!generated.isEmpty()) { sascha@714: ProtocolUtils.appendOutputModes( ingo@943: doc, outs, generated); sascha@714: } ingo@693: } ingo@693: else { ingo@693: logger.debug("No facets found for the current state."); ingo@693: } ingo@693: 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: { sascha@661: List 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: ingo@624: ui.appendChild(state.describeStatic(this, 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@687: /** 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: */ sascha@709: public CalculationResult getWaterlevelData() ingo@362: { ingo@362: logger.debug("WINFOArtifact.getWaterlevelData"); ingo@362: felix@1102: River river = FLYSUtils.getRiver(this); ingo@362: if (river == null) { sascha@709: return error(new WQKms[0], "No river selected."); ingo@362: } ingo@362: ingo@362: double[] kms = getKms(); ingo@362: if (kms == null) { sascha@709: return error(new WQKms[0], "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); sascha@735: if (qs == null) { sascha@735: return error(new WQKms[0], "conversion ws to qs failed."); sascha@735: } ingo@362: } ingo@362: sascha@443: WstValueTable wst = WstValueTableFactory.getTable(river); ingo@362: if (wst == null) { sascha@709: return error(new WQKms[0], "No Wst found for selected river."); ingo@362: } ingo@362: sascha@708: ingo@1095: double [] range = FLYSUtils.getKmRange(this); sascha@738: if (range == null) { sascha@738: return error(new WQKms[0], "No range found"); sascha@738: } sascha@736: sascha@738: double refKm; sascha@738: sascha@738: if (isFreeQ()) { sascha@738: refKm = range[0]; sascha@738: logger.debug("'free' calculation (km " + refKm + ")"); sascha@738: } sascha@738: else { sascha@736: Gauge gauge = river.determineGaugeByPosition(range[0]); sascha@708: if (gauge == null) { sascha@736: return error( sascha@736: new WQKms[0], "No gauge found for km " + range[0]); sascha@708: } sascha@736: sascha@736: refKm = gauge.getStation().doubleValue(); sascha@736: sascha@736: logger.debug( sascha@736: "reference gauge: " + gauge.getName() + " (km " + refKm + ")"); sascha@708: } sascha@708: sascha@738: return computeWaterlevelData(kms, qs, ws, wst, refKm); ingo@447: } ingo@447: felix@1115: 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: */ sascha@709: public static CalculationResult computeWaterlevelData( ingo@686: double [] kms, ingo@686: double [] qs, ingo@686: double [] ws, sascha@635: WstValueTable wst, sascha@738: double refKm sascha@635: ) { ingo@362: logger.info("WINFOArtifact.computeWaterlevelData"); ingo@362: sascha@738: Calculation1 calc1 = new Calculation1(kms, qs, ws, refKm); sascha@636: sascha@709: return calc1.calculate(wst); 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: */ sascha@709: public CalculationResult getDurationCurveData() { ingo@385: logger.debug("WINFOArtifact.getDurationCurveData"); ingo@385: felix@1102: River r = FLYSUtils.getRiver(this); ingo@385: ingo@385: if (r == null) { sascha@709: return error(null, "Cannot determine river."); ingo@385: } ingo@385: ingo@385: Gauge g = getGauge(); ingo@385: ingo@385: if (g == null) { sascha@709: return error(null, "Cannot determine gauge."); ingo@385: } ingo@385: ingo@1095: double[] locations = FLYSUtils.getLocations(this); ingo@385: ingo@385: if (locations == null) { sascha@709: return error(null, "Cannot determine location."); ingo@385: } ingo@385: sascha@443: WstValueTable wst = WstValueTableFactory.getTable(r); ingo@385: if (wst == null) { sascha@709: return error(null, "No Wst found for selected river."); ingo@385: } 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: */ sascha@709: public static CalculationResult 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@686: Calculation3 calculation = new Calculation3(location, days, qs); ingo@385: ingo@686: return calculation.calculate(wst); ingo@385: } ingo@393: ingo@393: ingo@393: /** ingo@687: * Returns the data that is used to create discharge curves. ingo@687: * ingo@687: */ sascha@721: public CalculationResult getDischargeCurveData() { ingo@687: felix@1102: River river = FLYSUtils.getRiver(this); sascha@721: if (river == null) { sascha@721: return error(new WQKms[0], "no river found"); ingo@687: } ingo@687: ingo@1095: double [] distance = FLYSUtils.getKmRange(this); ingo@687: sascha@721: if (distance == null) { sascha@721: return error(new WQKms[0], "no range found"); ingo@687: } ingo@687: sascha@721: List gauges = river.determineGauges(distance[0], distance[1]); ingo@687: sascha@721: if (gauges.isEmpty()) { sascha@721: return error(new WQKms[0], "no gauges found"); sascha@721: } sascha@721: sascha@721: String [] names = new String[gauges.size()]; sascha@721: sascha@721: for (int i = 0; i < names.length; ++i) { sascha@721: names[i] = gauges.get(i).getName(); sascha@721: } sascha@721: sascha@721: DischargeTables dt = new DischargeTables(river.getName(), names); sascha@721: sascha@721: Map map = dt.getValues(100d); sascha@721: sascha@721: ArrayList res = new ArrayList(); sascha@721: sascha@721: for (Gauge gauge: gauges) { sascha@721: String name = gauge.getName(); sascha@721: double [][] values = map.get(name); sascha@721: if (values == null) { sascha@721: continue; sascha@721: } sascha@721: double [] kms = new double[values[0].length]; sascha@721: Arrays.fill(kms, gauge.getStation().doubleValue()); sascha@723: res.add(new WQKms(kms, values[0], values[1], name)); sascha@721: } sascha@721: sascha@721: return new CalculationResult( sascha@721: res.toArray(new WQKms[res.size()]), sascha@721: new Calculation()); ingo@687: } ingo@687: ingo@687: ingo@687: /** 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: */ sascha@709: public CalculationResult getComputedDischargeCurveData() ingo@393: throws NullPointerException ingo@393: { ingo@393: logger.debug("WINFOArtifact.getComputedDischargeCurveData"); ingo@393: felix@1102: River r = FLYSUtils.getRiver(this); ingo@393: ingo@393: if (r == null) { sascha@709: return error(new WQKms[0], "Cannot determine river."); ingo@393: } ingo@393: ingo@1095: double[] locations = FLYSUtils.getLocations(this); ingo@393: ingo@393: if (locations == null) { sascha@709: return error(new WQKms[0], "Cannot determine location."); ingo@393: } ingo@393: sascha@443: WstValueTable wst = WstValueTableFactory.getTable(r); ingo@393: if (wst == null) { sascha@709: return error(new WQKms[0], "No Wst found for selected river."); ingo@393: } ingo@393: sascha@709: return computeDischargeCurveData(wst, locations[0]); ingo@456: } ingo@456: ingo@456: ingo@456: /** 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: */ sascha@709: public static CalculationResult computeDischargeCurveData( ingo@393: WstValueTable wst, ingo@393: double location) ingo@393: { ingo@393: logger.info("WINFOArtifact.computeDischargeCurveData"); ingo@393: ingo@686: Calculation2 calculation = new Calculation2(location); ingo@393: sascha@709: return calculation.calculate(wst); sascha@709: } ingo@393: sascha@709: protected static final CalculationResult error(Object data, String msg) { sascha@709: return new CalculationResult(data, new Calculation(msg)); ingo@393: } ingo@402: felix@1148: 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@709: public CalculationResult getDischargeLongitudinalSectionData() { sascha@451: ingo@402: logger.debug("WINFOArtifact.getDischargeLongitudinalSectionData"); ingo@402: felix@1102: River river = FLYSUtils.getRiver(this); ingo@402: if (river == null) { sascha@709: logger.debug("No river selected."); sascha@709: return error(new WQKms[0], "No river selected."); ingo@402: } ingo@402: sascha@655: WstValueTable table = WstValueTableFactory.getTable(river); sascha@655: if (table == null) { sascha@709: logger.debug("No wst found for selected river."); sascha@709: return error(new WQKms[0], "No wst found for selected river."); ingo@402: } ingo@402: sascha@655: List segments = getSegments(); sascha@637: sascha@655: if (segments == null) { sascha@709: logger.debug("Cannot create segments."); sascha@709: return error(new WQKms[0], "Cannot create segments."); sascha@451: } ingo@450: sascha@655: double [] range = getFromToStep(); sascha@451: sascha@655: if (range == null) { sascha@709: logger.debug("Cannot figure out range."); sascha@709: return error(new WQKms[0], "Cannot figure out range."); sascha@451: } sascha@451: sascha@655: Calculation4 calc4 = new Calculation4(segments, river, isQ()); sascha@455: sascha@709: return calc4.calculate(table, range[0], range[1], range[2]); ingo@450: } sascha@1055: felix@1137: sascha@1055: public List getSegments() { sascha@1055: StateData wqValues = getData("wq_values"); sascha@1055: if (wqValues == null) { sascha@1055: logger.warn("no wq_values given"); sascha@1055: return Collections.emptyList(); sascha@1055: } sascha@1055: String input = (String)wqValues.getValue(); sascha@1055: if (input == null || (input = input.trim()).length() == 0) { sascha@1055: logger.warn("wq_values are empty"); sascha@1055: return Collections.emptyList(); sascha@1055: } sascha@1055: return Segment.parseSegments(input); sascha@1055: } sascha@1055: felix@1122: felix@1122: /** felix@1122: * Get List of all cross-sections for current river. felix@1122: * felix@1122: * @return List of CrossSections for current river, null in case of error. felix@1122: */ felix@1122: protected List getCrossSections() { felix@1116: River river = FLYSUtils.getRiver(this); felix@1116: if (river == null) { felix@1116: logger.warn("No river in WINFO found"); felix@1116: return null; felix@1116: } felix@1116: return CrossSectionFactory.getCrossSections(river); felix@1116: } felix@1116: sascha@1055: sascha@1055: /** felix@1122: * Get sorted, valid Points of a CrossSectionLine. felix@1122: * felix@1122: * @param line line of interest. felix@1122: * felix@1122: * @return list of points of CrossSectionLine, sorted. felix@1122: */ felix@1122: protected List getCrossSectionLinesPoints(CrossSectionLine line) { felix@1155: List points = new ArrayList(); felix@1155: felix@1155: if (line == null) { felix@1155: return points; felix@1155: } felix@1155: felix@1122: List linePoints = line.getPoints(); felix@1122: if (linePoints.isEmpty()) { felix@1122: logger.info("No points in selected CrossSectionLine."); felix@1122: return points; felix@1122: } felix@1122: Collections.sort(linePoints, CrossSectionApp.COL_POS_CMP); felix@1122: for (CrossSectionPoint p: linePoints) { felix@1122: double x = p.getX().doubleValue(); felix@1122: double y = p.getY().doubleValue(); felix@1122: if (CrossSectionApp.isValid(x) && CrossSectionApp.isValid(y)) { felix@1122: points.add(new Point2D.Double(x,y)); felix@1122: } felix@1122: } felix@1122: return points; felix@1122: } felix@1122: felix@1122: felix@1122: /** felix@1139: * Get CrossSectionLine spatially closest to what is specified in the data felix@1139: * "cross_section.km". felix@1122: * felix@1139: * @return CrossSectionLine closest to "cross_section.km". felix@1122: */ felix@1139: protected CrossSectionLine searchCrossSectionKmLine() { felix@1138: double wishKM = 0.0f; felix@1138: try { felix@1138: wishKM = Double.parseDouble(getDataAsString("cross_section.km")); felix@1138: } felix@1138: catch (Exception e) { felix@1138: ; felix@1138: } felix@1138: felix@1138: // Get the cross section closest to requested km. felix@1138: // Naive, linear approach. felix@1155: List sections = getCrossSections(); felix@1155: if (sections.size() == 0 || sections.get(0).getLines().size() == 0) { felix@1155: return null; felix@1155: } felix@1138: List crossSectionLines = felix@1155: sections.get(0).getLines(); felix@1138: CrossSectionLine oldLine = crossSectionLines.get(0); felix@1138: double oldDiff = Math.abs(wishKM - oldLine.getKm().doubleValue()); felix@1138: for (CrossSectionLine line: crossSectionLines) { felix@1138: double diff = Math.abs(wishKM - line.getKm().doubleValue()); felix@1138: if (diff > oldDiff) { felix@1138: break; felix@1138: } felix@1138: oldDiff = diff; felix@1138: oldLine = line; felix@1138: } felix@1139: return oldLine; felix@1139: } felix@1139: felix@1139: felix@1139: /** felix@1142: * Get km for which a CrossSection is actually available (this may vary felix@1142: * from the user picked "cross_section.km" data). felix@1142: * felix@1142: * @return km for which cross section is calculated. felix@1142: */ felix@1142: public double getCrossSectionSnapKm() { felix@1142: // Note that this is this triggers a linear search. felix@1155: CrossSectionLine line = searchCrossSectionKmLine(); felix@1155: if (line == null) { felix@1155: logger.warn("No Cross Section for given km found."); felix@1155: return 0.0f; felix@1155: } felix@1155: else { felix@1155: return line.getKm().doubleValue(); felix@1155: } felix@1142: } felix@1142: felix@1142: felix@1142: /** felix@1139: * Get points of Profile of cross section. felix@1139: * felix@1139: * @return an array holding coordinates of points of profile ( felix@1139: * in the form {{x1, x2} {y1, y2}} ). felix@1139: */ felix@1139: public double [][] getCrossSectionData() { felix@1139: logger.info("getCrossSectionData() for cross_section.km " felix@1139: + getDataAsString("cross_section.km")); felix@1139: CrossSectionLine line = searchCrossSectionKmLine(); felix@1139: return getCrossSectionProfile(line); felix@1122: } felix@1122: felix@1122: felix@1122: /** felix@1122: * Get points of line describing the surface of water at cross section. felix@1122: * felix@1122: * @return an array holding coordinates of points of surface of water ( felix@1122: * in the form {{x1, x2} {y1, y2}} ). felix@1122: */ felix@1122: public double [][] getWaterLines() { felix@1139: logger.debug("getWaterLines()"); felix@1139: CrossSectionLine csl = searchCrossSectionKmLine(); felix@1122: List points = getCrossSectionLinesPoints(csl); felix@1139: // Need W at km felix@1139: WQKms [] wqkms = (WQKms[]) getWaterlevelData().getData(); felix@1139: if (wqkms.length == 0) { felix@1139: logger.error("No WQKms found."); felix@1139: return CrossSectionApp.createWaterLines(points, 0.0f); felix@1139: } felix@1139: else felix@1139: { felix@1139: if (wqkms.length > 1) { felix@1139: logger.warn("More than one wqkms found, taking first one."); felix@1139: } felix@1139: // Find W at km, linear naive approach. felix@1139: WQKms triple = wqkms[0]; felix@1139: // Find index of km. felix@1139: double wishKM = 0.0f; felix@1139: int old_idx = 0; felix@1139: try { felix@1139: wishKM = Double.parseDouble(getDataAsString("cross_section.km")); felix@1139: } felix@1139: catch (Exception e) { felix@1139: ; felix@1139: } felix@1139: felix@1155: if (triple.size() == 0) { felix@1155: logger.warn("Calculation of waterline is empty."); felix@1155: return CrossSectionApp.createWaterLines(points, 0.0f); felix@1155: } felix@1155: felix@1139: // Linear seach in WQKms for closest km. felix@1139: double old_dist_wish = Math.abs(wishKM - triple.getKm(0)); felix@1155: double last_w = triple.getW(0); felix@1139: felix@1139: for (int i = 0; i < triple.size(); i++) { felix@1139: double diff = Math.abs(wishKM - triple.getKm(i)); felix@1139: if (diff > old_dist_wish) { felix@1155: break; felix@1139: } felix@1155: last_w = triple.getW(i); felix@1139: old_dist_wish = diff; felix@1139: } felix@1155: return CrossSectionApp.createWaterLines(points, last_w); felix@1139: } felix@1122: } felix@1122: felix@1122: felix@1122: /** felix@1125: * Get name of cross section. felix@1125: */ felix@1125: public String getCrossSectionName() { felix@1154: List sections = getCrossSections(); felix@1154: felix@1154: if (sections.size() > 0) { felix@1154: return sections.get(0).getDescription(); felix@1154: } felix@1154: else { felix@1154: return ""; felix@1154: } felix@1125: } felix@1125: felix@1125: felix@1125: /** felix@1122: * Get points of CrossSection Line. felix@1155: * @param csl The crossSectionline of interest. felix@1155: * @return x and y positions of cross section profile. felix@1122: */ felix@1122: protected double [][] getCrossSectionProfile(CrossSectionLine csl) { felix@1122: List points = getCrossSectionLinesPoints(csl); felix@1122: double [] xs = new double[points.size()]; felix@1122: double [] ys = new double[points.size()]; felix@1122: felix@1122: if (points.isEmpty()) { felix@1122: return new double [][] {xs, ys}; felix@1122: } felix@1122: felix@1122: xs[0] = points.get(0).getX(); felix@1122: ys[0] = points.get(0).getY(); felix@1122: felix@1122: for (int i = 1; i < points.size(); i++) { felix@1122: Point2D p = points.get(i); felix@1122: xs[i] = p.getX(); felix@1122: if (xs[i] < xs[i-1]) { felix@1122: xs[i] = xs[i-1] + CrossSectionApp.EPSILON; felix@1122: } felix@1122: ys[i] = p.getY(); felix@1122: } felix@1122: return new double [][] { xs, ys }; felix@1122: } felix@1122: felix@1122: felix@1122: /** sascha@1055: * Returns the Qs for a number of Ws. This method makes use of sascha@1055: * DischargeTables.getQForW(). sascha@1055: * sascha@1055: * @param ws An array of W values. sascha@1055: * sascha@1055: * @return an array of Q values. sascha@1055: */ sascha@1055: public double[] getQsForWs(double[] ws) { sascha@1055: sascha@1055: boolean debug = logger.isDebugEnabled(); sascha@1055: sascha@1055: if (debug) { sascha@1055: logger.debug("FLYSArtifact.getQsForWs"); sascha@1055: } sascha@1055: felix@1102: River r = FLYSUtils.getRiver(this); sascha@1055: if (r == null) { sascha@1055: logger.warn("no river found"); sascha@1055: return null; sascha@1055: } sascha@1055: ingo@1095: double [] range = FLYSUtils.getKmRange(this); sascha@1055: if (range == null) { sascha@1055: logger.warn("no ranges found"); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: if (debug) { sascha@1055: logger.debug("range: " + Arrays.toString(range)); sascha@1055: } sascha@1055: sascha@1055: Gauge g = r.determineGaugeByPosition(range[0]); sascha@1055: if (g == null) { sascha@1055: logger.warn("no gauge found for km: " + range[0]); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: if (debug) { sascha@1055: logger.debug("convert w->q with gauge '" + g.getName() + "'"); sascha@1055: } sascha@1055: sascha@1055: DischargeTables dt = new DischargeTables(r.getName(), g.getName()); sascha@1055: Map tmp = dt.getValues(); sascha@1055: sascha@1055: double[][] values = tmp.get(g.getName()); sascha@1055: double[] qs = new double[ws.length]; sascha@1055: sascha@1055: for (int i = 0; i < ws.length; i++) { sascha@1055: qs[i] = dt.getQForW(values, ws[i]); sascha@1055: if (debug) { sascha@1055: logger.debug("w: " + ws[i] + " -> q: " + qs[i]); sascha@1055: } sascha@1055: } sascha@1055: sascha@1055: return qs; sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * Determines the selected mode of distance/range input. sascha@1055: * sascha@1055: * @return true, if the range mode is selected otherwise false. sascha@1055: */ sascha@1055: public boolean isRange() { sascha@1055: StateData mode = getData("ld_mode"); sascha@1055: sascha@1055: if (mode == null) { sascha@1055: logger.warn("No mode location/range chosen. Defaults to range."); sascha@1055: return true; sascha@1055: } sascha@1055: sascha@1055: String value = (String) mode.getValue(); sascha@1055: sascha@1055: return value.equals("distance"); sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * Returns the selected distance based on a given range (from, to). sascha@1055: * sascha@1055: * @param dFrom The StateData that contains the lower value. sascha@1055: * @param dTo The StateData that contains the upper value. sascha@1055: * sascha@1055: * @return the selected distance. sascha@1055: */ sascha@1055: protected double[] getDistanceByRange(StateData dFrom, StateData dTo) { sascha@1055: double from = Double.parseDouble((String) dFrom.getValue()); sascha@1055: double to = Double.parseDouble((String) dTo.getValue()); sascha@1055: sascha@1055: return new double[] { from, to }; sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * Returns the selected Kms. sascha@1055: * sascha@1055: * @param distance An 2dim array with [lower, upper] values. sascha@1055: * sascha@1055: * @return the selected Kms. sascha@1055: */ sascha@1055: public double[] getKms(double[] distance) { sascha@1055: StateData dStep = getData("ld_step"); sascha@1055: sascha@1055: if (dStep == null) { sascha@1055: logger.warn("No step width given. Cannot compute Kms."); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: double step = Double.parseDouble((String) dStep.getValue()); sascha@1055: sascha@1055: // transform step from 'm' into 'km' sascha@1055: step = step / 1000; sascha@1055: sascha@1055: if (step == 0d) { sascha@1055: step = DEFAULT_KM_STEPS; sascha@1055: } sascha@1055: sascha@1055: return DoubleUtil.explode(distance[0], distance[1], step); sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * Returns the selected Kms. sascha@1055: * sascha@1055: * @return the selected kms. sascha@1055: */ sascha@1055: public double[] getKms() { sascha@1055: if (isRange()) { ingo@1095: double[] distance = FLYSUtils.getKmRange(this); sascha@1055: return getKms(distance); sascha@1055: sascha@1055: } sascha@1055: else { sascha@1055: return LocationDistanceSelect.getLocations(this); sascha@1055: } sascha@1055: } sascha@1055: felix@1137: sascha@1055: public double [] getFromToStep() { sascha@1055: if (!isRange()) { sascha@1055: return null; sascha@1055: } ingo@1095: double [] fromTo = FLYSUtils.getKmRange(this); sascha@1055: sascha@1055: if (fromTo == null) { sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: StateData dStep = getData("ld_step"); sascha@1055: if (dStep == null) { sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: double [] result = new double[3]; sascha@1055: result[0] = fromTo[0]; sascha@1055: result[1] = fromTo[1]; sascha@1055: sascha@1055: try { sascha@1055: String step = (String)dStep.getValue(); sascha@1055: result[2] = DoubleUtil.round(Double.parseDouble(step) / 1000d); sascha@1055: } sascha@1055: catch (NumberFormatException nfe) { sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: return result; sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * Returns the gauge based on the current distance and river. sascha@1055: * sascha@1055: * @return the gauge. sascha@1055: */ sascha@1055: public Gauge getGauge() { felix@1102: River river = FLYSUtils.getRiver(this); sascha@1055: sascha@1055: if (river == null) { sascha@1055: logger.debug("no river found"); sascha@1055: return null; sascha@1055: } sascha@1055: ingo@1095: double[] dist = FLYSUtils.getKmRange(this); sascha@1055: sascha@1055: if (dist == null) { sascha@1055: logger.debug("no range found"); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: if (logger.isDebugEnabled()) { sascha@1055: logger.debug("Determine gauge for:"); sascha@1055: logger.debug("... river: " + river.getName()); sascha@1055: logger.debug("... distance: " + dist[0] + " - " + dist[1]); sascha@1055: } sascha@1055: sascha@1055: Gauge gauge = river.determineGauge(dist[0], dist[1]); sascha@1055: sascha@1055: String name = gauge != null ? gauge.getName() : "'n/a"; sascha@1055: logger.debug("Found gauge: " + name); sascha@1055: sascha@1055: return gauge; sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * Returns the gauges that match the selected kilometer range. sascha@1055: * sascha@1055: * @return the gauges based on the selected kilometer range. sascha@1055: */ sascha@1055: public List getGauges() { sascha@1055: felix@1102: River river = FLYSUtils.getRiver(this); sascha@1055: if (river == null) { sascha@1055: return null; sascha@1055: } sascha@1055: felix@1102: double [] dist = FLYSUtils.getKmRange(this); sascha@1055: if (dist == null) { sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: return river.determineGauges(dist[0], dist[1]); sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * This method returns the Q values. sascha@1055: * sascha@1055: * @return the selected Q values or null, if no Q values are selected. sascha@1055: */ sascha@1055: public double[] getQs() { sascha@1055: StateData dMode = getData("wq_mode"); sascha@1055: StateData dSelection = getData("wq_selection"); sascha@1055: sascha@1055: String mode = dMode != null ? (String) dMode.getValue() : ""; sascha@1055: String sel = dSelection != null ? (String)dSelection.getValue() : null; sascha@1055: sascha@1055: if (mode.equals("Q")) { sascha@1055: if (sel != null && sel.equals("single")) { sascha@1055: return getSingleWQValues(); sascha@1055: } sascha@1055: else { sascha@1055: return getWQTriple(); sascha@1055: } sascha@1055: } sascha@1055: else { sascha@1055: logger.warn("You try to get Qs, but W has been inserted."); sascha@1055: return null; sascha@1055: } sascha@1055: } sascha@1055: sascha@1055: sascha@1055: public boolean isQ() { sascha@1055: StateData mode = getData("wq_mode"); sascha@1055: return mode != null && mode.getValue().equals("Q"); sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * Returns true, if the parameter is set to compute data on a free range. sascha@1055: * Otherwise it returns false, which tells the calculation that it is bound sascha@1055: * to a gauge. sascha@1055: * sascha@1055: * @return true, if the calculation should compute on a free range otherwise sascha@1055: * false and the calculation is bound to a gauge. sascha@1055: */ sascha@1055: public boolean isFreeQ() { sascha@1055: StateData mode = getData("wq_free"); felix@1115: String value = (mode != null) ? (String) mode.getValue() : null; sascha@1055: sascha@1055: logger.debug("isFreeQ: " + value); sascha@1055: sascha@1055: if (value == null) { sascha@1055: return false; sascha@1055: } sascha@1055: sascha@1055: return Boolean.valueOf(value); sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * Returns the Q values based on a specified kilometer range. sascha@1055: * sascha@1055: * @param range A 2dim array with lower and upper kilometer range. sascha@1055: * sascha@1055: * @return an array of Q values. sascha@1055: */ sascha@1055: public double[] getQs(double[] range) { sascha@1055: StateData dMode = getData("wq_mode"); sascha@1055: StateData dValues = getData("wq_values"); sascha@1055: felix@1115: String mode = (dMode != null) ? (String) dMode.getValue() : ""; sascha@1055: sascha@1055: if (mode.equals("Q")) { sascha@1055: return getWQForDist(range); sascha@1055: } sascha@1055: sascha@1055: logger.warn("You try to get Qs, but Ws has been inserted."); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * Returns the W values based on a specified kilometer range. sascha@1055: * sascha@1055: * @param range A 2dim array with lower and upper kilometer range. sascha@1055: * sascha@1055: * @return an array of W values. sascha@1055: */ sascha@1055: public double[] getWs(double[] range) { sascha@1055: StateData dMode = getData("wq_mode"); sascha@1055: StateData dValues = getData("wq_values"); sascha@1055: felix@1115: String mode = (dMode != null) ? (String) dMode.getValue() : ""; sascha@1055: sascha@1055: if (mode.equals("W")) { sascha@1055: return getWQForDist(range); sascha@1055: } sascha@1055: sascha@1055: logger.warn("You try to get Ws, but Qs has been inserted."); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * This method returns the W values. sascha@1055: * sascha@1055: * @return the selected W values or null, if no W values are selected. sascha@1055: */ sascha@1055: public double[] getWs() { sascha@1055: StateData dMode = getData("wq_mode"); sascha@1055: StateData dSingle = getData("wq_single"); sascha@1055: felix@1139: String mode = (dMode != null) ? (String) dMode.getValue() : ""; sascha@1055: sascha@1055: if (mode.equals("W")) { sascha@1055: if (dSingle != null) { sascha@1055: return getSingleWQValues(); sascha@1055: } sascha@1055: else { sascha@1055: return getWQTriple(); sascha@1055: } sascha@1055: } sascha@1055: else { sascha@1055: logger.warn("You try to get Qs, but W has been inserted."); sascha@1055: return null; sascha@1055: } sascha@1055: } sascha@1055: sascha@1055: /** sascha@1055: * This method returns the given W or Q values for a specific range sascha@1055: * (inserted in the WQ input panel for discharge longitudinal sections). sascha@1055: * sascha@1055: * @param dist A 2dim array with lower und upper kilometer values. sascha@1055: * sascha@1055: * @return an array of W or Q values. sascha@1055: */ sascha@1055: protected double[] getWQForDist(double[] dist) { sascha@1055: logger.debug("Search wq values for range: " + dist[0] + " - " + dist[1]); sascha@1055: StateData data = getData("wq_values"); sascha@1055: sascha@1055: if (data == null) { sascha@1055: logger.warn("Missing wq values!"); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: String dataString = (String) data.getValue(); sascha@1055: String[] ranges = dataString.split(":"); sascha@1055: sascha@1055: for (String range: ranges) { sascha@1055: String[] parts = range.split(";"); sascha@1055: sascha@1055: double lower = Double.parseDouble(parts[0]); sascha@1055: double upper = Double.parseDouble(parts[1]); sascha@1055: sascha@1055: if (lower <= dist[0] && upper >= dist[1]) { sascha@1055: String[] values = parts[2].split(","); sascha@1055: sascha@1055: int num = values.length; sascha@1055: double[] res = new double[num]; sascha@1055: sascha@1055: for (int i = 0; i < num; i++) { sascha@1055: try { sascha@1055: res[i] = Double.parseDouble(values[i]); sascha@1055: } sascha@1055: catch (NumberFormatException nfe) { sascha@1055: logger.warn(nfe, nfe); sascha@1055: } sascha@1055: } sascha@1055: sascha@1055: return res; sascha@1055: } sascha@1055: } sascha@1055: sascha@1055: logger.warn("Specified range for WQ not found!"); sascha@1055: sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * This method returns an array of inserted WQ triples that consist of from, sascha@1055: * to and the step width. sascha@1055: * sascha@1055: * @return an array of from, to and step width. sascha@1055: */ sascha@1055: protected double[] getWQTriple() { sascha@1055: StateData dFrom = getData("wq_from"); sascha@1055: StateData dTo = getData("wq_to"); sascha@1055: sascha@1055: if (dFrom == null || dTo == null) { sascha@1055: logger.warn("Missing start or end value for range."); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: double from = Double.parseDouble((String) dFrom.getValue()); sascha@1055: double to = Double.parseDouble((String) dTo.getValue()); sascha@1055: sascha@1055: StateData dStep = getData("wq_step"); sascha@1055: sascha@1055: if (dStep == null) { sascha@1055: logger.warn("No step width given. Cannot compute Qs."); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: double step = Double.parseDouble((String) dStep.getValue()); sascha@1055: sascha@1055: // if no width is given, the DEFAULT_Q_STEPS is used to compute the step sascha@1055: // width. Maybe, we should round the value to a number of digits. sascha@1055: if (step == 0d) { sascha@1055: double diff = to - from; sascha@1055: step = diff / DEFAULT_Q_STEPS; sascha@1055: } sascha@1055: sascha@1055: return DoubleUtil.explode(from, to, step); sascha@1055: } sascha@1055: sascha@1055: sascha@1055: /** sascha@1055: * Returns an array of inserted WQ double values stored as whitespace sascha@1055: * separated list. sascha@1055: * sascha@1055: * @return an array of W or Q values. sascha@1055: */ sascha@1055: protected double[] getSingleWQValues() { sascha@1055: StateData dSingle = getData("wq_single"); sascha@1055: sascha@1055: if (dSingle == null) { sascha@1055: logger.warn("Cannot determine single WQ values. No data given."); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: String tmp = (String) dSingle.getValue(); sascha@1055: String[] strValues = tmp.split(" "); sascha@1055: sascha@1055: TDoubleArrayList values = new TDoubleArrayList(); sascha@1055: sascha@1055: for (String strValue: strValues) { sascha@1055: try { sascha@1055: values.add(Double.parseDouble(strValue)); sascha@1055: } sascha@1055: catch (NumberFormatException nfe) { sascha@1055: logger.warn(nfe, nfe); sascha@1055: } sascha@1055: } sascha@1055: sascha@1055: values.sort(); sascha@1055: sascha@1055: return values.toNativeArray(); sascha@1055: } ingo@105: } ingo@105: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :