gernotbelger@9067: /* Copyright (C) 2017 by Bundesanstalt für Gewässerkunde gernotbelger@9067: * Software engineering by gernotbelger@9067: * Björnsen Beratende Ingenieure GmbH gernotbelger@9067: * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt gernotbelger@9067: * gernotbelger@9067: * This file is Free Software under the GNU AGPL (>=v3) gernotbelger@9067: * and comes with ABSOLUTELY NO WARRANTY! Check out the gernotbelger@9067: * documentation coming with Dive4Elements River for details. gernotbelger@9067: */ gernotbelger@9067: package org.dive4elements.river.artifacts.sinfo.collision; gernotbelger@9067: gernotbelger@9150: import java.util.ArrayList; gernotbelger@9150: import java.util.Collection; mschaefer@9613: import java.util.Date; mschaefer@9157: import java.util.HashMap; mschaefer@9487: import java.util.List; mschaefer@9157: import java.util.Map; mschaefer@9613: import java.util.Map.Entry; mschaefer@9487: import java.util.NavigableSet; mschaefer@9613: import java.util.TreeMap; mschaefer@9487: import java.util.TreeSet; gernotbelger@9150: gernotbelger@9067: import org.apache.commons.lang.math.DoubleRange; gernotbelger@9067: import org.dive4elements.artifacts.CallContext; gernotbelger@9150: import org.dive4elements.river.artifacts.common.GeneralResultType; gernotbelger@9150: import org.dive4elements.river.artifacts.common.ResultRow; gernotbelger@9067: import org.dive4elements.river.artifacts.model.Calculation; gernotbelger@9067: import org.dive4elements.river.artifacts.model.CalculationResult; mschaefer@9157: import org.dive4elements.river.artifacts.model.DateRange; gernotbelger@9067: import org.dive4elements.river.artifacts.resources.Resources; gernotbelger@9067: import org.dive4elements.river.artifacts.sinfo.SINFOArtifact; mschaefer@9176: import org.dive4elements.river.artifacts.sinfo.common.GaugeDischargeValuesFinder; mschaefer@9202: import org.dive4elements.river.artifacts.sinfo.common.GaugeMainValueFinder; mschaefer@9157: import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType; gernotbelger@9067: import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils; gernotbelger@9067: import org.dive4elements.river.artifacts.sinfo.util.RiverInfo; mschaefer@9157: import org.dive4elements.river.backend.utils.DateUtil; mschaefer@9613: import org.dive4elements.river.model.DischargeTable; mschaefer@9613: import org.dive4elements.river.model.Gauge; mschaefer@9176: import org.dive4elements.river.model.MainValueType.MainValueTypeKey; gernotbelger@9067: import org.dive4elements.river.model.River; mschaefer@9157: import org.dive4elements.river.model.sinfo.CollisionAggregateValue; mschaefer@9157: import org.dive4elements.river.model.sinfo.CollisionValue; gernotbelger@9067: gernotbelger@9067: class CollisionCalculation { gernotbelger@9067: gernotbelger@9067: // private static Logger log = Logger.getLogger(FloodDurationCalculation.class); gernotbelger@9067: gernotbelger@9067: private final CallContext context; gernotbelger@9067: mschaefer@9613: private CollisionAccess access; mschaefer@9613: private River river; mschaefer@9613: private List years; mschaefer@9613: private Calculation problems; mschaefer@9613: private int qfinderProblemCount; mschaefer@9613: gernotbelger@9067: public CollisionCalculation(final CallContext context) { gernotbelger@9067: this.context = context; gernotbelger@9067: } gernotbelger@9067: gernotbelger@9067: public CalculationResult calculate(final SINFOArtifact sinfo) { gernotbelger@9067: gernotbelger@9067: final String user = CalculationUtils.findArtifactUser(this.context, sinfo); gernotbelger@9067: mschaefer@9157: // access input data mschaefer@9613: this.access = new CollisionAccess(sinfo); mschaefer@9613: this.river = this.access.getRiver(); mschaefer@9613: final RiverInfo riverInfo = new RiverInfo(this.river); mschaefer@9613: final DoubleRange calcRange = this.access.getRange(); gernotbelger@9067: mschaefer@9157: // calculate results for each year or epoch mschaefer@9613: this.problems = new Calculation(); mschaefer@9613: this.qfinderProblemCount = 0; gernotbelger@9067: final String calcModeLabel = Resources.getMsg(this.context.getMeta(), sinfo.getCalculationMode().name()); gernotbelger@9067: mschaefer@9613: final CollisionCalculationResults results = new CollisionCalculationResults(calcModeLabel, user, riverInfo, calcRange, this.access.getYearsHeader()); gernotbelger@9067: gernotbelger@9150: final Collection overViewRows = new ArrayList<>(); mschaefer@9157: mschaefer@9613: this.years = new ArrayList<>(); mschaefer@9487: final NavigableSet detailYears = new TreeSet<>(); mschaefer@9613: if (this.access.getYears() != null) { mschaefer@9613: for (final int year : this.access.getYears()) { mschaefer@9613: calculateOverview(overViewRows, year, year, false); mschaefer@9613: this.years.add(new DateRange(DateUtil.getStartDateFromYear(year), DateUtil.getEndDateFromYear(year))); mschaefer@9487: detailYears.add(Integer.valueOf(year)); mschaefer@9487: } gernotbelger@9318: } else { mschaefer@9613: for (final DateRange dr : this.access.getEpochs()) { mschaefer@9613: calculateOverview(overViewRows, dr.getFromYear(), dr.getToYear(), true); mschaefer@9613: this.years.add(dr); mschaefer@9487: detailYearsAdd(detailYears, dr); mschaefer@9487: } mschaefer@9157: } mschaefer@9613: final CollisionCalcOverviewResult overviewResult = new CollisionCalcOverviewResult(this.access.getYearsHeader(), (this.access.getYears() == null), mschaefer@9613: this.years, overViewRows); mschaefer@9613: results.addResult(overviewResult, this.problems); mschaefer@9157: mschaefer@9533: // calculate secondary results for each year mschaefer@9613: final Map> qFinders = new HashMap<>(); mschaefer@9533: final Map zoneFinders = new HashMap<>(); gernotbelger@9150: final Collection detailsRows = new ArrayList<>(); mschaefer@9487: for (final Integer year : detailYears) mschaefer@9613: calculateDetails(detailsRows, year, qFinders, zoneFinders); gernotbelger@9150: final CollisionCalcDetailResult detailResult = new CollisionCalcDetailResult("Details", detailsRows); mschaefer@9613: results.addResult(detailResult, this.problems); gernotbelger@9150: mschaefer@9613: return new CalculationResult(results, this.problems); gernotbelger@9067: } mschaefer@9157: mschaefer@9157: /** mschaefer@9487: * Adds all years of an epoch to a set mschaefer@9487: */ mschaefer@9487: private void detailYearsAdd(final NavigableSet detailYears, final DateRange epoch) { mschaefer@9487: for (int year = epoch.getFromYear(); year <= epoch.getToYear(); year++) mschaefer@9487: detailYears.add(Integer.valueOf(year)); mschaefer@9487: } mschaefer@9487: mschaefer@9487: /** mschaefer@9157: * Calculates the collision counts for a km range of a river and a year or year range (epoch), mschaefer@9157: * and adds them to a ResultRow collection mschaefer@9157: */ mschaefer@9613: private void calculateOverview(final Collection rows, final int fromYear, final int toYear, final boolean isEpoch) { mschaefer@9613: for (final CollisionAggregateValue aggregate : CollisionAggregateValue.getValuesByKm(this.river, this.access.getLowerKm(), this.access.getUpperKm(), mschaefer@9613: fromYear, toYear)) { mschaefer@9157: rows.add(ResultRow.create().putValue(GeneralResultType.station, aggregate.getStation()) gernotbelger@9582: .putValue(SInfoResultType.years, yearsToString(isEpoch, fromYear, toYear)).putValue(SInfoResultType.collisionCount, aggregate.getCount())); mschaefer@9157: } mschaefer@9157: } mschaefer@9157: mschaefer@9157: /** mschaefer@9487: * Returns the string representation of a year or epoch mschaefer@9487: */ mschaefer@9487: public static String yearsToString(final boolean isEpoch, final DateRange years) { mschaefer@9487: return yearsToString(isEpoch, years.getFromYear(), years.getToYear()); mschaefer@9487: } mschaefer@9487: mschaefer@9487: /** mschaefer@9487: * Returns the string representation of a year or epoch mschaefer@9487: */ mschaefer@9487: public static String yearsToString(final boolean isEpoch, final int fromYear, final int toYear) { mschaefer@9487: return (isEpoch ? String.format("%d-%d", fromYear, toYear) : Integer.toString(fromYear)); mschaefer@9487: } mschaefer@9487: mschaefer@9487: /** mschaefer@9487: * Calculates the collision details for a km range of a river and a year, and adds them to a ResultRow collection mschaefer@9157: */ mschaefer@9613: private void calculateDetails(final Collection rows, final int year, final Map> qFinders, mschaefer@9613: final Map zoneFinders) { mschaefer@9533: mschaefer@9613: for (final CollisionValue collision : CollisionValue.getValues(this.river, this.access.getLowerKm(), this.access.getUpperKm(), mschaefer@9613: DateUtil.getStartDateFromYear(year), DateUtil.getEndDateFromYear(year))) { mschaefer@9533: final String gaugeName = collision.getGaugeName(); mschaefer@9613: final double q = getQ(qFinders, gaugeName, collision.getGaugeW().doubleValue(), collision.getEventDate()); mschaefer@9157: final double qOut = Double.isInfinite(q) ? Double.NaN : q; mschaefer@9613: final String zone = getZone(zoneFinders, gaugeName, q); mschaefer@9157: rows.add(ResultRow.create().putValue(GeneralResultType.station, collision.getStation()) gernotbelger@9582: .putValue(GeneralResultType.dateShort, collision.getEventDate()).putValue(SInfoResultType.collisionGaugeW, collision.getGaugeW()) gernotbelger@9582: .putValue(GeneralResultType.gaugeLabel, gaugeName).putValue(SInfoResultType.dischargeLong, qOut) mschaefer@9533: .putValue(SInfoResultType.dischargeZone, zone)); mschaefer@9157: } mschaefer@9157: } mschaefer@9533: mschaefer@9533: /** mschaefer@9533: * Gets the discharge of a gauge and a W mschaefer@9533: */ mschaefer@9613: private double getQ(final Map> qFinders, final String gaugeName, final double w, final Date when) { mschaefer@9533: // Find the gauge and load its discharge table, if not already in the map mschaefer@9533: final String gnKey = gaugeName.toLowerCase(); mschaefer@9533: if (!qFinders.containsKey(gnKey)) mschaefer@9613: addQFinders(qFinders, gaugeName); mschaefer@9533: // Interpolate W. mschaefer@9613: final GaugeDischargeValuesFinder qFinder = getQFinder(qFinders.get(gnKey), when); mschaefer@9613: if (qFinder == null) { mschaefer@9613: this.qfinderProblemCount++; mschaefer@9613: if (this.qfinderProblemCount == 1) mschaefer@9613: this.problems.addProblem("gauge_discharge_table.missing", gaugeName); mschaefer@9533: return Double.NaN; mschaefer@9613: } mschaefer@9613: return qFinder.getDischarge(w); mschaefer@9613: } mschaefer@9613: mschaefer@9613: /** mschaefer@9613: * Add the discharge finders for a gauge and the active time period mschaefer@9613: */ mschaefer@9613: private void addQFinders(final Map> qFinders, final String gaugeName) { mschaefer@9613: final String gnKey = gaugeName.toLowerCase(); mschaefer@9613: final Gauge gauge = this.river.determineGaugeByName(gaugeName); mschaefer@9613: if (gauge == null) { mschaefer@9613: qFinders.put(gnKey, new TreeMap()); mschaefer@9613: return; mschaefer@9613: } mschaefer@9613: final List qtables = DischargeTable.fetchHistoricalDischargeTables(gauge, this.years.get(0).getFrom(), mschaefer@9613: this.years.get(this.years.size() - 1).getTo()); mschaefer@9613: qFinders.put(gnKey, new TreeMap()); mschaefer@9613: for (final DischargeTable qtable : qtables) mschaefer@9613: qFinders.get(gnKey).put(qtable.getTimeInterval().getStartTime(), mschaefer@9613: GaugeDischargeValuesFinder.loadValues(qtable, this.river, gaugeName, this.problems)); mschaefer@9613: } mschaefer@9613: mschaefer@9613: /** mschaefer@9613: * Searches a q values finder map for a date time mschaefer@9613: */ mschaefer@9613: private GaugeDischargeValuesFinder getQFinder(final TreeMap qFinders, final Date when) { mschaefer@9613: if (qFinders.containsKey(when)) mschaefer@9613: return qFinders.get(when); mschaefer@9613: final Entry found = qFinders.floorEntry(when); mschaefer@9613: if (found == null) mschaefer@9613: return null; mschaefer@9613: if ((found.getValue().getEndTime() != null) && found.getValue().getEndTime().before(when)) mschaefer@9613: return null; mschaefer@9613: return found.getValue(); mschaefer@9533: } mschaefer@9533: mschaefer@9533: /** mschaefer@9533: * Gets the main value zone name of a gauge and a Q mschaefer@9533: */ mschaefer@9613: private String getZone(final Map zoneFinders, final String gaugeName, final double q) { mschaefer@9533: // Find the gauge and load its main value list, if not already in the map mschaefer@9533: final String gnKey = gaugeName.toLowerCase(); mschaefer@9533: if (!zoneFinders.containsKey(gnKey)) mschaefer@9613: zoneFinders.put(gnKey, GaugeMainValueFinder.loadValues(MainValueTypeKey.Q, this.river, gaugeName, this.problems, "GLQ")); mschaefer@9533: // Build the zone name mschaefer@9533: if (zoneFinders.get(gnKey) == null) mschaefer@9533: return ""; mschaefer@9533: return zoneFinders.get(gnKey).findZoneName(q); mschaefer@9533: } gernotbelger@9067: }