001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    http://www.geoapi.org
004 *
005 *    Copyright (C) 2018-2019 Open Geospatial Consortium, Inc.
006 *    All Rights Reserved. http://www.opengeospatial.org/ogc/legal
007 *
008 *    Permission to use, copy, and modify this software and its documentation, with
009 *    or without modification, for any purpose and without fee or royalty is hereby
010 *    granted, provided that you include the following on ALL copies of the software
011 *    and documentation or portions thereof, including modifications, that you make:
012 *
013 *    1. The full text of this NOTICE in a location viewable to users of the
014 *       redistributed or derivative work.
015 *    2. Notice of any changes or modifications to the OGC files, including the
016 *       date changes were made.
017 *
018 *    THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE
019 *    NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
020 *    TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT
021 *    THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY
022 *    PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
023 *
024 *    COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
025 *    CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
026 *
027 *    The name and trademarks of copyright holders may NOT be used in advertising or
028 *    publicity pertaining to the software without specific, written prior permission.
029 *    Title to copyright in this software and any associated documentation will at all
030 *    times remain with copyright holders.
031 */
032package org.opengis.test.dataset;
033
034import java.io.File;
035import java.net.URL;
036import java.net.URI;
037import java.net.URISyntaxException;
038import java.io.EOFException;
039import java.io.IOException;
040import java.io.InputStream;
041import java.io.FileOutputStream;
042
043import static org.junit.Assert.*;
044
045
046/**
047 * Provides access to small built-in test files.
048 * Data can be obtained as {@link URL}, {@link File}, {@link InputStream} or {@code byte[]} array.
049 *
050 * @author  Martin Desruisseaux (Geomatys)
051 * @version 3.1
052 * @since   3.1
053 */
054public enum TestData {
055    /**
056     * A two-dimensional netCDF file using a geographic CRS for global data over the world.
057     * The file contains temperature data from model analysis, without missing values.
058     *
059     * <ul>
060     *   <li><b>Data type:</b> 16 bits signed integers (only the positive range is used)</li>
061     *   <li><b>Coordinate Reference System:</b> two-dimensional geographic</li>
062     *   <li><b>Geographic area:</b> world, with latitudes ranging from 90°S to 90°N and longitudes from 180°E to 180°W</li>
063     *   <li><b>Size:</b> 73 × 73 cells in a file of 12.7 kilobytes</li>
064     * </ul>
065     * <table class="ogc">
066     *   <caption>Global attributes</caption>
067     *   <tr><th>Name</th><th>Value</th></tr>
068     *   <tr><td>{@code Conventions}</td><td>CF-1.4</td></tr>
069     *   <tr><td>{@code Metadata_Conventions}</td><td>Unidata Dataset Discovery v1.0</td></tr>
070     *   <tr><td>{@code title}</td><td>Test data from Sea Surface Temperature Analysis Model</td></tr>
071     *   <tr><td>{@code purpose}</td><td>GeoAPI conformance tests</td></tr>
072     *   <tr><td>{@code summary}</td><td>Global, two-dimensional model data</td></tr>
073     *   <tr><td>{@code keywords}</td><td>{@literal EARTH SCIENCE > Oceans > Ocean Temperature > Sea Surface Temperature}</td></tr>
074     *   <tr><td>{@code keywords_vocabulary}</td><td>GCMD Science Keywords</td></tr>
075     *   <tr><td>{@code geospatial_lat_min}</td><td>-90.0</td></tr>
076     *   <tr><td>{@code geospatial_lat_max}</td><td>90.0</td></tr>
077     *   <tr><td>{@code geospatial_lon_min}</td><td>-180.0</td></tr>
078     *   <tr><td>{@code geospatial_lon_max}</td><td>180.0</td></tr>
079     *   <tr><td>{@code geospatial_vertical_min}</td><td>0.0</td></tr>
080     *   <tr><td>{@code geospatial_vertical_max}</td><td>0.0</td></tr>
081     *   <tr><td>{@code time_coverage_start}</td><td>2005-09-22T00:00</td></tr>
082     *   <tr><td>{@code time_coverage_duration}</td><td>0.0</td></tr>
083     *   <tr><td>{@code cdm_data_type}</td><td>Grid</td></tr>
084     *   <tr><td>{@code id}</td><td>NCEP/SST/Global_5x2p5deg/SST_Global_5x2p5deg_20050922_0000.nc</td></tr>
085     *   <tr><td>{@code naming_authority}</td><td>edu.ucar.unidata</td></tr>
086     *   <tr><td>{@code creator_name}</td><td>NOAA/NWS/NCEP</td></tr>
087     *   <tr><td>{@code date_created}</td><td>2005-09-22T00:00</td></tr>
088     *   <tr><td>{@code date_modified}</td><td>2018-05-15T13:00</td></tr>
089     *   <tr><td>{@code date_metadata_modified}</td><td>2018-05-15T13:01</td></tr>
090     *   <tr><td>{@code history}</td><td>Decimated and modified by GeoAPI for inclusion in conformance test suite.</td></tr>
091     *   <tr><td>{@code comment}</td><td>For testing purpose only.</td></tr>
092     *   <tr><td>{@code license}</td><td>Freely available</td></tr>
093     * </table>
094     *
095     * In this file, all global attributes are character sequences, including the attributes that should
096     * be floating points numbers ({@code geospatial_lat_min}, {@code geospatial_lat_max}, <i>etc.</i>).
097     * Implementations are encouraged to be tolerant.
098     */
099    NETCDF_2D_GEOGRAPHIC("Cube2D_geographic_packed.nc", 12988),
100
101    /**
102     * A four-dimensional netCDF file using a projected CRS with elevation and time.
103     * The file contains <cite>Current Icing Product</cite> data without missing values.
104     * The coordinate reference system contains also an height axis and a time axis.
105     *
106     * <ul>
107     *   <li><b>Data type:</b> 32 bits floating point numbers</li>
108     *   <li><b>Coordinate Reference System:</b> four-dimensional projected + elevation + temporal</li>
109     *   <li><b>Geographic area:</b> East part of North America</li>
110     *   <li><b>Size:</b> 38 × 19 × 4 × 1 cells in a file of 14.2 kilobytes</li>
111     * </ul>
112     * <table class="ogc">
113     *   <caption>Global attributes</caption>
114     *   <tr><th>Name</th><th>Value</th></tr>
115     *   <tr><td>{@code Conventions}</td><td>CF-1.4</td></tr>
116     *   <tr><td>{@code title}</td><td>Test data from Current Icing Product (CIP)</td></tr>
117     *   <tr><td>{@code purpose}</td><td>GeoAPI conformance tests</td></tr>
118     *   <tr><td>{@code summary}</td><td>Hourly, three-dimensional diagnosis of the icing environment.</td></tr>
119     *   <tr><td>{@code source}</td><td>U.S. National Weather Service - NCEP (WMC)</td></tr>
120     *   <tr><td>{@code institution}</td><td>UCAR</td></tr>
121     *   <tr><td>{@code topic_category}</td><td>climatology meteorology atmosphere</td></tr>
122     *   <tr><td>{@code geospatial_lat_min}</td><td>15.94</td></tr>
123     *   <tr><td>{@code geospatial_lat_max}</td><td>58.37</td></tr>
124     *   <tr><td>{@code geospatial_lon_min}</td><td>-107.75</td></tr>
125     *   <tr><td>{@code geospatial_lon_max}</td><td>-56.66</td></tr>
126     *   <tr><td>{@code geospatial_vertical_min}</td><td>300.0</td></tr>
127     *   <tr><td>{@code geospatial_vertical_max}</td><td>4875.0</td></tr>
128     *   <tr><td>{@code geospatial_vertical_positive}</td><td>up</td></tr>
129     *   <tr><td>{@code geospatial_lat_resolution}</td><td>0.93</td></tr>
130     *   <tr><td>{@code geospatial_lon_resolution}</td><td>1.34</td></tr>
131     *   <tr><td>{@code creator_name}</td><td>John Doe</td></tr>
132     *   <tr><td>{@code creator_email}</td><td>john.doe@example.org</td></tr>
133     *   <tr><td>{@code date_modified}</td><td>2012-02-21T21:14Z</td></tr>
134     *   <tr><td>{@code date_metadata_modified}</td><td>2018-05-14T14:45Z</td></tr>
135     *   <tr><td>{@code history}</td><td>Decimated and modified by GeoAPI for inclusion in conformance test suite.</td></tr>
136     *   <tr><td>{@code comment}</td><td>For testing purpose only.</td></tr>
137     * </table>
138     *
139     * If this file, all global attribute for numeric values use the {@code float} type.
140     */
141    NETCDF_4D_PROJECTED("Cube4D_projected_float.nc", 14544);
142
143    /**
144     * Name of the test file, located in the same directory (after JAR packaging) than the {@code TestData.class} file.
145     */
146    private final String filename;
147
148    /**
149     * Expected length in bytes.
150     */
151    private final int length;
152
153    /**
154     * Path to the (possibly temporary) file, or {@code null} if not yet created.
155     */
156    private transient File file;
157
158    /**
159     * Creates a new enumeration value.
160     */
161    private TestData(final String filename, final int length) {
162        this.filename = filename;
163        this.length = length;
164    }
165
166    /**
167     * Returns a URL to the test file.
168     * The URL is not necessary a file on the default file system; it may be an entry inside a JAR file.
169     * If a path on the file system is desired, use {@link #file()} instead.
170     *
171     * @return a URL to the test file, possibly as an entry inside a JAR file.
172     */
173    public URL location() {
174        final URL location = TestData.class.getResource(filename);
175        assertNotNull(filename, location);
176        return location;
177    }
178
179    /**
180     * Returns a path on the file system to the test file. If the test file is inside a JAR file,
181     * then it will be copied in a temporary directory and the path to the temporary file will be returned.
182     *
183     * @return a path on the default file system, possible as a temporary file.
184     * @throws IOException if a copy operation was necessary but failed.
185     */
186    public synchronized File file() throws IOException {
187        if (file == null) {
188            final URI location;
189            try {
190                location = location().toURI();
191            } catch (URISyntaxException e) {
192                throw new IOException(e);
193            } try {
194                file = new File(location);
195            } catch (IllegalArgumentException e) {
196                try {
197                    final byte[] data = content();
198                    final File tmp = File.createTempFile("geoapi", filename.substring(filename.lastIndexOf('.')));
199                    tmp.deleteOnExit();
200                    try (FileOutputStream out = new FileOutputStream(tmp)) {
201                        out.write(data);
202                    }
203                    file = tmp;                     // Set only on success.
204                } catch (IOException fe) {
205                    fe.addSuppressed(e);
206                    throw fe;
207                }
208            }
209        }
210        return file;
211    }
212
213    /**
214     * Opens an input stream on the test file.
215     * It is caller responsibility to close the stream after usage.
216     *
217     * @return an input stream on the test file.
218     */
219    public InputStream open() {
220        final InputStream stream = TestData.class.getResourceAsStream(filename);
221        assertNotNull(filename, stream);
222        return stream;
223    }
224
225    /**
226     * Returns the full content of the test file as an array of bytes.
227     *
228     * @return the test file content.
229     * @throws IOException if an error occurred while reading the test file.
230     */
231    public byte[] content() throws IOException {
232        /*
233         * We rely on the file having exactly the expected length, for avoiding array reallocations.
234         * This assumption is verified by the TestDataTest.
235         */
236        final byte[] content = new byte[length];
237        try (InputStream stream = open()) {
238            int offset = 0, r, n;
239            do {                        // TODO: replace this loop by stream.readNBytes(content, 0, length) in JDK9.
240                r = length - offset;
241                n = stream.read(content, offset, r);
242                if (n < 0) throw new EOFException(filename);
243            } while (r != n);
244            if (stream.read() >= 0) {
245                throw new IOException("Unexpected file length.");
246            }
247        }
248        return content;
249    }
250}