ClimaticDailyData.java

/*
 * This file is part of Indicators.
 *
 * Indicators is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Indicators is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Indicators. If not, see <https://www.gnu.org/licenses/>.
 */
package fr.inrae.agroclim.indicators.model.data.climate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;

import fr.inrae.agroclim.indicators.model.TimeScale;
import fr.inrae.agroclim.indicators.model.data.DailyData;
import fr.inrae.agroclim.indicators.model.data.Variable;
import fr.inrae.agroclim.indicators.model.data.Variable.Type;
import fr.inrae.agroclim.indicators.resources.Messages;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;

/**
 * Class to handle reading climatic daily data.
 *
 * @author $Author$
 */
public final class ClimaticDailyData extends DailyData {

    /**
     * UUID for Serializable.
     */
    private static final long serialVersionUID = 4872847709393688042L;

    /**
     * @param timescale related timescale
     * @return Names of all columns, according to timescale.
     */
    public static List<String> getAllColumnNames(@NonNull final TimeScale timescale) {
        switch (timescale) {
        case DAILY:
            return Arrays.asList("year", "month", "day", "tmin", "tmax", "tmean", "radiation", "rain", "wind", "etp",
                    "rh", "soilwatercontent");
        case HOURLY:
            return Arrays.asList("year", "month", "day", "hour", "th", "radiation", "rain", "wind", "etp", "rh");
        default:
            throw new UnsupportedOperationException("Not implemented: " + timescale);
        }
    }

    /**
     * @param timescale related timescale
     * @return Names of all columns, according to timescale.
     */
    public static List<String> getOptionalColumnNames(final TimeScale timescale) {
        switch (timescale) {
        case DAILY:
            return Arrays.asList("rh", "soilwatercontent");
        case HOURLY:
            return Arrays.asList("rh");
        default:
            throw new UnsupportedOperationException("Not implemented: " + timescale);
        }
    }

    /**
     * @param timescale related timescale
     * @return Names of required columns, according to timescale, used by GUI.
     */
    public static List<String> getRequiredColumnNames(final TimeScale timescale) {
        switch (timescale) {
        case DAILY:
            return Arrays.asList("year", "month", "day");
        case HOURLY:
            return Arrays.asList("year", "month", "hour");
        default:
            throw new UnsupportedOperationException("Not implemented: " + timescale);
        }
    }

    /**
     * Errors for the data.
     */
    private final List<String> errors = new ArrayList<>();

    /**
     * Compute ETP from climatic daily data.
     */
    @Getter
    @Setter
    private EtpCalculator etpCalculator;

    /**
     * Related time scale.
     */
    @Setter
    private TimeScale timescale;

    /**
     * Warnings for the data.
     */
    private final List<String> warnings = new ArrayList<>();

    /**
     * Constructor.
     */
    public ClimaticDailyData() {
        //
    }

    /**
     * Copy constructor.
     *
     * @param data instance to copy
     */
    public ClimaticDailyData(final ClimaticDailyData data) {
        super(data);
        this.timescale = data.timescale;
        this.etpCalculator = data.etpCalculator;
    }

    @Override
    public void check(final int line, final String path) {
        if (getTmin() != null && getTmax() != null && getTmin() > getTmax()) {
            warnings.add(Messages.format("warning.tmax.inferiorto.tmin", path, line));
        }
        final double rhMin = 0;
        final double rhMax = 100;
        if (getRh() != null && (getRh() < rhMin || getRh() > rhMax)) {
            warnings.add(Messages.format("error.rh.outofrange", path, line));
        }
        if (timescale == TimeScale.DAILY && getRawValue(Variable.ETP) == null) {
            boolean missing = false;
            for (final Variable variable : etpCalculator.getVariables()) {
                if (getRawValue(variable) == null) {
                    missing = true;
                }
            }
            if (missing) {
                warnings.add(Messages.format("warning.missing", path, line, "ETP"));
            }
        }
        for (final Variable variable : Variable.getByTimeScaleAndType(timescale, Type.CLIMATIC)) {
            if (variable != Variable.ETP && getValue(variable) == null) {
                warnings.add(Messages.format("warning.missing", path, line, variable.getName()));
            }
        }
        if (getMonth() == null || getMonth() != null && getMonth() == 0) {
            errors.add(Messages.format("error.month.null", path, line));
        }
        if (getDay() == null || getDay() != null && getDay() == 0) {
            errors.add(Messages.format("error.day.null", path, line));
        }
    }

    @Override
    public ClimaticDailyData clone() throws CloneNotSupportedException {
        final ClimaticDailyData clone = (ClimaticDailyData) super.clone();
        if (etpCalculator != null) {
            clone.etpCalculator = etpCalculator.clone();
        }
        clone.errors.addAll(errors);
        clone.warnings.addAll(warnings);
        return clone;
    }

    @Override
    public List<String> getErrors() {
        return errors;
    }

    /**
     * @return stored ETP value or compute it.
     */
    public Double getEtp() {
        Double etp = getRawValue(Variable.ETP);
        if (etp == null && etpCalculator != null) {
            try {
                etp = etpCalculator.compute(this);
                setEtp(etp);
            } catch (IllegalArgumentException e) {
                etp = null;
            }
        }
        return etp;
    }

    /**
     * @return Global radiation [W/m²].
     */
    public Double getRadiation() {
        return getRawValue(Variable.RADIATION);
    }

    /**
     * @return Rain precipitation [mm/d].
     */
    public Double getRain() {
        return getRawValue(Variable.RAIN);
    }

    /**
     * @return relative humidity (%)
     */
    public Double getRh() {
        return getRawValue(Variable.RH);
    }

    /**
     * @return Soil water content [% mass]
     */
    public Double getSoilwatercontent() {
        return getRawValue(Variable.SOILWATERCONTENT);
    }

    /**
     * @return Instantaneous hourly air temperature [°C].
     */
    public Double getTh() {
        return getRawValue(Variable.TH);
    }

    /**
     * @return Maximal air temperature [°C]
     */
    public Double getTmax() {
        return getRawValue(Variable.TMAX);
    }

    /**
     * @return Average air temperature [°C]
     */
    public Double getTmean() {
        return getRawValue(Variable.TMEAN);
    }

    /**
     * @return Minimal air temperature [°C]
     */
    public Double getTmin() {
        return getRawValue(Variable.TMIN);
    }

    @Override
    public Double getValue(final Variable variable) {
        if (variable == Variable.ETP) {
            return getEtp();
        }
        return super.getRawValue(variable);
    }

    @Override
    public List<String> getWarnings() {
        return warnings;
    }

    /**
     * @return Wind speed [m/s]
     */
    public Double getWind() {
        return getValue(Variable.WIND);
    }

    /**
     * @param value Evapotranspiration [mm/d].
     */
    public void setEtp(final Double value) {
        setValue(Variable.ETP, value);
    }

    /**
     * @param value Global radiation [W/m²]
     */
    public void setRadiation(final Double value) {
        setValue(Variable.RADIATION, value);
    }

    /**
     * @param value Rain precipitation [mm]
     */
    public void setRain(final Double value) {
        setValue(Variable.RAIN, value);
    }

    /**
     * @param value Relative humidity.
     */
    public void setRh(final Double value) {
        setValue(Variable.RH, value);
    }

    /**
     * @param value Soil water content [% mass]
     */
    public void setSoilwatercontent(final Double value) {
        setValue(Variable.SOILWATERCONTENT, value);
    }

    /**
     * @param value Instantaneous hourly air temperature [°C]
     */
    public void setTh(final Double value) {
        setValue(Variable.TH, value);
    }

    /**
     * @param value Maximal air temperature [°C]
     */
    public void setTmax(final Double value) {
        setValue(Variable.TMAX, value);
    }

    /**
     * @param value Average air temperature [°C]
     */
    public void setTmean(final Double value) {
        setValue(Variable.TMEAN, value);
    }

    /**
     * @param value Minimal air temperature [°C]
     */
    public void setTmin(final Double value) {
        setValue(Variable.TMIN, value);
    }

    /**
     * @param value Wind speed [m/s]
     */
    public void setWind(final Double value) {
        setValue(Variable.WIND, value);
    }

    @Override
    public String toString() {
        final StringJoiner sj = new StringJoiner(", ");
        for (final Variable variable : Variable.values()) {
            sj.add(variable.name() + "=" + getRawValue(variable));
        }
        return "ClimaticDailyData [year=" + getYear() + ", month=" + getMonth()
        + ", day=" + getDay() + ", hour=" + getHour() + ", " + sj.toString() + "]";
    }
}