Source Code Cross Referenced for NetcdfMetadata.java in  » GIS » GeoTools-2.4.1 » org » geotools » image » io » netcdf » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.image.io.netcdf 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /*
002:         *    GeoTools - OpenSource mapping toolkit
003:         *    http://geotools.org
004:         *    (C) 2007, GeoTools Project Managment Committee (PMC)
005:         *    (C) 2007, Geomatys
006:         *
007:         *    This library is free software; you can redistribute it and/or
008:         *    modify it under the terms of the GNU Lesser General Public
009:         *    License as published by the Free Software Foundation;
010:         *    version 2.1 of the License.
011:         *
012:         *    This library is distributed in the hope that it will be useful,
013:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
014:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015:         *    Lesser General Public License for more details.
016:         */
017:        package org.geotools.image.io.netcdf;
018:
019:        // J2SE dependencies
020:        import java.text.DateFormat;
021:        import java.text.Format;
022:        import java.text.NumberFormat;
023:        import java.text.SimpleDateFormat;
024:        import java.util.Date;
025:        import java.util.Map;
026:        import java.util.HashMap;
027:        import java.util.List;
028:        import java.util.Locale;
029:        import java.util.TimeZone;
030:        import java.util.logging.Level;
031:        import java.util.logging.LogRecord;
032:        import javax.imageio.ImageReader;
033:
034:        // OpenGIS dependencies
035:        import org.opengis.referencing.cs.AxisDirection;
036:
037:        // NetCDF dependencies
038:        import ucar.nc2.Variable;
039:        import ucar.nc2.Attribute;
040:        import ucar.nc2.dataset.AxisType;
041:        import ucar.nc2.dataset.CoordinateAxis;
042:        import ucar.nc2.dataset.CoordinateAxis1D;
043:        import ucar.nc2.dataset.CoordinateSystem;
044:        import ucar.nc2.dataset.NetcdfDataset;
045:        import ucar.nc2.dataset.VariableDS;
046:
047:        // Geotools dependencies
048:        import org.geotools.image.io.metadata.Axis;
049:        import org.geotools.image.io.metadata.ImageGeometry;
050:        import org.geotools.image.io.metadata.ImageReferencing;
051:        import org.geotools.image.io.metadata.MetadataAccessor;
052:        import org.geotools.image.io.metadata.GeographicMetadata;
053:        import org.geotools.image.io.metadata.GeographicMetadataFormat;
054:        import org.geotools.util.logging.LoggedFormat;
055:        import org.geotools.resources.i18n.Errors;
056:        import org.geotools.resources.i18n.ErrorKeys;
057:
058:        /**
059:         * Metadata from NetCDF file. This implementation assumes that the NetCDF file follows the
060:         * <A HREF="http://www.cfconventions.org/">CF Metadata conventions</A>.
061:         * <p>
062:         * <b>Limitation:</b>
063:         * Current implementation retains only the first {@linkplain CoordinateSystem coordinate system}
064:         * found in the NetCDF file or for a given variable. The {@link org.geotools.coverage.io} package
065:         * would not know what to do with the extra coordinate systems anyway.
066:         *
067:         * @since 2.4
068:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio-netcdf/src/main/java/org/geotools/image/io/netcdf/NetcdfMetadata.java $
069:         * @version $Id: NetcdfMetadata.java 27848 2007-11-12 13:10:32Z desruisseaux $
070:         * @author Martin Desruisseaux
071:         */
072:        public class NetcdfMetadata extends GeographicMetadata {
073:            /**
074:             * Forces usage of UCAR libraries in some places where we use our own code instead.
075:             * This may result in rounding errors and absence of information regarding fill values,
076:             * but is useful for checking if we are doing the right thing compared to the UCAR way.
077:             */
078:            private static final boolean USE_UCAR_LIB = false;
079:
080:            /**
081:             * The mapping between UCAR axis type and ISO axis directions.
082:             */
083:            private static final Map/*<AxisType,AxisDirection>*/DIRECTIONS = new HashMap(
084:                    16);
085:            static {
086:                add(AxisType.Time, AxisDirection.FUTURE);
087:                add(AxisType.GeoX, AxisDirection.EAST);
088:                add(AxisType.GeoY, AxisDirection.NORTH);
089:                add(AxisType.GeoZ, AxisDirection.UP);
090:                add(AxisType.Lat, AxisDirection.NORTH);
091:                add(AxisType.Lon, AxisDirection.EAST);
092:                add(AxisType.Height, AxisDirection.UP);
093:                add(AxisType.Pressure, AxisDirection.UP);
094:            }
095:
096:            /**
097:             * Adds a mapping between UCAR type and ISO direction.
098:             */
099:            private static void add(final AxisType type,
100:                    final AxisDirection direction) {
101:                if (DIRECTIONS.put(type, direction) != null) {
102:                    throw new IllegalArgumentException(String.valueOf(type));
103:                }
104:            }
105:
106:            /**
107:             * Creates metadata from the specified file. This constructor is typically invoked
108:             * for creating {@linkplain NetcdfReader#getStreamMetadata stream metadata}. Note that
109:             * {@link ucar.nc2.dataset.CoordSysBuilder#addCoordinateSystems} should have been invoked
110:             * (if needed) before this constructor.
111:             */
112:            public NetcdfMetadata(final ImageReader reader,
113:                    final NetcdfDataset file) {
114:                super (reader);
115:                final List/*<CoordinateSystem>*/systems = file
116:                        .getCoordinateSystems();
117:                if (!systems.isEmpty()) {
118:                    addCoordinateSystem((CoordinateSystem) systems.get(0));
119:                }
120:            }
121:
122:            /**
123:             * Creates metadata from the specified file. This constructor is typically invoked
124:             * for creating {@linkplain NetcdfReader#getImageMetadata image metadata}. Note that
125:             * {@link ucar.nc2.dataset.CoordSysBuilder#addCoordinateSystems} should have been invoked
126:             * (if needed) before this constructor.
127:             */
128:            public NetcdfMetadata(final ImageReader reader,
129:                    final VariableDS variable) {
130:                super (reader);
131:                final List/*<CoordinateSystem>*/systems = variable
132:                        .getCoordinateSystems();
133:                if (!systems.isEmpty()) {
134:                    addCoordinateSystem((CoordinateSystem) systems.get(0));
135:                }
136:                setSampleType(GeographicMetadataFormat.PACKED);
137:                addSampleDimension(variable);
138:            }
139:
140:            /**
141:             * Adds the specified coordinate system. Current implementation can adds at most one
142:             * coordinate system, but this limitation may be revisited in a future Geotools version.
143:             *
144:             * @param cs The coordinate system to add.
145:             */
146:            public void addCoordinateSystem(final CoordinateSystem cs) {
147:                String crsType, csType;
148:                if (cs.isLatLon()) {
149:                    crsType = cs.hasVerticalAxis() ? GeographicMetadataFormat.GEOGRAPHIC_3D
150:                            : GeographicMetadataFormat.GEOGRAPHIC;
151:                    csType = GeographicMetadataFormat.ELLIPSOIDAL;
152:                } else if (cs.isGeoXY()) {
153:                    crsType = cs.hasVerticalAxis() ? GeographicMetadataFormat.PROJECTED_3D
154:                            : GeographicMetadataFormat.PROJECTED;
155:                    csType = GeographicMetadataFormat.CARTESIAN;
156:                } else {
157:                    crsType = null;
158:                    csType = null;
159:                }
160:                final ImageReferencing referencing = getReferencing();
161:                referencing.setCoordinateReferenceSystem(null, crsType);
162:                referencing.setCoordinateSystem(cs.getName(), csType);
163:                final ImageGeometry geometry = getGeometry();
164:                geometry.setPixelOrientation("center");
165:                /*
166:                 * Adds the axis in reverse order, because the NetCDF image reader put the last
167:                 * dimensions in the rendered image. Typical NetCDF convention is to put axis in
168:                 * the (time, depth, latitude, longitude) order, which typically maps to
169:                 * (longitude, latitude, depth, time) order in Geotools referencing framework.
170:                 */
171:                final List/*<CoordinateAxis>*/axis = cs.getCoordinateAxes();
172:                for (int i = axis.size(); --i >= 0;) {
173:                    addCoordinateAxis((CoordinateAxis) axis.get(i));
174:                }
175:            }
176:
177:            /**
178:             * Gets the name, as the "description", "title" or "standard name"
179:             * attribute if possible, or as the variable name otherwise.
180:             */
181:            private static String getName(final Variable variable) {
182:                String name = variable.getDescription();
183:                if (name == null || (name = name.trim()).length() == 0) {
184:                    name = variable.getName();
185:                }
186:                return name;
187:            }
188:
189:            /**
190:             * Adds the specified coordinate axis. This method is invoked recursively
191:             * by {@link #addCoordinateSystem}.
192:             *
193:             * @param axis The axis to add.
194:             */
195:            public void addCoordinateAxis(final CoordinateAxis axis) {
196:                final String name = getName(axis);
197:                final AxisType type = axis.getAxisType();
198:                String units = axis.getUnitsString();
199:                /*
200:                 * Gets the axis direction, taking in account the possible reversal or vertical axis.
201:                 * Note that geographic and projected CRS have the same directions. We can distinguish
202:                 * them either using the ISO CRS type ("geographic" or "projected"), the ISO CS type
203:                 * ("ellipsoidal" or "cartesian") or the units ("degrees" or "m").
204:                 */
205:                String direction = null;
206:                AxisDirection directionCode = (AxisDirection) DIRECTIONS
207:                        .get(type);
208:                if (directionCode != null) {
209:                    if (CoordinateAxis.POSITIVE_DOWN.equalsIgnoreCase(axis
210:                            .getPositive())) {
211:                        directionCode = directionCode.opposite();
212:                    }
213:                    direction = directionCode.name();
214:                    final int offset = units.lastIndexOf('_');
215:                    if (offset >= 0) {
216:                        final String unitsDirection = units.substring(
217:                                offset + 1).trim();
218:                        final String opposite = directionCode.opposite().name();
219:                        if (unitsDirection.equalsIgnoreCase(opposite)) {
220:                            warning("addCoordinateAxis",
221:                                    ErrorKeys.INCONSISTENT_AXIS_ORIENTATION_$2,
222:                                    new String[] { name, direction });
223:                            direction = opposite;
224:                        }
225:                        if (unitsDirection.equalsIgnoreCase(direction)) {
226:                            units = units.substring(0, offset).trim();
227:                        }
228:                    }
229:                }
230:                /*
231:                 * Gets the axis origin. In the particular case of time axis, units are typically
232:                 * written in the form "days since 1990-01-01 00:00:00". We extract the part before
233:                 * "since" as the units and the part after "since" as the date.
234:                 */
235:                final Axis axisNode = getReferencing().addAxis(name, direction,
236:                        units);
237:                if (AxisType.Time.equals(type)) {
238:                    String origin = null;
239:                    final String[] unitsParts = units
240:                            .split("(?i)\\s+since\\s+");
241:                    if (unitsParts.length == 2) {
242:                        units = unitsParts[0].trim();
243:                        origin = unitsParts[1].trim();
244:                    } else {
245:                        final Attribute attribute = axis
246:                                .findAttribute("time_origin");
247:                        if (attribute != null) {
248:                            origin = attribute.getStringValue();
249:                        }
250:                    }
251:                    Date epoch = null;
252:                    if (origin != null) {
253:                        origin = MetadataAccessor.trimFractionalPart(origin);
254:                        epoch = (Date) parse(type, origin, Date.class,
255:                                "addCoordinateAxis");
256:                    }
257:                    axisNode.setTimeOrigin(epoch);
258:                    axisNode.setUnits(units);
259:                }
260:                /*
261:                 * If the axis is not numeric, we can't process any further.
262:                 * If it is, then adds the coordinate and index ranges.
263:                 */
264:                if (!axis.isNumeric()) {
265:                    return;
266:                }
267:                if (axis instanceof  CoordinateAxis1D) {
268:                    final CoordinateAxis1D axis1D = (CoordinateAxis1D) axis;
269:                    final ImageGeometry geometry = getGeometry();
270:                    final int length = axis1D.getDimension(0).getLength();
271:                    if (length > 2 && axis1D.isRegular()) {
272:                        // Reminder: pixel orientation is "center", maximum value is inclusive.
273:                        final double increment = axis1D.getIncrement();
274:                        final double start = axis1D.getStart();
275:                        final double end = start + increment * (length - 1); // Inclusive
276:                        geometry.addCoordinateRange(0, length - 1, start, end);
277:                    } else {
278:                        final double[] values = axis1D.getCoordValues();
279:                        geometry.addCoordinateValues(0, values);
280:                    }
281:                }
282:            }
283:
284:            /**
285:             * Adds sample dimension information for the specified variable.
286:             *
287:             * @param variable The variable to add as a sample dimension.
288:             */
289:            public void addSampleDimension(final VariableDS variable) {
290:                final VariableMetadata m;
291:                if (USE_UCAR_LIB) {
292:                    m = new VariableMetadata(variable);
293:                } else {
294:                    m = new VariableMetadata(variable,
295:                            forcePacking("valid_range"));
296:                }
297:                m.copyTo(addBand(getName(variable)));
298:            }
299:
300:            /**
301:             * Parses the given string as a value along the specified axis.
302:             *
303:             * @param  type     The type of the axis.
304:             * @param  value    The value along that axis.
305:             * @param  expected The expected type.
306:             * @return The value after parsing.
307:             */
308:            private Object /*<T>*/parse(final AxisType type, String value,
309:                    final Class/*<T>*/expected, final String caller) {
310:                final LoggedFormat format = createLoggedFormat(getAxisFormat(
311:                        type, value), expected);
312:                format.setLogger("org.geotools.image.io.netcdf");
313:                format.setCaller(NetcdfMetadata.class, caller);
314:                return format.parse(value);
315:            }
316:
317:            /**
318:             * Returns a format to use for parsing values along the specified axis type. This method
319:             * is invoked when parsing the date part of axis units like "<cite>days since 1990-01-01
320:             * 00:00:00</cite>". Subclasses should override this method if the date part is formatted
321:             * in a different way. The default implementation returns the following formats:
322:             * <p>
323:             * <ul>
324:             *   <li>For {@linkplain AxisType#Time time axis}, a {@link DateFormat} using the
325:             *       {@code "yyyy-MM-dd HH:mm:ss"} pattern in UTC {@linkplain TimeZone timezone}.</li>
326:             *   <li>For all other kind of axis, a {@link NumberFormat}.</li>
327:             * </ul>
328:             * <p>
329:             * The {@linkplain Locale#CANADA Canada locale} is used by default for most formats because
330:             * it is relatively close to ISO (for example regarding days and months order in dates) while
331:             * using the English symbols.
332:             *
333:             * @param  type The type of the axis.
334:             * @param  prototype An example of the values to be parsed. Implementations may parse this
335:             *         prototype when the axis type alone is not suffisient. For example the {@linkplain
336:             *         AxisType#Time time axis type} should uses the {@code "yyyy-MM-dd"} date pattern,
337:             *         but some files do not follow this convention and use the default local instead.
338:             * @return The format for parsing values along the axis.
339:             */
340:            protected Format getAxisFormat(final AxisType type,
341:                    final String prototype) {
342:                if (!type.equals(AxisType.Time)) {
343:                    return NumberFormat.getNumberInstance(Locale.CANADA);
344:                }
345:                char dateSeparator = '-'; // The separator used in ISO format.
346:                boolean yearLast = false; // Year is first in ISO pattern.
347:                boolean namedMonth = false; // Months are numbers in the ISO pattern.
348:                if (prototype != null) {
349:                    /*
350:                     * Performs a quick check on the prototype content. If the prototype seems to use a
351:                     * different date separator than the ISO one, we will adjust the pattern accordingly.
352:                     * Also checks if the year seems to appears last rather than first, and if the month
353:                     * seems to be written using letters rather than digits.
354:                     */
355:                    int field = 1;
356:                    int digitCount = 0;
357:                    final int length = prototype.length();
358:                    for (int i = 0; i < length; i++) {
359:                        final char c = prototype.charAt(i);
360:                        if (Character.isWhitespace(c)) {
361:                            break; // Checks only the dates, ignore the hours.
362:                        }
363:                        if (Character.isDigit(c)) {
364:                            digitCount++;
365:                            continue; // Digits are legal in all cases.
366:                        }
367:                        if (field == 2 && Character.isLetter(c)) {
368:                            namedMonth = true;
369:                            continue; // Letters are legal for month only.
370:                        }
371:                        if (field == 1) {
372:                            dateSeparator = c;
373:                        }
374:                        digitCount = 0;
375:                        field++;
376:                    }
377:                    if (digitCount >= 4) {
378:                        yearLast = true;
379:                    }
380:                }
381:                String pattern;
382:                if (yearLast) {
383:                    pattern = namedMonth ? "dd-MMM-yyyy" : "dd-MM-yyyy";
384:                } else {
385:                    pattern = namedMonth ? "yyyy-MMM-dd" : "yyyy-MM-dd";
386:                }
387:                pattern = pattern.replace('-', dateSeparator);
388:                pattern += " HH:mm:ss";
389:                final DateFormat format = new SimpleDateFormat(pattern,
390:                        Locale.CANADA);
391:                format.setTimeZone(TimeZone.getTimeZone("UTC"));
392:                return format;
393:            }
394:
395:            /**
396:             * Returns {@code true} if an attribute (usually the <cite>valid range</cite>) should be
397:             * converted from unpacked to packed units. The <A HREF="http://www.cfconventions.org/">CF
398:             * Metadata conventions</A> states that valid ranges should be in packed units, but not
399:             * every NetCDF files follow this advice in practice. The UCAR NetCDF library applies the
400:             * following heuristic rules (quoting from {@link ucar.nc2.dataset.EnhanceScaleMissing}):
401:             *
402:             * <blockquote>
403:             * If {@code valid_range} is the same type as {@code scale_factor} (actually the wider of
404:             * {@code scale_factor} and {@code add_offset}) and this is wider than the external data,
405:             * then it will be interpreted as being in the units of the internal (unpacked) data.
406:             * Otherwise it is in the units of the external (packed) data.
407:             * <blockquote>
408:             *
409:             * However some NetCDF files stores unpacked ranges using the same type than packed data.
410:             * The above cited heuristic rule can not resolve those cases.
411:             * <p>
412:             * If this method returns {@code true}, then the attribute is assumed in unpacked units no
413:             * matter what the CF convention and the heuristic rules said. If this method returns
414:             * {@code false}, then UCAR's heuristic rules applies.
415:             * <p>
416:             * The default implementation returns {@code false} in all cases.
417:             *
418:             * @param  attribute The attribute (usually {@code "valid_range"}).
419:             * @return {@code true} if the attribute should be converted from unpacked to packed units
420:             *         regardless CF convention and UCAR's heuristic rules.
421:             *
422:             * @see ucar.nc2.dataset.EnhanceScaleMissing
423:             */
424:            protected boolean forcePacking(final String attribute) {
425:                return false;
426:            }
427:
428:            /**
429:             * Convenience method for logging a warning.
430:             */
431:            private void warning(final String method, final int key,
432:                    final Object value) {
433:                final LogRecord record = Errors.getResources(getLocale())
434:                        .getLogRecord(Level.WARNING, key, value);
435:                record.setSourceClassName(NetcdfMetadata.class.getName());
436:                record.setSourceMethodName(method);
437:                warningOccurred(record);
438:            }
439:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.