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  /**
18   * 
19   */
20  package au.csiro.netcdf.util;
21  
22  import java.io.BufferedReader;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InputStreamReader;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import ucar.ma2.Array;
30  import ucar.ma2.DataType;
31  import ucar.nc2.Attribute;
32  import ucar.nc2.Dimension;
33  import ucar.nc2.NetcdfFile;
34  import ucar.nc2.Variable;
35  
36  /**
37   * A collection of netCDF specific utility functions.
38   * 
39   * Copyright 2010, CSIRO Australia
40   * All rights reserved.
41   * 
42   * @author      James Dempsey on 22/03/2010
43   * @version     $Revision: 78 $  $Date: 2010-07-24 16:23:13 +1000 (Sat, 24 Jul 2010) $
44   */
45  public class NetCDFUtils
46  {
47      /** Index value to be returned when the value cannot be found in the array */
48      public static final int NOT_FOUND = -1;
49  
50      public static final String NULL_VALUE = "null";
51  
52      /**
53       * Lookup the index of a value in a dimension. The dimension to be searched must 
54       * have a coordinate variable (a variable with the same name as the dimension). 
55       * A binary search is tried first for speed based on the common scenario that 
56       * the corresponding variable contain numeric or string data stored in ascending 
57       * order. If the binary search fails a scan of the variable is made for the value.
58       * 
59       * @param file The NetCDF file being processed.
60       * @param dimension The dimension the value comes from
61       * @param value The value to be found
62       * @return The index of the item, or -1 if it cannot be found.
63       * @throws IOException If data cannot be read from the dimension
64       */
65      public static int lookupIndex(NetcdfFile file, Dimension dimension, String value) throws IOException
66      {
67          int index;
68          Variable coordVar = file.findVariable(dimension.getName());
69          if (coordVar != null && coordVar.isCoordinateVariable())
70          {
71              Array cvData = coordVar.read();
72              if (coordVar.getDataType().isIntegral())
73              {
74                  Long target = Long.valueOf(value);
75                  index = NetCDFUtils.searchIntegralArray(cvData, target); 
76                  if (index < 0)
77                  {
78                      for (int i = 0; i < cvData.getSize(); i++)
79                      {
80                          if (target == cvData.getLong(i))
81                          {
82                              index = i;
83                              break;
84                          }
85                      }
86                  }
87              }
88              else if (coordVar.getDataType().isNumeric()) // Non integral numeric = float or double
89              {
90                  final double floatingPointTolerance = 0.00001d;
91                  Double target = Double.valueOf(value);
92                  index = NetCDFUtils.searchFloatingPointArray(cvData, target,
93                          floatingPointTolerance);
94                  if (index < 0)
95                  {
96                      for (int i = 0; i < cvData.getSize(); i++)
97                      {
98                          Double cellVal = cvData.getDouble(i);
99                          if (Math.abs(target - cellVal) < floatingPointTolerance)
100                         {
101                             index = i;
102                             break;
103                         }
104                     }
105                 }
106             }
107             else if (coordVar.getDataType().isString()) // isString() equivalent to isChar()
108             {
109                 Character target = Character.valueOf(value.charAt(0));
110                 index = NetCDFUtils.searchCharArray(cvData, target); 
111                 if (index < 0)
112                 {
113                     for (int i = 0; i < cvData.getSize(); i++)
114                     {
115                         if (target == cvData.getChar(i))
116                         {
117                             index = i;
118                             break;
119                         }
120                     }
121                 }
122             }
123             else
124             {
125                 throw new IllegalArgumentException("Unable to search dimension '" + dimension.getName()
126                         + "' of type " + coordVar.getDataType().toString());
127             }
128         }
129         else
130         {
131             throw new IllegalArgumentException("No coordinate variable defined for dimension '"
132                     + dimension.getName() + "'. Unable to process range of lookup(" + value + ")");
133         }
134         return index;
135     }
136     
137     /**
138      * Search a netCDF Array containing integer numeric values (int, long, short, 
139      * byte) stored in ascending order.
140      * 
141      * @param source The array to be searched
142      * @param value The value to be found
143      * @return The index of the value in the array, or -1 if not found.
144      */
145     public static int searchIntegralArray(Array source, long value)
146     {
147         long low = 0;
148         long high = source.getSize() - 1;
149         int mid;
150 
151         while( low <= high )
152         {
153             mid = (int) (( low + high ) / 2);
154 
155             if (source.getLong(mid) < value)
156             {
157                 low = mid + 1;
158             }
159             else if (source.getLong(mid) > value)
160             {
161                 high = mid - 1;
162             }
163             else
164             {
165                 return mid;
166             }
167         }
168 
169         return NOT_FOUND;
170     }
171 
172 
173     /**
174      * Search a netCDF Array containing floating point numeric values 
175      * (double, float) stored in ascending order.
176      * 
177      * @param source The array to be searched
178      * @param value The value to be found
179      * @param tolerance The comparison tolerance for floating point value comparisons
180      * @return The index of the value in the array, or -1 if not found.
181      */
182     public static int searchFloatingPointArray(Array source, double value, double tolerance)
183     {
184         long low = 0;
185         long high = source.getSize() - 1;
186         int mid;
187         //Math.abs(a-b) < tolerance
188         while( low <= high )
189         {
190             mid = (int) (( low + high ) / 2);
191 
192             if( value - source.getDouble(mid) > tolerance )
193             {
194                 low = mid + 1;
195             }
196             else if( source.getDouble(mid) - value > tolerance )
197             {
198                 high = mid - 1;
199             }
200             else
201             {
202                 return mid;
203             }
204         }
205 
206         return NOT_FOUND;
207     }
208 
209 
210     /**
211      * Search a netCDF Array containing character values stored 
212      * in ascending order, i.e. ascending unicode numeric values.
213      * 
214      * @param source The array to be searched
215      * @param value The value to be found
216      * @return The index of the value in the array, or -1 if not found.
217      */
218     public static int searchCharArray(Array source, Character value)
219     {
220         long low = 0;
221         long high = source.getSize() - 1;
222         int mid;
223 
224         while( low <= high )
225         {
226             mid = (int) (( low + high ) / 2);
227             
228             Character midValue = (Character) source.getObject(mid);
229             if( value.compareTo(midValue) > 0 )
230             {
231                 low = mid + 1;
232             }
233             else if( value.compareTo(midValue) < 0 )
234             {
235                 high = mid - 1;
236             }
237             else
238             {
239                 return mid;
240             }
241         }
242 
243         return NOT_FOUND;
244     }
245     
246     /**
247      * Converts a comma separated <code>String</code> into a String {@link Attribute} list.  
248      * 
249      * @param commaSeparatedAttributeValueString
250      *            a list of comma separated attribute-value pairs, e.g. attribute1=value1,attribute2=value2,...
251      * @return an {@link Attribute} list.
252      * @throws IllegalArgumentException
253      *             thrown if the <code>String</code> can not be converted into an {@link Attribute} list.
254      */
255     public static List<Attribute> mapStringToAttributeValueList(String commaSeparatedAttributeValueString)
256             throws IllegalArgumentException
257     {
258         return mapStringToAttributeValueList(commaSeparatedAttributeValueString, DataType.STRING);
259     }
260     
261     /**
262      * Converts a comma separated <code>String</code> into an {@link Attribute} list. 
263      * 
264      * @param commaSeparatedAttributeValueString
265      *            a list of comma separated attribute-value pairs, e.g. attribute1=value1,attribute2=value2,...
266      * @param dataType The type of attributes to be created.           
267      * @return an {@link Attribute} list.
268      * @throws IllegalArgumentException
269      *             thrown if the <code>String</code> can not be converted into an {@link Attribute} list.
270      */
271     public static List<Attribute> mapStringToAttributeValueList(String commaSeparatedAttributeValueString, DataType dataType)
272         throws IllegalArgumentException
273     {
274         List<Attribute> attributeValues = new ArrayList<Attribute>();
275 
276         List<String> attributePairs = Util.tokeniseCommaSeparatedString(commaSeparatedAttributeValueString);
277         for (String attributePair : attributePairs)
278         {
279             String[] keyValuePair = Util.splitOnNonBackslashedCharacter(attributePair, "=");// split on all '='
280                                                                                             // characters that are not
281                                                                                             // delimited by a backslash
282             if (keyValuePair.length == 2 && !keyValuePair[0].isEmpty() && !keyValuePair[1].isEmpty())
283             {
284                 String key = keyValuePair[0].replaceAll("\\\\,", ",").replaceAll("\\\\=", "=");
285                 key = key.trim();
286                 String strVal = keyValuePair[1].replaceAll("\\\\,", ",").replaceAll("\\\\=", "=");
287                 Attribute attrib;
288                 
289                 if (NULL_VALUE.equals(strVal) || dataType == DataType.STRING || dataType == DataType.CHAR)
290                 {
291                     attrib = new Attribute(key, strVal);
292                 }
293                 else if (dataType == DataType.FLOAT)
294                 {
295                     Float value;
296                     try
297                     {
298                         value = Float.parseFloat(strVal);
299                     }
300                     catch (NumberFormatException e)
301                     {
302                         throw new IllegalArgumentException("Invalid float value '" + strVal + "' for key "
303                                 + key + ".");
304                     }
305                     attrib = new Attribute(key, value);
306                 }
307                 else if (dataType == DataType.DOUBLE)
308                 {
309                     Double value;
310                     try
311                     {
312                         value = Double.parseDouble(strVal);
313                     }
314                     catch (NumberFormatException e)
315                     {
316                         throw new IllegalArgumentException("Invalid double value '" + strVal + "' for key "
317                                 + key + ".");
318                     }
319                     attrib = new Attribute(key, value);
320                 }
321                 else if (dataType == DataType.INT)
322                 {
323                     Integer value;
324                     try
325                     {
326                         value = Integer.parseInt(strVal);
327                     }
328                     catch (NumberFormatException e)
329                     {
330                         throw new IllegalArgumentException("Invalid integer value '" + strVal + "' for key "
331                                 + key + ".");
332                     }
333                     attrib = new Attribute(key, value);
334                 }
335                 else if (dataType == DataType.SHORT)
336                 {
337                     Short value;
338                     try
339                     {
340                         value = Short.parseShort(strVal);
341                     }
342                     catch (NumberFormatException e)
343                     {
344                         throw new IllegalArgumentException("Invalid short value '" + strVal + "' for key "
345                                 + key + ".");
346                     }
347                     attrib = new Attribute(key, value);
348                 }
349                 else if (dataType == DataType.BYTE)
350                 {
351                     Byte value;
352                     try
353                     {
354                         value = Byte.parseByte(strVal);
355                     }
356                     catch (NumberFormatException e)
357                     {
358                         throw new IllegalArgumentException("Invalid byte value '" + strVal + "' for key "
359                                 + key + ".");
360                     }
361                     attrib = new Attribute(key, value);
362                 }
363                 else
364                 {
365                     throw new IllegalArgumentException(
366                             "Unexpected datatype of " + dataType + " supplied to mapStringToAttributeValueList.");
367                 }
368                 attributeValues.add(attrib);
369             }
370             else
371             {
372                 throw new IllegalArgumentException(
373                         "The following String could not be converted into a list of Attributes: "
374                                 + commaSeparatedAttributeValueString);
375             }
376         }
377 
378         return attributeValues;
379     }
380     
381     /**
382      * Converts an input stream into an {@link Attribute} list.
383      * 
384      * @param input
385      *            the input stream
386      * @return an {@link Attribute} list.
387      * @throws IOException
388      */
389     public static List<Attribute> readAttributesFromStream(InputStream input) throws IOException
390     {
391         List<Attribute> attributes = new ArrayList<Attribute>();
392         
393         try
394         {
395             // if an input stream is specified, read it into a string
396             if (input != null)
397             {
398                 // get attribute from input file or standard input
399                 BufferedReader dataIn = new BufferedReader(new InputStreamReader(input));
400 
401                 // read file in to a String
402                 StringBuffer strBuf = new StringBuffer();
403                 char cbuf[] = new char[2048];
404                 @SuppressWarnings("unused")
405                 int n;
406                 while (dataIn.ready() && (n = dataIn.read(cbuf)) != -1)
407                 {
408                     strBuf.append(cbuf);
409                     cbuf = new char[2048];
410                 }
411 
412                 // combine attribute specified on the command line with any attribute specified via an input file or
413                 // stdin
414                 try
415                 {
416                     attributes = NetCDFUtils.mapStringToAttributeValueList(strBuf.toString().trim());
417                 }
418                 catch (IllegalArgumentException iae)
419                 {
420                     throw new IllegalArgumentException(
421                             "input file or standard input does not contain a comma separated String of attribute-value pairs: "
422                                     + strBuf.toString().trim());
423                 }
424             }
425         }
426         finally
427         {
428             if (input != null)
429             {
430                 input.close();
431             }
432         }
433         
434         return attributes;
435     }
436 }