mschaefer@8898: /* Copyright (C) 2017 by Bundesanstalt für Gewässerkunde mschaefer@8898: * Software engineering by mschaefer@8898: * Björnsen Beratende Ingenieure GmbH mschaefer@8898: * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt mschaefer@8898: * mschaefer@8898: * This file is Free Software under the GNU AGPL (>=v3) mschaefer@8898: * and comes with ABSOLUTELY NO WARRANTY! Check out the mschaefer@8898: * documentation coming with Dive4Elements River for details. mschaefer@8898: */ mschaefer@8898: gernotbelger@8915: package org.dive4elements.river.artifacts.sinfo.tkhcalculation; mschaefer@8898: mschaefer@8898: import java.util.ArrayList; mschaefer@8898: import java.util.List; mschaefer@8898: mschaefer@8898: import org.apache.commons.lang.math.DoubleRange; mschaefer@8898: import org.apache.log4j.Logger; gernotbelger@8915: import org.dive4elements.river.artifacts.math.Linear; gernotbelger@8915: import org.dive4elements.river.artifacts.math.Utils; gernotbelger@8915: import org.dive4elements.river.artifacts.sinfo.flowdepth.FlowVelocityKmModelValues; gernotbelger@8915: import org.dive4elements.river.backend.SessionHolder; gernotbelger@8915: import org.dive4elements.river.model.River; mschaefer@8898: import org.hibernate.SQLQuery; mschaefer@8898: import org.hibernate.Session; mschaefer@8898: import org.hibernate.type.StandardBasicTypes; mschaefer@8898: mschaefer@8898: import gnu.trove.TDoubleArrayList; mschaefer@8898: mschaefer@8898: /** gernotbelger@8915: * Searchable sorted km array with parallel FlowVelocityKmModelValues array and linear interpolation for km and the gernotbelger@8915: * model values between the array elements.
mschaefer@8898: * {@link loadValues} loads all the model values for a given km range of a river.
mschaefer@8898: * {@link findKmQValues} then searches a km in the values table or the nearest including km interval, resp. mschaefer@8898: * The v and tau values for a given discharge are either found directly or also interpolated linearly.
mschaefer@8898: * gernotbelger@8915: * (Created based on a copy of FlowVelocityMeasurementFactory.) gernotbelger@8915: * gernotbelger@8915: * @author Matthias Schäfer mschaefer@8898: */ gernotbelger@8915: final class FlowVelocityModelKmValueFinder { mschaefer@8898: /***** FIELDS *****/ gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * Private log to use here. mschaefer@8898: */ mschaefer@8898: private static Logger log = Logger.getLogger(FlowVelocityModelKmValueFinder.class); gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * Query for a range of stations of a river with all their q, main-v and tau values.
mschaefer@8898: * (Might be several 10000 rows if many stations and large q range) mschaefer@8898: */ gernotbelger@8915: private static final String SQL_SELECT_ALL = // gernotbelger@8915: "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau" gernotbelger@8915: + " FROM (discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)" gernotbelger@8915: + " INNER JOIN flow_velocity_model_values fvmv ON fvm.id = fvmv.flow_velocity_model_id" gernotbelger@8915: + " WHERE (dz.river_id = :river_id) AND (fvmv.station BETWEEN :kmfrom - 0.0001 AND :kmto + 0.0001)" gernotbelger@8915: /* + " WHERE (dz.river_id = :river_id) AND (fvmv.q BETWEEN :qmin AND :qmax)" */ gernotbelger@8915: + " ORDER BY fvmv.station ASC, fvmv.q ASC"; mschaefer@8898: mschaefer@8898: /** mschaefer@8898: * Query for a river's max km below a limit with all its q, main-v and tau values. mschaefer@8898: */ gernotbelger@8915: private static final String SQL_SELECT_KMLOWER = // gernotbelger@8915: "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau" + " FROM flow_velocity_model_values fvmv" gernotbelger@8915: + " INNER JOIN (SELECT MAX(fvmvi.station) AS kmmax" gernotbelger@8915: + " FROM(discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)" gernotbelger@8915: + " INNER JOIN flow_velocity_model_values fvmvi ON fvm.id = fvmvi.flow_velocity_model_id" gernotbelger@8915: + " WHERE (dz.river_id = :river_id) AND (fvmvi.station < :kmfrom - 0.0001)) finf ON fvmv.station = finf.kmmax" gernotbelger@8915: + " ORDER BY fvmv.q ASC"; mschaefer@8898: mschaefer@8898: /** mschaefer@8898: * Query for a river's min km above a limit with all its q, main-v and tau values. mschaefer@8898: */ gernotbelger@8915: private static final String SQL_SELECT_KMUPPER = // gernotbelger@8915: "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau" + " FROM flow_velocity_model_values fvmv" gernotbelger@8915: + " INNER JOIN (SELECT MIN(fvmvi.station) AS kmmin" gernotbelger@8915: + " FROM(discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)" gernotbelger@8915: + " INNER JOIN flow_velocity_model_values fvmvi ON fvm.id = fvmvi.flow_velocity_model_id" gernotbelger@8915: + " WHERE (dz.river_id = :river_id) AND (fvmvi.station > :kmto + 0.0001)) fsup ON fvmv.station = fsup.kmmin" gernotbelger@8915: + " ORDER BY fvmv.q ASC"; mschaefer@8898: gernotbelger@8915: // /** gernotbelger@8915: // * Query to select all km-q-v-tau of a river that are the q maxima below a q limit gernotbelger@8915: // */ gernotbelger@8915: // private static final String SQL_SELECT_QLOWER = gernotbelger@8915: // "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau" gernotbelger@8915: // + " FROM flow_velocity_model_values fvmv" gernotbelger@8915: // + " INNER JOIN (SELECT fv2.station, MAX(fv2.q) AS q" gernotbelger@8915: // + " FROM (discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)" gernotbelger@8915: // + " INNER JOIN flow_velocity_model_values fv2 ON fvm.id = fv2.flow_velocity_model_id" gernotbelger@8915: // + " WHERE (dz.river_id = :river_id) AND (fv2.q < :qlim) GROUP BY fv2.station) qx" gernotbelger@8915: // + " ON (fvmv.station=qx.station) AND (fvmv.q=qx.q)" gernotbelger@8915: // + " ORDER BY fvmv.station ASC"; gernotbelger@8915: // gernotbelger@8915: // /** gernotbelger@8915: // * Query to select all km-q-v-tau of a river that are the q minima above a q limit gernotbelger@8915: // */ gernotbelger@8915: // private static final String SQL_SELECT_QUPPER = gernotbelger@8915: // "SELECT fvmv.station AS station, fvmv.q AS q, fvmv.main_channel AS vmain, fvmv.shear_stress AS tau" gernotbelger@8915: // + " FROM flow_velocity_model_values fvmv" gernotbelger@8915: // + " INNER JOIN (SELECT fv2.station, MIN(fv2.q) AS q" gernotbelger@8915: // + " FROM (discharge_zone dz INNER JOIN flow_velocity_model fvm ON dz.id = fvm.discharge_zone_id)" gernotbelger@8915: // + " INNER JOIN flow_velocity_model_values fv2 ON fvm.id = fv2.flow_velocity_model_id" gernotbelger@8915: // + " WHERE (dz.river_id = :river_id) AND (fv2.q > :qlim) GROUP BY fv2.station) qx" gernotbelger@8915: // + " ON (fvmv.station=qx.station) AND (fvmv.q=qx.q)" gernotbelger@8915: // + " ORDER BY fvmv.station ASC"; gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * Kms of the loaded river range mschaefer@8898: */ gernotbelger@8915: private final TDoubleArrayList kms = new TDoubleArrayList(); gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * For each km in kms a list of q-v-tau-tupels mschaefer@8898: */ gernotbelger@8915: private final List values = new ArrayList<>(); mschaefer@8898: mschaefer@8898: /** mschaefer@8898: * Searched km of the last findKmValue mschaefer@8898: */ mschaefer@8898: private double findKm; gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * kms and values index of the interval start found by the last findKmValue mschaefer@8898: */ mschaefer@8898: private int leftIndexFound = -1; mschaefer@8898: mschaefer@8898: /** mschaefer@8898: * kms and values index of the interval end found by the last findKmValue mschaefer@8898: */ mschaefer@8898: private int rightIndexFound = -1; mschaefer@8898: mschaefer@8898: /** mschaefer@8898: * Q of the last findKmQValues mschaefer@8898: */ mschaefer@8898: private double findQ; mschaefer@8898: mschaefer@8898: /***** METHODS *****/ gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * Discharge of the last {@link findKmQValue} mschaefer@8898: */ mschaefer@8898: public double getFindQ() { gernotbelger@8915: return this.findQ; mschaefer@8898: } gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * Velocity of the last {@link findKmQValues} mschaefer@8898: */ mschaefer@8898: public double getFindVmainFound() { gernotbelger@8915: if (this.leftIndexFound < 0) mschaefer@8898: return Double.NaN; gernotbelger@8915: else if (this.leftIndexFound == this.rightIndexFound) mschaefer@8898: return getLeftValues().getVmainFound(); mschaefer@8898: else gernotbelger@8915: return Linear.linear(this.findKm, getLeftValues().getKm(), getRightValues().getKm(), getLeftValues().getVmainFound(), gernotbelger@8915: getRightValues().getVmainFound()); mschaefer@8898: } gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * Shear stress tau of the last {@link findKmQValues} mschaefer@8898: */ mschaefer@8898: public double getFindTauFound() { gernotbelger@8915: if (this.leftIndexFound < 0) mschaefer@8898: return Double.NaN; gernotbelger@8915: else if (this.leftIndexFound == this.rightIndexFound) mschaefer@8898: return getLeftValues().getTauFound(); mschaefer@8898: else gernotbelger@8915: return Linear.linear(this.findKm, getLeftValues().getKm(), getRightValues().getKm(), getLeftValues().getTauFound(), getRightValues().getTauFound()); mschaefer@8898: } gernotbelger@8915: mschaefer@8898: /** gernotbelger@8915: * Whether the discharge has been interpolated in the last {@link findKmQValues} mschaefer@8898: */ mschaefer@8898: public boolean getFindIsQInterpolated() { mschaefer@8898: return (getLeftValues() != null) && (getLeftValues().getIsInterpolated() || getRightValues().getIsInterpolated()); mschaefer@8898: } gernotbelger@8915: mschaefer@8898: /** gernotbelger@8915: * Static constructor: queries a range of a river's kms with all their q-v-tau values. gernotbelger@8915: * gernotbelger@8915: * @return Whether the load has been successful the new instance, null otherwise. mschaefer@8898: */ gernotbelger@8915: public static FlowVelocityModelKmValueFinder loadValues(final River river, final DoubleRange kmRange, final DoubleRange qRange) { mschaefer@8898: // DB session gernotbelger@8915: log.debug(String.format("loadValues km %.3f - %.3f / q %.1f - %.1f", kmRange.getMinimumDouble(), kmRange.getMaximumDouble(), qRange.getMinimumDouble(), gernotbelger@8915: qRange.getMaximumDouble())); gernotbelger@8915: gernotbelger@8915: final FlowVelocityModelKmValueFinder instance = new FlowVelocityModelKmValueFinder(); gernotbelger@8915: gernotbelger@8915: final TDoubleArrayList kms = instance.kms; gernotbelger@8915: final List values = instance.values; gernotbelger@8915: gernotbelger@8915: final boolean isDemoValuesCorrection = river.getName().equalsIgnoreCase("beispielfluss"); mschaefer@8898: final Session session = SessionHolder.HOLDER.get(); gernotbelger@8915: mschaefer@8898: // Select km infimum gernotbelger@8915: SQLQuery sqlQuery = session.createSQLQuery(SQL_SELECT_KMLOWER).addScalar("station", StandardBasicTypes.DOUBLE).addScalar("q", StandardBasicTypes.DOUBLE) gernotbelger@8915: .addScalar("vmain", StandardBasicTypes.DOUBLE).addScalar("tau", StandardBasicTypes.DOUBLE); mschaefer@8898: sqlQuery.setParameter("river_id", river.getId()); mschaefer@8898: sqlQuery.setParameter("kmfrom", kmRange.getMinimumDouble()); gernotbelger@8915: instance.addKms(sqlQuery.list(), isDemoValuesCorrection); mschaefer@8898: mschaefer@8898: // Select km range gernotbelger@8915: sqlQuery = session.createSQLQuery(SQL_SELECT_ALL).addScalar("station", StandardBasicTypes.DOUBLE).addScalar("q", StandardBasicTypes.DOUBLE) gernotbelger@8915: .addScalar("vmain", StandardBasicTypes.DOUBLE).addScalar("tau", StandardBasicTypes.DOUBLE); mschaefer@8898: sqlQuery.setParameter("river_id", river.getId()); mschaefer@8898: sqlQuery.setParameter("kmfrom", kmRange.getMinimumDouble()); mschaefer@8898: sqlQuery.setParameter("kmto", kmRange.getMaximumDouble()); gernotbelger@8915: // sqlQuery.setParameter("qmin", qRange.getMinimumDouble()); gernotbelger@8915: // sqlQuery.setParameter("qmax", qRange.getMaximumDouble()); gernotbelger@8915: mschaefer@8898: int kmcount = kms.size(); gernotbelger@8915: final int rowcount = instance.addKms(sqlQuery.list(), isDemoValuesCorrection); mschaefer@8898: kmcount = kms.size() - kmcount; gernotbelger@8915: gernotbelger@8915: // Select km supremum gernotbelger@8915: sqlQuery = session.createSQLQuery(SQL_SELECT_KMUPPER).addScalar("station", StandardBasicTypes.DOUBLE).addScalar("q", StandardBasicTypes.DOUBLE) gernotbelger@8915: .addScalar("vmain", StandardBasicTypes.DOUBLE).addScalar("tau", StandardBasicTypes.DOUBLE); mschaefer@8898: sqlQuery.setParameter("river_id", river.getId()); mschaefer@8898: sqlQuery.setParameter("kmto", kmRange.getMaximumDouble()); gernotbelger@8915: final int supcnt = instance.addKms(sqlQuery.list(), isDemoValuesCorrection); mschaefer@8898: mschaefer@8898: // Add copy of last km for search of max km value mschaefer@8898: if ((supcnt == 0) && (values.size() >= 1)) { mschaefer@8898: kms.add(kms.getQuick(kms.size()) + 0.0001); gernotbelger@8915: values.add(new FlowVelocityKmModelValues(kms.getQuick(kms.size() - 1), values.get(values.size() - 1))); mschaefer@8898: } gernotbelger@8915: mschaefer@8898: // log.debug mschaefer@8898: if (values.size() - 1 >= 0) { mschaefer@8898: log.debug(String.format("loadValues %d: km %.3f - %d values", 0, values.get(0).getKm(), values.get(0).size())); gernotbelger@8915: mschaefer@8898: if (values.size() - 1 >= 1) { mschaefer@8898: log.debug(String.format("loadValues %d: km %.3f - %d values", 1, values.get(1).getKm(), values.get(1).size())); gernotbelger@8915: mschaefer@8898: if (values.size() - 1 >= 2) mschaefer@8898: log.debug("loadValues ..."); gernotbelger@8915: mschaefer@8898: if (values.size() - 2 >= 3) gernotbelger@8915: log.debug(String.format("loadValues %d: km %.3f - %d values", values.size() - 2, values.get(values.size() - 2).getKm(), gernotbelger@8915: values.get(values.size() - 2).size())); gernotbelger@8915: mschaefer@8898: if (values.size() - 1 >= 3) gernotbelger@8915: log.debug(String.format("loadValues %d: km %.3f - %d values", values.size() - 1, values.get(values.size() - 1).getKm(), gernotbelger@8915: values.get(values.size() - 1).size())); mschaefer@8898: } mschaefer@8898: } gernotbelger@8915: mschaefer@8898: log.debug(String.format("loadValues %d kms, %d values loaded", kmcount, rowcount)); gernotbelger@8915: gernotbelger@8915: if (kms.size() == 0) gernotbelger@8915: return null; gernotbelger@8915: gernotbelger@8915: return instance; mschaefer@8898: } gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * Adds the km-q-v-tau values of a query result row to the last km of the list, or a new one resp. gernotbelger@8915: * mschaefer@8898: * @return Number of rows mschaefer@8898: */ gernotbelger@8915: private int addKms(final List rows, final boolean isDemoValuesCorrection) { gernotbelger@8915: for (final Object[] row : rows) { gernotbelger@8915: if ((this.kms.size() == 0) || !Utils.epsilonEquals(this.kms.get(this.kms.size() - 1), (double) row[0], 0.0001)) { gernotbelger@8915: this.kms.add((double) row[0]); gernotbelger@8915: this.values.add(new FlowVelocityKmModelValues(this.kms.get(this.kms.size() - 1))); mschaefer@8898: } mschaefer@8898: if (isDemoValuesCorrection) mschaefer@8898: // "Verfremdung" der v-Werte etwas korrigieren (Originalwerte wurden mit Zufallswert zwischen 10 und 20 multipliziert) gernotbelger@8915: this.values.get(this.values.size() - 1).addValues((double) row[1], ((double) row[2]) / 10, (double) row[3]); mschaefer@8898: else gernotbelger@8915: this.values.get(this.values.size() - 1).addValues((double) row[1], (double) row[2], (double) row[3]); mschaefer@8898: } mschaefer@8898: return rows.size(); mschaefer@8898: } gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * Searches a km and finds or interpolates the velocity and shear stress values for a discharge
mschaefer@8898: * The values may be got via {@link getVmainFound} etc. gernotbelger@8915: * mschaefer@8898: * @return Whether values have been found mschaefer@8898: */ gernotbelger@8915: public boolean findKmQValues(final double km, final double q) { gernotbelger@8915: this.findQ = q; mschaefer@8898: if (!searchKm(km)) mschaefer@8898: return false; gernotbelger@8915: if (this.leftIndexFound == this.rightIndexFound) { mschaefer@8898: // Exact km match mschaefer@8898: final double qfound = getLeftValues().findQ(q); gernotbelger@8915: log.debug(String.format("findKmQValues km %.3f q %.0f = %.0f (%d)", km, q, qfound, this.leftIndexFound)); mschaefer@8898: return !Double.isNaN(qfound); gernotbelger@8915: } else { gernotbelger@8915: final double[] qfound = { getLeftValues().findQ(q), getRightValues().findQ(q) }; gernotbelger@8915: log.debug(String.format("findKmQValues km %.3f q %.0f = %.0f (%d, %.3f) - %.0f (%d, %.3f)", km, q, qfound[0], this.leftIndexFound, gernotbelger@8915: getLeftValues().getKm(), qfound[1], this.rightIndexFound, getRightValues().getKm())); mschaefer@8898: return !Double.isNaN(qfound[0]) && !Double.isNaN(qfound[1]); mschaefer@8898: } mschaefer@8898: } gernotbelger@8915: mschaefer@8898: /** mschaefer@8898: * Searches a km gernotbelger@8915: * mschaefer@8898: * @return Whether the km was within the supported range mschaefer@8898: */ gernotbelger@8915: private boolean searchKm(final double km) { gernotbelger@8915: this.findKm = km; gernotbelger@8915: this.leftIndexFound = -1; gernotbelger@8915: this.rightIndexFound = -1; gernotbelger@8915: if ((this.kms == null) || (this.kms.size() == 0)) mschaefer@8898: return false; gernotbelger@8915: int i = this.kms.binarySearch(km); mschaefer@8898: if (i >= 0) { mschaefer@8898: // Exact km match gernotbelger@8915: this.leftIndexFound = i; gernotbelger@8915: this.rightIndexFound = i; mschaefer@8898: return true; gernotbelger@8915: } else { mschaefer@8898: // Out of range or within km interval mschaefer@8898: if (i < 0) mschaefer@8898: i = -i - 1; gernotbelger@8915: if ((i <= 0) || (i >= this.kms.size())) mschaefer@8898: return false; gernotbelger@8915: this.leftIndexFound = i - 1; gernotbelger@8915: this.rightIndexFound = i; mschaefer@8898: return true; mschaefer@8898: } mschaefer@8898: } gernotbelger@8915: mschaefer@8898: private FlowVelocityKmModelValues getLeftValues() { gernotbelger@8915: return this.values.get(this.leftIndexFound); mschaefer@8898: } mschaefer@8898: gernotbelger@8915: private FlowVelocityKmModelValues getRightValues() { gernotbelger@8915: return this.values.get(this.rightIndexFound); gernotbelger@8915: } gernotbelger@8915: }