blob: 60949119a72bcd617d499603674a748311cd91a5 [file] [log] [blame]
/*
* 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);
}
}