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 :