| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.storage.netcdf; |
| |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.net.URI; |
| import java.util.List; |
| import java.util.Collection; |
| import java.util.Optional; |
| import org.opengis.util.NameSpace; |
| import org.opengis.util.NameFactory; |
| import org.opengis.util.GenericName; |
| import org.opengis.metadata.Metadata; |
| import org.opengis.parameter.ParameterValueGroup; |
| import org.apache.sis.storage.DataStore; |
| import org.apache.sis.storage.DataStoreException; |
| import org.apache.sis.storage.UnsupportedStorageException; |
| import org.apache.sis.storage.StorageConnector; |
| import org.apache.sis.storage.Aggregate; |
| import org.apache.sis.internal.netcdf.Decoder; |
| import org.apache.sis.internal.netcdf.RasterResource; |
| import org.apache.sis.internal.storage.URIDataStore; |
| import org.apache.sis.internal.util.UnmodifiableArrayList; |
| import org.apache.sis.internal.util.Strings; |
| import org.apache.sis.setup.OptionKey; |
| import org.apache.sis.storage.Resource; |
| import org.apache.sis.storage.event.StoreEvent; |
| import org.apache.sis.storage.event.StoreListener; |
| import org.apache.sis.storage.event.WarningEvent; |
| import org.apache.sis.util.CharSequences; |
| import org.apache.sis.util.Version; |
| import ucar.nc2.constants.ACDD; |
| import ucar.nc2.constants.CDM; |
| |
| |
| /** |
| * A data store backed by netCDF files. |
| * Instances of this data store are created by {@link NetcdfStoreProvider#open(StorageConnector)}. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.0 |
| * |
| * @see NetcdfStoreProvider |
| * |
| * @since 0.3 |
| * @module |
| */ |
| public class NetcdfStore extends DataStore implements Aggregate { |
| /** |
| * The object to use for decoding the netCDF file content. There is two different implementations, |
| * depending on whether we are using the embedded SIS decoder or a wrapper around the UCAR library. |
| */ |
| private final Decoder decoder; |
| |
| /** |
| * The {@link NetcdfStoreProvider#LOCATION} parameter value, or {@code null} if none. |
| * This is used for information purpose only, not for actual reading operations. |
| * |
| * @see #getOpenParameters() |
| */ |
| private final URI location; |
| |
| /** |
| * The object returned by {@link #getMetadata()}, created when first needed and cached. |
| */ |
| private Metadata metadata; |
| |
| /** |
| * The data (raster or features) found in the netCDF file. This list is created when first needed. |
| * |
| * @see #components() |
| */ |
| private List<Resource> components; |
| |
| /** |
| * Creates a new netCDF store from the given file, URL, stream or {@link ucar.nc2.NetcdfFile} object. |
| * This constructor invokes {@link StorageConnector#closeAllExcept(Object)}, keeping open only the |
| * needed resource. |
| * |
| * @param provider the factory that created this {@code DataStore} instance, or {@code null} if unspecified. |
| * @param connector information about the storage (URL, stream, {@link ucar.nc2.NetcdfFile} instance, <i>etc</i>). |
| * @throws DataStoreException if an error occurred while opening the netCDF file. |
| * |
| * @since 0.8 |
| */ |
| public NetcdfStore(final NetcdfStoreProvider provider, final StorageConnector connector) throws DataStoreException { |
| super(provider, connector); |
| location = connector.getStorageAs(URI.class); |
| final Path path = connector.getStorageAs(Path.class); |
| try { |
| decoder = NetcdfStoreProvider.decoder(listeners, connector); |
| } catch (IOException | ArithmeticException e) { |
| throw new DataStoreException(e); |
| } |
| if (decoder == null) { |
| throw new UnsupportedStorageException(super.getLocale(), NetcdfStoreProvider.NAME, |
| connector.getStorage(), connector.getOption(OptionKey.OPEN_OPTIONS)); |
| } |
| decoder.location = path; |
| String id = decoder.stringValue(ACDD.id); |
| if (id == null || (id = id.trim()).isEmpty()) { |
| id = decoder.getFilename(); |
| } |
| if (id != null) { |
| final NameFactory f = decoder.nameFactory; |
| decoder.namespace = f.createNameSpace(f.createLocalName(null, id), null); |
| } |
| } |
| |
| /** |
| * Returns the parameters used to open this netCDF data store. |
| * If non-null, the parameters are described by {@link NetcdfStoreProvider#getOpenParameters()} and contains at |
| * least a parameter named {@value org.apache.sis.storage.DataStoreProvider#LOCATION} with a {@link URI} value. |
| * This method may return {@code null} if the storage input can not be described by a URI |
| * (for example a netCDF file reading directly from a {@link java.nio.channels.ReadableByteChannel}). |
| * |
| * @return parameters used for opening this data store, or {@code null} if not available. |
| * |
| * @since 0.8 |
| */ |
| @Override |
| public ParameterValueGroup getOpenParameters() { |
| return URIDataStore.parameters(provider, location); |
| } |
| |
| /** |
| * Returns the version number of the Climate and Forecast (CF) conventions used in the netCDF file. |
| * The use of CF convention is mandated by the OGC 11-165r2 standard |
| * (<cite>CF-netCDF3 Data Model Extension standard</cite>). |
| * |
| * @return CF-convention version, or {@code null} if no information about CF convention has been found. |
| * @throws DataStoreException if an error occurred while reading the data. |
| * |
| * @since 0.8 |
| */ |
| public synchronized Version getConventionVersion() throws DataStoreException { |
| for (final CharSequence value : CharSequences.split(decoder.stringValue(CDM.CONVENTIONS), ',')) { |
| if (CharSequences.regionMatches(value, 0, "CF-", true)) { |
| return new Version(value.subSequence(3, value.length()).toString()); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns an identifier constructed from global attributes or the filename of the netCDF file. |
| * |
| * @return the identifier fetched from global attributes or the filename. May be absent. |
| * @throws DataStoreException if an error occurred while fetching the identifier. |
| * |
| * @since 1.0 |
| */ |
| @Override |
| public Optional<GenericName> getIdentifier() throws DataStoreException { |
| final NameSpace namespace = decoder.namespace; |
| return (namespace != null) ? Optional.of(namespace.name()) : Optional.empty(); |
| } |
| |
| /** |
| * Returns information about the dataset as a whole. The returned metadata object can contain information |
| * such as the spatiotemporal extent of the dataset, contact information about the creator or distributor, |
| * data quality, usage constraints and more. |
| * |
| * @return information about the dataset. |
| * @throws DataStoreException if an error occurred while reading the data. |
| */ |
| @Override |
| public synchronized Metadata getMetadata() throws DataStoreException { |
| if (metadata == null) try { |
| final MetadataReader reader = new MetadataReader(decoder); |
| metadata = reader.read(); |
| } catch (IOException | ArithmeticException e) { |
| throw new DataStoreException(e); |
| } |
| return metadata; |
| } |
| |
| /** |
| * Returns the resources (features or coverages) in this netCDF store. |
| * |
| * @return children resources that are components of this netCDF store. |
| * @throws DataStoreException if an error occurred while fetching the components. |
| * |
| * @since 0.8 |
| */ |
| @Override |
| @SuppressWarnings("ReturnOfCollectionOrArrayField") |
| public synchronized Collection<Resource> components() throws DataStoreException { |
| if (components == null) try { |
| Resource[] resources = decoder.getDiscreteSampling(); |
| final List<Resource> grids = RasterResource.create(decoder, this); |
| if (!grids.isEmpty()) { |
| grids.addAll(UnmodifiableArrayList.wrap(resources)); |
| resources = grids.toArray(new Resource[grids.size()]); |
| } |
| components = UnmodifiableArrayList.wrap(resources); |
| } catch (IOException e) { |
| throw new DataStoreException(e); |
| } |
| return components; |
| } |
| |
| /** |
| * Registers a listener to notify when the specified kind of event occurs in this data store. |
| * The current implementation of this data store can emit only {@link WarningEvent}s; |
| * any listener specified for another kind of events will be ignored. |
| */ |
| @Override |
| public <T extends StoreEvent> void addListener(Class<T> eventType, StoreListener<? super T> listener) { |
| // If an argument is null, we let the parent class throws (indirectly) NullArgumentException. |
| if (listener == null || eventType == null || eventType.isAssignableFrom(WarningEvent.class)) { |
| super.addListener(eventType, listener); |
| } |
| } |
| |
| /** |
| * Closes this netCDF store and releases any underlying resources. |
| * |
| * @throws DataStoreException if an error occurred while closing the netCDF file. |
| */ |
| @Override |
| public synchronized void close() throws DataStoreException { |
| metadata = null; |
| try { |
| decoder.close(); |
| } catch (IOException e) { |
| throw new DataStoreException(e); |
| } |
| } |
| |
| /** |
| * Returns a string representation of this netCDF store for debugging purpose. |
| * The content of the string returned by this method may change in any future SIS version. |
| * |
| * @return a string representation of this data store for debugging purpose. |
| */ |
| @Override |
| public String toString() { |
| return Strings.bracket(getClass(), decoder); |
| } |
| } |