/* Copyright 2000 Charles G. Wright * This software may be distributed under the terms of the * GNU General Public License. * * $Id: TMYdata.java,v 1.1 2000-06-15 10:14:51-05 chuckles Exp chuckles $ */ import java.io.*; import java.util.zip.*; /**
The TMYdata class defines an object to hold a 1-year * TMY (Typical Meteorological Year) data set, and methods * to access that data. *
Creating an instance of this class does not initialize * data. That is done using the readData() method. Different * varieties of this can read either stream input or a file. * Data can be in TMY format or the SOLMET format. Also, it * can be in compressed (.gz) format. *
Creating the TMYdata class causes it to register its * monitorable elements with the CWmonitoredElements class. */ public class TMYdata implements CWmonitorable { //---------------------- Class variables ---------------------------- /** Identifier specifying the type of query being made of getDegreeDays().*/ public static final int HDD = 0; /** Identifier specifying the type of query being made of getDegreeDays().*/ public static final int CDD = 1; /** 12-element array specifying the number of days in the 12 months. */ public static int MONTHLENGTH[] = {31,28,31,30,31,30,31,31,30,31,30,31}; // Index numbers for the various fields...must match above values /** Names of TMY fields stored in this object. */ public static final String[] fields = {"Global Horizontal Radiation", "Direct Normal Radiation", "Diffuse Horizontal Radiation", "Dry Bulb Temperature", "Dewpoint", "Relative Humidity", "Wind Direction", "Wind Speed"}; public static final int[] field_types = {UnitConverter.POWER_PER_AREA, UnitConverter.POWER_PER_AREA, UnitConverter.POWER_PER_AREA, UnitConverter.TEMPERATURE, UnitConverter.TEMPERATURE, UnitConverter.NOTHING, UnitConverter.NOTHING, UnitConverter.VELOCITY_MPH}; /** An array of 12 Month identifiers corresponding to the month indices used * in this object. Possibly useful as labels.*/ public static final String[] MONTHS = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep","Oct", "Nov", "Dec"}; /** An array of 31 day identifiers corresponding to the day indices used in this * object. Possibly useful as labels. */ public static final String[] days = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"}; /** An array of 24 hour identifiers corresponding to the hour indices used in this * object. Possibly useful as labels. */ public static final String[] hours = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"}; public static final String[] hours12 = {"1A", "2A", "3A", "4A", "5A", "6A", "7A", "8A", "9A", "10A", "11A", "12P", "1P", "2P", "3P", "4P", "5P", "6P", "7P", "8P", "9P", "10P", "11P", "12A"}; /** Field range definitions */ private static final int[] TMY_FIELDS_LOW = {17,23,29,67,73,79,90,95}; private static final int[] TMY_FIELDS_HIGH = {21,27,33,71,77,82,93,98}; private static final int[] SOLMET_FIELDS_LOW = {23,31,39,53,59,65,74,78}; private static final int[] SOLMET_FIELDS_HIGH = {27,35,43,58,64,68,77,82}; /** Used to identify a field. Field identifier for Global Horizontal Radiation. */ public static final int GLOBAL_HORIZONTAL_RADIATION = 0; /** Used to identify a field. Field identifier for Direct Normal Radiation. */ public static final int DIRECT_NORMAL_RADIATION = 1; /** Used to identify a field. Field identifier for Diffuse Horizontal Radiation. */ public static final int DIFFUSE_HORIZONTAL_RADIATION = 2; /** Used to identify a field. Field identifier for Dry Bulb Temperature. */ public static final int DRY_BULB_TEMP = 3; /** Used to identify a field. Field identifier for Dewpoint. */ public static final int DEWPOINT = 4; /** Used to identify a field. Field identifier for Relative Humidity. */ public static final int RELATIVE_HUMIDITY = 5; /** Used to identify a field. Field identifier for Wind Direction. */ public static final int WIND_DIRECTION = 6; /** Used to identify a field. Field Identifier for Wind Direction. */ public static final int WIND_SPEED = 7; // Value by which the integer value in the field is multipled to get true value private static final double TMY_FIELD_MULTIPLIERS[] = {1.0, 1.0, 1.0, 0.1, 0.1, 1.0, 1.0, 0.1}; // Maximum integer value in the field private static final int MAXVAL[] = {1200, 1100, 700, 500, 300, 100, 360, 400}; // Minimum integer value in the field private static final int MINVAL[] = {0, 0, 0, -500, -600, 0, 0, 0}; // Conversion factors - metric to english private static final double TOMETRIC[] = {3.1525, 3.1525, 3.1525, 0.5555556, 0.5555556, 1.0, 1.0, 0.44704}; private static final double TOMETRIC_OFFSET[] = {0.0, 0.0, 0.0, 32.0, 32.0, 0.0, 0.0, 0.0}; //------------------- month constants ------------------------------------ /** Used to specify a month to a method. January. */ public static final int JANUARY = 0; /** Used to specify a month to a method. February. */ public static final int FEBRUARY = 1; /** Used to specify a month to a method. March. */ public static final int MARCH = 2; /** Used to specify a month to a method. April. */ public static final int APRIL = 3; /** Used to specify a month to a method. May. */ public static final int MAY = 4; /** Used to specify a month to a method. June. */ public static final int JUNE = 5; /** Used to specify a month to a method. July. */ public static final int JULY = 6; /** Used to specify a month to a method. August. */ public static final int AUGUST = 7; /** Used to specify a month to a method. September. */ public static final int SEPTEMBER = 8; /** Used to specify a month to a method. October. */ public static final int OCTOBER = 9; /** Used to specify a month to a method. November.*/ public static final int NOVEMBER = 10; /** Used to specify a month to a method. December. */ public static final int DECEMBER = 11; //------------------- constants for filtering ----------------------------- /** Used to specify data filtering. Don't filter daily data. */ public static final int DAILY_NO_FILTER = 0; /** Used to specify data filtering. Take daily average of data */ public static final int DAILY_AVERAGE = 1; /** Used to specify data filtering. Keep the daily high. */ public static final int DAILY_HIGH = 2; /** Used to specify data filtering. keep the daily low. */ public static final int DAILY_LOW = 3; private static final int FILE_FORMAT_TMY = 0; private static final int FILE_FORMAT_SOLMET = 1; //----------------------- Instance variables------------------------------------ // File Header int wban_number; private String state; private String city; public SOLlocation location; /** 4-dimensional array holding TMY data. First index selects month. * (JANUARY, FEBRUARY, etc). * second index selects day in month (0-30). Third index selects hour in * day. Fourth index selects the data field. */ public double[][][][] tmydat; /** Tells whether we are using metric or english units. Default is true.*/ public boolean use_metric; //----------------------- constructor ------------------------------- /** Constructor for class TMYdata. */ TMYdata(){ use_metric = true; tmydat = new double[12][31][24][8]; // Register the elements of this class as monitorable. } //----------------------- methods ----------------------------------- /** Reads a TMY or SOLMET format data file, creating data structures. * Decides on file format based on slight differences in the first * (header) line. Returns 0 on normal completion, 1 otherwise. */ public int readTMYStream(BufferedReader br) throws IOException { // method to read a TMY file into TMYdata object // the class variables int month = 0; int day = 0; int hour = 0; int numhours = 0; String lb; int file_format = 0; double lat_degrees; double lat_minutes; double lon_degrees; double lon_minutes; double latitude; double longitude; int elevation; int tz; // get the first line lb = br.readLine(); wban_number = Integer.parseInt(lb.substring(1, 6)); city = lb.substring(7,29).trim(); state = lb.substring(30,32).trim(); elevation = Integer.parseInt(lb.substring(55,59).trim()); tz = Integer.parseInt(lb.substring(33,36).trim()); // jdk1.2 has parseDouble() that will simplify the following. lat_degrees = Double.valueOf(lb.substring(39, 41).trim()).doubleValue(); lat_minutes = Double.valueOf(lb.substring(42, 44).trim()).doubleValue(); latitude = lat_degrees + (lat_minutes / 60); lon_degrees = Double.valueOf(lb.substring(47, 50).trim()).doubleValue(); lon_minutes = Double.valueOf(lb.substring(51, 53).trim()).doubleValue(); longitude = lon_degrees + (lon_minutes / 60); location = new SOLlocation(latitude, longitude, tz * -15, elevation, city+","+state); // Try to distinguish between TMY and SOLMET from slight header difference if ((lb.substring(45,46).equals("W") || lb.substring(45,46).equals("E")) && (lb.substring(37,38).equals("N") || lb.substring(37,38).equals("S"))) { //System.out.println("I think the file is TMY format"); file_format = FILE_FORMAT_TMY; if (lb.substring(45,46).equals("E")) longitude = longitude * -1; if (lb.substring(37,38).equals("S")) latitude = latitude * -1; }else if ((lb.substring(46,47).equals("W") || lb.substring(46,47).equals("E")) && (lb.substring(38,39).equals("N") || lb.substring(38,39).equals("S"))) { //System.out.println("I think the file is SOLMET format"); file_format = FILE_FORMAT_SOLMET; if (lb.substring(46,47).equals("E")) longitude = longitude * -1; if (lb.substring(38,39).equals("S")) latitude = latitude * -1; }else{ //System.out.println("I don't think this file is either TMY or SOLMET format"); return(1); } if (file_format == FILE_FORMAT_TMY){ while((lb = br.readLine()) != null) { numhours++; month = Integer.parseInt(lb.substring(3,5)) - 1; day = Integer.parseInt(lb.substring(5,7)) - 1; hour = Integer.parseInt(lb.substring(7,9)) - 1; for (int fieldnum = 0; fieldnum < 8; fieldnum++){ tmydat[month][day][hour][fieldnum] = Double.valueOf(lb.substring(TMY_FIELDS_LOW[fieldnum], TMY_FIELDS_HIGH[fieldnum])).doubleValue() * TMY_FIELD_MULTIPLIERS[fieldnum]; } } } else { // skip blank line lb = br.readLine(); int line_number = 0; while((lb = br.readLine()) != null) { //System.out.println("line: " + line_number++ + " length: " + lb.length()); numhours++; //System.out.println(lb); //System.out.println(lb.substring(4,6)); month = Integer.parseInt(lb.substring(4,6).trim()) - 1; //System.out.println(lb.substring(7,9)); day = Integer.parseInt(lb.substring(7,9).trim()) - 1; //System.out.println(lb.substring(10,12)); hour = Integer.parseInt(lb.substring(10,12).trim()) - 1; //System.out.println("month: " + month + " day: " + day + " hour: " + hour ); for (int fieldnum = 0; fieldnum < 8; fieldnum++){ tmydat[month][day][hour][fieldnum] = Double.valueOf(lb.substring(SOLMET_FIELDS_LOW[fieldnum], SOLMET_FIELDS_HIGH[fieldnum]).trim()).doubleValue(); } } } // process it //System.out.println("Input Reading is complete. Read " + numhours + " records."); // Register monitorable elements. CWmonitoredElements.addElements(this); return(0); } // /** Read a TMY2 file into the TMYdata object. Arguments are // * directory name and file name. If file name ends in // * .zip or .gz, appropriate decompression is provided.*/ //public void readData(String dirname, String fname) // throws FileNotFoundException, IOException { // File file = new File(dirname, fname); // readData(file); //} // /** Read a TMY2 file into the TMYdata object. Argument is // * directory name and file name. If file name ends in // * .zip or .gz, appropriate decompression is provided. */ public void readData(String fname) throws FileNotFoundException, IOException { File file = new File(fname); readData(file); } /** Read a TMY2 file into the TMYdata object. Argument is * a File. If the filename ends with .zip or .gz, appropriate * decompression is performed. */ public void readData(File file) throws FileNotFoundException, IOException { String fname = file.getName(); InputStream is; InputStream is1 = new FileInputStream(file); if (fname.endsWith(".Z") || fname.endsWith(".gz")){ //System.out.println("reading compressed climate file " + fname); is = new GZIPInputStream(is1); } else if (fname.endsWith(".zip")){ //System.out.println("Reading compressed climate file " + fname); is = new ZipInputStream(is1); } else { //System.out.println("Reading climate file " + fname); is = is1; } // input stream to buffered reader readData(is); } /** Read a TMY2 file into the TMYdata object. Argument is * a File. If the filename ends with .zip or .gz, appropriate * decompression is performed. */ public void readData(InputStream input_stream, String fname) throws IOException { InputStream is; if (fname.endsWith(".Z") || fname.endsWith(".gz")){ //System.out.println("reading compressed climate file " + fname); is = new GZIPInputStream(input_stream); } else if (fname.endsWith(".zip")){ //System.out.println("Reading compressed climate file " + fname); is = new ZipInputStream(input_stream); } else { //System.out.println("Reading climate file " + fname); is = input_stream; } // input stream to buffered reader readData(is); } /** Read TMY2 data from an InputStream. */ public void readData(InputStream is) throws IOException { readTMYStream(new BufferedReader(new InputStreamReader(is))); } //------------------------- data access --------------------- /** Returns a data value for the specified month, day, hour and field number. */ public double getValue(int month, int day, int hour, int fieldnum){ // add range checking here...after I understand exception processing double result = tmydat[month][day][hour][fieldnum]; if (usingMetric() == true){ return result ; }else{ return value2English(result, fieldnum); } } //--------------------------- averages ------------------------------------ /** Calculates the average value of some field over the period of a day */ public double getAverageValue(int month, int day, int fieldnum) { int numsamples = 0; double sumvalue = 0.0; for (int hour = 0; hour < 24; hour++) { numsamples++; sumvalue += getValue(month, day, hour,fieldnum); } return (sumvalue / numsamples); } /** Calculates the average value of some field over the period of a month. */ public double getAverageValue(int month, int fieldnum) { int numsamples = 0; double sumvalue = 0; for (int day = 0; day < MONTHLENGTH[month]; day++) { numsamples++; // add up daily averages sumvalue += getAverageValue(month, day, fieldnum); } return (sumvalue / numsamples); } /**Calculates the average of some field over the period of a year. */ public double getAverageValue(int fieldnum) { int numsamples = 0; double sumvalue = 0; for (int month = 0; month < 12; month++) { numsamples++; // add up monthly averages sumvalue += getAverageValue(month, fieldnum); } return (sumvalue / numsamples); } //------------------------------------------------------------------------- //--------------------------- highs --------------------------------------- //------------------------------------------------------------------------- /** Finds the maximum value of a set of fields over the period of a day. * When used on a single field, is useful for determining things like high * temperatures. If multiple fields are specified, useful for determining * bounds before plotting. */ public double getHighValue(int month, int day, int[] fieldnums) { double newvalue = 0.0; // start with value for hour zero. double maxvalue = getValue(month, day, 0, fieldnums[0]); // look through the rest, and keep anything bigger. for (int i = 0; i < fieldnums.length; i++){ for (int hour = 0; hour < 24; hour++) { if ((newvalue = getValue(month, day, hour, fieldnums[i])) > maxvalue){ maxvalue = newvalue; } } } return maxvalue; } /** Finds the maximum value of a set of fields over the period of a month. * When used on a single field, is useful for determining things like high * temperatures. If multiple fields are specified, useful for determining * bounds before plotting. */ public double getHighValue(int month, int[] fieldnums) { double newvalue = 0.0; double maxvalue = getHighValue(month, 0, fieldnums); for (int day = 1; day < MONTHLENGTH[month]; day++) { // keep anything higher if ((newvalue = getHighValue(month, day, fieldnums)) > maxvalue){ maxvalue = newvalue; } } return maxvalue; } /** Finds the maximum value of a set of fields over the period of a year. * When used on a single field, is useful for determining things like high * temperatures. If multiple fields are specified, useful for determining * bounds before plotting. */ public double getHighValue(int[] fieldnums) { //System.out.println(fieldnums[0]); double newvalue = 0.0; double maxvalue = getHighValue(0, 0, fieldnums); for (int month = 1; month < 12; month++) { // keep anything higher if ((newvalue = getHighValue(month, fieldnums)) > maxvalue){ maxvalue = newvalue; } } return maxvalue; } //------------------------------------------------------------------------- //--------------------------- lows --------------------------------------- //------------------------------------------------------------------------- /** Finds the minimum value of a set of fields over the period of a day. * When used on a single field, is useful for determining things like low * temperatures. If multiple fields are specified, useful for determining * bounds before plotting. */ public double getLowValue(int month, int day, int[] fieldnums) { double newvalue = 0.0; // start with value for hour zero. double minvalue = getValue(month, day, 0, fieldnums[0]); // look through the rest, and keep anything bigger. for (int i = 0; i < fieldnums.length; i++){ for (int hour = 0; hour < 24; hour++) { if ((newvalue = getValue(month, day, hour, fieldnums[i])) < minvalue){ minvalue = newvalue; } } } return minvalue; } /** Finds the minimum value of a field over the period of a month. * When used on a single field, is useful for determining things like low * temperatures. If multiple fields are specified, useful for determining * bounds before plotting. */ public double getLowValue(int month, int[] fieldnums) { double newvalue = 0.0; double minvalue = getLowValue(month, 0, fieldnums); for (int day = 1; day < MONTHLENGTH[month]; day++) { // keep anything higher if ((newvalue = getLowValue(month, day, fieldnums)) < minvalue){ minvalue = newvalue; } } return minvalue; } /** Finds the minimum value of a set of fields over the period of a year. * When used on a single field, is useful for determining things like low * temperatures. If multiple fields are specified, useful for determining * bounds before plotting. */ public double getLowValue(int[] fieldnums) { double newvalue = 0.0; double minvalue = getLowValue(0, 0, fieldnums); for (int month = 1; month < 12; month++) { // keep anything higher if ((newvalue = getLowValue(month, fieldnums)) < minvalue){ minvalue = newvalue; } } return minvalue ; } //------------------------------------------------------------------------- //--------------------------- Degree Days --------------------------------- //------------------------------------------------------------------------- /** Finds heating or cooling degree days over one day. Specify HDD for heatcool if * heating degree days are desired. Specify CDD for cooling degree days. * base specifies the temperature base used. */ public double getDegreeDays(int month, int day, int heatcool, double base) { double dd = 0.0; int hour; double temp; if (heatcool == CDD){ for (hour = 0; hour < 24; hour++){ if ((temp = getValue(month, day, hour, DRY_BULB_TEMP)) > base) dd += temp - base; } }else{ for (hour = 0; hour < 24; hour++){ if ((temp = getValue(month, day, hour, DRY_BULB_TEMP)) < base) dd += base - temp; } } return dd / 24; } /** Calculate degree days for one month. Specify HDD for heatcool if * heating degree days are desired. Specify CDD for cooling degree days. * base specifies the temperature base used. */ public double getDegreeDays(int month, int heatcool, double base) { double dd = 0.0; for (int day = 0; day < MONTHLENGTH[month]; day++) { dd += getDegreeDays(month, day, heatcool, base); } return dd; } /** Calculate degree days for one year. Specify HDD for heatcool if * heating degree days are desired. Specify CDD for cooling degree days. * base specifies the temperature base used. */ public double getDegreeDays(int heatcool, double base) { double dd = 0.0; for (int month = 0; month < 12; month++) { dd += getDegreeDays(month, heatcool, base); } return dd; } //----------------------------------------------------------------- // methods dealing with units //------------------------------------------------------------------ // All units are stored internally as metric // Conversions are made on data input or output. /** Sets the object to use Metric units in data exchanges. */ public void useMetric(){ use_metric = true; } /** Sets the object to use English units in all data exchanges. */ public void useEnglish(){ use_metric = false; } /** Returns true if the object is set for Metric units, false for English units. */ public boolean usingMetric(){ return use_metric; } // methods for converting radiation // convert BTUH/ft**2 to Watts/m**2 private double value2Metric(double english, int fieldnum){ return ((english - TOMETRIC_OFFSET[fieldnum]) * TOMETRIC[fieldnum]); } // and vice versa private double value2English(double metric, int fieldnum){ return (TOMETRIC_OFFSET[fieldnum] + (metric / TOMETRIC[fieldnum])); } //-------- end of methods dealing with units ---------- //----------------------------------------------------- // Methods to access TMY Data Records //----------------------------------------------------- /** Returns the WBAN number of the station. */ public int getWBAN(){ return wban_number; } /** Returns the name of the City of the data collection station. */ public String getCity(){ return city; } /** Returns the name of the State of the data collecton station. */ public String getState(){ return state; } /** Returns the name (City,State) of the location. Part of the * CWmonitorable interface.*/ public String getName(){ // System.out.println(location); return(location.name); } /** Returns the list of monitorable fields. Part of the CWmonitorable * interface. */ public String[] getFields(){ return(fields); } /** Returns the list of types of the monitorable fields. Part of the CWmonitorable * interface. Used for unit conversions. Types are defined in class UnitConverter.*/ public int[] getFieldTypes(){ return(field_types); } /** Given month (0-11), day(0-31), and hour, return the * hour in the year. */ static public int hourOfYear(int month, int day, int hour){ int days = 0; for (int i = 0; i < month; i++){ days += MONTHLENGTH[i]; } days += day; return ((days * 24) + hour); } static public int dayOfYear(int month, int day_of_month){ int days_into_year = 0; for (int i = 0; i < month; i++){ days_into_year += MONTHLENGTH[i]; } days_into_year += day_of_month; return (days_into_year); } /** Returns the number of days in the specified month */ static public int daysInMonth(int month){ return(MONTHLENGTH[month]); } /** Sets the data value for the specified month, day, hour and field number. */ public boolean setValue(int month, int day, int hour, int fieldnum, double value){ // Likewise, put some range checking in later... tmydat[month][day][hour][fieldnum] = (int) Math.round(value); return true; } /** Extract monthly values from the TMY dataset for a period of time. Result is a 2-dimensional array. * filter_type is one of * DAILY_HIGH, DAILY_LOW, DAILY_AVERAGE, DAILY_NO_FILTER. * Returned value: val[0] = time, val[1] = data value.*/ public double[][] getDailyFilteredData(int first_month, int last_month, int filter_type, int fieldnum){ int num_points = 0; int day = 0; int hour = 0; int month = 0; int hour_of_year = 0; int data_out_index = 0; double[][] plot_data; int[] fieldnums = new int[1]; fieldnums[0] = fieldnum; // Count the number of elements in the final data. for (month = first_month; month <= last_month; month++){ num_points += MONTHLENGTH[month]; } if (filter_type == DAILY_NO_FILTER){ num_points *= 24; } // create a properly sized data structure: plot_data = new double[2][num_points]; // get the data: for (month = first_month; month <= last_month; month++){ for (day = 0; day < MONTHLENGTH[month]; day++){ switch (filter_type){ case DAILY_NO_FILTER: for (hour = 0; hour < 24; hour++){ // X data plot_data[0][data_out_index] = 1.0 * hourOfYear(month, day, hour); // Y data plot_data[1][data_out_index++] = 1.0 * getValue(month, day, hour, fieldnum); } break; case DAILY_AVERAGE: plot_data[0][data_out_index] = 1.0 * hourOfYear(month, day, 12); plot_data[1][data_out_index++] = 1.0 * getAverageValue(month, day, fieldnum); break; case DAILY_HIGH: plot_data[0][data_out_index] = 1.0 * hourOfYear(month, day, 12); plot_data[1][data_out_index++] = 1.0 * getHighValue(month, day, fieldnums); break; case DAILY_LOW: plot_data[0][data_out_index] = 1.0 * hourOfYear(month, day, 12); plot_data[1][data_out_index++] = 1.0 * getLowValue(month, day, fieldnums); break; default: break; } } } return plot_data; } class TMYField{ String fieldname; int tmy_max_index; int tmy_min_index; double tmy_field_mult; int solmet_min_index; int solmet_max_index; TMYField(String fieldname, int tmy_min_index, int tmy_max_index, double tmy_field_mult, int solmet_min_index, int solmet_max_index){ } } }