CligenData.java [tools/MetaModelTools/src/data/interpretors] Revision: cc41e45f67fa3e290531b41dca43638a993daf2e  Date: Wed Oct 30 15:09:04 MDT 2019
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package data.interpretors;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author <a href="mailto:shaun.case@colostate.edu">Shaun Case</a>
 */
public class CligenData {

    public static final int MAX_YEARS = 100;
    public static final int MONTH_AVERAGE_INDEX = 31;
    public static final int MAX_YEAR_AVERAGE_INDEX = MAX_YEARS;

    public static final int PRCP_INDEX = 0;
    public static final int DUR_INDEX = 1;
    public static final int TP_INDEX = 2;
    public static final int IP_INDEX = 3;
    public static final int TMAX_INDEX = 4;
    public static final int TMIN_INDEX = 5;
    public static final int RAD_INDEX = 6;
    public static final int W_VL_INDEX = 7;
    public static final int W_DIR_INDEX = 8;
    public static final int TDEW_INDEX = 9;
    public static final int DATA_TYPES_LENGTH = 10;

    public static final int OBS_MONTHLY_TMAX = 0;
    public static final int OBS_MONTHLY_TMIN = 1;
    public static final int OBS_MONTHLY_RAD = 2;
    public static final int OBS_MONTHLY_PRCP = 3;
    public static final int OBS_MONTHLY_LENGTH = 4;

    private static final int DAILY_VALUE_TOKENS = 13;

    //  All monthly data for observeredYears years.  (Max out, i.e. cap output, at MAX_YEARS yrs)
    private double[][][][] dailyData = new double[MAX_YEARS + 1][12][MONTH_AVERAGE_INDEX + 1][DATA_TYPES_LENGTH];  //Daily Averages
    private double[][] yearlyData = new double[MAX_YEARS][DATA_TYPES_LENGTH];  //Yearly summaries

    //  Montly averages, over entire observed years period, for prcp, dur, tp, ip, tmax, tmin, rad, w-vl, w-dir, tdew.
    private double[][] monthlyAverage = new double[12][DATA_TYPES_LENGTH];

    public static final int[] monthDays = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    private String messages = "";
    private boolean badClimateData = false;
    private String stationName = "";

    //  Entire observedYears years averaged by month
    private double[][] observedMmonthlyAverage = new double[12][OBS_MONTHLY_LENGTH];

    //  How many years total in the file
    private int observedYears = 0;

    private int yearCount = 0;

    private int elevation = 0;
    private String climateVersion;
    private int yearsSimulated;
    private int beginningYear;
    private double annualAvgPrecip = Double.NaN;

    public CligenData(String cliFileData) {
        try {
            readClimateData(cliFileData);
        } catch (IOException ex) {
            Logger.getLogger(WindGenData.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public boolean badClimateData() {
        return badClimateData;
    }

    public String cligenDataMessages() {
        return messages;
    }

    /**
     * Calculates if not yet calculated, the annual average precipitation based
     * on "observed" monthly averages found at the top of the climate file. This
     * does not represent the actual yearly or yearsSimulated averages, which
     * can be found in other functions.
     *
     * @return Returns a double value representing a calculated annual average
     * precipitation based on "observed" monthly values found in the top of the
     * climate file.
     */
    public double annualAvgPrecip() {
        if (Double.isNaN(annualAvgPrecip)) {
            double avg = 0.0;
            for (int i = 0; i < 12; i++) {
                avg += observedMmonthlyAverage[i][3];
            }
            annualAvgPrecip = avg / 12.0;
        }

        return annualAvgPrecip;
    }

    public int observedYears() {
        return observedYears;
    }

    public int yearsSimulated() {
        return yearsSimulated;
    }

    public int yearsInFile() {
        return yearCount;
    }

    public int elevation() {
        return elevation;
    }

    public String climateVersion() {
        return climateVersion;
    }

    public int beginningYear() {
        return beginningYear;
    }

    public String stationName() {
        return stationName;
    }

    public double[][] monthlyAverages() {
        return monthlyAverage;
    }

    public double[][] observedMonthlyAverages() {
        return observedMmonthlyAverage;
    }

    public double[] observedMonthlyAverages(int month) {
        if ((month >= 0) && (month < 12)) {
            return observedMmonthlyAverage[month];
        }

        return null;
    }

    public double observedMonthlyAverages(int month, int dataType) {
        if ((month >= 0) && (month < 12) && (dataType >= 0) && (dataType < OBS_MONTHLY_LENGTH)) {
            return observedMmonthlyAverage[month][dataType];
        }
        return Double.NaN;
    }

    public double[] yearlySummary(int yearIndex) {
        if ((yearIndex < yearCount) && (yearIndex >= 0)) {
            return yearlyData[yearIndex];
        }

        return null;
    }

    public double yearlySummary(int yearIndex, int dataType) {
        if ((yearIndex < yearCount) && (yearIndex >= 0) && (dataType >= 0) && (dataType < DATA_TYPES_LENGTH)) {
            return yearlyData[yearIndex][dataType];
        }

        return Double.NaN;
    }

    public double simulationAverage(int dataType) {
        if ((dataType >= 0) && (dataType < DATA_TYPES_LENGTH)) {
            double total = 0.0;

            for (int i = 0; i < 12; i++) {
                total += this.monthlyAverage[i][dataType];
            }

            return (total / 12.0);
        }

        return Double.NaN;
    }

    private void readClimateData(String windFileData) throws IOException {
        BufferedReader climateData;
        climateData = new BufferedReader(new StringReader(windFileData));

        String climateLine;
        int count = 0;
        int febCount = 0;
        boolean foundData = false;
        boolean foundEndOfCycle = false;

        while ((climateLine = climateData.readLine()) != null) {
            count++;
            if (count == 1) {
                climateVersion = climateLine;
                continue;
            }

            if (!foundData) {

                if (climateLine.contains("Station:")) {
                    stationName = climateLine.substring(climateLine.indexOf("Station:") + 9, climateLine.indexOf("CLIGEN") - (climateLine.indexOf("Station:") + 9)).trim();
                    continue;
                }

                if (climateLine.contains("Latitude ")) {
                    climateLine = climateData.readLine();
                    if (null != climateLine) {
                        count++;
                        String[] tokens = climateLine.trim().split("\\s+");

                        if (tokens.length >= 7) {
                            elevation = Integer.parseInt(tokens[2]);
                            observedYears = Integer.parseInt(tokens[3]);
                            beginningYear = Integer.parseInt(tokens[4]);
                            yearsSimulated = Integer.parseInt(tokens[5]);
                        } else {
                            messages += "\n#\nClimate file is invalid.  Data line that follows the headers on line " + count + " is incomplete.";
                            badClimateData = true;
                            break;
                        }

                    } else {
                        messages += "\n#\nClimate file is invalid.  No data line follows the headers on line " + count;
                        badClimateData = true;
                        break;
                    }
                    continue;
                }

                if (climateLine.contains("Observed monthly ave max temperature (C)")) {
                    climateLine = climateData.readLine();
                    if (null != climateLine) {
                        count++;
                        String[] tokens = climateLine.trim().split("\\s+");

                        if (tokens.length >= 12) {
                            for (int i = 0; i < 12; i++) {
                                observedMmonthlyAverage[i][OBS_MONTHLY_TMAX] = Double.parseDouble(tokens[i]);
                            }
                        } else {
                            messages += "\n#\nClimate file is invalid.  Data line that follows the observed monthly average max temperatures header on line " + count + " is incomplete.";
                            badClimateData = true;
                            break;
                        }

                    } else {
                        messages += "\n#\nClimate file is invalid.  No data line that follows the observed monthly average max temperatures header on line " + count;
                        badClimateData = true;
                        break;
                    }
                    continue;
                }

                if (climateLine.contains("Observed monthly ave min temperature (C)")) {
                    climateLine = climateData.readLine();
                    if (null != climateLine) {
                        count++;
                        String[] tokens = climateLine.trim().split("\\s+");

                        if (tokens.length >= 12) {
                            for (int i = 0; i < 12; i++) {
                                observedMmonthlyAverage[i][OBS_MONTHLY_TMIN] = Double.parseDouble(tokens[i]);
                            }
                        } else {
                            messages += "\n#\nClimate file is invalid.  Data line that follows the observed monthly average min temperatures header on line " + count + " is incomplete.";
                            badClimateData = true;
                            break;
                        }

                    } else {
                        messages += "\n#\nClimate file is invalid.  No data line that follows the observed monthly average min temperatures header on line " + count;
                        badClimateData = true;
                        break;
                    }
                    continue;
                }

                if (climateLine.contains("Observed monthly ave solar radiation (Langleys/day)")) {
                    climateLine = climateData.readLine();
                    if (null != climateLine) {
                        count++;
                        String[] tokens = climateLine.trim().split("\\s+");

                        if (tokens.length >= 12) {
                            for (int i = 0; i < 12; i++) {
                                observedMmonthlyAverage[i][OBS_MONTHLY_RAD] = Double.parseDouble(tokens[i]);
                            }
                        } else {
                            messages += "\n#\nClimate file is invalid.  Data line that follows the Observed monthly ave solar radiation (Langleys/day) header on line " + count + " is incomplete.";
                            badClimateData = true;
                            break;
                        }

                    } else {
                        messages += "\n#\nClimate file is invalid.  No data line that follows the Observed monthly ave solar radiation (Langleys/day) header on line " + count;
                        badClimateData = true;
                        break;
                    }
                    continue;
                }

                if (climateLine.contains("Observed monthly ave precipitation (mm)")) {
                    climateLine = climateData.readLine();
                    if (null != climateLine) {
                        count++;
                        String[] tokens = climateLine.trim().split("\\s+");

                        if (tokens.length >= 12) {
                            for (int i = 0; i < 12; i++) {
                                observedMmonthlyAverage[i][OBS_MONTHLY_PRCP] = Double.parseDouble(tokens[i]);
                            }
                        } else {
                            messages += "\n#\nClimate file is invalid.  Data line that follows the Observed monthly ave precipitation (mm) header on line " + count + " is incomplete.";
                            badClimateData = true;
                            break;
                        }

                    } else {
                        messages += "\n#\nClimate file is invalid.  No data line that follows the Observed monthly ave precipitation (mm) header on line " + count;
                        badClimateData = true;
                        break;
                    }
                    continue;
                }

                if (climateLine.contains("da mo year  prcp")) {
                    climateLine = climateData.readLine();
                    if (null != climateLine) {
                        count++;
                        foundData = true;
                    }
                    continue;
                }
            } else {
                //  If we got to here, then we are now reading the monthly data lines...
                int currentYear = -1;
                int currentMonth = -1;
                int numDays = 0;
                int monthDayCount = 0;
                yearCount = 0;
                int[] runningTotal = new int[DATA_TYPES_LENGTH];

                while (true) {
                    String[] tokens = climateLine.trim().split("\\s+");

                    if (tokens.length >= DAILY_VALUE_TOKENS) {
                        int day, month, year;

                        day = Integer.parseInt(tokens[0]) - 1;
                        month = Integer.parseInt(tokens[1]) - 1;
                        year = Integer.parseInt(tokens[2]) - 1;

                        if (((year >= (MAX_YEARS - 1)) || (yearCount >= yearsSimulated)) && (month == 11) && (day == 30)) {
                            foundEndOfCycle = true;
                        }

                        //Catch month transition and save yearly averages...do this before checking year transition to avoid doing this twice.
                        if (month != currentMonth) {
                            if (currentMonth == -1) {
                                currentMonth = month;
                                monthDayCount = 0;
                            } else {
                                for (int i = 0; i < DATA_TYPES_LENGTH; i++) {
                                    dailyData[currentYear][currentMonth][MONTH_AVERAGE_INDEX][i] /= monthDayCount;
                                    dailyData[MAX_YEAR_AVERAGE_INDEX][currentMonth][MONTH_AVERAGE_INDEX][i] += dailyData[currentYear][currentMonth][MONTH_AVERAGE_INDEX][i];
                                }
                                monthDayCount = 0;
                                currentMonth = month;
                            }
                        }

                        //Catch year transition and save yearly averages.
                        if (year != currentYear) {
                            if (currentYear == -1) {
                                currentYear = year;
                                for (int i = 0; i < DATA_TYPES_LENGTH; i++) {
                                    runningTotal[i] = 0;
                                }
                            } else {
                                for (int i = 0; i < DATA_TYPES_LENGTH; i++) {
                                    yearlyData[currentYear][i] = runningTotal[i] / numDays;
                                    runningTotal[i] = 0;
                                }
                                currentYear = year;
                            }
                            numDays = 0;
                            yearCount++;
                        }

                        if (year < MAX_YEARS) {
                            for (int i = 0; i < DATA_TYPES_LENGTH; i++) {
                                double dataValue = Double.parseDouble(tokens[i + 3]);

                                //Set value in matrix for this day/month/year/dataType
                                dailyData[year][month][day][i] = dataValue;

                                //Add to MAX_YEAR_AVERAGE_INDEX averages for this day/month/year/dataType
                                dailyData[MAX_YEAR_AVERAGE_INDEX][month][day][i] += dataValue;

                                //Add to yearly averages for this dataTypeIndex
                                runningTotal[i] += dataValue;

                                //Add to monthly averages for this year for this dataTypeIndex
                                dailyData[year][month][MONTH_AVERAGE_INDEX][i] += dataValue;
                            }

                            //  If its February and a leap year, count the number of these that occured in this data set.
                            if ((month == 1) && (day == 29)) {
                                febCount++;
                            }

                            //Increment number of days of data read this year.
                            numDays++;

                            //Increment number of days of data read this month/year
                            monthDayCount++;

                        } else {
                            messages += "\n#\nClimate file was longer than " + MAX_YEARS + " years.  Data clipped to " + MAX_YEARS + " years.";
                            foundEndOfCycle = true;
                            break;
                        }

                    } else {
                        if (!foundEndOfCycle) {
                            messages += "\n#\nMissing daily climate data on line " + count + "of the cligen data file.  Expected 13 fields and found only " + tokens.length + " fields instead.";
                            badClimateData = true;
                            break;
                        }
                    }

                    climateLine = climateData.readLine();
                    if (null == climateLine) {
                        break;
                    }
                    count++;
                }

                if (foundEndOfCycle) {

                    //Get final year's averages since while loop will exit right after filling in the last year of data, but not do that year's averages.
                    for (int i = 0; i < DATA_TYPES_LENGTH; i++) {
                        yearlyData[currentYear][i] = runningTotal[i] / numDays;
                        dailyData[currentYear][currentMonth][MONTH_AVERAGE_INDEX][i] /= monthDayCount;
                        dailyData[MAX_YEAR_AVERAGE_INDEX][currentMonth][MONTH_AVERAGE_INDEX][i] += dailyData[currentYear][currentMonth][MONTH_AVERAGE_INDEX][i];
                    }

                    ////////////////////////////////////////////////////////////////////////
                    // Calculates MAX_YEARS year averages...by day and by month for each dataType.
                    //
                    //    Also calculate daily averages each month over the MAX_YEARS years...saves a 
                    //  daily average for the entire MAX_YEARS years, so the MAX_YEAR_AVERAGE_INDEX line of this matrix contains an average for the entire simulation.
                    /////////////////////////////////////////////////////////////////////////////////////////////////
                    for (int monthIndex = 0; monthIndex < 12; monthIndex++) {
                        double[] monthlyAvg = new double[DATA_TYPES_LENGTH];
                        int febDayCounter = 0;

                        // For each day of this month.
                        for (int monthDay = 0; monthDay < monthDays[monthIndex]; monthDay++) {
                            // For each data type on this day
                            for (int dataTypeIndex = 0; dataTypeIndex < DATA_TYPES_LENGTH; dataTypeIndex++) {
                                if (monthIndex != 1) {
                                    dailyData[MAX_YEAR_AVERAGE_INDEX][monthIndex][monthDay][dataTypeIndex] /= yearsSimulated;
                                    monthlyAvg[dataTypeIndex] += dailyData[MAX_YEAR_AVERAGE_INDEX][monthIndex][monthDay][dataTypeIndex];
                                } else { // In February
                                    if (monthDay == 28) { // On leap year day 29
                                        dailyData[MAX_YEAR_AVERAGE_INDEX][monthIndex][monthDay][dataTypeIndex] /= febCount;
                                        monthlyAvg[dataTypeIndex] += dailyData[MAX_YEAR_AVERAGE_INDEX][monthIndex][monthDay][dataTypeIndex] * febCount;
                                        febDayCounter += febCount;
                                    } else {
                                        dailyData[MAX_YEAR_AVERAGE_INDEX][monthIndex][monthDay][dataTypeIndex] /= yearsSimulated;
                                        monthlyAvg[dataTypeIndex] += dailyData[MAX_YEAR_AVERAGE_INDEX][monthIndex][monthDay][dataTypeIndex] * 28;
                                        febDayCounter += 28;
                                    }
                                }
                            }
                        }

                        //Calculate monthly averages over entire yearsSimulated years.
                        for (int dataTypeIndex = 0; dataTypeIndex < DATA_TYPES_LENGTH; dataTypeIndex++) {
                            if (monthIndex != 1) {
                                monthlyAverage[monthIndex][dataTypeIndex] = monthlyAvg[dataTypeIndex] / monthDays[monthIndex];
                            } else {
                                monthlyAverage[monthIndex][dataTypeIndex] = monthlyAvg[dataTypeIndex] / febDayCounter;
                            }

                            //  This value should match the one saved just above...check on this...
                            dailyData[MAX_YEAR_AVERAGE_INDEX][monthIndex][MONTH_AVERAGE_INDEX][dataTypeIndex] /= yearsSimulated;
                        }
                    }

                    //Done with this file...
                    break;
                } else {
                    messages += "\n#\nDid not find an entire " + MAX_YEARS + " year simulation of climate data.";
                    badClimateData = true;
                    break;
                }
            }
        }

    }
}