001    /*
002     *    GeoAPI - Java interfaces for OGC/ISO standards
003     *    http://www.geoapi.org
004     *
005     *    This file is hereby placed into the Public Domain.
006     *    This means anyone is free to do whatever they wish with this file.
007     *
008     *    The NetCDF wrappers are provided as code examples, in the hope to facilitate
009     *    GeoAPI implementations backed by other libraries. Implementors can take this
010     *    source code and use it for any purpose, commercial or non-commercial, copyrighted
011     *    or open-source, with no legal obligation to acknowledge the borrowing/copying
012     *    in any way.
013     */
014    package org.opengis.wrapper.netcdf;
015    
016    import java.util.Map;
017    import java.util.List;
018    import java.util.Iterator;
019    import java.util.LinkedHashMap;
020    import java.io.IOException;
021    import ucar.nc2.NetcdfFile;
022    
023    import org.opengis.metadata.*;
024    import org.opengis.metadata.extent.*;
025    import org.opengis.metadata.spatial.*;
026    import org.opengis.metadata.content.*;
027    import org.opengis.metadata.quality.*;
028    import org.opengis.metadata.lineage.*;
029    import org.opengis.metadata.citation.*;
030    import org.opengis.metadata.constraint.*;
031    import org.opengis.metadata.identification.*;
032    import org.opengis.util.GenericName;
033    import org.opengis.test.metadata.RootValidator;
034    import org.opengis.test.Validators;
035    
036    import org.junit.Test;
037    
038    import static org.opengis.test.Assert.*;
039    
040    
041    /**
042     * Tests the {@link NetcdfMetadata} class.
043     * The main properties tested by this class are:
044     *
045     * <ul>
046     *   <li>The {@linkplain Metadata#getIdentificationInfo() identification} identifier, title, abstract and date.</li>
047     *   <li>The {@linkplain ResponsibleParty responsible party} name, role and email address.</li>
048     *   <li>The {@linkplain GeographicBoundingBox geographic bounding box}.</li>
049     * </ul>
050     *
051     * <b><u>Inheriting tests in external projects</u></b><br>
052     * In order to test their own implementation, external projects can override the
053     * {@link #wrap(NetcdfFile)} method as in the example below:
054     *
055     * <blockquote><pre>public class MyTest extends NetcdfMetadataTest {
056     *    &#64;Override
057     *    protected Metadata wrap(NetcdfFile file) throws IOException {
058     *        return new MyWrapper(file);
059     *    }
060     *}</pre></blockquote>
061     *
062     * In addition, implementors can alter the expected and actual attribute values before they
063     * are compared, and alter the way the comparison is performed. More specifically, every
064     * {@code testXXX()} method in this class proceeds in two steps:
065     *
066     * <ul>
067     *   <li>First, {@linkplain #fetchMetadataProperties(String) fill a map of all property values}
068     *       which are going to be tested.</li>
069     *   <li>Then, {@linkplain #compareProperties(String,double) compare the expected and actual values},
070     *       considering all properties as optional (i.e. no exception is thrown if the implementation
071     *       didn't mapped a NetCDF attribute to a metadata property).</li>
072     * </ul>
073     *
074     * Subclasses can override the {@link #fetchMetadataProperties(String)} and
075     * {@link #compareProperties(String,double)} methods in order to modify the
076     * default behavior, or for performing additional checks.
077     *
078     * @author  Martin Desruisseaux (Geomatys)
079     * @version 3.1
080     * @since   3.1
081     */
082    public strictfp class NetcdfMetadataTest extends IOTestCase {
083        /**
084         * The validator to use for validating the {@link Metadata} instance.
085         * This validator is specified at construction time.
086         */
087        protected final RootValidator validator;
088    
089        /**
090         * The metadata object being tested. This field is set to the value returned
091         * by {@link #wrap(NetcdfFile)} when a {@code testXXX()} method is executed.
092         */
093        protected Metadata metadata;
094    
095        /**
096         * The properties that are expected for the test being run.
097         * The content of this map is filled by the {@code testXXX()} method.
098         */
099        protected final Map<String,Object> expectedProperties;
100    
101        /**
102         * The actual properties found in the {@linkplain #metadata}.
103         * The content of this map is filled by the {@link #fetchMetadataProperties(String)} method.
104         */
105        protected final Map<String,Object> actualProperties;
106    
107        /**
108         * Creates a new test case using the default validator.
109         * This constructor sets the {@link RootValidator#requireMandatoryAttributes} field
110         * to {@code false}, since NetCDF metadata are sometime incomplete.
111         */
112        public NetcdfMetadataTest() {
113            this(new RootValidator(Validators.DEFAULT));
114            validator.requireMandatoryAttributes = false;
115        }
116    
117        /**
118         * Creates a new test case using the given validator. This constructor is provided for
119         * subclasses wanting to use different validation methods. It is caller responsibility
120         * to configure the given validator (for example whether to
121         * {@linkplain RootValidator#requireMandatoryAttributes require mandatory attributes} or not).
122         *
123         * @param validator The validator to use for validating the {@link Metadata} instance.
124         */
125        protected NetcdfMetadataTest(final RootValidator validator) {
126            this.validator = validator;
127            expectedProperties = new LinkedHashMap<String,Object>(32);
128            actualProperties   = new LinkedHashMap<String,Object>(32);
129        }
130    
131        /**
132         * Wraps the given NetCDF file into a GeoAPI Metadata object. The default implementation
133         * creates a {@link NetcdfMetadata} instance. Subclasses can override this method for
134         * creating their own instance.
135         *
136         * @param  file The NetCDF file to wrap.
137         * @return A metadata implementation created from the attributes found in the given file.
138         * @throws IOException If an error occurred while wrapping the given NetCDF file.
139         */
140        protected Metadata wrap(final NetcdfFile file) throws IOException {
141            return new NetcdfMetadata(file);
142        }
143    
144        /**
145         * Returns the single element from the given collection, or {@code null} if none.
146         * The default implementation makes the following choice:
147         *
148         * <ul>
149         *   <li>If the given collection is null or empty, returns {@code null}.</li>
150         *   <li>If the given collection contains exactly one element, returns that element.</li>
151         *   <li>Otherwise, throws {@link AssertionError}.</li>
152         * </ul>
153         *
154         * Subclasses can override this method if they want to select the element in a different way.
155         *
156         * @param  <E> The type of collection elements.
157         * @param  collection The collection from which to get the singleton.
158         * @return The singleton element from the collection, or {@code null} if none.
159         */
160        protected <E> E getSingleton(final Iterable<? extends E> collection) {
161            if (collection != null) {
162                final Iterator<? extends E> it = collection.iterator();
163                if (it.hasNext()) {
164                    final E element = it.next();
165                    assertFalse("The collection has more than one element.", it.hasNext());
166                    return element;
167                }
168            }
169            return null;
170        }
171    
172        /**
173         * Puts the given (key,value) pair in the map of actual properties, provided that the value is non-null.
174         */
175        private void put(final String key, Object value) {
176            if (value != null) {
177                if (value instanceof CharSequence || value instanceof GenericName) {
178                    value = value.toString();
179                } else if (value instanceof Double) {
180                    if (Double.isNaN((Double) value)) {
181                        return;
182                    }
183                }
184                assertNull(actualProperties.put(key, value));
185            }
186        }
187    
188        /**
189         * Fills the {@link #actualProperties} map with the property values found in the current {@link #metadata}.
190         * This method fetches only the metadata properties which are mapped to NetCDF attributes,
191         * and intentionally ignores all other metadata.
192         *
193         * @param filename The NetCDF file being tested, typically as one of the constants defined
194         *         in the {@link IOTestCase} class.
195         */
196        protected void fetchMetadataProperties(final String filename) {
197            put("metadataStandardName",    metadata.getMetadataStandardName());
198            put("metadataStandardVersion", metadata.getMetadataStandardVersion());
199            put("fileIdentifier",          metadata.getFileIdentifier());
200            /*
201             * Metadata / Contact.
202             */
203            ResponsibleParty party = getSingleton(metadata.getContacts());
204            if (party != null) {
205                put("contact.role",             party.getRole());
206                put("contact.individualName",   party.getIndividualName());
207                put("contact.organisationName", party.getOrganisationName());
208                final Contact contact = party.getContactInfo();
209                if (contact != null) {
210                    final Address address = contact.getAddress();
211                    if (address != null) {
212                        put("contact.contactInfo.address.electronicMailAddress", getSingleton(address.getElectronicMailAddresses()));
213                    }
214                }
215            }
216            final Identification identification = getSingleton(metadata.getIdentificationInfo());
217            if (identification != null) {
218                put("identificationInfo.abstract", identification.getAbstract());
219                /*
220                 * Metadata / Data Identification / Point of Contact.
221                 */
222                party = getSingleton(identification.getPointOfContacts());
223                if (party != null) {
224                    put("identificationInfo.pointOfContact.role",             party.getRole());
225                    put("identificationInfo.pointOfContact.individualName",   party.getIndividualName());
226                    put("identificationInfo.pointOfContact.organisationName", party.getOrganisationName());
227                    final Contact contact = party.getContactInfo();
228                    if (contact != null) {
229                        final Address address = contact.getAddress();
230                        if (address != null) {
231                            put("identificationInfo.pointOfContact.contactInfo.address.electronicMailAddress",
232                                    getSingleton(address.getElectronicMailAddresses()));
233                        }
234                    }
235                }
236                /*
237                 * Metadata / Data Identification / Citation.
238                 */
239                final Citation citation = identification.getCitation();
240                if (citation != null) {
241                    put("identificationInfo.citation.title", citation.getTitle());
242                    final Identifier identifier = getSingleton(citation.getIdentifiers());
243                    if (identifier != null) {
244                        put("identificationInfo.citation.identifier.code", identifier.getCode());
245                        final Citation authority = identifier.getAuthority();
246                        if (authority != null) {
247                            put("identificationInfo.citation.identifier.authority", authority.getTitle());
248                        }
249                    }
250                    final CitationDate date = getSingleton(citation.getDates());
251                    if (date != null) {
252                        put("identificationInfo.citation.date.date", date.getDate());
253                        put("identificationInfo.citation.date.dateType", date.getDateType());
254                    }
255                    /*
256                     * Metadata / Data Identification / Citation / Responsibly party.
257                     */
258                    party = getSingleton(citation.getCitedResponsibleParties());
259                    if (party != null) {
260                        put("identificationInfo.citation.citedResponsibleParty.role",             party.getRole());
261                        put("identificationInfo.citation.citedResponsibleParty.individualName",   party.getIndividualName());
262                        put("identificationInfo.citation.citedResponsibleParty.organisationName", party.getOrganisationName());
263                        final Contact contact = party.getContactInfo();
264                        if (contact != null) {
265                            final Address address = contact.getAddress();
266                            if (address != null) {
267                                put("identificationInfo.citation.citedResponsibleParty.contactInfo.address.electronicMailAddress",
268                                        getSingleton(address.getElectronicMailAddresses()));
269                            }
270                        }
271                    }
272                }
273                /*
274                 * Metadata / Data Identification / Descriptive Keywords.
275                 */
276                final Keywords keywords = getSingleton(identification.getDescriptiveKeywords());
277                if (keywords != null) {
278                    put("identificationInfo.descriptiveKeywords.type", keywords.getType());
279                    put("identificationInfo.descriptiveKeywords.thesaurusName", keywords.getThesaurusName().getTitle());
280                    put("identificationInfo.descriptiveKeywords.keyword", getSingleton(keywords.getKeywords()));
281                }
282                /*
283                 * Metadata / Data Identification / Resource Constraints / Use Limitation.
284                 */
285                final Constraints constraints = getSingleton(identification.getResourceConstraints());
286                if (constraints != null) {
287                    put("identificationInfo.resourceConstraints.useLimitation", getSingleton(constraints.getUseLimitations()));
288                }
289                /*
290                 * Metadata / Data Identification / Extents.
291                 */
292                if (identification instanceof DataIdentification) {
293                    final DataIdentification data = (DataIdentification) identification;
294                    put("identificationInfo.supplementalInformation", data.getSupplementalInformation());
295                    put("identificationInfo.spatialRepresentationType", getSingleton(data.getSpatialRepresentationTypes()));
296                    final Extent extent = getSingleton(data.getExtents());
297                    if (extent != null) {
298                        final GeographicExtent geoExtent = getSingleton(extent.getGeographicElements());
299                        if (geoExtent != null) {
300                            /*
301                             * Metadata / Data Identification / Extents / Geographic Bounding Box.
302                             */
303                            put("identificationInfo.extent.geographicElement.extentTypeCode", geoExtent.getInclusion());
304                            if (geoExtent instanceof GeographicBoundingBox) {
305                                final GeographicBoundingBox bbox = (GeographicBoundingBox) geoExtent;
306                                put("identificationInfo.extent.geographicElement.westBoundLongitude", bbox.getWestBoundLongitude());
307                                put("identificationInfo.extent.geographicElement.eastBoundLongitude", bbox.getEastBoundLongitude());
308                                put("identificationInfo.extent.geographicElement.southBoundLatitude", bbox.getSouthBoundLatitude());
309                                put("identificationInfo.extent.geographicElement.northBoundLatitude", bbox.getNorthBoundLatitude());
310                            }
311                            /*
312                             * Metadata / Data Identification / Extents / Vertical Extent.
313                             */
314                            final VerticalExtent vext = getSingleton(extent.getVerticalElements());
315                            if (vext != null) {
316                                put("identificationInfo.extent.verticalElement.minimumValue", vext.getMinimumValue());
317                                put("identificationInfo.extent.verticalElement.maximumValue", vext.getMaximumValue());
318                            }
319                        }
320                    }
321                }
322            }
323            /*
324             * Metadata / Quality / Lineage.
325             */
326            final DataQuality quality = getSingleton(metadata.getDataQualityInfo());
327            if (quality != null) {
328                final Lineage lineage = quality.getLineage();
329                if (lineage != null) {
330                    put("dataQualityInfo.lineage.statement", lineage.getStatement());
331                }
332            }
333            /*
334             * Metadata / Grid Spatial Representation.
335             */
336            final SpatialRepresentation spatial = getSingleton(metadata.getSpatialRepresentationInfo());
337            if (spatial instanceof GridSpatialRepresentation) {
338                final GridSpatialRepresentation gridSpatial = (GridSpatialRepresentation) spatial;
339                put("spatialRepresentationInfo.numberOfDimensions", gridSpatial.getNumberOfDimensions());
340                final List<? extends Dimension> axes = gridSpatial.getAxisDimensionProperties();
341                if (axes != null) { // Should never be null, but let be safe in case an implementation choose to do so.
342                    int dimension = 0;
343                    for (final Dimension axis : axes) {
344                        if (axis != null) {
345                            put("spatialRepresentationInfo.axisDimensionProperties[" + dimension + "].dimensionName", axis.getDimensionName());
346                            put("spatialRepresentationInfo.axisDimensionProperties[" + dimension + "].dimensionSize", axis.getDimensionSize());
347                            put("spatialRepresentationInfo.axisDimensionProperties[" + dimension + "].resolution",    axis.getResolution());
348                        }
349                        dimension++;
350                    }
351                }
352            }
353            /*
354             * Metadata / Content information / Range dimensions.
355             */
356            final ContentInformation content = getSingleton(metadata.getContentInfo());
357            if (content instanceof CoverageDescription) {
358                final RangeDimension band = getSingleton(((CoverageDescription) content).getDimensions());
359                if (band != null) {
360                    put("contentInfo.dimension.descriptor",         band.getDescriptor());
361                    put("contentInfo.dimension.sequenceIdentifier", band.getSequenceIdentifier());
362                }
363            }
364        }
365    
366        /**
367         * Compares the properties found in the {@linkplain #metadata} with the expected properties.
368         * The default implementation removes all properties found from the properties maps.
369         * Consequently, after this method call the maps contain the following entries:
370         *
371         * <ul>
372         *   <li>The {@link #expectedProperties} map contains all properties not found in the NetCDF file.</li>
373         *   <li>The {@link #actualProperties} map contains all properties from the NetCDF file which have not
374         *       been verified.</li>
375         * </ul>
376         *
377         * Subclasses can override this method if they want to modify the values before the comparison
378         * is performed, or for ensuring that their mandatory values have been removed from the maps
379         * (i.e. have been compared) after the comparison.
380         *
381         *
382         * @param filename The NetCDF file being tested, typically as one of the constants defined
383         *                  in the {@link IOTestCase} class.
384         * @param eps Tolerance factor for comparing floating-point numbers.
385         */
386        protected void compareProperties(final String filename, final double eps) {
387            for (final Iterator<Map.Entry<String,Object>> it=actualProperties.entrySet().iterator(); it.hasNext();) {
388                final Map.Entry<String,Object> entry = it.next();
389                final String key = entry.getKey();
390                final Object expectedValue = expectedProperties.remove(key);
391                if (expectedValue != null) {
392                    final Object actualValue = entry.getValue();
393                    assertEquals(key, expectedValue.getClass(), actualValue.getClass());
394                    if (expectedValue instanceof Double) {
395                        assertEquals(key, (Double) expectedValue, (Double) actualValue, eps);
396                    } else {
397                        assertEquals(key, expectedValue, actualValue);
398                    }
399                    it.remove();
400                }
401            }
402        }
403    
404        /*
405         * Note: this test case shall not verify the hard-coded constants (metadataStandardName,
406         * metadataStandardVersion, hierarchyLevel) since they are obviously implementation-dependent.
407         */
408    
409        /**
410         * Tests the {@value org.opengis.wrapper.netcdf.IOTestCase#THREDDS} file (XML format).
411         * The current implementation tests:
412         *
413         * <ul>
414         *   <li>The {@linkplain Metadata#getFileIdentifier() file identifier}.</li>
415         *   <li>The {@linkplain ResponsibleParty responsible party} name, role and email address.</li>
416         *   <li>The {@linkplain GeographicBoundingBox geographic bounding box}.</li>
417         *   <li>The {@linkplain Dimension axis dimensions} names, sizes and resolution.</li>
418         *   <li>The {@linkplain Lineage lineage} statement.</li>
419         * </ul>
420         *
421         * Note that implementations don't need to support all those metadata.
422         * See the class javadoc for more information.
423         *
424         * @throws IOException If the test file can not be read.
425         */
426        @Test
427        public void testTHREDDS() throws IOException {
428            final Map<String,Object> expected = expectedProperties;
429            assertNull(expected.put("fileIdentifier",                                                   "crm_v1"));
430            assertNull(expected.put("identificationInfo.citation.citedResponsibleParty.role",           Role.ORIGINATOR));
431            assertNull(expected.put("identificationInfo.citation.citedResponsibleParty.individualName", "David Neufeld"));
432            assertNull(expected.put("identificationInfo.pointOfContact.individualName",                 "David Neufeld"));
433            assertNull(expected.put("contact.individualName",                                           "David Neufeld"));
434            assertNull(expected.put("contact.contactInfo.address.electronicMailAddress",                                           "xxxxx.xxxxxxx@noaa.gov"));
435            assertNull(expected.put("identificationInfo.citation.citedResponsibleParty.contactInfo.address.electronicMailAddress", "xxxxx.xxxxxxx@noaa.gov"));
436            assertNull(expected.put("identificationInfo.pointOfContact.contactInfo.address.electronicMailAddress",                 "xxxxx.xxxxxxx@noaa.gov"));
437            assertNull(expected.put("identificationInfo.extent.geographicElement.extentTypeCode",         Boolean.TRUE));
438            assertNull(expected.put("identificationInfo.extent.geographicElement.westBoundLongitude",    -80.0));
439            assertNull(expected.put("identificationInfo.extent.geographicElement.eastBoundLongitude",    -64.0));
440            assertNull(expected.put("identificationInfo.extent.geographicElement.southBoundLatitude",     40.0));
441            assertNull(expected.put("identificationInfo.extent.geographicElement.northBoundLatitude",     48.0));
442            assertNull(expected.put("spatialRepresentationInfo.numberOfDimensions",                       2));
443            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[0].dimensionName", DimensionNameType.COLUMN));
444            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[1].dimensionName", DimensionNameType.ROW));
445            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[0].dimensionSize", 19201));
446            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[1].dimensionSize",  9601));
447            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[0].resolution", 8.332899328159992E-4));
448            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[1].resolution", 8.332465368190813E-4));
449            assertNull(expected.put("dataQualityInfo.lineage.statement", "xyz2grd -R-80/-64/40/48 -I3c -Gcrm_v1.grd"));
450            final NetcdfFile file = open(THREDDS);
451            try {
452                metadata = wrap(file);
453                validator.validate(metadata);
454                fetchMetadataProperties(THREDDS);
455                compareProperties(THREDDS, 1E-12);
456                if (getClass() == NetcdfMetadataTest.class) {
457                    NonInheritable.assertProcessedAllRelevant(actualProperties, "crm_v1.grd", false, false);
458                }
459            } finally {
460                file.close();
461            }
462        }
463    
464        /**
465         * Tests the {@value org.opengis.wrapper.netcdf.IOTestCase#NCEP} file (binary format).
466         * The current implementation tests:
467         *
468         * <ul>
469         *   <li>The {@linkplain Metadata#getIdentificationInfo() identification} identifier, title, abstract and date.</li>
470         *   <li>The {@linkplain Citation citation} title, date and abstract.</li>
471         *   <li>The {@linkplain Keywords keywords} type and thesaurus.</li>
472         *   <li>The {@linkplain ResponsibleParty responsible party} name and role.</li>
473         *   <li>The {@linkplain GeographicBoundingBox geographic bounding box}.</li>
474         *   <li>The {@linkplain VerticalExtent vertical extent}.</li>
475         *   <li>The {@linkplain Constraints constraints} use limitation.</li>
476         *   <li>The {@linkplain Lineage lineage} statement.</li>
477         *   <li>The {@linkplain RangeDimension range dimensions}.</li>
478         * </ul>
479         *
480         * Note that implementations don't need to support all those metadata.
481         * See the class javadoc for more information.
482         *
483         * @throws IOException If the test file can not be read.
484         */
485        @Test
486        public void testNCEP() throws IOException {
487            final Map<String,Object> expected = expectedProperties;
488            assertNull(expected.put("fileIdentifier",                                  "edu.ucar.unidata:NCEP/SST/Global_5x2p5deg/SST_Global_5x2p5deg_20050922_0000.nc"));
489            assertNull(expected.put("identificationInfo.citation.identifier.code",                      "NCEP/SST/Global_5x2p5deg/SST_Global_5x2p5deg_20050922_0000.nc"));
490            assertNull(expected.put("identificationInfo.citation.identifier.authority",                 "edu.ucar.unidata"));
491            assertNull(expected.put("identificationInfo.citation.citedResponsibleParty.role",           Role.ORIGINATOR));
492            assertNull(expected.put("identificationInfo.citation.citedResponsibleParty.individualName", "NOAA/NWS/NCEP"));
493            assertNull(expected.put("identificationInfo.pointOfContact.individualName",                 "NOAA/NWS/NCEP"));
494            assertNull(expected.put("contact.individualName",                                           "NOAA/NWS/NCEP"));
495            assertNull(expected.put("identificationInfo.citation.date.date",                            NetcdfMetadata.parseDate("2005-09-22T00:00")));
496            assertNull(expected.put("identificationInfo.citation.date.dateType",                        DateType.CREATION));
497            assertNull(expected.put("identificationInfo.citation.title",                                "Sea Surface Temperature Analysis Model"));
498            assertNull(expected.put("identificationInfo.abstract",                                      "NCEP SST Global 5.0 x 2.5 degree model data"));
499            assertNull(expected.put("identificationInfo.descriptiveKeywords.type",                      KeywordType.THEME));
500            assertNull(expected.put("identificationInfo.descriptiveKeywords.thesaurusName",             "GCMD Science Keywords"));
501            assertNull(expected.put("identificationInfo.descriptiveKeywords.keyword",                   "EARTH SCIENCE > Oceans > Ocean Temperature > Sea Surface Temperature"));
502            assertNull(expected.put("identificationInfo.resourceConstraints.useLimitation",             "Freely available"));
503            assertNull(expected.put("identificationInfo.extent.geographicElement.extentTypeCode",       Boolean.TRUE));
504            assertNull(expected.put("identificationInfo.extent.geographicElement.westBoundLongitude",   -180.0));
505            assertNull(expected.put("identificationInfo.extent.geographicElement.eastBoundLongitude",   +180.0));
506            assertNull(expected.put("identificationInfo.extent.geographicElement.southBoundLatitude",    -90.0));
507            assertNull(expected.put("identificationInfo.extent.geographicElement.northBoundLatitude",    +90.0));
508            assertNull(expected.put("identificationInfo.extent.verticalElement.minimumValue",              0.0));
509            assertNull(expected.put("identificationInfo.extent.verticalElement.maximumValue",              0.0));
510            assertNull(expected.put("identificationInfo.spatialRepresentationType",                       SpatialRepresentationType.GRID));
511            assertNull(expected.put("spatialRepresentationInfo.numberOfDimensions",                        3));
512            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[0].dimensionSize", 73));
513            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[1].dimensionSize", 73));
514            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[2].dimensionSize",  1));
515            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[0].dimensionName", DimensionNameType.COLUMN));
516            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[1].dimensionName", DimensionNameType.ROW));
517            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[2].dimensionName", DimensionNameType.TIME));
518            assertNull(expected.put("contentInfo.dimension.sequenceIdentifier",                           "SST"));
519            assertNull(expected.put("contentInfo.dimension.descriptor",                                   "Sea temperature"));
520            assertNull(expected.put("dataQualityInfo.lineage.statement",
521                    "2003-04-07 12:12:50 - created by gribtocdl              " +
522                    "2005-09-26T21:50:00 - edavis - add attributes for dataset discovery"));
523            final NetcdfFile file = open(NCEP);
524            try {
525                metadata = wrap(file);
526                validator.validate(metadata);
527                fetchMetadataProperties(NCEP);
528                compareProperties(NCEP, 0.0);
529                if (getClass() == NetcdfMetadataTest.class) {
530                    NonInheritable.assertProcessedAllRelevant(actualProperties, null, false, false);
531                }
532            } finally {
533                file.close();
534            }
535        }
536    
537        /**
538         * Tests the {@value org.opengis.wrapper.netcdf.IOTestCase#LANDSAT} file (binary format).
539         *
540         * @throws IOException If the test file can not be read.
541         */
542        @Test
543        public void testLandsat() throws IOException {
544            final Map<String,Object> expected = expectedProperties;
545            assertNull(expected.put("contentInfo.dimension.sequenceIdentifier", "Band1"));
546            assertNull(expected.put("contentInfo.dimension.descriptor",         "GDAL Band Number 1"));
547            final NetcdfFile file = open(LANDSAT);
548            try {
549                metadata = wrap(file);
550                // Do not validate, because the metadata is known to be invalid
551                // since the test file is not providing suffisient information.
552                fetchMetadataProperties(LANDSAT);
553                compareProperties(LANDSAT, 0.0);
554                if (getClass() == NetcdfMetadataTest.class) {
555                    NonInheritable.assertProcessedAllRelevant(actualProperties, null, true, true);
556                }
557            } finally {
558                file.close();
559            }
560        }
561    
562        /**
563         * Tests the {@value org.opengis.wrapper.netcdf.IOTestCase#CIP} file (binary format).
564         *
565         * @throws IOException If the test file can not be read.
566         */
567        @Test
568        public void testCIP() throws IOException {
569            final Map<String,Object> expected = expectedProperties;
570            assertNull(expected.put("identificationInfo.citation.citedResponsibleParty.role",              Role.ORIGINATOR));
571            assertNull(expected.put("identificationInfo.citation.citedResponsibleParty.organisationName", "UCAR"));
572            assertNull(expected.put("identificationInfo.pointOfContact.organisationName",                 "UCAR"));
573            assertNull(expected.put("contact.organisationName",                                           "UCAR"));
574            assertNull(expected.put("identificationInfo.supplementalInformation",                         "Created by Mdv2NetCDF"));
575            assertNull(expected.put("identificationInfo.extent.geographicElement.extentTypeCode",         Boolean.TRUE));
576            assertNull(expected.put("identificationInfo.extent.geographicElement.westBoundLongitude",     -140.290));
577            assertNull(expected.put("identificationInfo.extent.geographicElement.eastBoundLongitude",      -56.658));
578            assertNull(expected.put("identificationInfo.extent.geographicElement.southBoundLatitude",       15.944));
579            assertNull(expected.put("identificationInfo.extent.geographicElement.northBoundLatitude",       58.364));
580            assertNull(expected.put("identificationInfo.extent.verticalElement.minimumValue",            -15.0));
581            assertNull(expected.put("identificationInfo.extent.verticalElement.maximumValue",             35.0));
582            assertNull(expected.put("spatialRepresentationInfo.numberOfDimensions",                       4));
583            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[0].dimensionName", DimensionNameType.COLUMN));
584            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[1].dimensionName", DimensionNameType.ROW));
585            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[2].dimensionName", DimensionNameType.VERTICAL));
586            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[3].dimensionName", DimensionNameType.TIME));
587            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[0].dimensionSize", 61));
588            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[1].dimensionSize", 45));
589            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[2].dimensionSize",  6));
590            assertNull(expected.put("spatialRepresentationInfo.axisDimensionProperties[3].dimensionSize",  1));
591            assertNull(expected.put("contentInfo.dimension.sequenceIdentifier",                           "CIP"));
592            assertNull(expected.put("contentInfo.dimension.descriptor",                                   "Current Icing Product"));
593            assertNull(expected.put("dataQualityInfo.lineage.statement", "U.S. National Weather Service - NCEP (WMC)"));
594            final NetcdfFile file = open(CIP);
595            try {
596                metadata = wrap(file);
597                validator.validate(metadata);
598                fetchMetadataProperties(CIP);
599                compareProperties(CIP, 0.001);
600                if (getClass() == NetcdfMetadataTest.class) {
601                    NonInheritable.assertProcessedAllRelevant(actualProperties, null, true, false);
602                }
603            } finally {
604                file.close();
605            }
606        }
607    }