view artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/collision/CollisionCalculation.java @ 9617:1d4262a68f1f

#12 Minuend/Subtrahend + MergeConflict #19 CollisionCalculation
author dnt_bjoernsen <d.tironi@bjoernsen.de>
date Thu, 10 Oct 2019 15:29:02 +0200
parents f2473dc34535
children
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.collision;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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;
import org.dive4elements.artifacts.CallContext;
import org.dive4elements.river.artifacts.common.GeneralResultType;
import org.dive4elements.river.artifacts.common.ResultRow;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.DateRange;
import org.dive4elements.river.artifacts.resources.Resources;
import org.dive4elements.river.artifacts.sinfo.SINFOArtifact;
import org.dive4elements.river.artifacts.sinfo.common.GaugeDischargeValuesFinder;
import org.dive4elements.river.artifacts.sinfo.common.GaugeMainValueFinder;
import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
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;
import org.dive4elements.river.model.sinfo.CollisionValue;

class CollisionCalculation {

    // private static Logger log = Logger.getLogger(FloodDurationCalculation.class);

    private final CallContext context;

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

    public CalculationResult calculate(final SINFOArtifact sinfo) {

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

        final int qfinderProblemCount = 0;

        // 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();

        // calculate results for each year or epoch
        final Calculation problems = new Calculation();

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

        final CollisionCalculationResults results = new CollisionCalculationResults(calcModeLabel, user, riverInfo, calcRange, access.getYearsHeader());

        final Collection<ResultRow> overViewRows = new ArrayList<>();

        final List<DateRange> years = new ArrayList<>();
        final NavigableSet<Integer> detailYears = new TreeSet<>();
        final List<CollisionCalcOverviewResult> singleYearResults = new ArrayList<>();
        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)));
                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);
                detailYearsAdd(detailYears, dr);
            }

            for (final Integer year : detailYears) {

                final Collection<ResultRow> yearRows = new ArrayList<>();
                calculateOverview(yearRows, river, access.getLowerKm(), access.getUpperKm(), year, year, false);

                if (!yearRows.isEmpty()) {
                    final DateRange yearRange = new DateRange(DateUtil.getStartDateFromYear(year), DateUtil.getEndDateFromYear(year));
                    final CollisionCalcOverviewResult yearResult = new CollisionCalcOverviewResult(Integer.toString(year), false,
                            Collections.singleton(yearRange), yearRows);
                    singleYearResults.add(yearResult);
                }
            }
        }
        final CollisionCalcOverviewResult overviewResult = new CollisionCalcOverviewResult(access.getYearsHeader(), (access.getYears() == null), years,
                overViewRows);
        results.addResult(overviewResult, problems);

        /* add the single year from epochs, after the epochs, so they will be exported at the end of the table etc. */
        for (final CollisionCalcOverviewResult result : singleYearResults)
            results.addResult(result, problems);

        // calculate secondary results for each year
        final Map<String, TreeMap<Date, GaugeDischargeValuesFinder>> qFinders = new HashMap<>();
        final Map<String, GaugeMainValueFinder> zoneFinders = new HashMap<>();
        final Collection<ResultRow> detailsRows = new ArrayList<>();
        for (final Integer year : detailYears)
            calculateDetails(detailsRows, river, access, year, qFinders, zoneFinders, problems, years, qfinderProblemCount);
        final CollisionCalcDetailResult detailResult = new CollisionCalcDetailResult("Details", detailsRows);
        results.addResult(detailResult, problems);

        return new CalculationResult(results, problems);
    }

    /**
     * Adds all years of an epoch to a set
     */
    private void detailYearsAdd(final NavigableSet<Integer> detailYears, final DateRange epoch) {
        for (int year = epoch.getFromYear(); year <= epoch.getToYear(); year++)
            detailYears.add(Integer.valueOf(year));
    }

    /**
     * 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<ResultRow> 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)) {
            rows.add(ResultRow.create().putValue(GeneralResultType.station, aggregate.getStation())
                    .putValue(SInfoResultType.years, yearsToString(isEpoch, fromYear, toYear)).putValue(SInfoResultType.collisionCount, aggregate.getCount()));
        }
    }

    /**
     * Returns the string representation of a year or epoch
     */
    public static String yearsToString(final boolean isEpoch, final DateRange years) {
        return yearsToString(isEpoch, years.getFromYear(), years.getToYear());
    }

    /**
     * Returns the string representation of a year or epoch
     */
    public static String yearsToString(final boolean isEpoch, final int fromYear, final int toYear) {
        return (isEpoch ? String.format("%d-%d", fromYear, toYear) : Integer.toString(fromYear));
    }

    /**
     * Calculates the collision details for a km range of a river and a year, and adds them to a ResultRow collection
     *
     * @param qfinderProblemCount
     */
    private void calculateDetails(final Collection<ResultRow> rows, final River river, final CollisionAccess access, final int year,
            final Map<String, TreeMap<Date, GaugeDischargeValuesFinder>> qFinders, final Map<String, GaugeMainValueFinder> zoneFinders,
            final Calculation problems, final List<DateRange> years, final int qfinderProblemCount) {

        final double fromKm = access.getLowerKm();
        final double toKm = access.getUpperKm();

        for (final CollisionValue collision : CollisionValue.getValues(river, fromKm, toKm, DateUtil.getStartDateFromYear(year),
                DateUtil.getEndDateFromYear(year))) {
            final String gaugeName = collision.getGaugeName();
            final double q = getQ(qFinders, gaugeName, collision.getGaugeW().doubleValue(), collision.getEventDate(), river, problems, years,
                    qfinderProblemCount);
            final double qOut = Double.isInfinite(q) ? Double.NaN : q;
            final String zone = getZone(zoneFinders, gaugeName, q, river, problems);
            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)
                    .putValue(SInfoResultType.dischargeZone, zone));
        }
    }

    /**
     * Gets the discharge of a gauge and a W
     *
     * @param years
     * @param qfinderProblemCount
     */
    private double getQ(final Map<String, TreeMap<Date, GaugeDischargeValuesFinder>> qFinders, final String gaugeName, final double w, final Date when,
            final River river, final Calculation problems, final List<DateRange> years, int qfinderProblemCount) {
        // Find the gauge and load its discharge table, if not already in the map
        final String gnKey = gaugeName.toLowerCase();
        if (!qFinders.containsKey(gnKey))
            addQFinders(qFinders, gaugeName, problems, river, years);

        // Interpolate W.
        // Interpolate W.
        final GaugeDischargeValuesFinder qFinder = getQFinder(qFinders.get(gnKey), when);
        if (qFinder == null) {
            qfinderProblemCount++;
            if (qfinderProblemCount == 1)
                problems.addProblem("gauge_discharge_table.missing", gaugeName);
            return Double.NaN;
        }
        return qFinder.getDischarge(w);
    }

    /**
     * Add the discharge finders for a gauge and the active time period
     */
    private void addQFinders(final Map<String, TreeMap<Date, GaugeDischargeValuesFinder>> qFinders, final String gaugeName, final Calculation problems,
            final River river, final List<DateRange> years) {
        final String gnKey = gaugeName.toLowerCase();
        final Gauge gauge = river.determineGaugeByName(gaugeName);
        if (gauge == null) {
            qFinders.put(gnKey, new TreeMap<Date, GaugeDischargeValuesFinder>());
            return;
        }
        final List<DischargeTable> qtables = DischargeTable.fetchHistoricalDischargeTables(gauge, years.get(0).getFrom(), years.get(years.size() - 1).getTo());
        qFinders.put(gnKey, new TreeMap<Date, GaugeDischargeValuesFinder>());
        for (final DischargeTable qtable : qtables)
            qFinders.get(gnKey).put(qtable.getTimeInterval().getStartTime(), GaugeDischargeValuesFinder.loadValues(qtable, river, gaugeName, problems));
    }

    /**
     * Searches a q values finder map for a date time
     */
    private GaugeDischargeValuesFinder getQFinder(final TreeMap<Date, GaugeDischargeValuesFinder> qFinders, final Date when) {
        if (qFinders.containsKey(when))
            return qFinders.get(when);
        final Entry<Date, GaugeDischargeValuesFinder> 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<String, GaugeMainValueFinder> zoneFinders, final String gaugeName, final double q, final River river,
            final Calculation problems) {
        // 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"));
        // Build the zone name
        if (zoneFinders.get(gnKey) == null)
            return "";
        return zoneFinders.get(gnKey).findZoneName(q);
    }
}

http://dive4elements.wald.intevation.org