gernotbelger@8915: /** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde gernotbelger@8915: * Software engineering by gernotbelger@8915: * Björnsen Beratende Ingenieure GmbH gernotbelger@8915: * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt gernotbelger@8915: * gernotbelger@8915: * This file is Free Software under the GNU AGPL (>=v3) gernotbelger@8915: * and comes with ABSOLUTELY NO WARRANTY! Check out the gernotbelger@8915: * documentation coming with Dive4Elements River for details. gernotbelger@8915: */ gernotbelger@8915: package org.dive4elements.river.artifacts.sinfo.tkhstate; gernotbelger@8915: gernotbelger@8915: import java.util.ArrayList; gernotbelger@8915: import java.util.Collection; gernotbelger@8942: import java.util.HashMap; gernotbelger@8916: import java.util.List; gernotbelger@8942: import java.util.Map; gernotbelger@8942: import java.util.Map.Entry; gernotbelger@8942: import java.util.Set; gernotbelger@8964: import java.util.TreeSet; gernotbelger@8915: gernotbelger@9105: import org.apache.commons.lang.ArrayUtils; gernotbelger@8915: import org.apache.commons.lang.math.DoubleRange; gernotbelger@8942: import org.apache.commons.lang.math.NumberRange; gernotbelger@8915: import org.dive4elements.artifacts.CallContext; gernotbelger@8915: import org.dive4elements.river.artifacts.WINFOArtifact; gernotbelger@9312: import org.dive4elements.river.artifacts.common.GeneralResultType; gernotbelger@8996: import org.dive4elements.river.artifacts.common.ResultRow; gernotbelger@8915: import org.dive4elements.river.artifacts.model.Calculation; gernotbelger@8915: import org.dive4elements.river.artifacts.model.Calculation.Problem; gernotbelger@8915: import org.dive4elements.river.artifacts.model.CalculationResult; gernotbelger@8915: import org.dive4elements.river.artifacts.model.WQKms; mschaefer@9510: import org.dive4elements.river.artifacts.model.river.RiverInfoProvider; gernotbelger@8915: import org.dive4elements.river.artifacts.resources.Resources; gernotbelger@8915: import org.dive4elements.river.artifacts.sinfo.SINFOArtifact; gernotbelger@8915: import org.dive4elements.river.artifacts.sinfo.tkhcalculation.DischargeValuesFinder; gernotbelger@8915: import org.dive4elements.river.artifacts.sinfo.tkhcalculation.TkhCalculator; mschaefer@9335: import org.dive4elements.river.artifacts.sinfo.tkhcalculation.TkhCalculator.TkhCalculateState; gernotbelger@8946: import org.dive4elements.river.artifacts.sinfo.tkhcalculation.WaterlevelValuesFinder; gernotbelger@8942: import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo; gernotbelger@8915: import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils; gernotbelger@8915: import org.dive4elements.river.artifacts.sinfo.util.RiverInfo; gernotbelger@8915: import org.dive4elements.river.artifacts.sinfo.util.WstInfo; gernotbelger@8938: import org.dive4elements.river.exports.WaterlevelDescriptionBuilder; gernotbelger@8942: import org.dive4elements.river.model.BedHeight; gernotbelger@8915: import org.dive4elements.river.model.River; gernotbelger@8915: gernotbelger@8915: /** gernotbelger@8915: * @author Gernot Belger gernotbelger@8915: */ gernotbelger@8915: final class TkhCalculation { gernotbelger@8915: gernotbelger@8915: private final CallContext context; gernotbelger@8915: gernotbelger@8915: public TkhCalculation(final CallContext context) { gernotbelger@8915: this.context = context; gernotbelger@8915: } gernotbelger@8915: gernotbelger@8915: public CalculationResult calculate(final SINFOArtifact sinfo) { gernotbelger@8915: gernotbelger@8915: /* access input data */ gernotbelger@8915: final TkhAccess access = new TkhAccess(sinfo); gernotbelger@8915: final River river = access.getRiver(); gernotbelger@8915: final RiverInfo riverInfo = new RiverInfo(river); gernotbelger@8915: final DoubleRange calcRange = access.getRange(); gernotbelger@8915: gernotbelger@9131: final String calcModeLabel = Resources.getMsg(this.context.getMeta(), sinfo.getCalculationMode().name()); gernotbelger@9131: final String user = CalculationUtils.findArtifactUser(this.context, sinfo); gernotbelger@9131: gernotbelger@8915: final Calculation problems = new Calculation(); gernotbelger@8915: gernotbelger@8915: /* find relevant bed-heights */ gernotbelger@8942: final List defaultBedHeights = new DefaultBedHeights(river).getBedHeights(problems); gernotbelger@8964: final Collection bedHeights = BedHeightsFinder.createTkhBedHeights(problems, calcRange, defaultBedHeights); gernotbelger@9131: gernotbelger@9131: if (defaultBedHeights.isEmpty()) { gernotbelger@9131: final TkhCalculationResults results = new TkhCalculationResults(calcModeLabel, user, riverInfo, calcRange, ""); gernotbelger@9131: return new CalculationResult(results, problems); gernotbelger@9131: } gernotbelger@9131: gernotbelger@9105: final double[] stations = extractStations(bedHeights); mschaefer@9510: if (stations.length == 0) { mschaefer@9510: final TkhCalculationResults results = new TkhCalculationResults(calcModeLabel, user, riverInfo, calcRange, ""); mschaefer@9510: problems.addProblem("sinfo.bedheightsfinder.empty"); mschaefer@9510: return new CalculationResult(results, problems); mschaefer@9510: } gernotbelger@8915: gernotbelger@8938: /* misuse winfo-artifact to calculate waterlevels in the same way */ gernotbelger@8938: final WINFOArtifact winfo = new WinfoArtifactWrapper(sinfo); gernotbelger@8938: gernotbelger@8915: /* calculate waterlevels */ gernotbelger@9105: final WQKms[] kms = calculateWaterlevels(winfo, stations, problems); gernotbelger@8915: gernotbelger@8915: final RiverInfoProvider infoProvider = RiverInfoProvider.forRange(this.context, river, calcRange); gernotbelger@8915: gernotbelger@8938: final WaterlevelDescriptionBuilder descBuilder = new WaterlevelDescriptionBuilder(winfo, this.context); gernotbelger@8938: final String descriptionHeader = descBuilder.getColumnHeader(); gernotbelger@8938: gernotbelger@8915: /* for each waterlevel, do a tkh calculation */ gernotbelger@8938: final TkhCalculationResults results = new TkhCalculationResults(calcModeLabel, user, riverInfo, calcRange, descriptionHeader); gernotbelger@8915: gernotbelger@8964: /* determine calculation steps */ gernotbelger@8964: final Collection allStations = determineCalculationSteps(bedHeights); gernotbelger@8915: gernotbelger@8964: for (final WQKms wqKms : kms) { gernotbelger@8964: final TkhCalculationResult result = calculateResult(calcRange, allStations, infoProvider, wqKms, bedHeights, descBuilder, problems); gernotbelger@8980: results.addResult(result, problems); gernotbelger@8915: } gernotbelger@8915: gernotbelger@8915: return new CalculationResult(results, problems); gernotbelger@8915: } gernotbelger@8915: gernotbelger@9105: private double[] extractStations(final Collection bedHeights) { gernotbelger@9105: gernotbelger@9105: final Set allStations = new TreeSet<>(); gernotbelger@9105: gernotbelger@9105: for (final BedHeightsFinder bedHeight : bedHeights) gernotbelger@9105: allStations.addAll(bedHeight.getStations()); gernotbelger@9105: gernotbelger@9105: return ArrayUtils.toPrimitive(allStations.toArray(new Double[allStations.size()])); gernotbelger@9105: } gernotbelger@9105: gernotbelger@8964: /** gernotbelger@8964: * Calculation steps are simply the union of all stations of all involved bed-height datasets gernotbelger@8964: */ gernotbelger@8964: private Collection determineCalculationSteps(final Collection bedHeights) { gernotbelger@8964: gernotbelger@8964: final Collection allStations = new TreeSet<>(); gernotbelger@8964: gernotbelger@8964: for (final BedHeightsFinder bedHeight : bedHeights) { gernotbelger@8964: final Collection stations = bedHeight.getStations(); gernotbelger@8964: allStations.addAll(stations); gernotbelger@8964: } gernotbelger@8964: gernotbelger@8964: return allStations; gernotbelger@8964: } gernotbelger@8964: gernotbelger@9105: private WQKms[] calculateWaterlevels(final WINFOArtifact winfo, final double[] stations, final Calculation problems) { gernotbelger@9105: gernotbelger@9131: final CalculationResult waterlevelData = winfo.computeWaterlevelData(stations); gernotbelger@8915: gernotbelger@8915: /* copy all problems */ gernotbelger@8915: final Calculation winfoProblems = waterlevelData.getReport(); gernotbelger@8916: final List problems2 = winfoProblems.getProblems(); gernotbelger@8916: if (problems2 != null) { gernotbelger@8916: for (final Problem problem : problems2) { gernotbelger@8916: problems.addProblem(problem); gernotbelger@8916: } gernotbelger@8915: } gernotbelger@8915: gernotbelger@8915: return (WQKms[]) waterlevelData.getData(); gernotbelger@8915: } gernotbelger@8915: gernotbelger@8964: private TkhCalculationResult calculateResult(final DoubleRange calcRange, final Collection allStations, final RiverInfoProvider riverInfo, gernotbelger@8964: final WQKms wkms, final Collection bedHeights, final WaterlevelDescriptionBuilder descBuilder, final Calculation problems) { gernotbelger@8915: gernotbelger@8964: // We have no wst year as the wst is created by a calculation; we do not need it though gernotbelger@8915: final int wspYear = -1; gernotbelger@8964: // Remark: showAllGauges only true for Fixierungsanalyse, false for WInfo, so false here as well gernotbelger@8915: final boolean showAllGauges = false; gernotbelger@8915: mschaefer@9528: final RiverInfoProvider riverInfoProvider = riverInfo.forReferenceRange(calcRange, showAllGauges); gernotbelger@8915: gernotbelger@9205: final String waterlevelLabel = descBuilder.getDesc(wkms); gernotbelger@8915: gernotbelger@9362: final WstInfo wstInfo = new WstInfo(waterlevelLabel, wspYear, riverInfoProvider.getReferenceGauge(), true); gernotbelger@8915: gernotbelger@8942: /* build tkh calculators per bedheight */ gernotbelger@9205: final Map calculatorsByRanges = buildCalculators(calcRange, wkms, bedHeights, problems, riverInfoProvider, waterlevelLabel); gernotbelger@8942: if (calculatorsByRanges.isEmpty()) { gernotbelger@8942: /* there should already be some problems, so just abort */ gernotbelger@8942: return null; gernotbelger@8942: } gernotbelger@8942: gernotbelger@8996: final Collection rows = new ArrayList<>(); gernotbelger@8915: gernotbelger@8964: for (final Double stationDbl : allStations) { gernotbelger@8942: gernotbelger@8964: final double station = stationDbl; gernotbelger@8942: gernotbelger@9573: /* find the right calculator (i.e. bed level) depending on station, there should only be one maximal */ gernotbelger@8942: final TkhCalculator tkhCalculator = findCalculator(calculatorsByRanges, station); gernotbelger@8942: if (tkhCalculator == null) gernotbelger@8942: continue; gernotbelger@8942: gernotbelger@8996: final ResultRow row = ResultRow.create(); gernotbelger@8942: gernotbelger@9318: row.putValue(GeneralResultType.waterlevelLabel, waterlevelLabel); gernotbelger@9318: row.putValue(GeneralResultType.gaugeLabel, riverInfoProvider.findGauge(station)); gernotbelger@9312: row.putValue(GeneralResultType.location, riverInfoProvider.getLocation(station)); gernotbelger@8942: mschaefer@9335: final TkhCalculateState calcState = tkhCalculator.calculateTkh(station, row); mschaefer@9335: if (calcState == TkhCalculateState.SUCCESS) gernotbelger@8978: rows.add(row); gernotbelger@8942: } gernotbelger@8942: gernotbelger@9205: return new TkhCalculationResult(waterlevelLabel, wstInfo, true, rows); gernotbelger@8942: } gernotbelger@8942: gernotbelger@8942: private TkhCalculator findCalculator(final Map calculators, final double station) { gernotbelger@8942: gernotbelger@9573: // REMAKR: linear search at this point, put we expect the number of bed levels to be very small (1-2 items) gernotbelger@8942: final Set> x = calculators.entrySet(); gernotbelger@8942: for (final Entry entry : x) { gernotbelger@8942: final NumberRange range = entry.getKey(); gernotbelger@8942: // FIXME: check if we need comparison with a tolerance gernotbelger@8942: if (range.containsDouble(station)) gernotbelger@8942: return entry.getValue(); gernotbelger@8942: } gernotbelger@8942: gernotbelger@8942: return null; gernotbelger@8942: } gernotbelger@8942: gernotbelger@8942: private Map buildCalculators(final DoubleRange calcRange, final WQKms wkms, final Collection bedHeights, gernotbelger@8942: final Calculation problems, final RiverInfoProvider riverInfoProvider, final String wstLabel) { gernotbelger@8942: final Map calculatorByRanges = new HashMap<>(); gernotbelger@8915: for (final BedHeightsFinder bedHeightsProvider : bedHeights) { gernotbelger@8915: gernotbelger@8942: final BedHeightInfo info = bedHeightsProvider.getInfo(); gernotbelger@8942: gernotbelger@8942: final NumberRange range = new NumberRange(info.getFrom(), info.getTo()); gernotbelger@8942: gernotbelger@8964: final WaterlevelValuesFinder waterlevelProvider = WaterlevelValuesFinder.fromKms(problems, wkms); gernotbelger@8915: final DischargeValuesFinder dischargeProvider = DischargeValuesFinder.fromKms(wkms); gernotbelger@8915: gernotbelger@8915: /* initialize tkh calculator */ gernotbelger@8964: final TkhCalculator tkhCalculator = TkhCalculator.buildTkhCalculator(true, problems, wstLabel, riverInfoProvider.getRiver(), calcRange, gernotbelger@8964: waterlevelProvider, dischargeProvider, bedHeightsProvider); gernotbelger@8915: gernotbelger@8964: if (tkhCalculator.hasTkh()) { gernotbelger@8964: /* just ignore invalid ones, problems have already been updated by buildTkhCalculator() */ gernotbelger@8942: calculatorByRanges.put(range, tkhCalculator); gernotbelger@8915: } gernotbelger@8915: } gernotbelger@8915: gernotbelger@8942: return calculatorByRanges; gernotbelger@8915: } gernotbelger@8915: }