view artifacts/src/main/java/org/dive4elements/river/artifacts/model/minfo/SedimentLoadDataCalculation.java @ 8062:8489565ff563

Sedimen load: Added bundle for fraction results.
author Sascha L. Teichmann <teichmann@intevation.de>
date Wed, 30 Jul 2014 16:50:26 +0200
parents 25feef564d09
children fe5ef780f8b1
line wrap: on
line source
/* Copyright (C) 2014 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * 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.model.minfo;

import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.dive4elements.river.artifacts.access.SedimentLoadAccess;
import org.dive4elements.river.artifacts.model.Calculation;
import org.dive4elements.river.artifacts.model.CalculationResult;
import org.dive4elements.river.artifacts.model.RiverFactory;
import org.apache.log4j.Logger;
import org.dive4elements.river.artifacts.model.minfo.SedimentLoadData;
import org.dive4elements.river.artifacts.model.minfo.SedimentLoadData.Value;
import org.dive4elements.river.artifacts.model.minfo.SedimentLoadData.Station;
import org.dive4elements.river.artifacts.model.minfo.SedimentLoadDataValueFilter.And;
import org.dive4elements.river.artifacts.model.minfo.SedimentLoadDataValueFilter.IsEpoch;
import org.dive4elements.river.artifacts.model.minfo.SedimentLoadDataValueFilter.IsOfficial;
import org.dive4elements.river.artifacts.model.minfo.SedimentLoadDataValueFilter.Not;
import org.dive4elements.river.artifacts.model.minfo.SedimentLoadDataValueFilter.TimeRangeIntersects;
import org.dive4elements.river.model.River;
import org.dive4elements.river.utils.DoubleUtil;

public class SedimentLoadDataCalculation
extends      Calculation
{
    private static final Logger log = Logger
        .getLogger(SedimentLoadDataCalculation.class);

    public static final int [] TOTAL_LOAD_FLYS = {
        SedimentLoadData.GF_COARSE,
        SedimentLoadData.GF_FINE_MIDDLE,
        SedimentLoadData.GF_SAND,
        SedimentLoadData.GF_SUSP_SEDIMENT
    };

    public static final int [] BED_LOAD_FLYS = {
        SedimentLoadData.GF_COARSE,
        SedimentLoadData.GF_FINE_MIDDLE,
        SedimentLoadData.GF_SAND
    };

    public static final int [] BED_LOAD_SUSP_SAND_FLYS = {
        SedimentLoadData.GF_COARSE,
        SedimentLoadData.GF_FINE_MIDDLE,
        SedimentLoadData.GF_SAND,
        SedimentLoadData.GF_SUSP_SAND
    };

    public static final int [] TOTAL_LOAD_BFG = {
        SedimentLoadData.GF_TOTAL
    };

    public static final int [] BED_LOAD_BFG = {
        SedimentLoadData.GF_BED_LOAD
    };

    public static final int [] SUSPENDED_LOAD_BFG = {
        SedimentLoadData.GF_SUSPENDED_LOAD
    };

    public static final int [] COARSE_FLYS = {
        SedimentLoadData.GF_COARSE
    };

    public static final int [] FINE_MIDDLE_FLYS = {
        SedimentLoadData.GF_FINE_MIDDLE
    };

    public static final int [] SAND_FLYS = {
        SedimentLoadData.GF_SAND
    };

    public static final int [] SUSP_SAND_FLYS = {
        SedimentLoadData.GF_SUSP_SAND
    };

    public static final int [] SUSP_SAND_BED_FLYS = {
        SedimentLoadData.GF_SUSP_SAND_BED
    };

    public static final int [] SUSP_SEDIMENT_FLYS = {
        SedimentLoadData.GF_SUSP_SEDIMENT
    };

    public static final class GrainFraction {
        private String description;
        private int [] grainFractions;

        public GrainFraction(String description, int [] grainFractions) {
            this.description = description;
            this.grainFractions = grainFractions;
        }
        public static final GrainFraction make(String description, int [] grainFractions) {
            return new GrainFraction(description, grainFractions);
        }

        public String getDescription() {
            return description;
        }

        public int [] getGrainFractions() {
            return grainFractions;
        }
    } // class GrainFraction

    public static final GrainFraction [] GRAIN_FRACTIONS = {
        // TODO: i18n
        GrainFraction.make("minfo.total.load.flys",         TOTAL_LOAD_FLYS),
        GrainFraction.make("minfo.bed.load.flys",           BED_LOAD_FLYS),
        GrainFraction.make("minfo.bed.load.susp.sand.flys", BED_LOAD_SUSP_SAND_FLYS),
        GrainFraction.make("minfo.total.load.bfg",          TOTAL_LOAD_BFG),
        GrainFraction.make("minfo.bed.load.bfg",            BED_LOAD_BFG),
        GrainFraction.make("minfo.suspended.load.bfg",      SUSPENDED_LOAD_BFG),
        GrainFraction.make("minfo.coarse.flys",             COARSE_FLYS),
        GrainFraction.make("minfo.fine.middle.flys",        FINE_MIDDLE_FLYS),
        GrainFraction.make("minfo.sand.flys",               SAND_FLYS) ,
        GrainFraction.make("minfo.susp.sand.flys",          SUSP_SAND_FLYS),
        GrainFraction.make("minfo.susp.sand.bed.flys",      SUSP_SAND_BED_FLYS),
        GrainFraction.make("minfo.susp.sediment.flys",      SUSP_SEDIMENT_FLYS),
    };

    public static class Sum implements Value.Visitor {

        protected int    n;
        protected double sum;

        public Sum() {
        }

        public double getSum() {
            return sum;
        }

        public int getN() {
            return n;
        }

        public void reset() {
            n   = 0;
            sum = 0.0;
        }

        @Override
        public void visit(Value value) {
            sum += value.getValue();
            ++n;
        }
    } // class Sum

    public static final class Avg extends Sum {
        public Avg() {
        }

        @Override
        public double getSum() {
            return n == 0 ? 0.0 : sum/(double)n;
        }
    } // class Sum


    private String   river;
    private String   yearEpoch;
    private String   unit;
    private int [][] epochs;
    private int []   years;
    private double   from;
    private double   to;


    public SedimentLoadDataCalculation() {
    }

    public CalculationResult calculate(SedimentLoadAccess access) {
        log.info("SedimentLoadDataCalculation.calculate");

        String river     = access.getRiverName();
        String yearEpoch = access.getYearEpoch();
        String unit      = access.getUnit();

        int [] years  = null;
        int [][] epochs = null;

        double from = access.getLowerKM();
        double to   = access.getUpperKM();

        if (yearEpoch.equals("year")) {
            years = access.getPeriod();
        }
        else if (yearEpoch.equals("epoch") || yearEpoch.equals("off_epoch")) {
            epochs = access.getEpochs();
        }
        else {
            addProblem("minfo.missing.year_epoch");
        }

        if (river == null) {
            // TODO: i18n
            addProblem("minfo.missing.river");
        }

        if (years == null && epochs == null) {
            addProblem("minfo.missing.time");
        }

        if (!hasProblems()) {
            this.river     = river;
            this.yearEpoch = yearEpoch;
            this.unit      = unit;
            this.years     = years;
            this.epochs    = epochs;
            this.from      = from;
            this.to        = to;
            return internalCalculate();
        }

        return error(null);
    }

    private CalculationResult error(String msg) {
        if (msg != null) addProblem(msg);
        return new CalculationResult(this);
    }

    private CalculationResult internalCalculate() {
        if ("year".equals(yearEpoch))      return calculateYears();
        if ("epoch".equals(yearEpoch))     return calculateEpochs();
        if ("off_epoch".equals(yearEpoch)) return calculateOffEpochs();

        // TODO: i18n
        return error("minfo.sediment.load.unknown.calc.mode");
    }

    private CalculationResult calculateYears() {
        SedimentLoadData sld =
            SedimentLoadDataFactory.INSTANCE.getSedimentLoadData(river);
        if (sld == null) {
            // TODO: i18n
            return error("minfo.sediment.load.no.data");
        }

        SedimentLoadDataResult sldr = new SedimentLoadDataResult();

        boolean isKmUp = isKmUp();
        Set<Integer> missingFractions = new TreeSet<Integer>();

        Not notEpochs = new Not(IsEpoch.INSTANCE);

        Sum sum = new Sum();

        for (int year: years) {
            Value.Filter filter = new And()
                .add(notEpochs)
                .add(new TimeRangeIntersects(year));

            for (GrainFraction gf: GRAIN_FRACTIONS) {
                double [][] result = sum(
                    sld, gf.getGrainFractions(), filter, sum, isKmUp,
                    missingFractions);

                if (result[0].length == 0 || DoubleUtil.isNaN(result[1])) {
                    // TODO: resolve i18n
                    addProblem("minfo.sediment.load.no.fractions",
                        gf.getDescription());
                    continue;
                }
                // TODO: Optionally transform units.
                SedimentLoadDataResult.Fraction sldrf =
                    new SedimentLoadDataResult.Fraction(gf.getDescription(), result);
                sldr.addFraction(sldrf);
            }
        }
        // TODO: Generate messages for missing fractions.
        return new CalculationResult(sldr, this);
    }

    private CalculationResult calculateEpochs() {
        SedimentLoadData sld =
            SedimentLoadDataFactory.INSTANCE.getSedimentLoadData(river);
        if (sld == null) {
            // TODO: i18n
            return error("minfo.sediment.load.no.data");
        }

        SedimentLoadDataResult sldr = new SedimentLoadDataResult();

        boolean isKmUp = isKmUp();
        Set<Integer> missingFractions = new TreeSet<Integer>();

        // They are not epochs, they are single years!
        Not notEpochs = new Not(IsEpoch.INSTANCE);

        for (int [] epoch: epochs) {
            Value.Filter filter = new And()
                .add(notEpochs)
                .add(new TimeRangeIntersects(epoch[0], epoch[1]));

            Avg avg = new Avg();

            for (GrainFraction gf: GRAIN_FRACTIONS) {
                double [][] result = sum(
                    sld, gf.getGrainFractions(), filter, avg, isKmUp,
                    missingFractions);

                if (result[0].length == 0 || DoubleUtil.isNaN(result[1])) {
                    // TODO: resolve i18n
                    addProblem("minfo.sediment.load.no.fractions",
                        gf.getDescription());
                    continue;
                }
                // TODO: Optionally transform units.
                SedimentLoadDataResult.Fraction sldrf =
                    new SedimentLoadDataResult.Fraction(gf.getDescription(), result);
                sldr.addFraction(sldrf);
            }
        }
        // TODO: Generate messages for missing fractions.
        return new CalculationResult(sldr, this);
    }

    private CalculationResult calculateOffEpochs() {
        SedimentLoadData sld =
            SedimentLoadDataFactory.INSTANCE.getSedimentLoadData(river);
        if (sld == null) {
            // TODO: i18n
            return error("minfo.sediment.load.no.data");
        }

        SedimentLoadDataResult sldr = new SedimentLoadDataResult();

        boolean isKmUp = isKmUp();
        Set<Integer> missingFractions = new TreeSet<Integer>();

        for (int [] epoch: epochs) {
            Value.Filter filter = new And()
                .add(IsOfficial.INSTANCE)
                .add(new TimeRangeIntersects(epoch[0], epoch[1]));

            Sum sum = new Sum();

            for (GrainFraction gf: GRAIN_FRACTIONS) {
                double [][] result = sum(
                    sld, gf.getGrainFractions(), filter, sum, isKmUp,
                    missingFractions);

                if (result[0].length == 0 || DoubleUtil.isNaN(result[1])) {
                    // TODO: resolve i18n
                    addProblem("minfo.sediment.load.no.fractions",
                        gf.getDescription());
                    continue;
                }
                // TODO: Optionally transform units.
                SedimentLoadDataResult.Fraction sldrf =
                    new SedimentLoadDataResult.Fraction(gf.getDescription(), result);
                sldr.addFraction(sldrf);
            }
        }
        // TODO: Generate messages for missing fractions.
        return new CalculationResult(sldr, this);
    }

    /** Figure out flow direction of river. */
    private boolean isKmUp() {
        River r = RiverFactory.getRiver(river);
        if (r == null) {
            addProblem("minfo.missing.river");
            return true;
        }
        return r.getKmUp();
    }

    public double[][] sum(
        SedimentLoadData sld,
        int []           grainFractions,
        Value.Filter     filter,
        Sum              sum,
        boolean          isKMUp,
        Set<Integer>     missingFractions
    ) {
        List<Station> stations = sld.findStations(from, to);

        double [] values = new double[grainFractions.length];

        double [][] result = new double[2][stations.size()];

        for (int j = 0, S = stations.size(); j < S; ++j) {
            Station station = stations.get(j);
            for (int i = 0; i < grainFractions.length; ++i) {
                int gf = grainFractions[i];
                sum.reset();
                station.filterGrainFraction(gf, filter, sum);
                if (sum.getN() == 0) { // No values found
                    int msType = SedimentLoadData.measurementStationType(gf);
                    // Station of right fraction type already? No: take previous.
                    if (!station.isType(msType)) {
                        Station prev = station.prevByType(msType, isKMUp);
                        if (prev != null) {
                            prev.filterGrainFraction(gf, filter, sum);
                        }
                    }
                }

                if (sum.getN() == 0) {
                    missingFractions.add(gf);
                    values[i] = Double.NaN;
                } else {
                    values[i] = sum.getSum();
                }
            }
            result[0][j] = station.getStation();
            result[1][j] = DoubleUtil.sum(values);
        }

        // TODO: Handle 'virtual' measument stations 'from' and 'to'.

        return result;
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :

http://dive4elements.wald.intevation.org