View Javadoc
1   /**
2    * Copyright 2010, CSIRO Australia.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package au.csiro.netcdf.wron;
18  
19  import java.io.BufferedReader;
20  import java.io.ByteArrayInputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileNotFoundException;
24  import java.io.FileReader;
25  import java.io.IOException;
26  import java.io.PrintWriter;
27  import java.io.RandomAccessFile;
28  import java.io.StringWriter;
29  import java.io.UnsupportedEncodingException;
30  import java.text.DateFormat;
31  import java.text.NumberFormat;
32  import java.text.SimpleDateFormat;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.Calendar;
36  import java.util.Collections;
37  import java.util.Comparator;
38  import java.util.Date;
39  import java.util.HashMap;
40  import java.util.Iterator;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Scanner;
44  import java.util.Set;
45  import java.util.TreeSet;
46  import java.util.regex.Pattern;
47  
48  import org.apache.commons.cli.BasicParser;
49  import org.apache.commons.cli.CommandLine;
50  import org.apache.commons.cli.HelpFormatter;
51  import org.apache.commons.cli.MissingOptionException;
52  import org.apache.commons.cli.Option;
53  import org.apache.commons.cli.OptionBuilder;
54  import org.apache.commons.cli.Options;
55  import org.apache.commons.cli.ParseException;
56  import org.apache.log4j.Logger;
57  
58  import ucar.ma2.DataType;
59  import ucar.nc2.Attribute;
60  import ucar.nc2.units.DateFormatter;
61  import au.csiro.netcdf.NcDefineAttributes;
62  import au.csiro.netcdf.NcDefineDimension;
63  import au.csiro.netcdf.NcDefineVariable;
64  import au.csiro.netcdf.NcWriteVariable;
65  import au.csiro.netcdf.cli.CommandLineOptionsComparator;
66  import au.csiro.netcdf.util.CSVTokenizer;
67  import au.csiro.netcdf.util.NetCDFUtils;
68  
69  /**
70   * The class is a converter control class to convert Murray Darling Basin 
71   * Sustainable Yields CSV files into netCDF files. This class will process 
72   * the Scenario A historical dataset files.  
73   * 
74   * Copyright 2010, CSIRO Australia 
75   * All rights reserved.
76   * 
77   * @author James Dempsey on 17/04/2010
78   * @version $Revision: 84 $ $Date: 2010-08-25 15:56:46 +1000 (Wed, 25 Aug 2010) $
79   */
80  public class MdbsyScenarioAConverter
81  {
82      // netCDF file naming components
83      private static String COLLECTION = "Collection-MDBSY_Climate";
84      private static String SCENARIO = "";
85      private static String MODEL = "";
86      private static String CASE = "";
87  
88      /**
89       * The file containing the location lookup values.
90       */
91      private static String LOCATION_LOOKUP_FILE = "";
92  
93      /**
94       * The file containing the metadata attributes (if any).
95       */
96      private static String METADATA_FILE = "";
97  
98      /**
99       * The directory containing the Scenario C csv data files.
100      */
101     private static String INPUT_CSV_DIRECTORY = "";
102 
103     /**
104      * The netCDF file to be created.
105      */
106     private static String OUTPUT_NETCDF_DIRECTORY = "";
107 
108     /**
109      * NetCDF file extension.
110      */
111     private static final String NETCDF_FILE_EXTENSION = ".nc";
112 
113     /**
114      * Encoding used to fill variables.
115      */
116     private static final String ENCODING = "UTF-8";
117 
118     // Variables
119     private static final String[] variableNames = new String[] { "SILO_rainfall", "SILO_minTemp", "SILO_maxTemp",
120             "SILO_incomingSolarRadiation", "SILO_actualVapourPressure", "APET", "PPET", "relativeHumidity", "Evappt" };
121     private static final String[] variableUnits = new String[] { "mm", "degrees_celcius", "degrees_celcius", "MJ",
122             "hpa", "mm", "mm", "TBA", "mm" };
123     private static final String[] variableLongNames = new String[] { "SILO_rainfall", "SILO_minTemp", "SILO_maxTemp",
124             "SILO_incomingSolarRadiation", "SILO_actualVapourPressure", "APET", "PPET", "relativeHumidity", "P-TET" };
125     private static final String[] variableFillValues = new String[] { "-9999.0f", "-9999.0f", "-9999.0f", "-9999.0f",
126             "-9999.0f", "-9999.0f", "-9999.0f", "-9999.0f", "-9999.0f" };
127     private static final String[] variableMissingValues = variableFillValues;
128     private static final String[] variableMinValues = new String[] { "0.0f", "-20.0f", "-20.0f", "0.0f",
129         "0.0f", "0.0f", "0.0f", "0.0f", "0.0f" };
130     private static final String[] variableMaxValues = new String[] { "500.0f", "100.0f", "100.0f", "100.0f",
131         "100.0f", "100.0f", "100.0f", "100.0f", "500.0f" };
132     private static final DataType[] variableDataTypes = new DataType[] { DataType.FLOAT, DataType.FLOAT,
133             DataType.FLOAT, DataType.FLOAT, DataType.FLOAT, DataType.FLOAT, DataType.FLOAT, DataType.FLOAT,
134             DataType.FLOAT };
135     static final int numVariables = variableNames.length;
136     private static final String GRID_MAPPING = "crs";
137 
138     // time constants
139     private static final String TIME = "time";
140     private static final String TIME_RANGE = "0-43097";
141     private static final int TIME_SIZE = 43098; // 365
142     private static final String TIME_LONG_NAME = "reference time";
143     private static final String TIME_STANDARD_NAME = "time";
144     private static final String TIME_UNITS = "days since 1889-01-01 0:0:0";
145     private static final String TIME_CALENDAR = "Gregorian";
146     private static final String TIME_AXIS = "T";
147     private static final String TIME_BOUNDS = "time_bnds";
148 
149     // longitude constants
150     private static final String LONG = "longitude";
151     private static final int LONG_COLUMN_INDEX = 1;
152     private static final String LONG_RANGE = "0-296";
153     private static final int LONG_SIZE = 297;
154     private static final String LONG_UNITS = "degrees_east";
155     private static final String LONG_STANDARD_NAME = "longitude";
156     private static final String LONG_AXIS = "X";
157     private static final String LONG_LONG_NAME = "longitude";
158 
159     // latitude constants
160     private static final String LAT = "latitude";
161     private static final int LAT_COLUMN_INDEX = 2;
162     private static final String LAT_RANGE = "0-278";
163     private static final int LAT_SIZE = 279;
164     private static final String LAT_UNITS = "degrees_north";
165     private static final String LAT_STANDARD_NAME = "latitude";
166     private static final String LAT_AXIS = "Y";
167     private static final String LAT_LONG_NAME = "latitude";
168 
169     // num values constants
170     private static final String NV = "nv";
171     private static final String NV_RANGE = "0-1";
172     private static final int NV_SIZE = 2;
173 
174     // elevation constants
175     private static String ELEVATION = "elev";
176     private static int ELEVATION_COLUMN_INDEX = 3;
177     private static final String ELEVATION_UNITS = "m";
178     private static final String ELEVATION_STANDARD_NAME = "altitude";
179     private static final String ELEVATION_LONG_NAME = "Elevation above sea level";
180     private static final float ELEVATION_MISSING_VALUE = -999.99f;
181 
182     // catchment id constants
183     private static String CATCHMENT_ID = "catchmentId";
184     private static int CATCHMENT_ID_COLUMN_INDEX = 4;
185     private static final String CATCHMENT_ID_LONG_NAME = "MDB Catchment Id";
186     private static final float CATCHMENT_ID_MISSING_VALUE = 0f;
187 
188     // reporting region id constants
189     private static String REPORTING_REGION = "repRegionId";
190     private static int REPORTING_REGION_ID_COLUMN_INDEX = 5;
191     private static String REPORTING_REGION_LONG_NAME = "MDB Reporting Region Id";
192     private static final float REPORTING_REGION_MISSING_VALUE = 0f;
193 
194     // crs constants
195     private static String CRS_GRID_MAPPING_NAME = "latitude_longitude";  
196     private static float CRS_LONGITUDE_OF_PRIME_MERIDIAN = 0.0f;
197     private static float CRS_SEMI_MAJOR_AXIS = 6378137.0f;
198     private static float CRS_INVERSE_FLATTENING = 298.257222101f;
199     
200     // cell id
201     private static final int CELL_ID_COLUMN_INDEX = 0;
202 
203     // Other cell data
204 
205     // date conversion constants
206     private static final String EPOC_DATE_STRING = "1889-01-01";
207     private static final DateFormatter isoDateTimeFormat = new DateFormatter();
208     private static final int DATE_COLUMN_INDEX = 0;
209 
210     // number of time values to read from all the csv files and load into memory.
211     private static int ROW_CHUNK_SIZE = 1000;
212 
213     /** The number of columns expected in the CSV data files */
214     private static final int NUM_CSV_COLUMNS = 1 + numVariables;
215 
216     /**
217      * Constant that defines the logger to be used.
218      */
219     private static final Logger LOG = Logger.getLogger(MdbsyScenarioAConverter.class.getName());
220 
221     /** Conversion utilities  */
222     private ConversionUtils convUtils = new ConversionUtils();
223 
224     /**
225      * @param args
226      */
227     @SuppressWarnings("static-access")
228     public static void main(String[] args) throws Exception
229     {
230         Options options = new Options();
231         try
232         {
233             Option inputDirectory = OptionBuilder.withArgName("dir").hasArg().withDescription(
234                     " 1: the directory containing the csv files for the scenario.").isRequired(true).withLongOpt(
235                     "inputDirectory").create("i");
236 
237             Option lookupFile = OptionBuilder.withArgName("file").hasArg()
238                     .withDescription(" 2: the cellId lookup file.").isRequired(true).withLongOpt("lookupFile").create(
239                             "f");
240 
241             Option outputDirectory = OptionBuilder.withArgName("dir").hasArg().withDescription(
242                     " 3: the output directory.").isRequired(true).withLongOpt("outputDirectory").create("o");
243 
244             Option scenario = OptionBuilder.withArgName("text").hasArg().withDescription(
245                     " 4: the modelling scenario used to name the netCDF file.").isRequired(true).withLongOpt("scenario")
246                     .create("s");
247 
248             Option metadataFile = OptionBuilder.withArgName("file").hasArg().withDescription(
249                     " 5: a file of global metadata attributes to be added to the netCDF file.").isRequired(true)
250                     .withLongOpt("metadataFile").create("m");
251 
252             Option numRows = OptionBuilder.withArgName("int").hasArg().withDescription(
253                     " 6: the number of rows to read at a time.").isRequired(true).withLongOpt("rows").create("r");
254 
255             Option byDecade = OptionBuilder.withDescription(
256                     " 7: OPTIONAL, set if we want to convert into netCDF files by decade.").isRequired(false)
257                     .withLongOpt("decade").create("d");
258 
259             Option byLatitude = OptionBuilder.withDescription(
260                     " 8: OPTIONAL, set if we want to convert into netCDF files by degrees of latitude.").isRequired(
261                     false).withLongOpt("latitude").create("l");
262 
263             Option startLat = OptionBuilder.withArgName("latitude").hasArg().withDescription(
264                     " 9: OPTIONAL, The latitude at which to start processing. For byDecade, this option will "
265                             + "assume already existing netCDF files.").isRequired(false).create("startLat");
266 
267             Option endLat = OptionBuilder.withArgName("latitude").hasArg().withDescription(
268                     "10: OPTIONAL, The latitude at which to end processing (inclusive).").isRequired(false).create(
269                     "endLat");
270 
271             options.addOption(inputDirectory);
272             options.addOption(lookupFile);
273             options.addOption(outputDirectory);
274             options.addOption(scenario);
275             options.addOption(metadataFile);
276             options.addOption(numRows);
277             options.addOption(byDecade);
278             options.addOption(byLatitude);
279             options.addOption(startLat);
280             options.addOption(endLat);
281 
282             // parse the command line arguments
283             CommandLine parsedCommandLine = new BasicParser().parse(options, args);
284 
285             INPUT_CSV_DIRECTORY = (parsedCommandLine.hasOption("inputDirectory")) ? parsedCommandLine
286                     .getOptionValue("inputDirectory") : "";
287             LOCATION_LOOKUP_FILE = (parsedCommandLine.hasOption("lookupFile")) ? parsedCommandLine
288                     .getOptionValue("lookupFile") : "";
289             OUTPUT_NETCDF_DIRECTORY = (parsedCommandLine.hasOption("outputDirectory")) ? parsedCommandLine
290                     .getOptionValue("outputDirectory") : "";
291             SCENARIO = (parsedCommandLine.hasOption("scenario")) ? parsedCommandLine.getOptionValue("scenario") : "";
292             METADATA_FILE = (parsedCommandLine.hasOption("metadataFile")) ? parsedCommandLine
293                     .getOptionValue("metadataFile") : "";
294             MODEL = "";
295             CASE = "";
296             ROW_CHUNK_SIZE = (parsedCommandLine.hasOption("rows")) ? Integer.valueOf(parsedCommandLine
297                     .getOptionValue("rows")) : ROW_CHUNK_SIZE;
298             String startingLatitude = (parsedCommandLine.hasOption("startLat")) ? parsedCommandLine
299                     .getOptionValue("startLat") : "";
300             String endingLatitude = (parsedCommandLine.hasOption("endLat")) ? parsedCommandLine
301                     .getOptionValue("endLat") : "";
302 
303             boolean doSplitByDecade = parsedCommandLine.hasOption("decade");
304             boolean doSplitByLatitude = parsedCommandLine.hasOption("latitude");
305 
306             MdbsyScenarioAConverter converter = new MdbsyScenarioAConverter();
307 
308             if (doSplitByDecade)
309             {
310                 long start = System.currentTimeMillis();
311                 converter.splitByDecade(startingLatitude, endingLatitude);
312                 long end = System.currentTimeMillis() - start;
313                 LOG.warn("Split by decade in: " + end + " ms.");
314             }
315 
316             if (doSplitByLatitude)
317             {
318                 long start = System.currentTimeMillis();
319                 converter.splitByLatitude(startingLatitude, endingLatitude);
320                 long end = System.currentTimeMillis() - start;
321                 LOG.warn("Split by latitude in: " + end + " ms.");
322             }
323         }
324         catch (MissingOptionException moe)
325         {
326             LOG.error(moe.getMessage());
327 
328             StringWriter sw = new StringWriter();
329             HelpFormatter formatter = new HelpFormatter();
330             formatter.setOptionComparator(new CommandLineOptionsComparator());
331             formatter.printHelp(new PrintWriter(sw), 80, "-", "header", options, 0, 1, "footer");
332             System.out.println(sw.toString());
333         }
334     }
335 
336     /**
337      * Splits a directory of WRON csv files into corresponding netCDF files that are grouped by degrees of latitude.
338      * 
339      * @throws Exception
340      */
341     public void splitByLatitude(String startingLatitude, String endingLatitude) throws Exception
342     {
343         // group each row in lookup file according to latitude
344         Map<String, List<CellData>> triplesGroupedByLatitude = this.getTriplesGroupedByLatitude(this.readLookupFile());
345 
346         // sort by latitude
347         Set<String> latitudeKeySet = triplesGroupedByLatitude.keySet();
348         Set<String> limitedLatitudes = convUtils.getLimitedLatitudes(latitudeKeySet, startingLatitude, endingLatitude);
349         List<String> latitudeKeyList = new ArrayList<String>(limitedLatitudes);
350         Collections.sort(latitudeKeyList, new Comparator<String>()
351         {
352             @Override
353             public int compare(String o1, String o2)
354             {
355                 return Integer.valueOf(o2).compareTo(Integer.valueOf(o1));
356             }
357         });
358 
359         // for each latitude create a netCDF file.
360         for (String latitudeKey : latitudeKeyList)
361         {
362             LOG.warn("Started work on latitude: " + latitudeKey + ".");
363             String latitudeDegree = latitudeKey;
364             List<CellData> triples = triplesGroupedByLatitude.get(latitudeDegree);
365 
366             // get the values to fill the coordinate variables.
367             Set<String> sortedLatitudes = this.getSortedLatitudes(triples);
368             int blockLatSize = sortedLatitudes.size();
369             String blockLatRange = "0-" + String.valueOf(sortedLatitudes.size() - 1);
370 
371             Set<String> sortedLongitudes = this.getSortedLongitudes(triples);
372             int blockLongSize = sortedLongitudes.size();
373             String blockLongRange = "0-" + String.valueOf(sortedLongitudes.size() - 1);
374 
375             // create file names
376             String filenames[] = new String[numVariables];
377             List<String> dates = this.getDatesAsDaysSinceEpoc(this.getDates());
378             for (int i = 0; i < filenames.length; i++)
379             {
380                 filenames[i] = this.generateLatitudeFilename(latitudeDegree, variableNames[i]);
381                 this.createVariableFile(filenames[i], Arrays.asList(LAT, LONG, TIME), i, TIME_SIZE, blockLatSize,
382                         blockLongSize);
383                 this.fillCoordinateVariables(filenames[i], sortedLongitudes, sortedLatitudes, dates, blockLatRange,
384                         blockLongRange, TIME_RANGE);
385                 this.fillSpatialVariables(filenames[i], triples);
386                 this.fillDataForLatitude(filenames[i], i, sortedLongitudes, sortedLatitudes, dates, Arrays.asList(LAT,
387                         LONG, TIME));
388             }
389 
390             // for each cellId csv file we read, we will have available the values for both rainFall and PVET, therefore
391             // it makes sense to fill these variables at the same time.
392             this.fillDataByLatitudeVariables(filenames, triples, Arrays.asList(LAT, LONG, TIME));
393         }
394     }
395 
396     /**
397      * Splits a directory of WRON csv files into corresponding netCDF files that are grouped by decade.
398      * 
399      * @return
400      * 
401      * @throws Exception
402      */
403     public void splitByDecade(String startingLatitude, String endingLatitude) throws Exception
404     {
405         // get all rows in lookup file
406         List<CellData> triples = this.getAllTriples(this.readLookupFile());
407 
408         // get the values to fill the coordinate variables.
409         Set<String> sortedLatitudes = this.getSortedLatitudes(triples);
410         String blockLatRange = "0-" + String.valueOf(sortedLatitudes.size() - 1);
411 
412         Set<String> sortedLongitudes = this.getSortedLongitudes(triples);
413         String blockLongRange = "0-" + String.valueOf(sortedLongitudes.size() - 1);
414         String startingLongitude = new ArrayList<String>(sortedLongitudes).get(0);
415 
416         // groups dates by decade
417         Map<Integer, List<String>> datesByDecade = this.getDatesByDecade(this.getDates());
418 
419         // sort by decade
420         Set<Integer> decadeKeySet = datesByDecade.keySet();
421         List<Integer> decadeKeyList = new ArrayList<Integer>(decadeKeySet);
422         Collections.sort(decadeKeyList);
423         Map<Integer, String[]> allFilenames = new HashMap<Integer, String[]>();
424         
425         // for each decade create a netCDF file.
426         int start = 0;
427         int end = start;
428         int filenameStart = 0;
429         for (Integer decadeKey : decadeKeyList)
430         {
431             LOG.warn("Started work on decade: " + decadeKey + "0s.");
432             System.gc();
433             LOG.warn("  Mem: " + getMemDisplay());
434             List<String> dates = datesByDecade.get(decadeKey);
435             end = end + dates.size();
436 
437             int blockTimeSize = dates.size();
438             String blockTimeRange = "0-" + String.valueOf(dates.size() - 1);
439 
440 //            if (decadeKey > 189)
441 //            {
442 //                continue;
443 //            }
444             // create file names
445             String decadeStr = String.valueOf(decadeKey + "0");
446 
447             String filenames[] = new String[numVariables];
448             dates = this.getDatesAsDaysSinceEpoc(dates);
449             for (int i = 0; i < filenames.length; i++)
450             {
451                 filenames[i] = this.generateDecadeFilename(decadeStr, variableNames[i]);
452                 if ("".equals(startingLatitude))
453                 {
454                     this.createVariableFile(filenames[i], Arrays.asList(TIME, LAT, LONG), i, blockTimeSize,
455                             LAT_SIZE, LONG_SIZE);
456                     this.fillCoordinateVariables(filenames[i], sortedLongitudes, sortedLatitudes, dates, blockLatRange,
457                             blockLongRange, blockTimeRange);
458                     this.fillSpatialVariables(filenames[i], triples);
459                 }
460             }
461             
462             allFilenames.put(decadeKey, filenames);
463             filenameStart += numVariables;
464         }
465 
466         LOG.warn(" Latitude limits : " + startingLatitude + " to " + endingLatitude + ".");
467         
468         int numBlocks = LAT_SIZE / ROW_CHUNK_SIZE + (LAT_SIZE % ROW_CHUNK_SIZE > 0 ? 1 : 0);
469         Calendar baseCal = convUtils.getCalendar(new Date());
470         baseCal.clear();
471         baseCal.set(1889, 0, 1);
472         
473         Set<String> limitedLatitudes = convUtils.getLimitedLatitudes(sortedLatitudes, startingLatitude, endingLatitude);
474         for (int blockNum = 0; blockNum < numBlocks; blockNum++)
475         {
476             LOG.warn(" Processing block : " + blockNum + ".");
477             LOG.warn("  Mem: " + getMemDisplay());
478             // Grab the next set of latitudes to be read
479             List<String> lats = convUtils.getLatitudeBlock(limitedLatitudes, blockNum, ROW_CHUNK_SIZE);
480             if (lats.isEmpty())
481             {
482                 LOG.warn(" Skipping block as it is outside the latitude range.");
483             }
484             else
485             {
486                 LOG.warn(" Processing latitudes : " + lats + ".");
487                 
488                 // Build a set of files covering those latitudes
489                 List<CellData> targetCells = convUtils.buildCsvFilenamesForLatitudes(lats, triples, INPUT_CSV_DIRECTORY, ".csv");
490                 
491                 // Read all data from the file set
492                 LOG.warn(" Reading data from CSVs...");
493                 Map<Integer, Map<String, LongitudeRange>> latData = convUtils.readDataByLatitudes(targetCells, lats,
494                         numVariables, TIME_SIZE, LONG_SIZE, variableFillValues, startingLongitude);
495                 
496                 // Write out the data in latitude blocks
497                 LOG.warn(" Writing data to netCDF...");
498                 LOG.warn("  Mem: " + getMemDisplay());
499                 convUtils.writeLatDataByDecade(latData, allFilenames, variableNames, baseCal.getTime());
500     
501                 LOG.warn(" Writing finished.");
502             }
503         }
504     }
505 
506     public String getMemDisplay() 
507     {
508         Runtime rt = Runtime.getRuntime();
509         NumberFormat numFmt = NumberFormat.getNumberInstance();
510         numFmt.setMaximumFractionDigits(0);
511         StringBuffer sb = new StringBuffer();
512         sb.append(numFmt.format(rt.totalMemory() / 1024.0));
513         sb.append(" Kb total, ");
514         sb.append(numFmt.format(rt.freeMemory() / 1024.0));
515         sb.append(" Kb free, ");
516         sb.append(numFmt.format(rt.maxMemory() / 1024.0));
517         sb.append(" Kb max.");
518         return sb.toString();
519     }
520     
521     /**
522      * Defines a netCDF file for the rainFall variable, but does not fill the variables
523      * 
524      * @param outputFileName
525      * @throws Exception
526      */
527     private void createVariableFile(String outputFileName, List<String> dimensionOrdering, int variableIndex, int blockTimeSize, int blockLatSize, int blockLongSize)
528             throws Exception
529     {
530         this.defineDimensions(outputFileName, dimensionOrdering, blockTimeSize, blockLatSize, blockLongSize);
531         this.defineCoordinateVariables(outputFileName);
532         this.defineSpatialVariables(outputFileName);
533 
534         // Add attributes
535         DateFormat utcDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
536         Attribute converter = new Attribute("History", utcDateTime.format(new Date()) + " Converted to netCDF by "
537                 + "$Id: MdbsyScenarioAConverter.java 84 2010-08-25 05:56:46Z robertbridle $");
538         List<Attribute> globalAttrs = new ArrayList<Attribute>();
539         globalAttrs.add(converter);
540         if (METADATA_FILE.length() > 0)
541         {
542             globalAttrs.addAll(NetCDFUtils.readAttributesFromStream(new FileInputStream(METADATA_FILE)));
543         }
544         NcDefineAttributes ncDefineAttr = new NcDefineAttributes();
545         ncDefineAttr.execute(outputFileName, globalAttrs, false /* isLargeFileSupport */);
546 
547         String dimensions = "";
548         for (String dimension : dimensionOrdering)
549         {
550             dimensions += dimension + " ";
551         }
552         this.defineVariable(outputFileName, dimensions, variableIndex);
553     }
554 
555     /**
556      * Define the dimensions for a WRON netCDF file, e.g. latitude, longitude, time.
557      * 
558      * @param outputFileName
559      * @throws IllegalArgumentException
560      * @throws ParseException
561      * @throws IOException
562      */
563     private void defineDimensions(String outputFileName, List<String> dimensionOrdering, int blockTimeSize, int blockLatSize, int blockLongSize)
564             throws IllegalArgumentException, ParseException, IOException
565     {
566         NcDefineDimension command = new NcDefineDimension();
567 
568         for (String dimName : dimensionOrdering)
569         {
570             if (dimName.equals(TIME))
571             {
572                 command.execute(outputFileName, TIME, blockTimeSize, false /* isUnlimited */, false/* fillValue */);
573             }
574             else if (dimName.equals(LAT))
575             {
576                 command.execute(outputFileName, LAT, blockLatSize, false /* isUnlimited */, false/* fillValue */);
577             }
578             else if (dimName.equals(LONG))
579             {
580                 command.execute(outputFileName, LONG, blockLongSize, false /* isUnlimited */, false/* fillValue */);
581             }
582         }
583         
584         // Add NV as the final dimension
585         command.execute(outputFileName, NV, NV_SIZE, false /* isUnlimited */, false/* fillValue */);
586     }
587 
588     /**
589      * Defines the coordinate variables for a WRON netCDF file, e.g. latitude, longitude, time.
590      * 
591      * @param outputFileName
592      * @throws IllegalArgumentException
593      * @throws ParseException
594      * @throws IOException
595      */
596     private void defineCoordinateVariables(String outputFileName) throws IllegalArgumentException, ParseException,
597             IOException
598     {
599         NcDefineVariable command = new NcDefineVariable();
600 
601         command.execute(new File(outputFileName), LAT, DataType.FLOAT, Arrays.asList(new Attribute("units", LAT_UNITS),
602                 new Attribute("long_name", LAT_LONG_NAME), new Attribute("standard_name", LAT_STANDARD_NAME), new Attribute("axis", LAT_AXIS)), LAT,
603                 false /* isLargeFileSupport */, false/* fillValue */);
604         command.execute(new File(outputFileName), LONG, DataType.FLOAT, Arrays.asList(
605                 new Attribute("long_name", LONG_LONG_NAME), new Attribute("units", LONG_UNITS), new Attribute("standard_name", LONG_STANDARD_NAME), new Attribute(
606                         "axis", LONG_AXIS)), LONG, false /* isLargeFileSupport */, false/* fillValue */);
607         command.execute(new File(outputFileName), TIME, DataType.INT, Arrays.asList(new Attribute("units", TIME_UNITS),
608                 new Attribute("long_name", TIME_LONG_NAME), new Attribute("standard_name", TIME_STANDARD_NAME),
609                 new Attribute("axis", TIME_AXIS), new Attribute("calendar", TIME_CALENDAR), new Attribute("bounds",
610                         TIME_BOUNDS)), TIME, false /* isLargeFileSupport */, false/* fillValue */);
611         command.execute(new File(outputFileName), TIME_BOUNDS, DataType.INT, new ArrayList<Attribute>(), TIME + " "
612                 + NV, false /* isLargeFileSupport */, false/* fillValue */);
613         command.execute(new File(outputFileName), GRID_MAPPING, DataType.INT, Arrays.asList(new Attribute(
614                 "grid_mapping_name", CRS_GRID_MAPPING_NAME), new Attribute("longitude_of_prime_meridian",
615                 CRS_LONGITUDE_OF_PRIME_MERIDIAN), new Attribute("semi_major_axis", CRS_SEMI_MAJOR_AXIS),
616                 new Attribute("inverse_flattening", CRS_INVERSE_FLATTENING)), "",
617                 false /* isLargeFileSupport */, true/* fillValue */);
618     }
619 
620     /**
621      * Defines the spatial metadata variables for a WRON netCDF file, e.g. elevation, catchment id etc.
622      * 
623      * @param outputFileName
624      *            The file to written to
625      * @throws IOException
626      *             If the definitions cannot be written
627      */
628     private void defineSpatialVariables(String outputFileName) throws IOException
629     {
630         NcDefineVariable command = new NcDefineVariable();
631 
632         String spatialDim = LAT + " " + LONG;
633         command.execute(new File(outputFileName), ELEVATION, DataType.FLOAT, Arrays.asList(new Attribute("units",
634                 ELEVATION_UNITS), new Attribute("standard_name", ELEVATION_STANDARD_NAME), new Attribute("long_name",
635                 ELEVATION_LONG_NAME), new Attribute("_FillValue", ELEVATION_MISSING_VALUE), new Attribute(
636                 "grid_mapping", GRID_MAPPING)), spatialDim, false /* isLargeFileSupport */, false/* fillValue */);
637         command
638                 .execute(new File(outputFileName), CATCHMENT_ID, DataType.FLOAT, Arrays.asList(new Attribute(
639                         "long_name", CATCHMENT_ID_LONG_NAME), new Attribute("_FillValue", CATCHMENT_ID_MISSING_VALUE), new Attribute(
640                         "grid_mapping", GRID_MAPPING)), spatialDim, false /* isLargeFileSupport */, false/* fillValue */);
641         command
642                 .execute(new File(outputFileName), REPORTING_REGION, DataType.FLOAT, Arrays.asList(new Attribute(
643                         "long_name", REPORTING_REGION_LONG_NAME), new Attribute("_FillValue", REPORTING_REGION_MISSING_VALUE), new Attribute(
644                                 "grid_mapping", GRID_MAPPING)), spatialDim, false /* isLargeFileSupport */,
645                         false/* fillValue */);
646     }
647 
648     /**
649      * Defines the rainFall variable
650      * 
651      * @param outputFileName
652      * @throws IllegalArgumentException
653      * @throws IOException
654      * @throws ParseException
655      */
656     private void defineVariable(String outputFileName, String dimensions, int variableIndex)
657             throws IllegalArgumentException, IOException, ParseException
658     {
659         NcDefineVariable command = new NcDefineVariable();
660         command
661                 .execute(new File(outputFileName), variableNames[variableIndex], variableDataTypes[variableIndex],
662                         Arrays.asList(new Attribute("units", variableUnits[variableIndex]), new Attribute("long_name",
663                                 variableLongNames[variableIndex]), new Attribute("missing_value", Float
664                                 .valueOf(variableMissingValues[variableIndex])), new Attribute("_FillValue", Float
665                                 .valueOf(variableFillValues[variableIndex])), new Attribute("valid_min", Float
666                                 .valueOf(variableMinValues[variableIndex])), new Attribute("valid_max", Float
667                                 .valueOf(variableMaxValues[variableIndex])), new Attribute("grid_mapping", 
668                                 GRID_MAPPING)), dimensions,
669                         false /* isLargeFileSupport */, false/* fillValue */);
670     }
671 
672     /**
673      * Fills the coordinate variables, e.g. latitude, longitude, time.
674      * 
675      * @param outputFileName
676      * @param sortedLongitudes
677      * @param sortedLatitudes
678      * @param dates
679      * @param timeRange 
680      * @param timeSize 
681      * @param blockLongRange 
682      * @param blockLongSize 
683      * @param blockLatRange 
684      * @param blockLatSize 
685      * @throws UnsupportedEncodingException
686      * @throws IOException
687      * @throws java.text.ParseException
688      */
689     private void fillCoordinateVariables(String outputFileName, Set<String> sortedLongitudes,
690             Set<String> sortedLatitudes, List<String> dates, String blockLatRange, String blockLongRange, String timeRange) throws UnsupportedEncodingException, IOException,
691             java.text.ParseException
692     {
693         NcWriteVariable command = new NcWriteVariable();
694 
695         // ///////////////////////////////////////////////////////////////////////////////////////////
696         // fill longitude variable
697         // create String of latitudes to populate the variable
698         StringBuffer longitudeStringBuffer = new StringBuffer();
699         for (Iterator<String> iter = sortedLongitudes.iterator(); iter.hasNext();)
700         {
701             longitudeStringBuffer.append(iter.next()).append(System.getProperty("line.separator"));
702         }
703 
704         // call command to actually perform fill the file
705         command.execute(new File(outputFileName), LONG, blockLongRange, new ByteArrayInputStream(longitudeStringBuffer
706                 .toString().getBytes(ENCODING)), false);
707 
708         // ///////////////////////////////////////////////////////////////////////////////////////////
709         // fill latitude variable
710         // create String of latitudes to populate the variable
711         StringBuffer latitudeStringBuffer = new StringBuffer();
712         for (Iterator<String> iter = sortedLatitudes.iterator(); iter.hasNext();)
713         {
714             latitudeStringBuffer.append(iter.next()).append(System.getProperty("line.separator"));
715         }
716 
717         // call command to actually perform fill the file
718         command.execute(new File(outputFileName), LAT, blockLatRange, new ByteArrayInputStream(latitudeStringBuffer
719                 .toString().getBytes(ENCODING)), false);
720 
721         // ///////////////////////////////////////////////////////////////////////////////////////////
722         // fill time and time_bnds variables
723         StringBuffer timeBoundsStringBuffer = new StringBuffer();
724         StringBuffer dateStringBuffer = new StringBuffer();
725         boolean first = true;
726         for (String date : dates)
727         {
728             dateStringBuffer.append(date).append(System.getProperty("line.separator"));
729             if (!first)
730             {
731                 timeBoundsStringBuffer.append(date).append(System.getProperty("line.separator"));
732             }
733             timeBoundsStringBuffer.append(date).append(System.getProperty("line.separator"));
734             first = false;
735         }
736         int dayAfterLastDay = Integer.parseInt(dates.get(dates.size()-1)) +1;
737         String finalString = String.valueOf(dayAfterLastDay);
738         timeBoundsStringBuffer.append(finalString).append(System.getProperty("line.separator"));
739 
740         // call command to actually perform fill the file
741         command.execute(new File(outputFileName), TIME, timeRange, new ByteArrayInputStream(dateStringBuffer
742                 .toString().getBytes(ENCODING)), false);
743                 
744         // call command to actually perform fill the file
745         command.execute(new File(outputFileName), TIME_BOUNDS, timeRange+","+NV_RANGE, new ByteArrayInputStream(
746                 timeBoundsStringBuffer.toString().getBytes(ENCODING)), false);
747         
748     }
749 
750     /**
751      * Populates the spatial variables with data
752      * 
753      * @param outputFileName
754      *            The netCDF file to be updated
755      * @param sortedCellData
756      *            The list of cell data in the same order as the spatial coordinates
757      * @throws IOException
758      *             If the data cannot be read
759      */
760     private void fillSpatialVariables(String outputFileName, List<CellData> sortedCellData) throws IOException
761     {
762         NcWriteVariable command = new NcWriteVariable();
763 
764         Set<String> latitudes = getSortedLatitudes(sortedCellData);
765         Set<String> longitudes = getSortedLongitudes(sortedCellData);
766         String spatialRange = "0-" + (latitudes.size() - 1) + ",0-" + (longitudes.size() - 1);
767 
768         // ///////////////////////////////////////////////////////////////////////////////////////////
769         // fill elevation variable
770         // create String of elevations to populate the variable
771         StringBuffer dataBuffer = fillSpatialDataBuffer(sortedCellData, latitudes, longitudes,
772                 CellDataVariable.ELEVATION, "-999.99");
773 
774         // call command to actually perform fill the file
775         command.execute(new File(outputFileName), ELEVATION, spatialRange, new ByteArrayInputStream(dataBuffer
776                 .toString().getBytes(ENCODING)), false);
777 
778         // ///////////////////////////////////////////////////////////////////////////////////////////
779         // fill catchment id variable
780         // create String of catchment ids to populate the variable
781         dataBuffer = fillSpatialDataBuffer(sortedCellData, latitudes, longitudes, CellDataVariable.CATCHMENTID, "0");
782 
783         // call command to actually perform fill the file
784         command.execute(new File(outputFileName), CATCHMENT_ID, spatialRange, new ByteArrayInputStream(dataBuffer
785                 .toString().getBytes(ENCODING)), false);
786 
787         // ///////////////////////////////////////////////////////////////////////////////////////////
788         // fill reporting region id variable
789         // create String of reporting region ids to populate the variable
790         dataBuffer = fillSpatialDataBuffer(sortedCellData, latitudes, longitudes, CellDataVariable.REPORTINGREGIONID,
791                 "0");
792 
793         // call command to actually perform fill the file
794         command.execute(new File(outputFileName), REPORTING_REGION, spatialRange, new ByteArrayInputStream(dataBuffer
795                 .toString().getBytes(ENCODING)), false);
796     }
797 
798     /**
799      * @param sortedCellData
800      * @param latitudes
801      * @param longitudes
802      * @param varNum
803      * @param fillValue
804      * @return
805      */
806     private StringBuffer fillSpatialDataBuffer(List<CellData> sortedCellData, Set<String> latitudes,
807             Set<String> longitudes, CellDataVariable varNum, String fillValue)
808     {
809         StringBuffer dataBuffer = new StringBuffer();
810         int cellNum = 0;
811         for (String latVal : latitudes)
812         {
813             for (String longVal : longitudes)
814             {
815                 if (cellNum < sortedCellData.size())
816                 {
817                     CellData cellData = sortedCellData.get(cellNum);
818                     if (cellData.latitude.equals(latVal) && cellData.longitude.equals(longVal))
819                     {
820                         switch (varNum)
821                         {
822                         case ELEVATION:
823                             dataBuffer.append(cellData.elevation);
824                             break;
825                         case CATCHMENTID:
826                             dataBuffer.append(cellData.catchmentId);
827                             break;
828                         case REPORTINGREGIONID:
829                             dataBuffer.append(cellData.reportingRegionId);
830                             break;
831                         }
832                         cellNum++;
833                     }
834                     else
835                     {
836                         dataBuffer.append(fillValue);
837                     }
838                 }
839                 else
840                 {
841                     dataBuffer.append(fillValue);
842                 }
843 
844                 dataBuffer.append(System.getProperty("line.separator"));
845             }
846 
847         }
848         return dataBuffer;
849     }
850 
851     /**
852      * Populate the data variable with fill values. Used to ensure cells within 
853      * the rectangular bounds but for which we have no data are set to the fill value. 
854      *   
855      * @param varIndex The variable being filled
856      * @param sortedLongitudes The longitudes to fill.
857      * @param sortedLatitudes The latitudes to fill
858      * @param dates The dates to fill
859      * @param dimensionOrdering The order of the dimensions in the files
860      * @throws IOException If the data cannot be written
861      */
862     private void fillDataForLatitude(String filename, int varIndex, Set<String> sortedLongitudes, Set<String> sortedLatitudes,
863             List<String> dates, List<String> dimensionOrdering) throws IOException
864     {
865         StringBuffer fillBlock = new StringBuffer();
866         String fillLine = variableFillValues[varIndex] + "\n";
867         for (int i = 0; i < dates.size(); i++)
868         {
869             fillBlock.append(fillLine);
870         }
871 
872         NcWriteVariable writeVarCmd = new NcWriteVariable();
873         for (String latitude : sortedLatitudes)
874         {
875             for (String longitude : sortedLongitudes)
876             {
877                 String fillRange = this.constructFillRange(dimensionOrdering, latitude, longitude, dates.size());
878                 writeVarCmd.execute(new File(filename), variableNames[varIndex], fillRange,
879                         new ByteArrayInputStream(fillBlock.toString().getBytes(ENCODING)), false);
880             }
881         }
882     }
883 
884     /**
885      * Fills the variables with data for a particular latitude.
886      * 
887      * @param variableFileNames The filenames for this latitude for each of the variables
888      * @param triples The details of the cells that occur in this latitude 
889      * @param dimensionOrdering The order of the dimensions in the files
890      * @throws IOException If the data cannot be written
891      */
892     private void fillDataByLatitudeVariables(String[] variableFileNames, List<CellData> triples, List<String> dimensionOrdering)
893             throws IOException
894     {
895         NcWriteVariable command = new NcWriteVariable();
896 
897         int counter = 0;
898         for (CellData triple : triples)
899         {
900             if (++counter % 1000 == 0)
901             {
902                 LOG.warn("   Num files processed: " + counter);
903             }
904 
905             String csvFileName = triple.getCellId();
906             String latitude = triple.getLatitude();
907             String longitude = triple.getLongitude();
908 
909             String fillRange = this.constructFillRange(dimensionOrdering, latitude, longitude, TIME_SIZE);
910 
911             try
912             {
913                 StringBuffer[] valuesStringBuffer = new StringBuffer[numVariables];
914                 for (int i = 0; i < valuesStringBuffer.length; i++)
915                 {
916                     valuesStringBuffer[i] = new StringBuffer();
917                 }
918 
919                 Scanner s = null;
920                 try
921                 {
922                     s = new Scanner(new BufferedReader(new FileReader(INPUT_CSV_DIRECTORY + csvFileName + ".csv")))
923                             .useDelimiter(Pattern.compile("[\n,]"));
924 
925                     // remove header line
926                     for (int i = 0; i < NUM_CSV_COLUMNS; i++)
927                     {
928                         s.next();
929                     }
930 
931                     while (s.hasNext())
932                     {
933                         s.next(); // date column
934 
935                         for (int i = 0; i < numVariables; i++)
936                         {
937                             valuesStringBuffer[i].append(s.next());
938                             // Last variable will already have newline at its end.
939                             if (i < numVariables - 1)
940                             {
941                                 valuesStringBuffer[i].append(System.getProperty("line.separator"));
942                             }
943                         }
944                     }
945                 }
946                 finally
947                 {
948                     if (s != null)
949                     {
950                         s.close();
951                     }
952                 }
953 
954                 try
955                 {
956                     for (int i = 0; i < numVariables; i++)
957                     {
958                         command.execute(new File(variableFileNames[i]), variableNames[i], fillRange,
959                                 new ByteArrayInputStream(valuesStringBuffer[i].toString().getBytes(ENCODING)), false);
960                     }
961                 }
962                 catch (IllegalArgumentException iae)
963                 {
964                     LOG.error(iae);
965                     LOG.error("fillRange (date, lat, long): " + fillRange);
966                 }
967 
968             }
969             catch (FileNotFoundException fnfe)
970             {
971                 LOG.info(fnfe.getMessage());
972                 //System.out.println(fnfe.getMessage());
973             }
974         }
975     }
976 
977     /**
978      * @param dimensionOrdering
979      * @param latitude
980      * @param longitude
981      * @param dates
982      * @return
983      */
984     private String constructFillRange(List<String> dimensionOrdering, String latitude, String longitude, int dateSize)
985     {
986         String time_range = "0-" + String.valueOf(dateSize - 1);
987         String latitude_range = "lookup(" + latitude + ")-lookup(" + latitude + ")";
988         String longitude_range = "lookup(" + longitude + ")-lookup(" + longitude + ")";
989 
990         String fillRange = new String();
991         for (Iterator<String> iter = dimensionOrdering.iterator(); iter.hasNext();)
992         {
993             String dimension = iter.next();
994             if (dimension.equals(TIME))
995             {
996                 fillRange += time_range;
997             }
998             else if (dimension.equals(LAT))
999             {
1000                 fillRange += latitude_range;
1001             }
1002             else if (dimension.equals(LONG))
1003             {
1004                 fillRange += longitude_range;
1005             }
1006             if (iter.hasNext())
1007                 fillRange += ",";
1008         }
1009         return fillRange;
1010     }
1011 
1012     /**
1013      * Groups triples, i.e. <cellId,longitude,latitude> (a row in the WRON lookup file) by degrees of latitude.
1014      * 
1015      * @param locationLookupValues
1016      * @return
1017      */
1018     private Map<String, List<CellData>> getTriplesGroupedByLatitude(String[][] locationLookupValues)
1019     {
1020         Map<String, List<CellData>> triplesGroupedByLatitude = new HashMap<String, List<CellData>>();
1021         for (int i = 0; i < locationLookupValues.length; i++)
1022         {
1023             String cellId = locationLookupValues[i][CELL_ID_COLUMN_INDEX];
1024             String longitude = locationLookupValues[i][LONG_COLUMN_INDEX];
1025             String latitude = locationLookupValues[i][LAT_COLUMN_INDEX];
1026             String elevation = locationLookupValues[i][ELEVATION_COLUMN_INDEX];
1027             String catchmentId = locationLookupValues[i][CATCHMENT_ID_COLUMN_INDEX];
1028             String reportingRegionId = locationLookupValues[i][REPORTING_REGION_ID_COLUMN_INDEX];
1029 
1030             String latitudeDegree = String.valueOf(Float.valueOf(latitude).intValue());
1031 
1032             List<CellData> triplesInLatitude = triplesGroupedByLatitude.get(latitudeDegree);
1033             if (triplesInLatitude == null)
1034             {
1035                 triplesInLatitude = new ArrayList<CellData>();
1036                 triplesGroupedByLatitude.put(latitudeDegree, triplesInLatitude);
1037             }
1038             final CellData cellData = new CellData(cellId, longitude, latitude);
1039             cellData.elevation = elevation;
1040             cellData.catchmentId = catchmentId;
1041             cellData.reportingRegionId = reportingRegionId;
1042             triplesInLatitude.add(cellData);
1043         }
1044 
1045         // check that we have actually grouped all rows by latitude
1046         int count = 0;
1047         for (Iterator<String> iter = triplesGroupedByLatitude.keySet().iterator(); iter.hasNext();)
1048         {
1049             count += triplesGroupedByLatitude.get(iter.next()).size();
1050         }
1051         if (count != locationLookupValues.length)
1052         {
1053             LOG.error("latitudes where not grouped by latitude correctly");
1054             System.exit(-1);
1055         }
1056 
1057         return triplesGroupedByLatitude;
1058     }
1059 
1060     /**
1061      * Groups dates, e.g. 1895-01-01 by decade.
1062      * 
1063      * @param dates
1064      * @return
1065      */
1066     public Map<Integer, List<String>> getDatesByDecade(List<String> dates)
1067     {
1068         Map<Integer, List<String>> datesGroupedByDecade = new HashMap<Integer, List<String>>();
1069 
1070         for (String date : dates)
1071         {
1072             int year = Integer.valueOf(date.substring(0, 4));
1073             int decade = year / 10;
1074 
1075             List<String> datesInDecade = datesGroupedByDecade.get(decade);
1076             if (datesInDecade == null)
1077             {
1078                 datesInDecade = new ArrayList<String>();
1079                 datesGroupedByDecade.put(decade, datesInDecade);
1080             }
1081             datesInDecade.add(date);
1082         }
1083 
1084         // check that we have actually grouped all rows by latitude
1085         int count = 0;
1086         for (Iterator<Integer> iter = datesGroupedByDecade.keySet().iterator(); iter.hasNext();)
1087         {
1088             count += datesGroupedByDecade.get(iter.next()).size();
1089         }
1090         if (count != dates.size())
1091         {
1092             LOG.error("dates where not grouped decade correctly");
1093             System.exit(-1);
1094         }
1095 
1096         return datesGroupedByDecade;
1097     }
1098 
1099     /**
1100      * Loads all triples, i.e. <cellId,longitude,latitude> (a row in the WRON lookup file).
1101      * 
1102      * @param locationLookupValues
1103      * @return
1104      */
1105     private List<CellData> getAllTriples(String[][] locationLookupValues)
1106     {
1107         List<CellData> allTriples = new ArrayList<CellData>();
1108         for (int i = 0; i < locationLookupValues.length; i++)
1109         {
1110             String cellId = locationLookupValues[i][CELL_ID_COLUMN_INDEX];
1111             String longitude = locationLookupValues[i][LONG_COLUMN_INDEX];
1112             String latitude = locationLookupValues[i][LAT_COLUMN_INDEX];
1113             String elevation = locationLookupValues[i][ELEVATION_COLUMN_INDEX];
1114             String catchmentId = locationLookupValues[i][CATCHMENT_ID_COLUMN_INDEX];
1115             String reportingRegionId = locationLookupValues[i][REPORTING_REGION_ID_COLUMN_INDEX];
1116 
1117             CellData cellData = new CellData(cellId, longitude, latitude);
1118             cellData.elevation = elevation;
1119             cellData.catchmentId = catchmentId;
1120             cellData.reportingRegionId = reportingRegionId;
1121             allTriples.add(cellData);
1122         }
1123 
1124         // check that we have actually grouped all rows by latitude
1125         if (allTriples.size() != locationLookupValues.length)
1126         {
1127             LOG.error("lookup values where not read correctly");
1128             System.exit(-1);
1129         }
1130 
1131         return allTriples;
1132     }
1133 
1134     /**
1135      * Parses the WRON lookup file into an row-by-column string matrix.
1136      * 
1137      * @throws IOException
1138      */
1139     private String[][] readLookupFile() throws IOException
1140     {
1141         RandomAccessFile raf = new RandomAccessFile(new File(LOCATION_LOOKUP_FILE), "r");
1142         raf.readLine();// abandon the first line since it is a header line.
1143 
1144         List<String[]> lineList = new ArrayList<String[]>();
1145         String line = raf.readLine();
1146         while (line != null)
1147         {
1148             String[] cols = new CSVTokenizer(line).getAllColumns();
1149             lineList.add(cols);
1150             line = raf.readLine();
1151         }
1152 
1153         raf.close();
1154 
1155         String[][] lineCols = lineList.toArray(new String[][]{});
1156         return lineCols;
1157     }
1158 
1159     /**
1160      * Transpose a matrix, e.g. [a][b] -> [b][a]
1161      * 
1162      * @param gridValues
1163      * @return
1164      */
1165     private String[][] transpose(String[][] gridValues)
1166     {
1167         String[][] transposed = new String[gridValues[0].length][gridValues.length];
1168         for (int rows = 0; rows < gridValues.length; rows++)
1169         {
1170             for (int cols = 0; cols < gridValues[0].length; cols++)
1171             {
1172                 transposed[cols][rows] = gridValues[rows][cols];
1173             }
1174         }
1175         return transposed;
1176     }
1177 
1178     /**
1179      * Retrieve an ordered set containing all the latitude values from a list of triples.
1180      * 
1181      * @param triples
1182      * @return
1183      */
1184     private Set<String> getSortedLatitudes(List<CellData> triples)
1185     {
1186         Set<String> latitudes = new TreeSet<String>();
1187         for (CellData triple : triples)
1188         {
1189             latitudes.add(triple.getLatitude());
1190 //            if (latitudes.size() >= LAT_SIZE)
1191 //            {
1192 //                break;
1193 //            }
1194         }
1195         return latitudes;
1196     }
1197 
1198     /**
1199      * Retrieve an ordered set containing all the longitude values from a list of triples.
1200      * 
1201      * @param triples
1202      * @return
1203      */
1204     private Set<String> getSortedLongitudes(List<CellData> triples)
1205     {
1206         Set<String> longitudes = new TreeSet<String>();
1207         for (CellData triple : triples)
1208         {
1209             longitudes.add(triple.getLongitude());
1210 //            if (longitudes.size() >= LONG_SIZE)
1211 //            {
1212 //                break;
1213 //            }
1214         }
1215         return longitudes;
1216     }
1217 
1218     /**
1219      * @return
1220      * @throws IOException
1221      */
1222     private List<String> getDates() throws IOException
1223     {
1224         List<String> dates = new ArrayList<String>();
1225 
1226         RandomAccessFile raf = null;
1227         try
1228         {
1229             // open any csv file from scenario A directory and read in the date dimension
1230             File dir = new File(INPUT_CSV_DIRECTORY);
1231             String[] children = dir.list();
1232             if (children != null && children.length > 0)
1233             {
1234                 raf = new RandomAccessFile(new File(INPUT_CSV_DIRECTORY + children[0]), "r");
1235                 raf.readLine();// abandon the first line since it is a header line.
1236 
1237                 List<String[]> lineList = new ArrayList<String[]>();
1238                 String line = raf.readLine();
1239                 while (line != null)
1240                 {
1241                     String[] cols = new CSVTokenizer(line).getAllColumns();
1242                     lineList.add(cols);
1243                     line = raf.readLine();
1244                 }
1245                 String[][] lineCols = lineList.toArray(new String[][]{});
1246                 dates = Arrays.asList(transpose(lineCols)[DATE_COLUMN_INDEX]); // get the date column
1247             }
1248         }
1249         finally
1250         {
1251             if (raf != null)
1252                 raf.close();
1253         }
1254 
1255         return dates;
1256     }
1257 
1258     /**
1259      * Convert the date values into a newline delimited string of days since a given epoc.
1260      * 
1261      * @param dates
1262      * @return
1263      * @throws IOException
1264      * @throws java.text.ParseException
1265      */
1266     private List<String> getDatesAsDaysSinceEpoc(List<String> dates) throws IOException, java.text.ParseException
1267     {
1268         return daysSinceSinceEpocAsString(dates, isoDateTimeFormat.dateOnlyFormat(EPOC_DATE_STRING));
1269     }
1270 
1271     /**
1272      * Convert a string array representing dates to a list of strings representing the number of days since since a
1273      * given epoc.
1274      * 
1275      * @param dates
1276      * @param epoc
1277      * @return
1278      * @throws java.text.ParseException
1279      */
1280     private List<String> daysSinceSinceEpocAsString(List<String> dates, Date epoc) throws java.text.ParseException
1281     {
1282         List<String> convertedDates = new ArrayList<String>();
1283         for (String date : dates)
1284         {
1285             Date currentDate = isoDateTimeFormat.dateOnlyFormat(date);
1286             convertedDates.add(String.valueOf(daysBetween(epoc, currentDate)));
1287         }
1288         return convertedDates;
1289     }
1290 
1291     /**
1292      * @param d1
1293      *            the initial date.
1294      * @param d2
1295      *            the later date.
1296      * @return (d2 - d1) in days
1297      */
1298     public int daysBetween(Date d1, Date d2)
1299     {
1300         return (int) ((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24));
1301     }
1302 
1303     private String generateLatitudeFilename(String latitudeDegree, String variable)
1304     {
1305         String filename = OUTPUT_NETCDF_DIRECTORY + COLLECTION + "." + SCENARIO + ".";
1306         if (MODEL.length() > 0)
1307         {
1308             filename += MODEL + ".";
1309         }
1310         if (CASE.length() > 0)
1311         {
1312             filename += CASE + ".";
1313         }
1314         filename += "Latitude-";
1315         if (latitudeDegree.startsWith("-"))
1316         {
1317             filename += latitudeDegree.substring(1) + "S."; 
1318         }
1319         else
1320         {
1321             filename += latitudeDegree + "N.";
1322         }
1323         filename += "Var-" + variable + NETCDF_FILE_EXTENSION;
1324         return filename;
1325     }
1326 
1327     private String generateDecadeFilename(String decadeStr, String variable)
1328     {
1329         String filename = OUTPUT_NETCDF_DIRECTORY + COLLECTION + "." + SCENARIO + ".";
1330         if (MODEL.length() > 0)
1331         {
1332             filename += MODEL + ".";
1333         }
1334         if (CASE.length() > 0)
1335         {
1336             filename += CASE + ".";
1337         }
1338         filename += "Decade-" + decadeStr +  ".Var-" + variable + NETCDF_FILE_EXTENSION;
1339         return filename;
1340     }
1341 
1342     /**
1343      * Displays the contents of the in-memory data structure/
1344      * 
1345      * @param lookup
1346      */
1347     @SuppressWarnings("unused")
1348     private void printLookupInMemory(Map<Integer, Map<String, LongitudeRange>> lookup)
1349     {
1350         for (Iterator<Integer> dateIter = lookup.keySet().iterator(); dateIter.hasNext();)
1351         {
1352             Integer dateKey = dateIter.next();
1353             System.out.println(dateKey);
1354 
1355             for (Iterator<String> latIter = lookup.get(dateKey).keySet().iterator(); latIter.hasNext();)
1356             {
1357                 String latitude = latIter.next();
1358                 System.out.println("\t" + latitude);
1359 
1360                 LongitudeRange longitudeRange = lookup.get(dateKey).get(latitude);
1361                 System.out.println("\t\t" + longitudeRange.getStartLongitudeRange() + "-"
1362                         + longitudeRange.getEndLongitudeRange());
1363             }
1364         }
1365     }
1366 
1367     public enum CellDataVariable
1368     {
1369         CELLID, LONGITUDE, LATITUDE, ELEVATION, CATCHMENTID, REPORTINGREGIONID
1370     }
1371 
1372 }