001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    Copyright © 2018-2024 Open Geospatial Consortium, Inc.
004 *    http://www.geoapi.org
005 *
006 *    Licensed under the Apache License, Version 2.0 (the "License");
007 *    you may not use this file except in compliance with the License.
008 *    You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *    Unless required by applicable law or agreed to in writing, software
013 *    distributed under the License is distributed on an "AS IS" BASIS,
014 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 *    See the License for the specific language governing permissions and
016 *    limitations under the License.
017 */
018package org.opengis.test.dataset;
019
020import java.io.File;
021import java.net.URL;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.FileOutputStream;
027
028import static org.junit.jupiter.api.Assertions.*;
029
030
031/**
032 * Provides access to small built-in test files.
033 * Data can be obtained as {@link URL}, {@link File}, {@link InputStream} or {@code byte[]} array.
034 *
035 * @author  Martin Desruisseaux (Geomatys)
036 * @version 3.1
037 * @since   3.1
038 */
039public enum TestData {
040    /**
041     * A two-dimensional netCDF file using a geographic CRS for global data over the world.
042     * The file contains temperature data from model analysis, without missing values.
043     *
044     * <ul>
045     *   <li><b>Data type:</b> 16 bits signed integers (only the positive range is used)</li>
046     *   <li><b>Coordinate Reference System:</b> two-dimensional geographic</li>
047     *   <li><b>Geographic area:</b> world, with latitudes ranging from 90°S to 90°N and longitudes from 180°E to 180°W</li>
048     *   <li><b>Size:</b> 73 × 73 cells in a file of 12.7 kilobytes</li>
049     * </ul>
050     * <table class="ogc">
051     *   <caption>Global attributes</caption>
052     *   <tr><th>Name</th><th>Value</th></tr>
053     *   <tr><td>{@code Conventions}</td><td>CF-1.4</td></tr>
054     *   <tr><td>{@code Metadata_Conventions}</td><td>Unidata Dataset Discovery v1.0</td></tr>
055     *   <tr><td>{@code title}</td><td>Test data from Sea Surface Temperature Analysis Model</td></tr>
056     *   <tr><td>{@code purpose}</td><td>GeoAPI conformance tests</td></tr>
057     *   <tr><td>{@code summary}</td><td>Global, two-dimensional model data</td></tr>
058     *   <tr><td>{@code keywords}</td><td>{@literal EARTH SCIENCE > Oceans > Ocean Temperature > Sea Surface Temperature}</td></tr>
059     *   <tr><td>{@code keywords_vocabulary}</td><td>GCMD Science Keywords</td></tr>
060     *   <tr><td>{@code geospatial_lat_min}</td><td>-90.0</td></tr>
061     *   <tr><td>{@code geospatial_lat_max}</td><td>90.0</td></tr>
062     *   <tr><td>{@code geospatial_lon_min}</td><td>-180.0</td></tr>
063     *   <tr><td>{@code geospatial_lon_max}</td><td>180.0</td></tr>
064     *   <tr><td>{@code geospatial_vertical_min}</td><td>0.0</td></tr>
065     *   <tr><td>{@code geospatial_vertical_max}</td><td>0.0</td></tr>
066     *   <tr><td>{@code time_coverage_start}</td><td>2005-09-22T00:00</td></tr>
067     *   <tr><td>{@code time_coverage_duration}</td><td>0.0</td></tr>
068     *   <tr><td>{@code cdm_data_type}</td><td>Grid</td></tr>
069     *   <tr><td>{@code id}</td><td>NCEP/SST/Global_5x2p5deg/SST_Global_5x2p5deg_20050922_0000.nc</td></tr>
070     *   <tr><td>{@code naming_authority}</td><td>edu.ucar.unidata</td></tr>
071     *   <tr><td>{@code creator_name}</td><td>NOAA/NWS/NCEP</td></tr>
072     *   <tr><td>{@code date_created}</td><td>2005-09-22T00:00</td></tr>
073     *   <tr><td>{@code date_modified}</td><td>2018-05-15T13:00</td></tr>
074     *   <tr><td>{@code date_metadata_modified}</td><td>2018-05-15T13:01</td></tr>
075     *   <tr><td>{@code history}</td><td>Decimated and modified by GeoAPI for inclusion in conformance test suite.</td></tr>
076     *   <tr><td>{@code comment}</td><td>For testing purpose only.</td></tr>
077     *   <tr><td>{@code license}</td><td>Freely available</td></tr>
078     * </table>
079     *
080     * In this file, all global attributes are character sequences, including the attributes that should
081     * be floating points numbers ({@code geospatial_lat_min}, {@code geospatial_lat_max}, <i>etc.</i>).
082     * Implementations are encouraged to be tolerant.
083     */
084    NETCDF_2D_GEOGRAPHIC("Cube2D_geographic_packed.nc", 12988),
085
086    /**
087     * A four-dimensional netCDF file using a projected CRS with elevation and time.
088     * The file contains <cite>Current Icing Product</cite> data without missing values.
089     * The coordinate reference system also contains an height axis and a time axis.
090     *
091     * <ul>
092     *   <li><b>Data type:</b> 32 bits floating point numbers</li>
093     *   <li><b>Coordinate Reference System:</b> four-dimensional projected + elevation + temporal</li>
094     *   <li><b>Geographic area:</b> East part of North America</li>
095     *   <li><b>Size:</b> 38 × 19 × 4 × 1 cells in a file of 14.2 kilobytes</li>
096     * </ul>
097     * <table class="ogc">
098     *   <caption>Global attributes</caption>
099     *   <tr><th>Name</th><th>Value</th></tr>
100     *   <tr><td>{@code Conventions}</td><td>CF-1.4</td></tr>
101     *   <tr><td>{@code title}</td><td>Test data from Current Icing Product (CIP)</td></tr>
102     *   <tr><td>{@code purpose}</td><td>GeoAPI conformance tests</td></tr>
103     *   <tr><td>{@code summary}</td><td>Hourly, three-dimensional diagnosis of the icing environment.</td></tr>
104     *   <tr><td>{@code source}</td><td>U.S. National Weather Service - NCEP (WMC)</td></tr>
105     *   <tr><td>{@code institution}</td><td>UCAR</td></tr>
106     *   <tr><td>{@code topic_category}</td><td>climatology meteorology atmosphere</td></tr>
107     *   <tr><td>{@code geospatial_lat_min}</td><td>15.94</td></tr>
108     *   <tr><td>{@code geospatial_lat_max}</td><td>58.37</td></tr>
109     *   <tr><td>{@code geospatial_lon_min}</td><td>-107.75</td></tr>
110     *   <tr><td>{@code geospatial_lon_max}</td><td>-56.66</td></tr>
111     *   <tr><td>{@code geospatial_vertical_min}</td><td>300.0</td></tr>
112     *   <tr><td>{@code geospatial_vertical_max}</td><td>4875.0</td></tr>
113     *   <tr><td>{@code geospatial_vertical_positive}</td><td>up</td></tr>
114     *   <tr><td>{@code geospatial_lat_resolution}</td><td>0.93</td></tr>
115     *   <tr><td>{@code geospatial_lon_resolution}</td><td>1.34</td></tr>
116     *   <tr><td>{@code creator_name}</td><td>John Doe</td></tr>
117     *   <tr><td>{@code creator_email}</td><td>john.doe@example.org</td></tr>
118     *   <tr><td>{@code date_modified}</td><td>2012-02-21T21:14Z</td></tr>
119     *   <tr><td>{@code date_metadata_modified}</td><td>2018-05-14T14:45Z</td></tr>
120     *   <tr><td>{@code history}</td><td>Decimated and modified by GeoAPI for inclusion in conformance test suite.</td></tr>
121     *   <tr><td>{@code comment}</td><td>For testing purpose only.</td></tr>
122     * </table>
123     *
124     * In this file, all global attributes for numeric values use the {@code float} type.
125     */
126    NETCDF_4D_PROJECTED("Cube4D_projected_float.nc", 14544),
127
128    /**
129     * Trajectory of 3 features with 6, 4 and 3 points respectively, followed by two intentionally empty features.
130     * This is a small extract of {@code "JaPOPPO.csv"} file encoded as specified in
131     * <a href="http://docs.opengeospatial.org/bp/16-114r3/16-114r3.html">OGC 16-114</a> best practice paper.
132     * The two trailing empty features are for testing implementation capability to ignore them.
133     * Coordinates are latitude, longitude and time as minutes elapsed since 2014-11-29 midnight UTC.
134     * Features contain one property named "stations" defined as an enumeration:
135     *
136     * <pre>short stations(points):
137     *    stations:flag_values = 11s, 12s, 13s, 14s, 15s, 16s, 21s, 22s, 23s, 24s, 31s, 32s
138     *    stations:flag_meanings = "Yokohama Kawasaki Shinagawa Shinbashi Yurakucho Tokyo "
139     *                             "Shinjuku Yotsuya Ochanomizu Akihabara Koenji Nakano"</pre>
140     *
141     * <table class="ogc">
142     *   <caption>Global attributes</caption>
143     *   <tr><th>Name</th><th>Value</th></tr>
144     *   <tr><td>{@code Conventions}</td><td>CF-1.6</td></tr>
145     *   <tr><td>{@code featureType}</td><td>trajectory</td></tr>
146     *   <tr><td>{@code title}</td><td>Small moving features file</td></tr>
147     *   <tr><td>{@code purpose}</td><td>GeoAPI conformance tests</td></tr>
148     *   <tr><td>{@code source}</td><td>Extracts from JaPOPPO.csv</td></tr>
149     *   <tr><td>{@code geospatial_lat_min}</td><td>30</td></tr>
150     *   <tr><td>{@code geospatial_lat_max}</td><td>40</td></tr>
151     *   <tr><td>{@code geospatial_lon_min}</td><td>130</td></tr>
152     *   <tr><td>{@code geospatial_lon_max}</td><td>150</td></tr>
153     *   <tr><td>{@code time_coverage_start}</td><td>2014-11-29T00:00:00Z</td></tr>
154     *   <tr><td>{@code time_coverage_end}</td><td>2014-12-05T23:59:59Z</td></tr>
155     *   <tr><td>{@code comment}</td><td>Intentionally contains two empty features for testing robustness.</td></tr>
156     * </table>
157     */
158    MOVING_FEATURES("MovingFeatures.nc", 1932);
159
160    /**
161     * Name of the test file, located in the same directory (after JAR packaging) than the {@code TestData.class} file.
162     */
163    private final String filename;
164
165    /**
166     * Expected length in bytes.
167     */
168    private final int length;
169
170    /**
171     * Path to the (possibly temporary) file, or {@code null} if not yet created.
172     */
173    private transient File file;
174
175    /**
176     * Creates a new enumeration value.
177     *
178     * @param filename  name of the test file.
179     * @param length    expected length in bytes.
180     */
181    private TestData(final String filename, final int length) {
182        this.filename = filename;
183        this.length = length;
184    }
185
186    /**
187     * Returns a URL to the test file.
188     * The URL is not necessary a file on the default file system; it may be an entry inside a JAR file.
189     * If a path on the file system is desired, use {@link #file()} instead.
190     *
191     * @return a URL to the test file, possibly as an entry inside a JAR file.
192     */
193    public URL location() {
194        final URL location = TestData.class.getResource(filename);
195        assertNotNull(location, filename);
196        return location;
197    }
198
199    /**
200     * Returns a path on the file system to the test file. If the test file is inside a JAR file,
201     * then it will be copied in a temporary directory and the path to the temporary file will be returned.
202     *
203     * @return a path on the default file system, possible as a temporary file.
204     * @throws IOException if a copy operation was necessary but failed.
205     */
206    public synchronized File file() throws IOException {
207        if (file == null) {
208            final URI location;
209            try {
210                location = location().toURI();
211            } catch (URISyntaxException e) {
212                throw new IOException(e);
213            } try {
214                file = new File(location);
215            } catch (IllegalArgumentException e) {
216                try {
217                    final byte[] data = content();
218                    final File tmp = File.createTempFile("geoapi", filename.substring(filename.lastIndexOf('.')));
219                    tmp.deleteOnExit();
220                    try (FileOutputStream out = new FileOutputStream(tmp)) {
221                        out.write(data);
222                    }
223                    file = tmp;                     // Set only on success.
224                } catch (IOException fe) {
225                    fe.addSuppressed(e);
226                    throw fe;
227                }
228            }
229        }
230        return file;
231    }
232
233    /**
234     * Opens an input stream on the test file.
235     * It is caller responsibility to close the stream after usage.
236     *
237     * @return an input stream on the test file.
238     */
239    public InputStream open() {
240        final InputStream stream = TestData.class.getResourceAsStream(filename);
241        assertNotNull(stream, filename);
242        return stream;
243    }
244
245    /**
246     * Returns the full content of the test file as an array of bytes.
247     *
248     * @return the test file content.
249     * @throws IOException if an error occurred while reading the test file.
250     */
251    public byte[] content() throws IOException {
252        /*
253         * We rely on the file having exactly the expected length, for avoiding array reallocations.
254         * This assumption is verified by the TestDataTest.
255         */
256        final byte[] content = new byte[length];
257        try (InputStream stream = open()) {
258            if (stream.readNBytes(content, 0, length) != length) {
259                throw new IOException("Unexpected file length.");
260            }
261        }
262        return content;
263    }
264}