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 }