CligenData.java [tools/MetaModelTools/src/data/interpretors] Revision: ec5f4cade4553a8341e1cd241b111bfdb77a87a8  Date: Fri Jan 10 10:59:55 MST 2020
/*
 * 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 observedAnnualAverage(int dataType) {
    double ret_val = 0.0;

    for (int i = 0; i < 12; i++) {
      ret_val += observedMonthlyAverages(i, dataType);
    }

    ret_val /= 12.0;

    return ret_val;
  }

  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 monthlyAverages(int month, int dataType) {
    if ((month >= 0) && (month < 12) && (dataType >= 0) && (dataType < OBS_MONTHLY_LENGTH)) {
      return this.monthlyAverage[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 += monthlyAverage[i][dataType];
      }

      return ((dataType == PRCP_INDEX) ? total : (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;
        double[] runningTotal = new double[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] /= ((i == PRCP_INDEX) ? 1 : 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] / ((i == PRCP_INDEX) ? 1 : 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] / ((i == PRCP_INDEX) ? 1 : numDays);
            dailyData[currentYear][currentMonth][MONTH_AVERAGE_INDEX][i] /= ((i == PRCP_INDEX) ? 1 : 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;

            //Average all month data
            for (int dataTypeIndex = 0; dataTypeIndex < DATA_TYPES_LENGTH; dataTypeIndex++) {
              dailyData[MAX_YEAR_AVERAGE_INDEX][monthIndex][MONTH_AVERAGE_INDEX][dataTypeIndex] /= yearsSimulated;
              monthlyAverage[monthIndex][dataTypeIndex] = dailyData[MAX_YEAR_AVERAGE_INDEX][monthIndex][MONTH_AVERAGE_INDEX][dataTypeIndex];
            }

//                        // 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] / ((dataTypeIndex == PRCP_INDEX) ? 1 : monthDays[monthIndex]);
//                            } else {
//                                monthlyAverage[monthIndex][dataTypeIndex] = monthlyAvg[dataTypeIndex] / ((dataTypeIndex == PRCP_INDEX) ? 1 : 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;
        }
      }
    }

  }
}