blob: 656745a1c401a18720691d5a93fc03b62a4e5832 [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.landsat;
import java.nio.file.Path;
import java.util.Optional;
import org.opengis.util.LocalName;
import org.opengis.util.GenericName;
import org.opengis.metadata.Metadata;
import org.opengis.metadata.identification.Identification;
import org.opengis.metadata.content.CoverageContentType;
import org.apache.sis.storage.GridCoverageResource;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.geotiff.GeoTiffStore;
import org.apache.sis.storage.geotiff.spi.SchemaModifier;
import org.apache.sis.storage.base.GridResourceWrapper;
import org.apache.sis.metadata.iso.DefaultMetadata;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.content.DefaultImageDescription;
import org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
import org.apache.sis.metadata.iso.content.DefaultSampleDimension;
import org.apache.sis.metadata.iso.content.DefaultBand;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.measure.Units;
import static org.apache.sis.util.privy.CollectionsExt.first;
/**
* A band in a Landsat data set. Each band is represented by a separated GeoTIFF file.
*
* @author Martin Desruisseaux (Geomatys)
*/
final class Band extends GridResourceWrapper implements SchemaModifier {
/**
* The data store that contains this band.
* Also the object on which to perform synchronization locks.
*/
private final LandsatStore parent;
/**
* The band for which this instance provides data.
*/
final BandName band;
/**
* Identifier of the band for which this instance provides data.
* Should not be modified after the end of metadata parsing.
*
* @see #getIdentifier()
*/
LocalName identifier;
/**
* Filename of the file to read for band data.
* This is relative to {@link LandsatStore#directory}.
* Should not be modified after the end of metadata parsing.
*/
String filename;
/**
* Metadata about the band.
* Should not be modified after the end of metadata parsing.
*/
final DefaultSampleDimension sampleDimension;
/**
* Creates a new resource for the specified band.
*/
Band(final LandsatStore parent, final BandName band) {
this.parent = parent;
this.band = band;
if (band.wavelength != 0) {
final DefaultBand b = new DefaultBand();
b.setPeakResponse((double) band.wavelength);
b.setBoundUnits(Units.NANOMETRE);
sampleDimension = b;
} else {
sampleDimension = new DefaultSampleDimension();
}
sampleDimension.setDescription(band.title);
// Can not set units in GeoAPI 3.0 because the API is restricted to units of length.
}
/**
* Returns the object on which to perform all synchronizations for thread-safety.
*/
@Override
protected final Object getSynchronizationLock() {
return parent;
}
/**
* Creates the GeoTIFF reader and get the first image from it.
*/
@Override
protected GridCoverageResource createSource() throws DataStoreException {
final Path file;
if (parent.directory != null) {
file = parent.directory.resolve(filename);
} else {
file = Path.of(filename);
}
final StorageConnector connector = new StorageConnector(file);
connector.setOption(SchemaModifier.OPTION, this);
return new GeoTiffStore(parent, parent.getProvider(), connector, true).components().get(0);
}
/**
* Returns the resource persistent identifier. The name is the {@link BandName#name()}
* and the scope (namespace) is the name of the directory that contains this band.
*/
@Override
public Optional<GenericName> getIdentifier() throws DataStoreException {
return Optional.of(identifier);
}
/**
* Returns whether the given source is for the main image.
*/
private static boolean isMain(final Source source) {
return source.getImageIndex().orElse(-1) == 0;
}
/**
* Invoked when the GeoTIFF reader creates the resource identifier.
* We use the identifier of the enclosing {@link Band}.
*/
@Override
public GenericName customize(final Source source, final GenericName fallback) {
return isMain(source) ? identifier : fallback;
}
/**
* Invoked when the GeoTIFF reader creates a metadata.
* This method modifies or completes some information inferred by the GeoTIFF reader.
*/
@Override
public Metadata customize(final Source source, final DefaultMetadata metadata) {
if (isMain(source)) {
for (final Identification id : metadata.getIdentificationInfo()) {
final var c = (DefaultCitation) id.getCitation();
if (c != null) {
c.setTitle(band.title);
break;
}
}
/*
* All collections below should be singleton and all casts should be safe because we use
* one specific implementation (`GeoTiffStore`) which is known to build metadata that way.
* A ClassCastException would be a bug in the handling of `isElectromagneticMeasurement(…)`.
*/
final var content = (DefaultImageDescription) first(metadata.getContentInfo());
final var group = (DefaultAttributeGroup) first(content.getAttributeGroups());
final var sd = (DefaultSampleDimension) first(group.getAttributes());
group.getContentTypes().add(CoverageContentType.PHYSICAL_MEASUREMENT);
sd.setDescription(sampleDimension.getDescription());
sd.setMinValue (sampleDimension.getMinValue());
sd.setMaxValue (sampleDimension.getMaxValue());
sd.setScaleFactor(sampleDimension.getScaleFactor());
sd.setOffset (sampleDimension.getOffset());
sd.setUnits (sampleDimension.getUnits());
if (sampleDimension instanceof DefaultBand) {
final DefaultBand s = (DefaultBand) sampleDimension;
final DefaultBand t = (DefaultBand) sd;
t.setPeakResponse(s.getPeakResponse());
t.setBoundUnits(s.getBoundUnits());
}
}
return metadata;
}
/**
* Invoked when a sample dimension is created for a band in an image.
*/
@Override
public SampleDimension customize(final BandSource source, final SampleDimension.Builder dimension) {
if (isMain(source) && source.getBandIndex() == 0) {
dimension.setName(identifier);
final NumberRange<?> sampleRange = source.getSampleRange().orElse(null);
if (sampleRange != null) {
final Number min = sampleRange.getMinValue();
final Number max = sampleRange.getMaxValue();
final Double scale = sampleDimension.getScaleFactor();
final Double offset = sampleDimension.getOffset();
if (min != null && max != null && scale != null && offset != null) {
int lower = min.intValue();
if (lower >= 0) { // Should always be zero but we are paranoiac.
dimension.addQualitative(null, 0);
if (lower == 0) lower = 1;
}
dimension.addQuantitative(this.band.group.measurement, lower, max.intValue(),
scale, offset, sampleDimension.getUnits());
}
}
}
return dimension.build();
}
/**
* Returns {@code true} if the converted values are measurement in the electromagnetic spectrum.
*/
@Override
public boolean isElectromagneticMeasurement(final Source source) {
return isMain(source) && band.wavelength != 0;
}
}