view artifacts/src/main/java/org/dive4elements/river/artifacts/bundu/bezugswst/BezugswstCalculation.java @ 9448:d32b11d585cd

CSV-Export bundu.wst fehlvolumen
author gernotbelger
date Wed, 22 Aug 2018 11:51:46 +0200
parents e60584f2a531
children 7e1fb8d0cb0d
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.bundu.bezugswst;

import java.util.ArrayList;
import java.util.List;

import org.dive4elements.artifacts.CallContext;
import org.dive4elements.river.artifacts.access.FixRealizingAccess;
import org.dive4elements.river.artifacts.bundu.BUNDUArtifact;
import org.dive4elements.river.artifacts.bundu.BunduResultType;
import org.dive4elements.river.artifacts.common.AbstractResultType;
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.Calculation.Problem;
import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.WQKms;
import org.dive4elements.river.artifacts.model.fixings.FixRealizingCalculation;
import org.dive4elements.river.artifacts.model.fixings.FixRealizingResult;
import org.dive4elements.river.artifacts.resources.Resources;
import org.dive4elements.river.artifacts.sinfo.common.RiverInfoProvider;
import org.dive4elements.river.artifacts.sinfo.common.SInfoResultType;
import org.dive4elements.river.artifacts.sinfo.tkhstate.BedHeightsFinder;
import org.dive4elements.river.artifacts.sinfo.tkhstate.WinfoArtifactWrapper;
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.exports.WaterlevelDescriptionBuilder;
import org.dive4elements.river.model.BedHeightValueType;
import org.dive4elements.river.model.River;
import org.dive4elements.river.utils.Formatter;

class BezugswstCalculation {

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

    /**
     * Additional depth (m) to compute the excavation volume
     */
    private final static double EXCAVATION_DEPTH = 0.2; // REMARK Sollte von außen einstellbar sein

    /**
     * Excavation costs (euro) per cubic meter
     */
    private final static double EXPENSE_PER_CBM = 12.0; // REMARK Sollte von außen einstellbar sein

    private final CallContext context;

    private final List<ResultRow> rows;

    public BezugswstCalculation(final CallContext context) {
        this.context = context;
        this.rows = new ArrayList<>();
    }

    /**
     * Calculates the result rows of a bundu bzws workflow
     */
    public CalculationResult calculate(final BUNDUArtifact bunduartifact) {

        // Get input data
        final String user = CalculationUtils.findArtifactUser(this.context, bunduartifact);
        final BunduAccess access = new BunduAccess(bunduartifact);
        final River river = access.getRiver();
        final RiverInfo riverInfo = new RiverInfo(river);
        final String calcModeLabel = Resources.getMsg(this.context.getMeta(), "bundu_bezugswst");
        final boolean preprocessing = access.getPreprocessing();
        final int startYear = access.getStartYear();
        final int endYear = access.getBezugsJahr();
        final Integer ud = access.getUd();
        final Double missingVolFrom = access.getMissingVolFrom();
        final Double missingVolTo = access.getMissingVolTo();

        final BezugswstCalculationResults results = new BezugswstCalculationResults(calcModeLabel, user, riverInfo, access.getRange());

        final Calculation problems = new Calculation();

        // Calculate the wspl for the selected river range as in fixa awspl
        bunduartifact.addStringData("wq_isq", "true"); // macht doch Sinn hier, sorry!
        final WinfoArtifactWrapper winfo = new WinfoArtifactWrapper(bunduartifact);
        final RiverInfoProvider riverInfoProvider = RiverInfoProvider.forRange(this.context, river, access.getRange(), true);
        final FixRealizingResult fixResult = calculateWspl(bunduartifact, problems);
        if (fixResult == null)
            return new CalculationResult(results, problems);

        final WQKms wqkms = fixResult.getWQKms()[0];
        final WstInfo wstInfo = new WstInfo(wqkms.getName(), 0, riverInfoProvider.getReferenceGauge(), true);

        // Fetch the bed heights of the selected sounding
        final int bedHeightId = access.getBedHeightID();
        final BedHeightsFinder bedHeightsFinder = BedHeightsFinder.forId(problems, bedHeightId, access.getRange());
        if (bedHeightsFinder == null)
            return new CalculationResult(results, problems);

        // Fetch the river channel data
        final ChannelFinder channelFinder = ChannelFinder.loadValues(problems, river, access.getBezugsJahr());
        if (channelFinder == null)
            return new CalculationResult(results, problems);

        // Compute the result rows
        for (int i = 0; i <= wqkms.size() - 1; i++) {
            this.rows.add(createRow(wqkms.getKm(i), wqkms.getW(i), wqkms.getQ(i), bedHeightsFinder, channelFinder, riverInfoProvider, wstInfo));
        }

        // Compute the missing volumes
        if (access.isCalculateMissingValue()) {
            computeMissingVolumes(access.getMissingVolFrom().doubleValue(), access.getMissingVolTo().doubleValue(), problems);
            // TODO Lagerungsdichte holen/berechnen (density) und Massen berechnen
        }

        // Add the result to the results collection
        final WaterlevelDescriptionBuilder descBuilder = new WaterlevelDescriptionBuilder(winfo, this.context);
        final String qtext = descBuilder.getMetadataQ();
        final BezugswstMainCalculationResult result = new BezugswstMainCalculationResult("bundu-bzws", this.rows, bedHeightsFinder.getInfo(), wstInfo,
                access.getFunction(), preprocessing, startYear, endYear, ud, qtext, wqkms, missingVolFrom, missingVolTo);
        results.addResult(result, problems);

        // Create the missing volume results
        if (access.getMissingVolFrom() != null) {
            final String title1 = Resources.getMsg(this.context.getMeta(), "bundu.export.csv.title.bezugswst.result1");
            final BezugswstMissVolCalculationResult1 r1 = new BezugswstMissVolCalculationResult1(title1, this.rows);
            results.addResult(r1, null);

            final String title2 = Resources.getMsg(this.context.getMeta(), "bundu.export.csv.title.bezugswst.result2");
            final BezugswstMissVolCalculationResult2 r2 = new BezugswstMissVolCalculationResult2(title2, this.rows);
            results.addResult(r2, null);

            final String title3 = Resources.getMsg(this.context.getMeta(), "bundu.export.csv.title.bezugswst.result3");
            final List<ResultRow> totalRows = new ArrayList<>();
            totalRows.add(createTotalsRow(missingVolFrom.doubleValue(), missingVolTo.doubleValue(), problems));
            final BezugswstMissVolCalculationResult3 r3 = new BezugswstMissVolCalculationResult3(title3, totalRows);
            results.addResult(r3, null);
        }

        return new CalculationResult(results, problems);
    }

    /**
     * Calculates a w-q-longitudinal section for a river range and Q specified in an artifact
     */
    private FixRealizingResult calculateWspl(final BUNDUArtifact bundu, final Calculation problems) {

        final FixRealizingAccess access = new FixRealizingAccess(bundu);
        final FixRealizingCalculation calc = new FixRealizingCalculation(access);

        final CalculationResult res = calc.calculate();

        final FixRealizingResult fixRes = (FixRealizingResult) res.getData();

        final List<Problem> problems2 = res.getReport().getProblems();
        if (problems2 != null) {
            for (final Problem problem : problems2) {
                problems.addProblem(problem);
            }
        }
        return fixRes;
    }

    /**
     * Create a result row for a station
     */
    private ResultRow createRow(final double station, final double w, final double q, final BedHeightsFinder bedHeightsFinder,
            final ChannelFinder channelFinder, final RiverInfoProvider riverInfoProv, final WstInfo wstInfo) {

        // Set W and Q
        final ResultRow row = ResultRow.create();
        row.putValue(GeneralResultType.station, station);
        row.putValue(BunduResultType.bezugswst, w);
        row.putValue(GeneralResultType.dischargeQwithUnit, q);
        row.putValue(GeneralResultType.waterlevelLabel, wstInfo.getLabel());
        row.putValue(GeneralResultType.gaugeLabel, riverInfoProv.findGauge(station));
        row.putValue(GeneralResultType.location, riverInfoProv.getLocation(station));

        // Set bed and channel bottom height
        final double msh = bedHeightsFinder.getMeanBedHeight(station);
        row.putValue(SInfoResultType.meanBedHeight, msh);
        if (!Double.isNaN(w) && !Double.isNaN(msh))
            row.putValue(SInfoResultType.flowdepth, Formatter.roundFlowDepth(w).subtract(Formatter.roundFlowDepth(msh)).doubleValue());
        else
            row.putValue(SInfoResultType.flowdepth, Double.NaN);
        final double channelDepth = channelFinder.getDepth(station);
        row.putValue(BunduResultType.channelDepth, channelDepth);
        double channelHeight;
        if (!Double.isNaN(w) && !Double.isNaN(channelDepth))
            channelHeight = Formatter.roundFlowDepth(w).subtract(Formatter.roundFlowDepth(channelDepth)).doubleValue();
        else
            channelHeight = Double.NaN;
        row.putValue(BunduResultType.channelLowerEdge, channelHeight);
        final double channelWidth = channelFinder.getWidth(station);
        row.putValue(BunduResultType.channelWidth, channelWidth);

        // Set field heights and missing heights
        final List<Double> fieldHeights = new ArrayList<>();
        final List<Double> fieldDepths = new ArrayList<>();
        final List<Double> fieldMissDepths = new ArrayList<>();
        final List<Double> fieldMissWidths = new ArrayList<>();
        int missFieldCnt = 0;
        for (int i = BedHeightValueType.FIELD_FIRST_INDEX; i <= BedHeightValueType.FIELD_LAST_INDEX; i++) {
            final double h = bedHeightsFinder.getFieldHeight(station, i);
            fieldHeights.add(Double.valueOf(h));
            fieldDepths.add(Double.valueOf(w - h));
            if (h > channelHeight + 0.001) {
                missFieldCnt++;
                fieldMissDepths.add(Double.valueOf(h - channelHeight));
                fieldMissWidths.add(Double.valueOf(channelWidth / BedHeightValueType.FIELD_LAST_INDEX));
            } else {
                fieldMissDepths.add(Double.valueOf(0.0));
                fieldMissWidths.add(Double.valueOf(0.0));
            }
        }
        row.putValue(BunduResultType.missDepthFields, fieldMissDepths);
        row.putValue(BunduResultType.missWidthFields, fieldMissWidths);
        row.putValue(BunduResultType.hasMissingDepth, (missFieldCnt >= 1));
        row.putValue(BunduResultType.bedHeightFields, fieldHeights);
        row.putValue(BunduResultType.depthFields, fieldDepths);

        // Preset the missing volume fields with NaN
        row.putValue(BunduResultType.excavationCosts, Double.NaN);
        row.putValue(BunduResultType.excavationVolume, Double.NaN);
        row.putValue(BunduResultType.missVolumeMeanBed, Double.NaN);
        row.putValue(BunduResultType.missMassMeanBed, Double.NaN);
        row.putValue(BunduResultType.missVolumeTotal, Double.NaN);
        row.putValue(BunduResultType.missMassTotal, Double.NaN);
        row.putValue(BunduResultType.density, Double.NaN);
        row.putValue(BunduResultType.missStationRangeFrom, Double.NaN);
        row.putValue(BunduResultType.missStationRangeTo, Double.NaN);

        return row;
    }

    /**
     * Computes the missing volumes in a km range
     */
    private void computeMissingVolumes(final double kmFrom, final double kmTo, final Calculation problems) {
        // Search start km
        int first = -1;
        for (int j = 0; j <= this.rows.size() - 1; j++) {
            if (this.rows.get(j).getDoubleValue(GeneralResultType.station) > kmFrom - 0.0001) {
                first = j;
                break;
            }
        }
        if (first < 0)
            return;
        int last = this.rows.size() - 1;
        int i = first;
        while (i <= this.rows.size() - 1) {
            if (this.rows.get(i).getDoubleValue(GeneralResultType.station) > kmTo + 0.0001)
                break;
            if (this.rows.get(i).getDoubleValue(GeneralResultType.station) > kmTo - 0.0001)
                last = i;
            final List<Double> areas = new ArrayList<>();
            final List<Double> volumes = new ArrayList<>();
            double vTotal = 0.0;
            double vExcav = 0.0;
            double expenses = 0.0;
            for (int j = BedHeightValueType.FIELD_FIRST_INDEX; j <= BedHeightValueType.FIELD_LAST_INDEX; j++) {
                if (getFieldValue(i, BunduResultType.missDepthFields, j) > 0.0001) {
                    computeMissingVolume(volumes, areas, i, first, last, j);
                    vTotal += volumes.get(j - 1);
                    vExcav += volumes.get(j - 1) + areas.get(j - 1) * EXCAVATION_DEPTH;
                    expenses += vExcav * EXPENSE_PER_CBM;
                } else {
                    volumes.add(Double.valueOf(0.0));
                    areas.add(Double.valueOf(0.0));
                }
            }
            this.rows.get(i).putValue(BunduResultType.missVolumeFields, volumes);
            // TODO: berechnete masse hier einfügen!
            this.rows.get(i).putValue(BunduResultType.missMassFields, volumes);
            this.rows.get(i).putValue(BunduResultType.missAreaFields, areas);
            this.rows.get(i).putValue(BunduResultType.missVolumeTotal, vTotal);
            this.rows.get(i).putValue(BunduResultType.excavationVolume, vExcav);
            this.rows.get(i).putValue(BunduResultType.excavationCosts, expenses);
            i++;
        }
    }

    /**
     * Computes the missing volume of a field of a km row
     */
    private void computeMissingVolume(final List<Double> volumes, final List<Double> areas, final int current, final int first, final int last,
            final int field) {
        final double areaCurr = missingArea(current, first, last, field);
        final double areaPrev = missingArea(current - 1, first, last, field);
        final double areaNext = missingArea(current + 1, first, last, field);
        final double kmCurr = missingKm(current);
        final double kmPrev = missingKm(current - 1);
        final double kmNext = missingKm(current + 1);
        if (Double.isNaN(kmPrev) || Double.isNaN(kmNext)) {
            volumes.add(Double.valueOf(0.0));
            areas.add(Double.valueOf(0.0));
        } else {
            final double area1 = 0.5 * (areaCurr + areaPrev);
            final double area2 = 0.5 * (areaCurr + areaNext);
            volumes.add(Double.valueOf((Math.abs(kmCurr - kmPrev) * 0.5 * area1) + (Math.abs(kmNext - kmCurr) * 0.5 * area2)));
            areas.add(Double.valueOf(area1 + area2));
        }
    }

    /**
     * Gets the missing area of a field and a row if in range, otherwise 0.0
     */
    private double missingArea(final int rowIndex, final int first, final int last, final int fieldIndex) {
        if ((first <= rowIndex) && (rowIndex <= last))
            return getFieldValue(rowIndex, BunduResultType.missDepthFields, fieldIndex) * getFieldValue(rowIndex, BunduResultType.missWidthFields, fieldIndex);
        else
            return 0.0;
    }

    /**
     * Gets the km of a row if within range, otherwise NaN
     */
    private double missingKm(final int rowIndex) {
        if ((0 <= rowIndex) && (rowIndex <= this.rows.size() - 1))
            return this.rows.get(rowIndex).getDoubleValue(GeneralResultType.station);
        else
            return Double.NaN;
    }

    /**
     * Gets a value of one of the field list types of a row
     *
     * @param rowIndex
     * @param type
     * @param fieldIndex
     *            1-based field index
     */
    private double getFieldValue(final int rowIndex, final AbstractResultType type, final int fieldIndex) {
        @SuppressWarnings("unchecked")
        final List<Double> values = (List<Double>) this.rows.get(rowIndex).getValue(type);
        return values.get(fieldIndex - 1);
    }

    /**
     * Computes the volume and mass total of all rows with missing volumes
     */
    private ResultRow createTotalsRow(final double kmFrom, final double kmTo, final Calculation problems) {
        // Search start km
        double vTotal = 0.0;
        double mTotal = 0.0;
        for (final ResultRow row : this.rows) {
            final double volume = row.getDoubleValue(BunduResultType.missVolumeMeanBed);
            final double mass = row.getDoubleValue(BunduResultType.missMassMeanBed);
            if (!Double.isNaN(volume) && !Double.isNaN(mass)) {
                vTotal += volume;
                mTotal += mass;
            }
        }
        final ResultRow sumRow = ResultRow.create();
        sumRow.putValue(BunduResultType.missStationRangeFrom, Double.valueOf(kmFrom));
        sumRow.putValue(BunduResultType.missStationRangeTo, Double.valueOf(kmTo));
        sumRow.putValue(BunduResultType.missVolumeTotal, vTotal);
        sumRow.putValue(BunduResultType.missMassTotal, mTotal);
        return sumRow;
    }
}

http://dive4elements.wald.intevation.org