# HG changeset patch # User mschaefer # Date 1570539804 -7200 # Node ID f2473dc34535fd33cf09c7d47901c2ec8cd6aa6f # Parent 6b2496d719361280e96bcb74a893cda9b7d432ce Nachtrag Pos. 19: Q calculation with historical discharge tables instead of master discharge table diff -r 6b2496d71936 -r f2473dc34535 artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/collision/CollisionCalculation.java --- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/collision/CollisionCalculation.java Tue Feb 12 14:08:16 2019 +0100 +++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/collision/CollisionCalculation.java Tue Oct 08 15:03:24 2019 +0200 @@ -11,10 +11,13 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.NavigableSet; +import java.util.TreeMap; import java.util.TreeSet; import org.apache.commons.lang.math.DoubleRange; @@ -32,6 +35,8 @@ import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils; import org.dive4elements.river.artifacts.sinfo.util.RiverInfo; import org.dive4elements.river.backend.utils.DateUtil; +import org.dive4elements.river.model.DischargeTable; +import org.dive4elements.river.model.Gauge; import org.dive4elements.river.model.MainValueType.MainValueTypeKey; import org.dive4elements.river.model.River; import org.dive4elements.river.model.sinfo.CollisionAggregateValue; @@ -43,6 +48,12 @@ private final CallContext context; + private CollisionAccess access; + private River river; + private List years; + private Calculation problems; + private int qfinderProblemCount; + public CollisionCalculation(final CallContext context) { this.context = context; } @@ -52,50 +63,49 @@ final String user = CalculationUtils.findArtifactUser(this.context, sinfo); // access input data - final CollisionAccess access = new CollisionAccess(sinfo); - final River river = access.getRiver(); - final RiverInfo riverInfo = new RiverInfo(river); - - final DoubleRange calcRange = access.getRange(); + this.access = new CollisionAccess(sinfo); + this.river = this.access.getRiver(); + final RiverInfo riverInfo = new RiverInfo(this.river); + final DoubleRange calcRange = this.access.getRange(); // calculate results for each year or epoch - final Calculation problems = new Calculation(); - + this.problems = new Calculation(); + this.qfinderProblemCount = 0; final String calcModeLabel = Resources.getMsg(this.context.getMeta(), sinfo.getCalculationMode().name()); - final CollisionCalculationResults results = new CollisionCalculationResults(calcModeLabel, user, riverInfo, calcRange, access.getYearsHeader()); + final CollisionCalculationResults results = new CollisionCalculationResults(calcModeLabel, user, riverInfo, calcRange, this.access.getYearsHeader()); final Collection overViewRows = new ArrayList<>(); - final List years = new ArrayList<>(); + this.years = new ArrayList<>(); final NavigableSet detailYears = new TreeSet<>(); - if (access.getYears() != null) { - for (final int year : access.getYears()) { - calculateOverview(overViewRows, river, access.getLowerKm(), access.getUpperKm(), year, year, false); - years.add(new DateRange(DateUtil.getStartDateFromYear(year), DateUtil.getEndDateFromYear(year))); + if (this.access.getYears() != null) { + for (final int year : this.access.getYears()) { + calculateOverview(overViewRows, year, year, false); + this.years.add(new DateRange(DateUtil.getStartDateFromYear(year), DateUtil.getEndDateFromYear(year))); detailYears.add(Integer.valueOf(year)); } } else { - for (final DateRange dr : access.getEpochs()) { - calculateOverview(overViewRows, river, access.getLowerKm(), access.getUpperKm(), dr.getFromYear(), dr.getToYear(), true); - years.add(dr); + for (final DateRange dr : this.access.getEpochs()) { + calculateOverview(overViewRows, dr.getFromYear(), dr.getToYear(), true); + this.years.add(dr); detailYearsAdd(detailYears, dr); } } - final CollisionCalcOverviewResult overviewResult = new CollisionCalcOverviewResult(access.getYearsHeader(), (access.getYears() == null), years, - overViewRows); - results.addResult(overviewResult, problems); + final CollisionCalcOverviewResult overviewResult = new CollisionCalcOverviewResult(this.access.getYearsHeader(), (this.access.getYears() == null), + this.years, overViewRows); + results.addResult(overviewResult, this.problems); // calculate secondary results for each year - final Map qFinders = new HashMap<>(); + final Map> qFinders = new HashMap<>(); final Map zoneFinders = new HashMap<>(); final Collection detailsRows = new ArrayList<>(); for (final Integer year : detailYears) - calculateDetails(detailsRows, river, access.getLowerKm(), access.getUpperKm(), year, qFinders, zoneFinders, problems); + calculateDetails(detailsRows, year, qFinders, zoneFinders); final CollisionCalcDetailResult detailResult = new CollisionCalcDetailResult("Details", detailsRows); - results.addResult(detailResult, problems); + results.addResult(detailResult, this.problems); - return new CalculationResult(results, problems); + return new CalculationResult(results, this.problems); } /** @@ -110,9 +120,9 @@ * Calculates the collision counts for a km range of a river and a year or year range (epoch), * and adds them to a ResultRow collection */ - private void calculateOverview(final Collection rows, final River river, final double fromKm, final double toKm, final int fromYear, - final int toYear, final boolean isEpoch) { - for (final CollisionAggregateValue aggregate : CollisionAggregateValue.getValuesByKm(river, fromKm, toKm, fromYear, toYear)) { + private void calculateOverview(final Collection rows, final int fromYear, final int toYear, final boolean isEpoch) { + for (final CollisionAggregateValue aggregate : CollisionAggregateValue.getValuesByKm(this.river, this.access.getLowerKm(), this.access.getUpperKm(), + fromYear, toYear)) { rows.add(ResultRow.create().putValue(GeneralResultType.station, aggregate.getStation()) .putValue(SInfoResultType.years, yearsToString(isEpoch, fromYear, toYear)).putValue(SInfoResultType.collisionCount, aggregate.getCount())); } @@ -135,15 +145,15 @@ /** * Calculates the collision details for a km range of a river and a year, and adds them to a ResultRow collection */ - private void calculateDetails(final Collection rows, final River river, final double fromKm, final double toKm, final int year, - final Map qFinders, final Map zoneFinders, final Calculation problems) { + private void calculateDetails(final Collection rows, final int year, final Map> qFinders, + final Map zoneFinders) { - for (final CollisionValue collision : CollisionValue.getValues(river, fromKm, toKm, DateUtil.getStartDateFromYear(year), - DateUtil.getEndDateFromYear(year))) { + for (final CollisionValue collision : CollisionValue.getValues(this.river, this.access.getLowerKm(), this.access.getUpperKm(), + DateUtil.getStartDateFromYear(year), DateUtil.getEndDateFromYear(year))) { final String gaugeName = collision.getGaugeName(); - final double q = getQ(qFinders, gaugeName, collision.getGaugeW().doubleValue(), river, problems); + final double q = getQ(qFinders, gaugeName, collision.getGaugeW().doubleValue(), collision.getEventDate()); final double qOut = Double.isInfinite(q) ? Double.NaN : q; - final String zone = getZone(zoneFinders, gaugeName, q, river, problems); + final String zone = getZone(zoneFinders, gaugeName, q); rows.add(ResultRow.create().putValue(GeneralResultType.station, collision.getStation()) .putValue(GeneralResultType.dateShort, collision.getEventDate()).putValue(SInfoResultType.collisionGaugeW, collision.getGaugeW()) .putValue(GeneralResultType.gaugeLabel, gaugeName).putValue(SInfoResultType.dischargeLong, qOut) @@ -154,27 +164,62 @@ /** * Gets the discharge of a gauge and a W */ - private double getQ(final Map qFinders, final String gaugeName, final double w, final River river, - final Calculation problems) { + private double getQ(final Map> qFinders, final String gaugeName, final double w, final Date when) { // Find the gauge and load its discharge table, if not already in the map final String gnKey = gaugeName.toLowerCase(); if (!qFinders.containsKey(gnKey)) - qFinders.put(gnKey, GaugeDischargeValuesFinder.loadValues(river, gaugeName, problems)); + addQFinders(qFinders, gaugeName); // Interpolate W. - if (qFinders.get(gnKey) == null) + final GaugeDischargeValuesFinder qFinder = getQFinder(qFinders.get(gnKey), when); + if (qFinder == null) { + this.qfinderProblemCount++; + if (this.qfinderProblemCount == 1) + this.problems.addProblem("gauge_discharge_table.missing", gaugeName); return Double.NaN; - return qFinders.get(gnKey).getDischarge(w); + } + return qFinder.getDischarge(w); + } + + /** + * Add the discharge finders for a gauge and the active time period + */ + private void addQFinders(final Map> qFinders, final String gaugeName) { + final String gnKey = gaugeName.toLowerCase(); + final Gauge gauge = this.river.determineGaugeByName(gaugeName); + if (gauge == null) { + qFinders.put(gnKey, new TreeMap()); + return; + } + final List qtables = DischargeTable.fetchHistoricalDischargeTables(gauge, this.years.get(0).getFrom(), + this.years.get(this.years.size() - 1).getTo()); + qFinders.put(gnKey, new TreeMap()); + for (final DischargeTable qtable : qtables) + qFinders.get(gnKey).put(qtable.getTimeInterval().getStartTime(), + GaugeDischargeValuesFinder.loadValues(qtable, this.river, gaugeName, this.problems)); + } + + /** + * Searches a q values finder map for a date time + */ + private GaugeDischargeValuesFinder getQFinder(final TreeMap qFinders, final Date when) { + if (qFinders.containsKey(when)) + return qFinders.get(when); + final Entry found = qFinders.floorEntry(when); + if (found == null) + return null; + if ((found.getValue().getEndTime() != null) && found.getValue().getEndTime().before(when)) + return null; + return found.getValue(); } /** * Gets the main value zone name of a gauge and a Q */ - private String getZone(final Map zoneFinders, final String gaugeName, final double q, final River river, - final Calculation problems) { + private String getZone(final Map zoneFinders, final String gaugeName, final double q) { // Find the gauge and load its main value list, if not already in the map final String gnKey = gaugeName.toLowerCase(); if (!zoneFinders.containsKey(gnKey)) - zoneFinders.put(gnKey, GaugeMainValueFinder.loadValues(MainValueTypeKey.Q, river, gaugeName, problems, "GLQ")); + zoneFinders.put(gnKey, GaugeMainValueFinder.loadValues(MainValueTypeKey.Q, this.river, gaugeName, this.problems, "GLQ")); // Build the zone name if (zoneFinders.get(gnKey) == null) return ""; diff -r 6b2496d71936 -r f2473dc34535 artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/GaugeDischargeValuesFinder.java --- a/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/GaugeDischargeValuesFinder.java Tue Feb 12 14:08:16 2019 +0100 +++ b/artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/common/GaugeDischargeValuesFinder.java Tue Oct 08 15:03:24 2019 +0200 @@ -9,6 +9,8 @@ */ package org.dive4elements.river.artifacts.sinfo.common; +import java.util.Date; + import org.apache.commons.lang.math.DoubleRange; import org.apache.commons.math.FunctionEvaluationException; import org.apache.commons.math.analysis.UnivariateRealFunction; @@ -41,6 +43,10 @@ private final DoubleRange wRange; + private final Date startTime; + + private final Date endTime; + /***** CONSTRUCTORS *****/ @@ -48,6 +54,8 @@ // Load W-Q-values from database this.gauge = gauge; this.problems = problems; + this.startTime = dischargeTable.getTimeInterval().getStartTime(); + this.endTime = dischargeTable.getTimeInterval().getStopTime(); final TDoubleArrayList ws = new TDoubleArrayList(); final TDoubleArrayList qs = new TDoubleArrayList(); for (final DischargeTableValue v : DischargeTable.fetchValuesSortedByW(dischargeTable)) { @@ -74,7 +82,7 @@ /***** METHODS *****/ /** - * Loads the the main discharge table of a gauge ({gauge}.at) + * Loads the main discharge table of a gauge ({gauge}.at) * * @return The discharge table values finder of the gauge, or null */ @@ -83,7 +91,7 @@ } /** - * Loads the the main discharge table of a river's gauge ({gauge}.at) + * Loads the main discharge table of a river's gauge ({gauge}.at) * * @return The discharge table values finder of the gauge, or null */ @@ -92,8 +100,21 @@ return loadValues(gauge, gaugeName, problems); } + /** + * Loads a discharge table of a river's gauge (*.at) + * + * @return The discharge table values finder, or null + */ + public static GaugeDischargeValuesFinder loadValues(final DischargeTable table, final River river, final String gaugeName, final Calculation problems) { + final Gauge gauge = river.determineGaugeByName(gaugeName); + return loadValues(table, gauge, gaugeName, problems); + } + private static GaugeDischargeValuesFinder loadValues(final Gauge gauge, final String gaugeName, final Calculation problems) { - final DischargeTable table = (gauge != null) ? gauge.fetchMasterDischargeTable() : null; + return loadValues((gauge != null) ? gauge.fetchMasterDischargeTable() : null, gauge, gaugeName, problems); + } + + private static GaugeDischargeValuesFinder loadValues(final DischargeTable table, final Gauge gauge, final String gaugeName, final Calculation problems) { if ((table == null) || (table.getDischargeTableValues().size() == 0)) { problems.addProblem("gauge_discharge_table.missing", gaugeName); return null; @@ -132,4 +153,22 @@ return Double.NaN; } } + + /** + * Start of the discharge table's time interval. + * + * @return + */ + public Date getStartTime() { + return this.startTime; + } + + /** + * End of the discharge table's time interval. + * + * @return + */ + public Date getEndTime() { + return this.endTime; + } } \ No newline at end of file diff -r 6b2496d71936 -r f2473dc34535 backend/src/main/java/org/dive4elements/river/model/DischargeTable.java --- a/backend/src/main/java/org/dive4elements/river/model/DischargeTable.java Tue Feb 12 14:08:16 2019 +0100 +++ b/backend/src/main/java/org/dive4elements/river/model/DischargeTable.java Tue Oct 08 15:03:24 2019 +0200 @@ -9,6 +9,7 @@ package org.dive4elements.river.model; import java.io.Serializable; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -223,6 +224,28 @@ } /** + * Fetches from the database a gauge's historical discharge tables of a time range in chronological order. + */ + public static List fetchHistoricalDischargeTables(final Gauge gauge, final Date from, final Date to) { + + final Session session = SessionHolder.HOLDER.get(); + + final Query query = session.createQuery("FROM DischargeTable" + + " WHERE (kind = 1) AND (gauge.id = :gauge_id)" + + " AND (timeInterval.startTime <= :to)" + + " AND ((timeInterval.stopTime IS NULL) OR (timeInterval.stopTime >= :from))" + + " ORDER BY timeInterval.startTime"); + + query.setParameter("gauge_id", gauge.getId()); + query.setParameter("from", from); + query.setParameter("to", to); + final List qtables = new ArrayList<>(); + for (final Object qtable : query.list()) + qtables.add((DischargeTable) qtable); + return qtables; + } + + /** * Selects from the database the values of a discharge table sorted by W */ public static List fetchValuesSortedByW(final DischargeTable dischargeTable) {