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: gernotbelger@9425: import java.io.Serializable; gernotbelger@9425: import java.util.Arrays; gernotbelger@9425: import java.util.Map; gernotbelger@9425: gernotbelger@9425: import org.apache.log4j.Logger; teichmann@5831: import org.dive4elements.artifactdatabase.data.StateData; teichmann@5831: import org.dive4elements.artifactdatabase.state.Facet; teichmann@5831: import org.dive4elements.artifactdatabase.state.FacetActivity; teichmann@5831: import org.dive4elements.artifacts.Artifact; teichmann@5831: import org.dive4elements.artifacts.CallContext; gernotbelger@9479: import org.dive4elements.artifacts.CallMeta; teichmann@5831: import org.dive4elements.artifacts.common.utils.StringUtils; teichmann@5831: import org.dive4elements.river.artifacts.access.Calculation4Access; gernotbelger@9425: import org.dive4elements.river.artifacts.access.ComputationRangeAccess; teichmann@5831: import org.dive4elements.river.artifacts.access.RangeAccess; gernotbelger@9130: import org.dive4elements.river.artifacts.access.RiverAccess; gernotbelger@9425: import org.dive4elements.river.artifacts.model.Calculation; 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.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: import org.dive4elements.river.artifacts.model.extreme.ExtremeResult; teichmann@5831: import org.dive4elements.river.artifacts.states.DefaultState.ComputeType; teichmann@5831: import org.dive4elements.river.model.DischargeTable; teichmann@5831: import org.dive4elements.river.model.Gauge; teichmann@5831: import org.dive4elements.river.model.River; 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: ingo@105: /** ingo@105: * The default WINFO artifact. ingo@105: * ingo@105: * @author Ingo Weinzierl ingo@105: */ gernotbelger@9425: public class WINFOArtifact extends D4EArtifact implements FacetTypes, WaterLineArtifact { ingo@105: teichmann@8202: /** The log for this class. */ teichmann@8202: private static Logger log = 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 */ gernotbelger@9425: public static final String XPATH_STATIC_UI = "/art:result/art:ui/art:static"; ingo@124: gernotbelger@9425: /** gernotbelger@9425: * The default number of steps between the start end end of a selected Q gernotbelger@9425: * range. gernotbelger@9425: */ sascha@1055: public static final int DEFAULT_Q_STEPS = 30; sascha@1055: gernotbelger@9425: private static final String[] INACTIVES = new String[] { LONGITUDINAL_Q, DURATION_Q, STATIC_WQKMS_Q }; sascha@3556: sascha@3556: static { sascha@3556: // TODO: Move to configuration. gernotbelger@9425: FacetActivity.Registry.getInstance().register(ARTIFACT_NAME, new FacetActivity() { gernotbelger@9425: @Override gernotbelger@9425: public Boolean isInitialActive(final Artifact artifact, final Facet facet, final String outputName) { gernotbelger@9425: final String fname = facet.getName(); gernotbelger@9425: if ((fname.equals(MAINVALUES_Q) || fname.equals(MAINVALUES_W)) && outputName.equals("computed_discharge_curve")) { gernotbelger@9425: return Boolean.FALSE; sascha@3556: } gernotbelger@9425: return !StringUtils.contains(fname, INACTIVES); gernotbelger@9425: } gernotbelger@9425: }); sascha@3556: } ingo@121: ingo@105: /** ingo@105: * The default constructor. ingo@105: */ ingo@105: public WINFOArtifact() { ingo@105: } ingo@105: 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: gernotbelger@9425: protected static boolean reportGeneratedWs(final Calculation report, final double[] ws) { sascha@2624: if (ws == null || ws.length < 2) { sascha@2624: return false; sascha@2624: } sascha@2624: gernotbelger@9425: 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: } gernotbelger@9425: } 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: // 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: gernotbelger@9479: private CalculationResult getDischargeLongitudinalSectionData(final CallMeta meta) { andre@8565: // TODO: This caluclation should be cached as it is quite expensive. gernotbelger@9479: return new Calculation4(new Calculation4Access(this)).calculate(meta); 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: */ gernotbelger@9425: public CalculationResult getWaterlevelData(final CallContext context) { teichmann@8202: log.debug("WINFOArtifact.getWaterlevelData"); ingo@362: gernotbelger@9425: final String calculationMode = getDataAsString("calculation_mode"); felix@4479: felix@6619: // If this WINFO-Artifact has a calculation trait. gernotbelger@9425: if (calculationMode != null) { gernotbelger@9130: if (calculationMode.equals("calc.discharge.longitudinal.section")) gernotbelger@9479: return getDischargeLongitudinalSectionData(context.getMeta()); gernotbelger@9130: gernotbelger@9425: if (calculationMode.equals("calc.w.differences")) gernotbelger@9425: return (CalculationResult) this.compute(context, ComputeType.ADVANCE, true); gernotbelger@9425: gernotbelger@9130: log.warn("Unhandled calculation_mode " + calculationMode); felix@4479: } gernotbelger@9425: felix@6619: // Otherwise get it from parameterization. gernotbelger@9425: // TODO: wrong comment: now always a waterlevle computation is executed; actually there is a calc_mode for that, why gernotbelger@9425: // dont check? gernotbelger@9130: return computeWaterlevelData(); gernotbelger@9130: } gernotbelger@9130: gernotbelger@9130: /** Execu5tes the calculation of 'waterlevel', fetches all input data from this artifact */ gernotbelger@9130: private CalculationResult computeWaterlevelData() { gernotbelger@9130: final double[] kms = new ComputationRangeAccess(this).getKms(); gernotbelger@9425: if (kms == null) gernotbelger@9130: return error(new WQKms[0], "no.kms.selected"); gernotbelger@9130: gernotbelger@9130: return computeWaterlevelData(kms); gernotbelger@9130: } gernotbelger@9425: gernotbelger@9425: /** gernotbelger@9130: * Execu5tes the calculation of 'waterlevel'. gernotbelger@9130: * Allows to override the stations for which the calculation is done. All other inputs are fetched from this artifact. gernotbelger@9130: */ gernotbelger@9130: public final CalculationResult computeWaterlevelData(final double kms[]) { gernotbelger@9130: gernotbelger@9130: final River river = new RiverAccess(this).getRiver(); gernotbelger@9425: if (river == null) sascha@2166: return error(new WQKms[0], "no.river.selected"); ingo@362: gernotbelger@9425: double[] qs = getQs(); gernotbelger@9425: double[] ws = null; ingo@447: gernotbelger@9425: final Calculation report = new Calculation(); sascha@2624: ingo@362: if (qs == null) { teichmann@8202: log.debug("Determine Q values based on a set of W values."); gernotbelger@9425: ws = getWs(); gernotbelger@9425: final double[][] qws = getQsForWs(ws, report); 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: gernotbelger@9425: final 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: gernotbelger@9425: final RangeAccess rangeAccess = new RangeAccess(this); gernotbelger@9425: final 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]; teichmann@8202: log.debug("'free' calculation (km " + refKm + ")"); gernotbelger@9425: } else { gernotbelger@9425: final Gauge gauge = river.determineRefGauge(range, rangeAccess.isRange()); tom@8728: sascha@708: if (gauge == null) { gernotbelger@9425: return error(new WQKms[0], "no.gauge.found.for.km", range[0]); sascha@708: } sascha@736: sascha@736: refKm = gauge.getStation().doubleValue(); sascha@736: gernotbelger@9425: log.debug("reference gauge: " + gauge.getName() + " (km " + refKm + ")"); sascha@708: } sascha@708: gernotbelger@9425: return computeWaterlevelData(kms, qs, ws, wst, refKm, report); ingo@447: } ingo@447: ingo@362: /** ingo@362: * Computes the data of a waterlevel computation based on the interpolation ingo@362: * in WstValueTable. ingo@362: * gernotbelger@9425: * @param kms gernotbelger@9425: * The kilometer values. gernotbelger@9425: * @param qs gernotbelger@9425: * The discharge values. gernotbelger@9425: * @param wst gernotbelger@9425: * 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: */ gernotbelger@9425: private static CalculationResult computeWaterlevelData(final double[] kms, final double[] qs, final double[] ws, final WstValueTable wst, gernotbelger@9425: final double refKm, final Calculation report) { teichmann@8202: log.info("WINFOArtifact.computeWaterlevelData"); ingo@362: gernotbelger@9425: final 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: * 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() { teichmann@8202: log.debug("WINFOArtifact.getDurationCurveData"); ingo@385: gernotbelger@9425: final RangeAccess rangeAccess = new RangeAccess(this); ingo@385: gernotbelger@9425: final River r = rangeAccess.getRiver(); ingo@385: if (r == null) { sascha@2166: return error(null, "no.river.selected"); ingo@385: } ingo@385: gernotbelger@9425: final double[] locations = rangeAccess.getLocations(); tom@8758: if (locations == null) { tom@8758: return error(null, "no.locations.selected"); tom@8758: } ingo@385: gernotbelger@9425: final Gauge g = r.determineGaugeByPosition(locations[0]); ingo@385: if (g == null) { gernotbelger@9425: return error(null, "no.gauge.selected"); ingo@385: } ingo@385: gernotbelger@9425: final 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: * Computes the data used to create duration curves. ingo@385: * gernotbelger@9425: * @param gauge gernotbelger@9425: * The selected gauge. gernotbelger@9425: * @param location gernotbelger@9425: * The selected location. ingo@385: * ingo@385: * @return the computed data. ingo@385: */ gernotbelger@9425: private static CalculationResult computeDurationCurveData(final Gauge gauge, final WstValueTable wst, final double location) { teichmann@8202: log.info("WINFOArtifact.computeDurationCurveData"); ingo@385: gernotbelger@9425: final Object[] obj = gauge.fetchDurationCurveData(); ingo@385: gernotbelger@9425: final int[] days = (int[]) obj[0]; gernotbelger@9425: final double[] qs = (double[]) obj[1]; ingo@385: mschaefer@9534: final Calculation3 calculation = new Calculation3(location, days, qs, gauge.getStation().doubleValue()); ingo@385: ingo@686: return calculation.calculate(wst); ingo@385: } 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: */ gernotbelger@9425: public CalculationResult getComputedDischargeCurveData() throws NullPointerException { teichmann@8202: log.debug("WINFOArtifact.getComputedDischargeCurveData"); ingo@393: gernotbelger@9425: final 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: gernotbelger@9425: final RangeAccess rangeAccess = new RangeAccess(this); gernotbelger@9425: final 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: gernotbelger@9425: final 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@393: * Computes the data used to create computed discharge curves. ingo@393: * gernotbelger@9425: * @param wst gernotbelger@9425: * The WstValueTable that is used for the interpolation (river- felix@3272: * bound). gernotbelger@9425: * @param location gernotbelger@9425: * 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 gernotbelger@9425: * location. ingo@393: */ gernotbelger@9425: private static CalculationResult computeDischargeCurveData(final WstValueTable wst, final double location) { teichmann@8202: log.info("WINFOArtifact.computeDischargeCurveData"); ingo@393: gernotbelger@9425: final Calculation2 calculation = new Calculation2(location); ingo@393: sascha@709: return calculation.calculate(wst); sascha@709: } ingo@393: felix@3272: /** Create CalculationResult with data and message. */ gernotbelger@9425: protected static final CalculationResult error(final Object data, final 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. */ gernotbelger@9425: protected static final CalculationResult error(final Object data, final String msg, final Object... args) { felix@5786: return new CalculationResult(data, new Calculation(msg, args)); felix@5786: } felix@5786: 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: */ gernotbelger@9425: public CalculationResult getReferenceCurveData(final CallContext context) { sascha@2194: gernotbelger@9425: final 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: gernotbelger@9425: final 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: gernotbelger@9425: final Calculation5 calc5 = new Calculation5(startKm, endKms); sascha@2194: gernotbelger@9425: final River r = RiverUtils.getRiver(this); sascha@2194: if (r == null) { sascha@2194: return error(new WW[0], "no.river.found"); sascha@2194: } sascha@2194: gernotbelger@9425: final 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: gernotbelger@9425: final Map kms2gaugeDatums = r.queryGaugeDatumsKMs(); sascha@2256: sascha@2326: return calc5.calculate(wst, kms2gaugeDatums, context); sascha@2194: } sascha@2194: felix@2251: /** Get reference (start) km. */ felix@2754: public Double getReferenceStartKm() { gernotbelger@9425: final StateData sd = getData("reference_startpoint"); sascha@2194: sascha@2194: if (sd == null) { teichmann@8202: log.warn("no reference start given."); sascha@2194: return null; sascha@2194: } sascha@2194: teichmann@8202: log.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) { teichmann@8202: log.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: } gernotbelger@9425: catch (final NumberFormatException nfe) { teichmann@8202: log.warn("reference start string is not numeric."); sascha@2194: } sascha@2194: sascha@2194: return null; sascha@2194: } sascha@2194: felix@2754: /** felix@2754: * Get end kms for reference curve (null if none). felix@2754: */ gernotbelger@9425: public double[] getReferenceEndKms() { gernotbelger@9425: final StateData sd = getData("reference_endpoint"); sascha@2194: sascha@2194: if (sd == null) { teichmann@8202: log.warn("no reference end given."); sascha@2194: return null; gernotbelger@9425: } else { teichmann@8202: log.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) { teichmann@8202: log.warn("reference end string is empty."); sascha@2194: return null; sascha@2194: } sascha@2194: gernotbelger@9425: final TDoubleArrayList endKms = new TDoubleArrayList(); sascha@2194: gernotbelger@9425: for (final String part : input.split("\\s+")) { sascha@2194: try { gernotbelger@9425: final double km = Double.parseDouble(part); sascha@2309: if (!endKms.contains(km)) { sascha@2309: endKms.add(km); sascha@2309: } sascha@2194: } gernotbelger@9425: catch (final NumberFormatException nfe) { teichmann@8202: log.warn("reference end string is not numeric."); sascha@2194: } sascha@2194: } sascha@2194: sascha@2194: return endKms.toNativeArray(); sascha@2194: } felix@1137: felix@1122: /** felix@3124: * Get corrected waterline against surface/profile. felix@3124: */ gernotbelger@9479: private double waterLineC(final int idx, final double currentKm, final CallMeta meta) { felix@3124: gernotbelger@9479: final WQKms[] wqckms = (WQKms[]) getDischargeLongitudinalSectionData(meta).getData(); felix@3124: felix@3124: // Find index of km. gernotbelger@9425: final double wishKM = currentKm; felix@3124: felix@3124: // Find W/C at km, linear naive approach. gernotbelger@9425: final WQCKms triple = (WQCKms) wqckms[idx - 1]; felix@3124: felix@3124: if (triple.size() == 0) { teichmann@8202: log.warn("Calculation of c/waterline is empty."); gernotbelger@9425: return Double.NaN; 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++) { gernotbelger@9425: final 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: gernotbelger@9425: return last_c; felix@3124: } felix@3124: felix@3124: /** felix@1122: * Get points of line describing the surface of water at cross section. felix@1122: * gernotbelger@9425: * @param idx gernotbelger@9425: * Index for getWaterlevelData. gernotbelger@9425: * @param csl gernotbelger@9425: * The profile/surface to fill with water. gernotbelger@9425: * @param nextIgnored gernotbelger@9425: * Ignored in this implementation of WaterLineArtifact. gernotbelger@9425: * @param prevIgnored gernotbelger@9425: * 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 gernotbelger@9425: public double getWaterLevel(final ComputeType type, final String hash, final String stateId, final double currentKm, final Serializable waterLineIndex, gernotbelger@9425: final double nextKm, final double prevKm, final CallContext context) { felix@1975: gernotbelger@9425: final int idx = (int) waterLineIndex; felix@1802: felix@1139: // Need W at km gernotbelger@9425: final Object waterlevelResult = getWaterlevelData(context).getData(); gernotbelger@9425: WQKms[] wqkms; felix@4479: felix@4479: if (waterlevelResult instanceof ExtremeResult) { felix@4479: wqkms = ((ExtremeResult) waterlevelResult).getWQKms(); gernotbelger@9425: } else { felix@4479: wqkms = (WQKms[]) waterlevelResult; felix@4479: } felix@4479: felix@1139: if (wqkms.length == 0) { teichmann@8202: log.error("No WQKms found."); gernotbelger@9425: return Double.NaN; felix@1139: } felix@1139: felix@3123: if (wqkms.length <= idx) { gernotbelger@9425: log.error("getWaterLines() requested index (" + idx + " not found."); gernotbelger@9479: return waterLineC(idx, currentKm, context.getMeta()); sascha@1651: } felix@1802: felix@4479: // Find W at km, linear naive approach. gernotbelger@9425: final WQKms triple = wqkms[idx]; felix@4479: felix@3124: // Find index of km. gernotbelger@9425: final double wishKM = currentKm; felix@3124: sascha@1651: if (triple.size() == 0) { teichmann@8202: log.warn("Calculation of waterline is empty."); gernotbelger@9425: return Double.NaN; sascha@1651: } sascha@1651: felix@3892: // Early abort if we would need to extrapolate. gernotbelger@9425: final int T = triple.size(); gernotbelger@9425: final 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 gernotbelger@9425: // implementations, too? teichmann@8202: log.warn("Will not extrapolate waterlevels."); gernotbelger@9425: return Double.NaN; felix@3892: } 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++) { gernotbelger@9425: final 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: gernotbelger@9425: return last_w; felix@1122: } felix@1122: felix@1122: /** tom@8728: * Returns the Qs for a number of Ws. sascha@1055: * gernotbelger@9425: * @param ws gernotbelger@9425: * An array of W values. sascha@1055: * sascha@1055: * @return an array of Q values. sascha@1055: */ gernotbelger@9425: private double[][] getQsForWs(final double[] ws, final Calculation report) { sascha@1055: sascha@2415: if (ws == null) { teichmann@8202: log.error("getQsForWs: ws == null"); sascha@2415: return null; sascha@2415: } sascha@2415: gernotbelger@9425: final boolean debug = log.isDebugEnabled(); sascha@1055: sascha@1055: if (debug) { teichmann@8202: log.debug("D4EArtifact.getQsForWs"); sascha@1055: } sascha@1055: gernotbelger@9425: final River r = RiverUtils.getRiver(this); sascha@1055: if (r == null) { teichmann@8202: log.warn("no river found"); sascha@1055: return null; sascha@1055: } sascha@1055: gernotbelger@9425: final RangeAccess rangeAccess = new RangeAccess(this); gernotbelger@9425: final double[] range = rangeAccess.getKmRange(); sascha@1055: if (range == null) { teichmann@8202: log.warn("no ranges found"); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@2164: if (isFreeW()) { teichmann@8202: log.debug("Bezugslinienverfahren I: W auf freier Strecke"); sascha@2165: // The simple case of the "Bezugslinienverfahren" sascha@2165: // "W auf freier Strecke". gernotbelger@9425: final WstValueTable wst = WstValueTableFactory.getTable(r); sascha@2164: if (wst == null) { teichmann@8202: log.warn("no wst value table found"); sascha@2164: return null; sascha@2164: } gernotbelger@9425: final double km = range[0]; sascha@2164: gernotbelger@9425: final TDoubleArrayList outQs = new TDoubleArrayList(ws.length); gernotbelger@9425: final TDoubleArrayList outWs = new TDoubleArrayList(ws.length); sascha@2165: sascha@2165: boolean generatedWs = false; sascha@2164: gernotbelger@9479: for (final double w : ws) { sascha@2415: if (debug) { teichmann@8202: log.debug("getQsForWs: lookup Q for W: " + w); sascha@2415: } tom@8704: // There could be more than one Q per W. gernotbelger@9425: final double[] qs = wst.findQsForW(km, w, report); gernotbelger@9479: for (final double element : qs) { gernotbelger@9479: outWs.add(w); gernotbelger@9479: outQs.add(element); sascha@2164: } sascha@2165: generatedWs |= qs.length != 1; sascha@2164: } sascha@2164: sascha@2415: if (debug) { teichmann@8202: log.debug("getQsForWs: number of Qs: " + outQs.size()); sascha@2415: } sascha@2415: gernotbelger@9425: return new double[][] { outQs.toNativeArray(), generatedWs ? outWs.toNativeArray() : null }; sascha@2164: } sascha@2164: sascha@1055: if (debug) { teichmann@8202: log.debug("range: " + Arrays.toString(range)); sascha@1055: } sascha@1055: gernotbelger@9425: final Gauge g = rangeAccess.getRiver().determineRefGauge(range, rangeAccess.isRange()); sascha@1055: if (g == null) { teichmann@8202: log.warn("no gauge found for km: " + range[0]); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: if (debug) { teichmann@8202: log.debug("convert w->q with gauge '" + g.getName() + "'"); sascha@1055: } sascha@1055: gernotbelger@9425: final DischargeTable dt = g.fetchMasterDischargeTable(); sascha@1055: sascha@2418: if (dt == null) { gernotbelger@9425: log.warn("No master discharge table found for gauge '" + g.getName() + "'"); sascha@2418: return null; sascha@2418: } sascha@2418: gernotbelger@9425: final double[][] values = DischargeTables.loadDischargeTableValues(dt); sascha@1055: gernotbelger@9425: final TDoubleArrayList wsOut = new TDoubleArrayList(ws.length); gernotbelger@9425: final TDoubleArrayList qsOut = new TDoubleArrayList(ws.length); sascha@2415: sascha@2418: boolean generatedWs = false; sascha@2418: gernotbelger@9479: for (final double element : ws) { gernotbelger@9479: if (Double.isNaN(element)) { teichmann@8202: log.warn("W is NaN: ignored"); sascha@2415: continue; sascha@2415: } gernotbelger@9479: final double[] qs = DischargeTables.getQsForW(values, element); sascha@2418: sascha@2418: if (qs.length == 0) { gernotbelger@9479: log.warn("No Qs found for W = " + element); gernotbelger@9425: } else { gernotbelger@9425: for (final double q : qs) { gernotbelger@9479: wsOut.add(element); aheinecke@6301: qsOut.add(q); sascha@2418: } sascha@1055: } sascha@2418: generatedWs |= qs.length != 1; sascha@1055: } sascha@1055: gernotbelger@9425: return new double[][] { qsOut.toNativeArray(), generatedWs ? wsOut.toNativeArray() : null }; sascha@1055: } sascha@1055: sascha@1055: /** sascha@1055: * Returns the selected distance based on a given range (from, to). sascha@1055: * gernotbelger@9425: * @param dFrom gernotbelger@9425: * The StateData that contains the lower value. gernotbelger@9425: * @param dTo gernotbelger@9425: * The StateData that contains the upper value. sascha@1055: * sascha@1055: * @return the selected distance. sascha@1055: */ gernotbelger@9425: protected double[] getDistanceByRange(final StateData dFrom, final StateData dTo) { gernotbelger@9425: final double from = Double.parseDouble((String) dFrom.getValue()); gernotbelger@9425: final double to = Double.parseDouble((String) dTo.getValue()); sascha@1055: sascha@1055: return new double[] { from, to }; 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() { gernotbelger@9425: final StateData dMode = getData("wq_isq"); gernotbelger@9425: final StateData dSelection = getData("wq_isrange"); sascha@1055: gernotbelger@9425: final boolean isRange = dSelection != null ? Boolean.valueOf((String) dSelection.getValue()) : false; sascha@1055: ingo@2422: if (isQ()) { ingo@2422: if (!isRange) { sascha@1055: return getSingleWQValues(); gernotbelger@9425: } else { sascha@1055: return getWQTriple(); sascha@1055: } gernotbelger@9425: } else { teichmann@8202: log.warn("You try to get Qs, but W has been inserted."); sascha@1055: return null; sascha@1055: } sascha@1055: } sascha@1055: sascha@1055: public boolean isQ() { gernotbelger@9425: final StateData mode = getData("wq_isq"); gernotbelger@9425: final 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() { gernotbelger@9425: final StateData mode = getData("wq_isq"); gernotbelger@9425: final 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() { gernotbelger@9425: if (!isW()) { ingo@2422: return false; ingo@2422: } gernotbelger@9425: final StateData mode = getData("wq_isfree"); gernotbelger@9425: final 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: * 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 gernotbelger@9425: * false and the calculation is bound to a gauge. sascha@1055: */ sascha@1055: public boolean isFreeQ() { gernotbelger@9425: if (!isQ()) { ingo@2422: return false; ingo@2422: } gernotbelger@9425: final StateData mode = getData("wq_isfree"); gernotbelger@9425: final String value = (mode != null) ? (String) mode.getValue() : null; sascha@1055: teichmann@8202: log.debug("isFreeQ: " + value); sascha@1055: sascha@2164: return value != null && Boolean.valueOf(value); sascha@1055: } sascha@1055: sascha@1055: /** sascha@1055: * Returns the Q values based on a specified kilometer range. sascha@1055: * gernotbelger@9425: * @param range gernotbelger@9425: * A 2dim array with lower and upper kilometer range. sascha@1055: * sascha@1055: * @return an array of Q values. sascha@1055: */ gernotbelger@9425: public double[] getQs(final double[] range) { gernotbelger@9425: final StateData dMode = getData("wq_isq"); sascha@1055: ingo@2422: if (isQ()) { sascha@1055: return getWQForDist(range); sascha@1055: } sascha@1055: teichmann@8202: log.warn("You try to get Qs, but Ws has been inserted."); sascha@1055: return null; sascha@1055: } sascha@1055: sascha@1055: /** sascha@1055: * Returns the W values based on a specified kilometer range. sascha@1055: * gernotbelger@9425: * @param range gernotbelger@9425: * A 2dim array with lower and upper kilometer range. sascha@1055: * sascha@1055: * @return an array of W values. sascha@1055: */ gernotbelger@9425: public double[] getWs(final double[] range) { ingo@2422: if (isW()) { sascha@1055: return getWQForDist(range); sascha@1055: } sascha@1055: teichmann@8202: log.warn("You try to get Ws, but Qs has been inserted."); sascha@1055: return null; 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()) { gernotbelger@9425: final StateData dSingle = getData("wq_single"); sascha@1055: if (dSingle != null) { sascha@1055: return getSingleWQValues(); gernotbelger@9425: } else { sascha@1055: return getWQTriple(); sascha@1055: } gernotbelger@9425: } else { teichmann@8202: log.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: * gernotbelger@9425: * @param dist gernotbelger@9425: * A 2dim array with lower und upper kilometer values. sascha@1055: * sascha@1055: * @return an array of W or Q values. sascha@1055: */ gernotbelger@9425: protected double[] getWQForDist(final double[] dist) { teichmann@8202: log.debug("Search wq values for range: " + dist[0] + " - " + dist[1]); gernotbelger@9425: final StateData data = getData("wq_values"); sascha@1055: sascha@1055: if (data == null) { teichmann@8202: log.warn("Missing wq values!"); sascha@1055: return null; sascha@1055: } sascha@1055: gernotbelger@9425: final String dataString = (String) data.getValue(); gernotbelger@9425: final String[] ranges = dataString.split(":"); sascha@1055: gernotbelger@9425: for (final String range : ranges) { gernotbelger@9425: final String[] parts = range.split(";"); sascha@1055: gernotbelger@9425: final double lower = Double.parseDouble(parts[0]); gernotbelger@9425: final double upper = Double.parseDouble(parts[1]); sascha@1055: sascha@1055: if (lower <= dist[0] && upper >= dist[1]) { gernotbelger@9425: final String[] values = parts[2].split(","); sascha@1055: gernotbelger@9425: final int num = values.length; gernotbelger@9425: final 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: } gernotbelger@9425: catch (final NumberFormatException nfe) { teichmann@8202: log.warn(nfe, nfe); sascha@1055: } sascha@1055: } sascha@1055: sascha@1055: return res; sascha@1055: } sascha@1055: } sascha@1055: teichmann@8202: log.warn("Specified range for WQ not found!"); sascha@1055: sascha@1055: return null; 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() { gernotbelger@9425: final StateData dFrom = getData("wq_from"); gernotbelger@9425: final StateData dTo = getData("wq_to"); sascha@1055: sascha@1055: if (dFrom == null || dTo == null) { teichmann@8202: log.warn("Missing start or end value for range."); sascha@1055: return null; sascha@1055: } sascha@1055: gernotbelger@9425: final double from = Double.parseDouble((String) dFrom.getValue()); gernotbelger@9425: final double to = Double.parseDouble((String) dTo.getValue()); sascha@1055: gernotbelger@9425: final StateData dStep = getData("wq_step"); sascha@1055: sascha@1055: if (dStep == null) { teichmann@8202: log.warn("No step width given. Cannot compute Qs."); sascha@1055: return null; sascha@1055: } sascha@1055: gernotbelger@9425: 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) { gernotbelger@9425: final 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: * 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() { gernotbelger@9425: final StateData dSingle = getData("wq_single"); sascha@1055: sascha@1055: if (dSingle == null) { teichmann@8202: log.warn("Cannot determine single WQ values. No data given."); sascha@1055: return null; sascha@1055: } sascha@1055: gernotbelger@9425: final String tmp = (String) dSingle.getValue(); gernotbelger@9425: final String[] strValues = tmp.split(" "); sascha@1055: gernotbelger@9425: final TDoubleArrayList values = new TDoubleArrayList(); sascha@1055: gernotbelger@9425: for (final String strValue : strValues) { sascha@1055: try { sascha@1055: values.add(Double.parseDouble(strValue)); sascha@1055: } gernotbelger@9425: catch (final NumberFormatException nfe) { teichmann@8202: log.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 :