view artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepthdev/FlowDepthDevelopmentCalculation.java @ 8980:b194fa64506a

SINFO - show results themes according to spec, either raw data or floating mean values. Some improvements to error handling and handling of empty results.
author gernotbelger
date Thu, 05 Apr 2018 18:30:34 +0200
parents b5600453bb8f
children d5802f22e4f5
line wrap: on
line source
/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
 * Software engineering by
 *  Björnsen Beratende Ingenieure GmbH
 *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */
package org.dive4elements.river.artifacts.sinfo.flowdepthdev;

import java.util.ArrayList;
import java.util.Collection;
import java.util.TreeSet;

import org.apache.commons.lang.math.DoubleRange;
import org.dive4elements.artifacts.CallContext;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.WKms;
import org.dive4elements.river.artifacts.resources.Resources;
import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
import org.dive4elements.river.artifacts.sinfo.common.RiverInfoProvider;
import org.dive4elements.river.artifacts.sinfo.common.SInfoResultRow;
import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
import org.dive4elements.river.artifacts.sinfo.flowdepth.FlowDepthUtils;
import org.dive4elements.river.artifacts.sinfo.flowdepth.WstSoundingIdPair;
import org.dive4elements.river.artifacts.sinfo.tkhcalculation.WaterlevelValuesFinder;
import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsFinder;
import org.dive4elements.river.artifacts.sinfo.util.BedHeightInfo;
import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils;
import org.dive4elements.river.artifacts.sinfo.util.RiverInfo;
import org.dive4elements.river.artifacts.sinfo.util.WstInfo;
import org.dive4elements.river.artifacts.states.WaterlevelData;
import org.dive4elements.river.artifacts.states.WaterlevelFetcher;
import org.dive4elements.river.model.River;

/**
 * @author Gernot Belger
 */
final class FlowDepthDevelopmentCalculation {

    private final CallContext context;

    public FlowDepthDevelopmentCalculation(final CallContext context) {
        this.context = context;
    }

    public CalculationResult calculate(final SINFOArtifact sinfo) {

        final String user = CalculationUtils.findArtifactUser(this.context, sinfo);

        /* access input data */
        final FlowDepthDevelopmentAccess access = new FlowDepthDevelopmentAccess(sinfo);
        final River river = access.getRiver();
        final RiverInfo riverInfo = new RiverInfo(river);

        final WstSoundingIdPair currentPair = access.getCurrentPair();
        final WstSoundingIdPair histPair = access.getHistoricalPair();

        final DoubleRange calcRange = access.getRange();

        /* calculate results for each diff pair */
        final Calculation problems = new Calculation();

        final RiverInfoProvider infoProvider = RiverInfoProvider.forRange(this.context, river, calcRange);

        final String calcModeLabel = Resources.getMsg(this.context.getMeta(), sinfo.getCalculationMode().name());

        final FlowDepthDevelopmentCalculationResults results = new FlowDepthDevelopmentCalculationResults(calcModeLabel, user, riverInfo, calcRange);

        final FlowDepthDevelopmentCalculationResult result = calculateResult(calcRange, currentPair, histPair, problems, infoProvider);
        results.addResult(result, problems);

        return new CalculationResult(results, problems);
    }

    private FlowDepthDevelopmentCalculationResult calculateResult(final DoubleRange calcRange, final WstSoundingIdPair currentPair,
            final WstSoundingIdPair histPair, final Calculation problems, final RiverInfoProvider infoProvider) {

        /* access real input data from database */
        final WaterlevelData currentWaterlevel = loadWaterlevel(currentPair, calcRange, problems);
        if (currentWaterlevel == null)
            return null;

        final WaterlevelData historicalWaterlevel = loadWaterlevel(histPair, calcRange, problems);
        if (historicalWaterlevel == null)
            return null;

        final BedHeightsFinder currentSounding = loadBedHeight(currentPair, calcRange, problems);
        if (currentSounding == null)
            return null;

        final BedHeightsFinder historicalSounding = loadBedHeight(histPair, calcRange, problems);
        if (historicalSounding == null)
            return null;

        // FIXME: check current/hist wst have same discharge...
        // FIXME: what means 'same discharge'

        final BedHeightInfo currentSoundingInfo = currentSounding.getInfo();
        final BedHeightInfo historicalSoundingInfo = historicalSounding.getInfo();

        final int currentWstYear = currentWaterlevel.getYear();
        final int historicalWstYear = historicalWaterlevel.getYear();
        final int currentSoundingYear = currentSoundingInfo.getYear();
        final int historicalSoundingYear = historicalSoundingInfo.getYear();

        if (currentWstYear < 0) {
            problems.addProblem("flowdepthdevelopmentcalculation.missingCurrentYear", currentWaterlevel.getName());
            return null;
        }

        if (historicalWstYear < 0) {
            problems.addProblem("flowdepthdevelopmentcalculation.missingHistoricalYear", historicalWaterlevel.getName());
            return null;
        }

        // FIXME: distinguish error messages
        FlowDepthUtils.checkYearDifference(Resources.getMsg(this.context.getMeta(), "flowdepthdevelopmentcalculation.yearDifferenceCurrent"), currentWstYear,
                currentSoundingYear, problems);
        FlowDepthUtils.checkYearDifference(Resources.getMsg(this.context.getMeta(), "flowdepthdevelopmentcalculation.yearDifferenceHistorical"),
                historicalWstYear,
                historicalSoundingYear, problems);

        /* re-determine the reference gauge, in the same way as the WaterlevelArtifact would do it */
        final RiverInfoProvider currentRiverInfoProvider = infoProvider.forWaterlevel(currentWaterlevel);
        final RiverInfoProvider histRiverInfoProvider = infoProvider.forWaterlevel(historicalWaterlevel);

        final WstInfo currentWstInfo = new WstInfo(currentWaterlevel.getName(), currentWstYear, currentRiverInfoProvider.getReferenceGauge());
        final WstInfo historicalWstInfo = new WstInfo(historicalWaterlevel.getName(), historicalWstYear, histRiverInfoProvider.getReferenceGauge());

        final WKms currentWkms = currentWaterlevel.getWkms();
        final WaterlevelValuesFinder currentWstProvider = WaterlevelValuesFinder.fromKms(problems, currentWkms);

        final WKms historicalWkms = historicalWaterlevel.getWkms();
        final WaterlevelValuesFinder historicalWstProvider = WaterlevelValuesFinder.fromKms(problems, historicalWkms);

        final int currentMeanYear = (currentWstYear + currentSoundingYear) / 2;
        final int historcialMeanYear = (historicalWstYear + historicalSoundingYear) / 2;

        final double diffYear = currentMeanYear - historcialMeanYear;

        /* real calculation loop */
        final Collection<SInfoResultRow> rows = new ArrayList<>();

        final Collection<Double> stations = determineCalculationSteps(currentSounding, historicalSounding);
        for (final double station : stations) {
            if (calcRange.containsDouble(station)) {

                final double currentWst = currentWstProvider.getWaterlevel(station);
                final double currentBedHeight = currentSounding.getMeanBedHeight(station);

                final double historicalWst = historicalWstProvider.getWaterlevel(station);
                final double historicalBedHeight = historicalSounding.getMeanBedHeight(station);

                /* ignore invalid lines */
                if (Double.isNaN(currentWst) || Double.isNaN(currentBedHeight) || Double.isNaN(historicalWst) || Double.isNaN(historicalBedHeight))
                    continue;

                final double diffWst = (currentWst - historicalWst) * 100;
                final double diffBedHeight = (currentBedHeight - historicalBedHeight) * 100;

                final double flowDepthDevelopment = diffWst - diffBedHeight;

                final double flowDepthDevelopmentPerYear = flowDepthDevelopment / diffYear;

                final double currentFlowDepth = currentWst - currentBedHeight;
                final double historicalFlowDepth = historicalWst - historicalBedHeight;

                // REMARK: access the location once only during calculation
                final String location = currentRiverInfoProvider.getLocation(station);

                final SInfoResultRow row = SInfoResultRow.create().//
                        putValue(SInfoResultType.station, station). //
                        putValue(SInfoResultType.flowdepthDevelopment, flowDepthDevelopment). //
                        putValue(SInfoResultType.flowdepthDevelopmentPerYear, flowDepthDevelopmentPerYear). //
                        putValue(SInfoResultType.waterlevelDifference, diffWst). //
                        putValue(SInfoResultType.bedHeightDifference, diffBedHeight). //
                        putValue(SInfoResultType.flowdepthCurrent, currentFlowDepth). //
                        putValue(SInfoResultType.flowdepthHistorical, historicalFlowDepth). //
                        putValue(SInfoResultType.location, location);
                rows.add(row);
            }
        }

        final String label = buildLabel(currentWaterlevel, currentSoundingInfo, historicalWaterlevel, historicalSoundingInfo);

        return new FlowDepthDevelopmentCalculationResult(label, currentWstInfo, historicalWstInfo, currentSoundingInfo, historicalSoundingInfo,
                rows);
    }

    /**
     * Calculation steps are simply the union of all stations of all involved bed-height datasets
     */
    private Collection<Double> determineCalculationSteps(final BedHeightsFinder currentSounding, final BedHeightsFinder historicalSounding) {

        final Collection<Double> allStations = new TreeSet<>();

        allStations.addAll(currentSounding.getStations());
        allStations.addAll(historicalSounding.getStations());

        return allStations;
    }

    private String buildLabel(final WaterlevelData currentWaterlevel, final BedHeightInfo currentSounding, final WaterlevelData historicalWaterlevel,
            final BedHeightInfo historicalSounding) {

        return new StringBuilder(). //
                append(currentWaterlevel.getName()). //
                append('/'). //
                append(historicalWaterlevel.getName()). //
                append(" - "). //
                append(currentSounding.getDescription()). //
                append('/'). //
                append(historicalSounding.getDescription()). //
                toString();
    }

    /* REMARK: fetch ALL wst kms, because we need to determine the original reference gauge */
    private WaterlevelData loadWaterlevel(final WstSoundingIdPair pair, final DoubleRange calcRange, final Calculation problems) {
        final String wstId = pair.getWstId();
        return new WaterlevelFetcher().findWaterlevel(this.context, wstId, calcRange, problems);
    }

    private BedHeightsFinder loadBedHeight(final WstSoundingIdPair pair, final DoubleRange calcRange, final Calculation problems) {
        final String soundingId = pair.getSoundingId();
        return BedHeightsFinder.forId(this.context, soundingId, calcRange, problems);
    }
}

http://dive4elements.wald.intevation.org