teichmann@5863: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@5863: * Software engineering by Intevation GmbH teichmann@5863: * teichmann@5994: * This file is Free Software under the GNU AGPL (>=v3) teichmann@5863: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@5994: * documentation coming with Dive4Elements River for details. teichmann@5863: */ teichmann@5863: teichmann@5831: package org.dive4elements.river.artifacts; sascha@3233: teichmann@5831: import org.dive4elements.artifactdatabase.data.StateData; teichmann@4812: teichmann@5831: import org.dive4elements.artifactdatabase.state.Facet; teichmann@5831: import org.dive4elements.artifactdatabase.state.FacetActivity; sascha@1055: teichmann@5831: import org.dive4elements.artifacts.Artifact; teichmann@5831: import org.dive4elements.artifacts.CallContext; ingo@362: teichmann@5831: import org.dive4elements.artifacts.common.utils.StringUtils; teichmann@5831: teichmann@5831: import org.dive4elements.river.artifacts.access.Calculation4Access; teichmann@5831: import org.dive4elements.river.artifacts.access.RangeAccess; teichmann@5831: teichmann@5831: import org.dive4elements.river.artifacts.geom.Lines; teichmann@5831: teichmann@5831: import org.dive4elements.river.artifacts.model.Calculation1; teichmann@5831: import org.dive4elements.river.artifacts.model.Calculation2; teichmann@5831: import org.dive4elements.river.artifacts.model.Calculation3; teichmann@5831: import org.dive4elements.river.artifacts.model.Calculation4; teichmann@5831: import org.dive4elements.river.artifacts.model.Calculation5; teichmann@5831: import org.dive4elements.river.artifacts.model.Calculation; teichmann@5831: import org.dive4elements.river.artifacts.model.CalculationResult; teichmann@5831: import org.dive4elements.river.artifacts.model.DischargeTables; teichmann@5831: import org.dive4elements.river.artifacts.model.FacetTypes; teichmann@5831: import org.dive4elements.river.artifacts.model.WQCKms; teichmann@5831: import org.dive4elements.river.artifacts.model.WQKms; teichmann@5831: import org.dive4elements.river.artifacts.model.WW; teichmann@5831: import org.dive4elements.river.artifacts.model.WstValueTable; teichmann@5831: import org.dive4elements.river.artifacts.model.WstValueTableFactory; teichmann@5831: teichmann@5831: import org.dive4elements.river.artifacts.model.extreme.ExtremeResult; teichmann@5831: teichmann@5831: import org.dive4elements.river.artifacts.states.DefaultState.ComputeType; teichmann@5831: teichmann@5831: import org.dive4elements.river.artifacts.states.LocationDistanceSelect; teichmann@5831: teichmann@5831: import org.dive4elements.river.model.DischargeTable; teichmann@5831: import org.dive4elements.river.model.FastCrossSectionLine; teichmann@5831: import org.dive4elements.river.model.Gauge; teichmann@5831: import org.dive4elements.river.model.River; teichmann@5831: teichmann@5831: import org.dive4elements.river.utils.DoubleUtil; teichmann@5865: import org.dive4elements.river.utils.RiverUtils; sascha@655: sascha@1055: import gnu.trove.TDoubleArrayList; sascha@451: sascha@2194: import java.awt.geom.Point2D; sascha@2194: sascha@1055: import java.util.Arrays; sascha@1055: import java.util.List; sascha@1055: import java.util.Map; sascha@1055: sascha@1055: import org.apache.log4j.Logger; sascha@1055: felix@2733: ingo@105: /** ingo@105: * The default WINFO artifact. ingo@105: * ingo@105: * @author Ingo Weinzierl ingo@105: */ felix@1809: public class WINFOArtifact teichmann@5867: extends D4EArtifact felix@1981: implements FacetTypes, WaterLineArtifact { 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: sascha@3556: private static final String [] INACTIVES = new String[] { sascha@3556: LONGITUDINAL_Q, felix@5400: DURATION_Q, felix@5400: HISTORICAL_DISCHARGE_MAINVALUES_W, felix@5848: HISTORICAL_DISCHARGE_MAINVALUES_Q, felix@5848: STATIC_WQKMS_Q sascha@3556: }; sascha@3556: sascha@3556: static { sascha@3556: // TODO: Move to configuration. sascha@3556: FacetActivity.Registry.getInstance().register( sascha@3556: ARTIFACT_NAME, sascha@3556: new FacetActivity() { sascha@3556: @Override sascha@3558: public Boolean isInitialActive( sascha@3556: Artifact artifact, sascha@3556: Facet facet, sascha@3556: String outputName sascha@3556: ) { sascha@3556: String fname = facet.getName(); sascha@3556: if ((fname.equals(COMPUTED_DISCHARGE_MAINVALUES_Q) felix@4181: || fname.equals(COMPUTED_DISCHARGE_MAINVALUES_W) felix@4181: || fname.equals(MAINVALUES_Q) felix@4181: || fname.equals(MAINVALUES_W)) felix@4181: && outputName.equals("computed_discharge_curve")) felix@4181: { sascha@3558: return Boolean.FALSE; sascha@3556: } sascha@3556: return !StringUtils.contains(fname, INACTIVES); sascha@3556: } sascha@3556: }); sascha@3556: } ingo@121: ingo@105: /** ingo@105: * The default constructor. ingo@105: */ ingo@105: public WINFOArtifact() { ingo@105: } ingo@105: ingo@105: 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: */ sascha@3193: @Override ingo@121: public String getName() { ingo@121: return ARTIFACT_NAME; ingo@121: } ingo@124: sascha@2624: protected static boolean reportGeneratedWs( sascha@2624: Calculation report, sascha@2624: double [] ws sascha@2624: ) { sascha@2624: if (ws == null || ws.length < 2) { sascha@2624: return false; sascha@2624: } sascha@2624: sascha@2624: double lastW = ws[0]; sascha@2624: boolean alreadyReported = false; sascha@2624: sascha@2624: for (int i = 1; i < ws.length; ++i) { sascha@2624: if (Math.abs(lastW - ws[i]) < 1e-5) { sascha@2624: if (!alreadyReported) { sascha@2624: alreadyReported = true; sascha@2624: report.addProblem("more.than.one.q.for.w", ws[i]); sascha@2624: } sascha@2624: } sascha@2624: else { sascha@2624: alreadyReported = false; sascha@2624: } sascha@2624: lastW = ws[i]; sascha@2624: } sascha@2624: sascha@2624: return true; sascha@2624: } sascha@2624: ingo@362: ingo@362: // ingo@362: // METHODS FOR RETRIEVING COMPUTED DATA FOR DIFFERENT CHART TYPES ingo@362: // felix@4479: // felix@4479: /** felix@4479: * Returns the data that is computed by a waterlevel computation. felix@4479: * felix@4479: * @return an array of data triples that consist of W, Q and Kms. felix@4479: */ felix@4479: public CalculationResult getWaterlevelData() { felix@4479: return this.getWaterlevelData(null); felix@4479: } ingo@362: teichmann@4812: // THIS IS FREAKY BULLSHIT! Felix, why do you call the calculation directly???? teichmann@4812: protected CalculationResult getDischargeLongitudinalSectionData() { teichmann@4812: // XXX: THIS AN _EXPENSIVE_ CALCULATION! CACHE IT! teichmann@4812: return new Calculation4(new Calculation4Access(this)).calculate(); teichmann@4812: } teichmann@4812: 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: */ felix@4478: public CalculationResult getWaterlevelData(CallContext context) ingo@362: { ingo@362: logger.debug("WINFOArtifact.getWaterlevelData"); ingo@362: felix@4479: String calculationMode = getDataAsString("calculation_mode"); felix@4479: felix@6619: // If this WINFO-Artifact has a calculation trait. felix@6619: if (calculationMode != null) { felix@6619: if (calculationMode.equals("calc.discharge.longitudinal.section") felix@6619: ) { felix@6619: return getDischargeLongitudinalSectionData(); felix@6619: } felix@6619: else if (calculationMode.equals("calc.extreme.curve")) { felix@6619: return (CalculationResult) felix@6619: this.compute(context, ComputeType.ADVANCE, false); felix@6619: } felix@6619: else if (calculationMode.equals("calc.w.differences")) { felix@6619: return (CalculationResult) felix@6619: this.compute(context, ComputeType.ADVANCE, true); felix@6619: } felix@6619: else { felix@6625: logger.warn("Unhandled calculation_mode " + calculationMode); felix@6619: } felix@4479: } felix@2765: felix@6619: // Otherwise get it from parameterization. teichmann@5865: River river = RiverUtils.getRiver(this); ingo@362: if (river == null) { sascha@2166: return error(new WQKms[0], "no.river.selected"); ingo@362: } ingo@362: ingo@362: double[] kms = getKms(); ingo@362: if (kms == null) { sascha@2166: 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: sascha@2624: Calculation report = new Calculation(); sascha@2624: 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(); sascha@2165: double [][] qws = getQsForWs(ws); sascha@2415: if (qws == null || qws.length == 0) { sascha@2166: return error(new WQKms[0], "converting.ws.to.qs.failed"); sascha@735: } sascha@2165: qs = qws[0]; sascha@2165: sascha@3076: if (reportGeneratedWs(report, qws[1])) { sascha@2165: ws = qws[1]; sascha@2165: } ingo@362: } ingo@362: sascha@443: WstValueTable wst = WstValueTableFactory.getTable(river); ingo@362: if (wst == null) { sascha@2166: return error(new WQKms[0], "no.wst.for.selected.river"); ingo@362: } ingo@362: teichmann@6101: RangeAccess rangeAccess = new RangeAccess(this); felix@4861: double [] range = rangeAccess.getKmRange(); sascha@738: if (range == null) { sascha@2166: return error(new WQKms[0], "no.range.found"); sascha@738: } sascha@736: sascha@738: double refKm; sascha@738: sascha@2164: if (isFreeQ() || isFreeW()) { 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( felix@5786: 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@2624: return computeWaterlevelData(kms, qs, ws, wst, refKm, report); 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. felix@3272: * @param qs 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@2624: double refKm, sascha@2624: Calculation report sascha@635: ) { ingo@362: logger.info("WINFOArtifact.computeWaterlevelData"); ingo@362: sascha@738: Calculation1 calc1 = new Calculation1(kms, qs, ws, refKm); sascha@636: sascha@2624: if (report != null) { sascha@2624: calc1.addProblems(report); sascha@2624: } sascha@2624: 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: teichmann@5865: River r = RiverUtils.getRiver(this); ingo@385: ingo@385: if (r == null) { sascha@2166: return error(null, "no.river.selected"); ingo@385: } ingo@385: ingo@385: Gauge g = getGauge(); ingo@385: ingo@385: if (g == null) { sascha@2166: return error(null, "no.gauge.selected"); ingo@385: } ingo@385: teichmann@6101: RangeAccess rangeAccess = new RangeAccess(this); felix@4846: double[] locations = rangeAccess.getLocations(); ingo@385: ingo@385: if (locations == null) { sascha@2166: return error(null, "no.locations.selected"); ingo@385: } ingo@385: sascha@443: WstValueTable wst = WstValueTableFactory.getTable(r); ingo@385: if (wst == null) { sascha@2166: return error(null, "no.wst.for.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( felix@1974: Gauge gauge, felix@1974: WstValueTable wst, felix@1974: double location) ingo@385: { ingo@385: logger.info("WINFOArtifact.computeDurationCurveData"); ingo@385: sascha@3647: Object[] obj = gauge.fetchDurationCurveData(); 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@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: teichmann@5865: River r = RiverUtils.getRiver(this); ingo@393: ingo@393: if (r == null) { sascha@2166: return error(new WQKms[0], "no.river.selected"); ingo@393: } ingo@393: teichmann@6101: RangeAccess rangeAccess = new RangeAccess(this); felix@4846: double[] locations = rangeAccess.getLocations(); ingo@393: ingo@393: if (locations == null) { sascha@2166: return error(new WQKms[0], "no.locations.selected"); ingo@393: } ingo@393: sascha@443: WstValueTable wst = WstValueTableFactory.getTable(r); ingo@393: if (wst == null) { sascha@2166: return error(new WQKms[0], "no.wst.for.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: * felix@3272: * @param wst The WstValueTable that is used for the interpolation (river- felix@3272: * bound). 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: felix@3272: felix@3272: /** Create CalculationResult with data and message. */ 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@5786: /** Create CalculationResult with data and message with args. */ felix@5786: protected static final CalculationResult error(Object data, String msg, Object ... args) { felix@5786: return new CalculationResult(data, new Calculation(msg, args)); felix@5786: } felix@5786: felix@1148: ingo@402: /** sascha@2194: * Returns the data that is computed by a reference curve computation. sascha@2194: * sascha@2194: * @return the data computed by a reference curve computation. sascha@2194: */ sascha@2326: public CalculationResult getReferenceCurveData(CallContext context) { sascha@2194: sascha@2194: Double startKm = getReferenceStartKm(); sascha@2194: sascha@2194: if (startKm == null) { sascha@2194: return error(new WW[0], "no.reference.start.km"); sascha@2194: } sascha@2194: sascha@2194: double [] endKms = getReferenceEndKms(); sascha@2194: sascha@2194: if (endKms == null || endKms.length == 0) { sascha@2194: return error(new WW[0], "no.reference.end.kms"); sascha@2194: } sascha@2194: sascha@2194: Calculation5 calc5 = new Calculation5(startKm, endKms); sascha@2194: teichmann@5865: River r = RiverUtils.getRiver(this); sascha@2194: if (r == null) { sascha@2194: return error(new WW[0], "no.river.found"); sascha@2194: } sascha@2194: sascha@2194: WstValueTable wst = WstValueTableFactory.getTable(r); sascha@2194: if (wst == null) { sascha@2194: return error(new WW[0], "no.wst.for.river"); sascha@2194: } sascha@2194: felix@2257: Map kms2gaugeDatums = r.queryGaugeDatumsKMs(); sascha@2256: sascha@2326: return calc5.calculate(wst, kms2gaugeDatums, context); sascha@2194: } sascha@2194: felix@2229: felix@2251: /** Get reference (start) km. */ felix@2754: public Double getReferenceStartKm() { felix@2308: StateData sd = getData("reference_startpoint"); sascha@2194: sascha@2194: if (sd == null) { sascha@2194: logger.warn("no reference start given."); sascha@2194: return null; sascha@2194: } sascha@2194: felix@2251: logger.debug("Reference start km given: " + sd.getValue()); felix@2251: felix@2251: String input = (String) sd.getValue(); sascha@2194: sascha@2726: if (input == null || (input = input.trim()).length() == 0) { sascha@2194: logger.warn("reference start string is empty."); sascha@2194: return null; sascha@2194: } sascha@2194: sascha@2194: try { sascha@2194: return Double.valueOf(input); sascha@2194: } sascha@2194: catch (NumberFormatException nfe) { sascha@2194: logger.warn("reference start string is not numeric."); sascha@2194: } sascha@2194: sascha@2194: return null; sascha@2194: } sascha@2194: felix@2251: felix@2754: /** felix@2754: * Get end kms for reference curve (null if none). felix@2754: */ felix@2754: public double [] getReferenceEndKms() { felix@2308: StateData sd = getData("reference_endpoint"); sascha@2194: sascha@2194: if (sd == null) { sascha@2194: logger.warn("no reference end given."); sascha@2194: return null; sascha@2194: } felix@2251: else { felix@2251: logger.debug("Reference end km : " + sd.getValue()); felix@2251: } sascha@2194: felix@2251: String input = (String) sd.getValue(); sascha@2194: sascha@2415: if (input == null || (input = input.trim()).length() == 0) { sascha@2194: logger.warn("reference end string is empty."); sascha@2194: return null; sascha@2194: } sascha@2194: sascha@2194: TDoubleArrayList endKms = new TDoubleArrayList(); sascha@2194: sascha@2194: for (String part: input.split("\\s+")) { sascha@2194: try { sascha@2309: double km = Double.parseDouble(part); sascha@2309: if (!endKms.contains(km)) { sascha@2309: endKms.add(km); sascha@2309: } sascha@2194: } sascha@2194: catch (NumberFormatException nfe) { sascha@2194: logger.warn("reference end string is not numeric."); sascha@2194: } sascha@2194: } sascha@2194: sascha@2194: return endKms.toNativeArray(); sascha@2194: } felix@1137: ingo@2215: felix@1122: /** felix@3124: * Get corrected waterline against surface/profile. felix@3124: */ felix@3124: public Lines.LineData waterLineC(int idx, FastCrossSectionLine csl) { felix@3124: List points = csl.getPoints(); felix@3124: felix@3124: WQKms[] wqckms = (WQKms[]) felix@3124: getDischargeLongitudinalSectionData().getData(); felix@3124: felix@3124: // Find index of km. felix@3124: double wishKM = csl.getKm(); felix@3124: felix@3124: // Find W/C at km, linear naive approach. felix@3124: WQCKms triple = (WQCKms) wqckms[idx-1]; felix@3124: felix@3124: if (triple.size() == 0) { felix@3124: logger.warn("Calculation of c/waterline is empty."); felix@3124: return Lines.createWaterLines(points, 0.0f); felix@3124: } felix@3124: felix@3124: // Linear seach in WQKms for closest km. felix@3124: double old_dist_wish = Math.abs(wishKM - triple.getKm(0)); felix@3124: double last_c = triple.getC(0); felix@3124: felix@3124: for (int i = 0, T = triple.size(); i < T; i++) { felix@3124: double diff = Math.abs(wishKM - triple.getKm(i)); felix@3124: if (diff > old_dist_wish) { felix@3124: break; felix@3124: } felix@3124: last_c = triple.getC(i); felix@3124: old_dist_wish = diff; felix@3124: } felix@3124: felix@3124: return Lines.createWaterLines(points, last_c); felix@3124: } felix@3124: felix@3124: felix@3124: /** felix@1122: * Get points of line describing the surface of water at cross section. felix@1122: * felix@3123: * @param idx Index for getWaterlevelData. felix@3123: * @param csl The profile/surface to fill with water. felix@3272: * @param nextIgnored Ignored in this implementation of WaterLineArtifact. felix@3272: * @param prevIgnored Ignored in this implementation of WaterLineArtifact. felix@3123: * 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@2652: @Override felix@3272: public Lines.LineData getWaterLines(int idx, FastCrossSectionLine csl, felix@4479: double nextIgnored, double prevIgnored, CallContext context) { felix@1975: logger.debug("getWaterLines(" + idx + ")"); felix@1975: sascha@2120: List points = csl.getPoints(); felix@1802: felix@1139: // Need W at km felix@4479: Object waterlevelResult = getWaterlevelData(context).getData(); felix@4479: WQKms [] wqkms; felix@4479: felix@4479: if (waterlevelResult instanceof ExtremeResult) { felix@4479: wqkms = ((ExtremeResult) waterlevelResult).getWQKms(); felix@4479: } felix@4479: else { felix@4479: wqkms = (WQKms[]) waterlevelResult; felix@4479: } felix@4479: felix@1139: if (wqkms.length == 0) { felix@1139: logger.error("No WQKms found."); sascha@1651: return Lines.createWaterLines(points, 0.0f); felix@1139: } felix@1139: felix@3123: if (wqkms.length <= idx) { felix@1802: logger.error("getWaterLines() requested index (" felix@1802: + idx + " not found."); felix@3124: return waterLineC(idx, csl); sascha@1651: } felix@1802: felix@4479: // Find W at km, linear naive approach. felix@4479: WQKms triple = wqkms[idx]; felix@4479: felix@3124: // Find index of km. felix@3124: double wishKM = csl.getKm(); felix@3124: sascha@1651: if (triple.size() == 0) { sascha@1651: logger.warn("Calculation of waterline is empty."); sascha@1651: return Lines.createWaterLines(points, 0.0f); sascha@1651: } sascha@1651: felix@3892: // Early abort if we would need to extrapolate. felix@3892: int T = triple.size(); felix@3892: double max_km = triple.getKm(T-1), min_km = triple.getKm(0); felix@3892: if (wishKM < min_km || wishKM > max_km) { felix@3892: // TODO Does this have to be done in the other WaterlineArtifact felix@3892: // implementations, too? felix@3892: logger.warn("Will not extrapolate waterlevels."); felix@3892: return Lines.createWaterLines(points, 0.0f); felix@3892: } felix@3892: felix@3892: int old_idx = 0; felix@3892: sascha@1651: // Linear seach in WQKms for closest km. sascha@1651: double old_dist_wish = Math.abs(wishKM - triple.getKm(0)); sascha@1651: double last_w = triple.getW(0); sascha@1651: felix@3892: for (int i = 0; i < T; i++) { sascha@1651: double diff = Math.abs(wishKM - triple.getKm(i)); sascha@1651: if (diff > old_dist_wish) { sascha@1651: break; felix@1139: } sascha@1651: last_w = triple.getW(i); sascha@1651: old_dist_wish = diff; felix@1139: } felix@3123: sascha@1651: return Lines.createWaterLines(points, last_w); 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@2165: public double [][] getQsForWs(double[] ws) { sascha@1055: sascha@2415: if (ws == null) { sascha@2415: logger.error("getQsForWs: ws == null"); sascha@2415: return null; sascha@2415: } sascha@2415: sascha@1055: boolean debug = logger.isDebugEnabled(); sascha@1055: sascha@1055: if (debug) { teichmann@5867: logger.debug("D4EArtifact.getQsForWs"); sascha@1055: } sascha@1055: teichmann@5865: River r = RiverUtils.getRiver(this); sascha@1055: if (r == null) { sascha@1055: logger.warn("no river found"); sascha@1055: return null; sascha@1055: } sascha@1055: teichmann@6101: RangeAccess rangeAccess = new RangeAccess(this); felix@4861: double [] range = rangeAccess.getKmRange(); sascha@1055: if (range == null) { sascha@1055: logger.warn("no ranges found"); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@2164: if (isFreeW()) { sascha@2415: logger.debug("Bezugslinienverfahren I: W auf freier Strecke"); sascha@2165: // The simple case of the "Bezugslinienverfahren" sascha@2165: // "W auf freier Strecke". sascha@2164: WstValueTable wst = WstValueTableFactory.getTable(r); sascha@2164: if (wst == null) { sascha@2164: logger.warn("no wst value table found"); sascha@2164: return null; sascha@2164: } sascha@2164: double km = range[0]; sascha@2164: sascha@2165: TDoubleArrayList outQs = new TDoubleArrayList(ws.length); sascha@2165: TDoubleArrayList outWs = new TDoubleArrayList(ws.length); sascha@2165: sascha@2165: boolean generatedWs = false; sascha@2164: sascha@2164: for (int i = 0; i < ws.length; ++i) { sascha@2415: double w = ws[i]; sascha@2415: if (debug) { sascha@2415: logger.debug("getQsForWs: lookup Q for W: " + w); sascha@2415: } sascha@2165: // There could bemore than one Q per W. sascha@2415: double [] qs = wst.findQsForW(km, w); sascha@2165: for (int j = 0; j < qs.length; ++j) { sascha@2165: outWs.add(ws[i]); sascha@2165: outQs.add(qs[j]); sascha@2164: } sascha@2165: generatedWs |= qs.length != 1; sascha@2164: } sascha@2164: sascha@2415: if (debug) { sascha@2415: logger.debug("getQsForWs: number of Qs: " + outQs.size()); sascha@2415: } sascha@2415: sascha@3076: return new double [][] { sascha@3076: outQs.toNativeArray(), sascha@2165: generatedWs ? outWs.toNativeArray() : null }; sascha@2164: } sascha@2164: 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@2418: DischargeTable dt = g.fetchMasterDischargeTable(); sascha@1055: sascha@2418: if (dt == null) { sascha@2418: logger.warn("No master discharge table found for gauge '" sascha@2418: + g.getName() + "'"); sascha@2418: return null; sascha@2418: } sascha@2418: aheinecke@6301: double [][] values = DischargeTables.loadDischargeTableValues(dt); sascha@1055: sascha@2415: TDoubleArrayList wsOut = new TDoubleArrayList(ws.length); sascha@2415: TDoubleArrayList qsOut = new TDoubleArrayList(ws.length); sascha@2415: sascha@2418: boolean generatedWs = false; sascha@2418: sascha@1055: for (int i = 0; i < ws.length; i++) { sascha@2415: if (Double.isNaN(ws[i])) { sascha@2415: logger.warn("W is NaN: ignored"); sascha@2415: continue; sascha@2415: } aheinecke@6301: double [] qs = DischargeTables.getQsForW(values, ws[i]); sascha@2418: sascha@2418: if (qs.length == 0) { sascha@2418: logger.warn("No Qs found for W = " + ws[i]); sascha@2415: } sascha@2418: else { sascha@2418: for (double q: qs) { sascha@2418: wsOut.add(ws[i]); aheinecke@6301: qsOut.add(q); sascha@2418: } sascha@1055: } sascha@2418: generatedWs |= qs.length != 1; sascha@1055: } sascha@1055: sascha@2415: return new double [][] { sascha@2415: qsOut.toNativeArray(), sascha@2418: generatedWs ? wsOut.toNativeArray() : null sascha@2415: }; 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()) { teichmann@6101: RangeAccess rangeAccess = new RangeAccess(this); felix@4861: double [] distance = rangeAccess.getKmRange(); 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: } teichmann@6101: RangeAccess rangeAccess = new RangeAccess(this); felix@4861: double [] fromTo = rangeAccess.getKmRange(); 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() { teichmann@5865: return RiverUtils.getGauge(this); sascha@1055: } sascha@1055: 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() { ingo@2423: StateData dMode = getData("wq_isq"); ingo@2423: StateData dSelection = getData("wq_isrange"); sascha@1055: ingo@2422: boolean isRange = dSelection != null ingo@2422: ? Boolean.valueOf((String)dSelection.getValue()) ingo@2422: : false; sascha@1055: ingo@2422: if (isQ()) { ingo@2422: if (!isRange) { 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() { ingo@2423: StateData mode = getData("wq_isq"); ingo@2422: String value = (mode != null) ? (String) mode.getValue() : null; ingo@2422: return value != null ? Boolean.valueOf(value) : false; ingo@2422: } ingo@2422: ingo@2422: public boolean isW() { ingo@2423: StateData mode = getData("wq_isq"); ingo@2422: String value = (mode != null) ? (String) mode.getValue() : null; ingo@2422: return value != null ? !Boolean.valueOf(value) : false; sascha@1055: } sascha@1055: sascha@2164: public boolean isFreeW() { ingo@2422: if(!isW()) { ingo@2422: return false; ingo@2422: } ingo@2423: StateData mode = getData("wq_isfree"); ingo@2422: String value = (mode != null) ? (String) mode.getValue() : null; ingo@2422: ingo@2422: return value != null ? Boolean.valueOf(value) : false; sascha@2164: } sascha@2164: 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() { ingo@2422: if(!isQ()) { ingo@2422: return false; ingo@2422: } ingo@2423: StateData mode = getData("wq_isfree"); felix@1115: String value = (mode != null) ? (String) mode.getValue() : null; sascha@1055: sascha@1055: logger.debug("isFreeQ: " + value); sascha@1055: sascha@2164: return value != null && 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) { ingo@2423: StateData dMode = getData("wq_isq"); sascha@1055: ingo@2422: if (isQ()) { 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) { ingo@2422: if (isW()) { 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() { ingo@2422: if (isW()) { felix@4652: StateData dSingle = getData("wq_single"); 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 { ingo@2422: logger.warn("You try to get Ws, but Q 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 :