You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/09/01 16:30:13 UTC
[sis] branch geoapi-4.0 updated (bd56303 -> 0701a79)
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git.
from bd56303 Parse some more metadata from Landsat.
new ef52731 Use enumeration and `EnumMap` instead of masks for managing the bands.
new 311d7ea Add a `StoreMetadata.isLowPriority` boolean for testing a provider last. This is used when a provider is too generic and we want to allow more specialized providers to be tested first.
new 0701a79 Allow Landsat file to be opened from its directory, without requerying to point specifically to the metadata file. Cleanup in Landsat metadata builder and the JavaFX metadata summary.
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../sis/gui/metadata/RepresentationInfo.java | 46 +++--
.../apache/sis/internal/gui/ResourceLoader.java | 12 --
.../apache/sis/storage/earthobservation/Band.java | 68 +++++++
.../sis/storage/earthobservation/BandGroup.java | 26 ++-
.../storage/earthobservation/LandsatReader.java | 201 +++++++--------------
.../sis/storage/earthobservation/LandsatStore.java | 68 +++++--
.../earthobservation/LandsatStoreProvider.java | 29 +++
.../earthobservation/LandsatReaderTest.java | 20 +-
.../sis/storage/netcdf/NetcdfStoreProvider.java | 3 +-
.../apache/sis/internal/storage/StoreMetadata.java | 12 ++
.../storage/folder/FolderStoreProvider.java | 16 +-
.../sis/internal/storage/folder/package-info.java | 2 +-
.../sis/internal/storage/wkt/FirstKeywordPeek.java | 166 +++++++++++------
.../sis/internal/storage/wkt/package-info.java | 2 +-
.../org/apache/sis/storage/DataStoreRegistry.java | 85 +++++++--
.../org/apache/sis/storage/StorageConnector.java | 18 +-
.../org.apache.sis.storage.DataStoreProvider | 1 +
.../sis/internal/storage/folder/StoreTest.java | 5 +-
18 files changed, 480 insertions(+), 300 deletions(-)
create mode 100644 storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/Band.java
copy core/sis-feature/src/test/java/org/apache/sis/filter/SpatialUsingJTS_Test.java => storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/BandGroup.java (71%)
[sis] 02/03: Add a `StoreMetadata.isLowPriority` boolean for
testing a provider last. This is used when a provider is too generic and we
want to allow more specialized providers to be tested first.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 311d7ea64a8f6eba63ad64f4917b53265ef3a6f3
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Sep 1 18:09:36 2021 +0200
Add a `StoreMetadata.isLowPriority` boolean for testing a provider last.
This is used when a provider is too generic and we want to allow more specialized providers to be tested first.
---
.../apache/sis/internal/gui/ResourceLoader.java | 12 ---
.../sis/storage/netcdf/NetcdfStoreProvider.java | 3 +-
.../apache/sis/internal/storage/StoreMetadata.java | 12 +++
.../storage/folder/FolderStoreProvider.java | 16 ++--
.../sis/internal/storage/folder/package-info.java | 2 +-
.../org/apache/sis/storage/DataStoreRegistry.java | 85 +++++++++++++++++-----
.../org/apache/sis/storage/StorageConnector.java | 18 +++--
.../org.apache.sis.storage.DataStoreProvider | 1 +
.../sis/internal/storage/folder/StoreTest.java | 5 +-
9 files changed, 102 insertions(+), 52 deletions(-)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ResourceLoader.java b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ResourceLoader.java
index 988a820..d18916c 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ResourceLoader.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ResourceLoader.java
@@ -21,7 +21,6 @@ import java.net.URL;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.nio.file.Files;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.function.Consumer;
@@ -36,7 +35,6 @@ import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStores;
import org.apache.sis.util.collection.Cache;
import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.internal.storage.folder.FolderStoreProvider;
import org.apache.sis.internal.storage.io.IOUtilities;
import org.apache.sis.storage.DataStore;
@@ -154,16 +152,6 @@ public final class ResourceLoader extends Task<Resource> {
* Loads the resource after we verified that it is not in the cache.
*/
private DataStore load() throws DataStoreException {
- Object input = source;
- if (input instanceof StorageConnector) {
- input = ((StorageConnector) input).getStorage();
- }
- if ((input instanceof File && ((File) input).isDirectory()) ||
- (input instanceof Path && Files.isDirectory((Path) input)))
- {
- return FolderStoreProvider.INSTANCE.open((source instanceof StorageConnector)
- ? (StorageConnector) source : new StorageConnector(input));
- }
return DataStores.open(source);
}
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
index 9459c54..50e97a9 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStoreProvider.java
@@ -74,7 +74,8 @@ import org.apache.sis.util.Version;
@StoreMetadata(formatName = NetcdfStoreProvider.NAME,
fileSuffixes = "nc",
capabilities = Capability.READ,
- resourceTypes = {Aggregate.class, FeatureSet.class, GridCoverageResource.class})
+ resourceTypes = {Aggregate.class, FeatureSet.class, GridCoverageResource.class},
+ isLowPriority = true)
public class NetcdfStoreProvider extends DataStoreProvider {
/**
* The format name.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreMetadata.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreMetadata.java
index 292d9c8..7be37c6 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreMetadata.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreMetadata.java
@@ -92,4 +92,16 @@ public @interface StoreMetadata {
* @return information about the expected resource types which might be encounter with this format.
*/
Class<? extends Resource>[] resourceTypes() default {};
+
+ /**
+ * Returns {@code true} if the data store should be tested last when searching for a data store capable
+ * to open a given file. This method should return {@code true} if the data store claims to be able to
+ * open a wide variety of files, in order to allow specialized data stores to be tested before generic
+ * data stores.
+ *
+ * <p>If many data stores have low priority, the ordering between them is unspecified.</p>
+ *
+ * @return {@code true} if this data store should be tested after all "normal priority" data stores.
+ */
+ boolean isLowPriority() default false;
}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java
index c630652..7ae0ade 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/FolderStoreProvider.java
@@ -52,9 +52,9 @@ import org.apache.sis.setup.OptionKey;
/**
- * The provider of {@link Store} instances. This provider is intentionally <strong>not</strong> registered
- * in {@code META-INF/services/org.apache.sis.storage.DataStoreProvider} because is will open any directory,
- * which may conflict with other providers opening only directory with some specific content.
+ * The provider of {@link Store} instances. This provider is intentionally registered with lowest priority
+ * because it will open any directory, which may conflict with other providers opening only directory with
+ * some specific content.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
@@ -64,7 +64,8 @@ import org.apache.sis.setup.OptionKey;
*/
@StoreMetadata(formatName = FolderStoreProvider.NAME,
capabilities = {Capability.READ, Capability.WRITE},
- resourceTypes = {Aggregate.class, FeatureSet.class, GridCoverageResource.class})
+ resourceTypes = {Aggregate.class, FeatureSet.class, GridCoverageResource.class},
+ isLowPriority = true)
public final class FolderStoreProvider extends DataStoreProvider {
/**
* A short name or abbreviation for the data format.
@@ -116,14 +117,9 @@ public final class FolderStoreProvider extends DataStoreProvider {
}
/**
- * The unique instance of this provider.
- */
- public static final FolderStoreProvider INSTANCE = new FolderStoreProvider();
-
- /**
* Creates a new provider.
*/
- private FolderStoreProvider() {
+ public FolderStoreProvider() {
}
/**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/package-info.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/package-info.java
index 9dfa98b..906c57f 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/package-info.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/package-info.java
@@ -19,7 +19,7 @@
* {@link org.apache.sis.storage.DataStore} implementation for a folder containing an arbitrary amount of data files.
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.8
* @module
*/
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
index 524880e..e2c8383 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
@@ -43,7 +43,7 @@ import org.apache.sis.util.ArraysExt;
* on the part of the caller.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.4
* @module
*/
@@ -139,17 +139,54 @@ final class DataStoreRegistry {
}
/**
+ * The kind of providers to test. The provider are divided in 4 categories depending on whether
+ * the file suffix matches the suffix expected by the provider, and whether the provider should
+ * be tested last for giving a chance to specialized providers to open the file.
+ */
+ private enum Category {
+ /** The provider can be tested now and the file suffix matches. */
+ SUFFIX_MATCH(true, false),
+
+ /** The provider could be tested now but the file suffix does not match. */
+ IGNORE_SUFFIX(false, false),
+
+ /** The provider should be tested last, but the file suffix matches. */
+ DEFERRED(true, true),
+
+ /** The provider should be tested last because it is too generic. */
+ DEFERRED_IGNORE_SUFFIX(false, true);
+
+ /** Whether this category checks if suffix matches. */
+ final boolean useSuffix;
+
+ /** Whether this category is for providers to test last. */
+ final boolean isLowPriority;
+
+ /** Creates a new enumeration value. */
+ private Category(final boolean useSuffix, final boolean isLowPriority) {
+ this.useSuffix = useSuffix;
+ this.isLowPriority = isLowPriority;
+ }
+
+ /** Returns the categories, optionally ignoring file suffix. */
+ static Category[] values(final boolean useSuffix) {
+ return useSuffix ? values() : new Category[] {IGNORE_SUFFIX, DEFERRED_IGNORE_SUFFIX};
+ }
+ }
+
+ /**
* Implementation of {@link #probeContentType(Object)} and {@link #open(Object)}.
*
* @param storage the input/output object as a URL, file, image input stream, <i>etc.</i>.
* @param open {@code true} for creating a {@link DataStore}, or {@code false} if not needed.
+ * @return the result, or {@code null} if the format is not recognized and {@code open} is {@code false}.
* @throws UnsupportedStorageException if no {@link DataStoreProvider} is found for a given storage object.
* @throws DataStoreException if an error occurred while opening the storage.
*
* @todo Iterate on {@code ServiceLoader.Provider.type()} on JDK9.
*/
private ProbeProviderPair lookup(final Object storage, final boolean open) throws DataStoreException {
- StorageConnector connector;
+ StorageConnector connector; // Will be reset to `null` if it shall not be closed.
if (storage instanceof StorageConnector) {
connector = (StorageConnector) storage;
} else {
@@ -158,20 +195,23 @@ final class DataStoreRegistry {
/*
* If we can get a filename extension from the given storage (file, URL, etc.), then we may perform two iterations
* on the provider list. The first iteration will use only the providers which declare capability to read files of
- * that suffix (matchCondition = TRUE). Only if no provider has been able to read that file, we will do a second
- * iteration on other providers (matchCondition = FALSE). The intent is to avoid DataStoreProvider.probeContent(…)
+ * that suffix (Category.SUFFIX_MATCH). Only if no provider has been able to read that file, we will do a second
+ * iteration on other providers (Category.IGNORE_SUFFIX). The intent is to avoid DataStoreProvider.probeContent(…)
* invocations loading large dependencies.
*/
- final String extension = connector.getFileExtension();
- Boolean matchCondition = (extension != null && !extension.isEmpty()) ? Boolean.TRUE : null;
+ final String extension = connector.getFileExtension();
+ final boolean useSuffix = !(extension == null || extension.isEmpty());
+ final Category[] categories = Category.values(useSuffix);
+ ProbeProviderPair selected = null;
final List<ProbeProviderPair> needMoreBytes = new LinkedList<>();
- ProbeProviderPair selected = null;
try {
-search: do {
+search: for (int ci=0; ci < categories.length; ci++) {
+ final Category category = categories[ci];
/*
- * All usages of 'loader' and its 'providers' iterator must be protected in a synchronized block,
+ * All usages of `loader` and its `providers` iterator must be protected in a synchronized block,
* because ServiceLoader is not thread-safe. We try to keep the synhronization block as small as
- * possible for less contention. In particular, the probeContent(connector) method call may be costly.
+ * possible for less contention. In particular, the `probeContent(connector)` method call may be
+ * costly.
*/
final Iterator<DataStoreProvider> providers;
DataStoreProvider provider;
@@ -180,10 +220,20 @@ search: do {
provider = providers.hasNext() ? providers.next() : null;
}
while (provider != null) {
- boolean accept = true;
- if (matchCondition != null) {
- final StoreMetadata md = provider.getClass().getAnnotation(StoreMetadata.class);
- accept = (md != null && ArraysExt.containsIgnoreCase(md.fileSuffixes(), extension)) == matchCondition;
+ /*
+ * Check if the provider should be tested in current iteration.
+ * The criteria for testing a provider is determined by comparing
+ * provider metadata with the category tested in current iteration.
+ */
+ boolean accept;
+ final StoreMetadata md = provider.getClass().getAnnotation(StoreMetadata.class);
+ if (md == null) {
+ accept = (ci == 0); // If no metadata, test only in first iteration.
+ } else {
+ accept = (md.isLowPriority() == category.isLowPriority);
+ if (accept & useSuffix) {
+ accept = ArraysExt.containsIgnoreCase(md.fileSuffixes(), extension) == category.useSuffix;
+ }
}
if (accept) {
final ProbeResult probe = provider.probeContent(connector);
@@ -219,7 +269,7 @@ search: do {
}
}
/*
- * If any provider did not had enough bytes for answering the 'probeContent(…)' question,
+ * If any provider did not had enough bytes for answering the `probeContent(…)` question,
* get more bytes and try again. We try to prefetch more bytes only if we have no choice
* in order to avoid latency on network connection.
*/
@@ -242,10 +292,9 @@ search: do {
/*
* If we filtered providers by the file extension without finding a suitable provider,
* try again with all other providers (even if they are for another file extension).
- * We do that by changing 'matchCondition' from TRUE to FALSE. In all other cases,
- * we stop the search.
+ * We do that by changing moving to the next `Category`.
*/
- } while (matchCondition != null && (matchCondition = !matchCondition) == false);
+ }
/*
* If a provider has been found, or if a provider returned UNDETERMINED, use that one
* for opening a DataStore. Note that if more than one provider returned UNDETERMINED,
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
index e04113f..b0b9e5f 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java
@@ -1091,14 +1091,16 @@ public class StorageConnector implements Serializable {
c = getView(ByteBuffer.class);
if (reset(c)) { // reset(c) as a matter of principle, but (c != null) would have worked.
final ByteBuffer buffer = (ByteBuffer) c.view;
- final int p = buffer.limit();
- final long mark = input.getStreamPosition();
- input.seek(Math.addExact(mark, p));
- final int n = input.read(buffer.array(), p, buffer.capacity() - p);
- input.seek(mark);
- if (n > 0) {
- buffer.limit(p + n);
- return true;
+ if (buffer != null) {
+ final int p = buffer.limit();
+ final long mark = input.getStreamPosition();
+ input.seek(Math.addExact(mark, p));
+ final int n = input.read(buffer.array(), p, buffer.capacity() - p);
+ input.seek(mark);
+ if (n > 0) {
+ buffer.limit(p + n);
+ return true;
+ }
}
}
}
diff --git a/storage/sis-storage/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider b/storage/sis-storage/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider
index 62b5285..5c1a70f 100644
--- a/storage/sis-storage/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider
+++ b/storage/sis-storage/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider
@@ -1,3 +1,4 @@
org.apache.sis.internal.storage.xml.StoreProvider
org.apache.sis.internal.storage.wkt.StoreProvider
org.apache.sis.internal.storage.csv.StoreProvider
+org.apache.sis.internal.storage.folder.FolderStoreProvider
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java
index 236116a..1f04666 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/folder/StoreTest.java
@@ -43,7 +43,7 @@ import static org.junit.Assume.assumeTrue;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.8
* @module
*/
@@ -89,11 +89,12 @@ public final strictfp class StoreTest extends TestCase {
*/
@Test
public void testSearchProviderParameter() throws URISyntaxException, DataStoreException, IOException {
+ final FolderStoreProvider provider = new FolderStoreProvider();
final Set<String> identifiers = new HashSet<>(Arrays.asList("Sample 1", "Sample 2", "Sample 3", "data4"));
final ParameterValueGroup params = FolderStoreProvider.PARAMETERS.createValue();
params.parameter("location").setValue(testDirectory());
params.parameter("format").setValue("XML");
- try (Store store = (Store) FolderStoreProvider.INSTANCE.open(params)) {
+ try (Store store = (Store) provider.open(params)) {
assertEquals("Expected one less data store.", 3, store.components().size());
verifyContent(store, identifiers);
}
[sis] 01/03: Use enumeration and `EnumMap` instead of masks for
managing the bands.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit ef527318f7151bbbb07b92f6e7180b10d2072c37
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Aug 31 21:23:41 2021 +0200
Use enumeration and `EnumMap` instead of masks for managing the bands.
---
.../apache/sis/storage/earthobservation/Band.java | 68 +++++++++
.../sis/storage/earthobservation/BandGroup.java | 44 ++++++
.../storage/earthobservation/LandsatReader.java | 163 ++++++---------------
.../earthobservation/LandsatReaderTest.java | 18 +--
4 files changed, 159 insertions(+), 134 deletions(-)
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/Band.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/Band.java
new file mode 100644
index 0000000..44e65a7
--- /dev/null
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/Band.java
@@ -0,0 +1,68 @@
+/*
+ * 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.earthobservation;
+
+
+/**
+ * Names of Landsat bands.
+ *
+ * @todo Those names and the wavelength could be moved to the {@code SpatialMetadata} database,
+ * as described in <a href="https://issues.apache.org/jira/browse/SIS-338">SIS-338</a>.
+ * It would make easier to enrich the metadata with more information.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+enum Band {
+ AEROSOL (BandGroup.REFLECTIVE, "Coastal Aerosol", (short) 433),
+ BLUE (BandGroup.REFLECTIVE, "Blue", (short) 482),
+ GREEN (BandGroup.REFLECTIVE, "Green", (short) 562),
+ RED (BandGroup.REFLECTIVE, "Red", (short) 655),
+ NEAR_INFRARED(BandGroup.REFLECTIVE, "Near-Infrared", (short) 865),
+ SWIR1 (BandGroup.REFLECTIVE, "Short Wavelength Infrared (SWIR) 1", (short) 1610),
+ SWIR2 (BandGroup.REFLECTIVE, "Short Wavelength Infrared (SWIR) 2", (short) 2200),
+ PANCHROMATIC (BandGroup.PANCHROMATIC, "Panchromatic", (short) 590),
+ CIRRUS (BandGroup.REFLECTIVE, "Cirrus", (short) 1375),
+ TIRS1 (BandGroup.THERMAL, "Thermal Infrared Sensor (TIRS) 1", (short) 10800),
+ TIRS2 (BandGroup.THERMAL, "Thermal Infrared Sensor (TIRS) 2", (short) 12000);
+
+ /**
+ * Group in which this band belong.
+ */
+ final BandGroup group;
+
+ /**
+ * Name of Landsat band.
+ */
+ final String name;
+
+ /**
+ * Peak response wavelength for the Landsat band, in nanometres.
+ */
+ final short wavelength;
+
+ /**
+ * Creates a new enumeration value.
+ */
+ private Band(final BandGroup group, final String name, final short wavelength) {
+ this.group = group;
+ this.name = name;
+ this.wavelength = wavelength;
+ }
+}
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/BandGroup.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/BandGroup.java
new file mode 100644
index 0000000..cce4884
--- /dev/null
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/BandGroup.java
@@ -0,0 +1,44 @@
+/*
+ * 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.earthobservation;
+
+
+/**
+ * Group of bands.
+ * All images of the same group for a given scene have the same size in pixels.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+enum BandGroup {
+ /**
+ * Group for band 8.
+ */
+ PANCHROMATIC,
+
+ /**
+ * Group for bands 1, 2, 3, 4, 5, 6, 7, 9.
+ */
+ REFLECTIVE,
+
+ /**
+ * Group for bands 10, 11.
+ */
+ THERMAL
+}
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
index 479696e..90a2d1b 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
@@ -16,11 +16,13 @@
*/
package org.apache.sis.storage.earthobservation;
+import java.awt.Dimension;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
+import java.util.EnumMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.LineNumberReader;
@@ -95,46 +97,12 @@ import static org.apache.sis.internal.util.CollectionsExt.singletonOrNull;
* @author Thi Phuong Hao Nguyen (VNSC)
* @author Rémi Maréchal (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.8
* @module
*/
final class LandsatReader extends MetadataBuilder {
/**
- * Names of Landsat bands.
- *
- * @todo Those names and the wavelength could be moved to the {@code SpatialMetadata} database,
- * as described in <a href="https://issues.apache.org/jira/browse/SIS-338">SIS-338</a>.
- * It would make easier to enrich the metadata with more information.
- *
- * @see #bands
- * @see #band(String, int)
- */
- private static final String[] BAND_NAMES = {
- "Coastal Aerosol", // 433 nm
- "Blue", // 482 nm
- "Green", // 562 nm
- "Red", // 655 nm
- "Near-Infrared", // 865 nm
- "Short Wavelength Infrared (SWIR) 1", // 1610 nm
- "Short Wavelength Infrared (SWIR) 2", // 2200 nm
- "Panchromatic", // 590 nm
- "Cirrus", // 1375 nm
- "Thermal Infrared Sensor (TIRS) 1", // 10800 nm
- "Thermal Infrared Sensor (TIRS) 2" // 12000 nm
- };
-
- /**
- * Peak response wavelength for the Landsat bands, in nanometres.
- *
- * @see #bands
- * @see #band(String, int)
- */
- private static final short[] WAVELENGTHS = {
- 433, 482, 562, 655, 865, 1610, 2200, 590, 1375, 10800, 12000
- };
-
- /**
* The pattern determining if the value of {@code ORIGIN} key is of the form
* “Image courtesy of the U.S. Geological Survey”.
*/
@@ -159,42 +127,6 @@ final class LandsatReader extends MetadataBuilder {
private static final String BAND_SUFFIX = "_BAND";
/**
- * A bit mask of the group in which to classify a given band.
- * There is three groups: panchromatic, reflective or thermal bands:
- *
- * <ul>
- * <li><b>Panchromatic:</b> band 8.</li>
- * <li><b>Reflective:</b> bands 1, 2, 3, 4, 5, 6, 7, 9.</li>
- * <li><b>Thermal:</b> bands 10, 11.</li>
- * </ul>
- *
- * For a band numbered from 1 to 11 inclusive, the group is computed by
- * (constants 2 and 3 in that formula depends on the {@link #NUM_GROUPS} value):
- *
- * {@preformat java
- * group = (BAND_GROUPS >>> 2*(band - 1)) & 3;
- * }
- *
- * The result is one of the {@link #PANCHROMATIC}, {@link #REFLECTIVE} or {@link #THERMAL} constant values
- * divided by {@value #DIM}.
- */
- static final int BAND_GROUPS = 2692437; // Value computed by LandsatReaderTest.verifyBandGroupsMask()
-
- /**
- * Maximum number of band groups that a metadata may contains.
- * See {@link #BAND_GROUPS} javadoc for the list of groups.
- */
- private static final int NUM_GROUPS = 3;
-
- /**
- * Index of panchromatic, reflective or thermal groups in the {@link #gridSizes} array.
- * The image size is each group is given by {@value #DIM} integers: the width and the height.
- */
- static final int PANCHROMATIC = 0*DIM,
- REFLECTIVE = 1*DIM,
- THERMAL = 2*DIM;
-
- /**
* Index of projected and geographic coordinates in the {@link #corners} array.
* Each kind of coordinates are stored as 4 corners of {@value #DIM} coordinate values.
*/
@@ -252,11 +184,10 @@ final class LandsatReader extends MetadataBuilder {
* Image width and hight in pixels, as unsigned integers. Values are (<var>width</var>,<var>height</var>) tuples.
* Tuples in this array are for {@link #PANCHROMATIC}, {@link #REFLECTIVE} or {@link #THERMAL} bands, in that order.
*/
- private final int[] gridSizes;
+ private final EnumMap<BandGroup,Dimension> gridSizes;
/**
- * The bands description. Any element can be null if the corresponding band is not defined.
- * The bands can be, in this exact order:
+ * The bands description. The bands can be, in this exact order:
*
* <ol>
* <li>Coastal Aerosol</li>
@@ -272,11 +203,14 @@ final class LandsatReader extends MetadataBuilder {
* <li>Thermal Infrared Sensor (TIRS) 2</li>
* </ol>
*
- * @see #BAND_NAMES
- * @see #WAVELENGTHS
* @see #band(String, int)
*/
- private final DefaultBand[] bands;
+ private final EnumMap<Band,DefaultBand> bands;
+
+ /**
+ * {@link Band#values()}, fetched once for avoiding multiple array creations.
+ */
+ private final Band[] bandEnumerations;
/**
* The enumeration for the {@code "DATUM"} element, to be used for creating the Coordinate Reference System.
@@ -310,10 +244,11 @@ final class LandsatReader extends MetadataBuilder {
this.filename = filename;
this.listeners = listeners;
this.factories = new ReferencingFactoryContainer();
- this.bands = new DefaultBand[BAND_NAMES.length];
- this.gridSizes = new int[NUM_GROUPS * DIM];
+ this.bands = new EnumMap<>(Band.class);
+ this.gridSizes = new EnumMap<>(BandGroup.class);
this.corners = new double[GEOGRAPHIC + (4*DIM)]; // GEOGRAPHIC is the last group of corners to store.
Arrays.fill(corners, Double.NaN);
+ bandEnumerations = Band.values();
}
/**
@@ -418,14 +353,15 @@ final class LandsatReader extends MetadataBuilder {
}
/**
- * Parses the given value and stores it at the given index in the {@link #gridSizes} array.
+ * Parses the given value and stores it in the {@link #gridSizes} map.
*
- * @param index {@link #PANCHROMATIC}, {@link #REFLECTIVE} or {@link #THERMAL},
- * +1 if parsing the height instead than the width.
* @param value the value to parse.
*/
- private void parseGridSize(final int index, final String value) throws NumberFormatException {
- gridSizes[index] = Integer.parseUnsignedInt(value);
+ private void parseGridSize(final BandGroup group, final boolean isX, final String value) throws NumberFormatException {
+ final int s = Integer.parseUnsignedInt(value);
+ final Dimension size = gridSizes.computeIfAbsent(group, (k) -> new Dimension());
+ if (isX) size.width = s;
+ else size.height = s;
}
/**
@@ -625,12 +561,12 @@ final class LandsatReader extends MetadataBuilder {
* The number of product lines and samples for the panchromatic, reflective and thermal bands.
* Those parameters are only present if the corresponding band is present in the product.
*/
- case "PANCHROMATIC_LINES": parseGridSize(PANCHROMATIC + 1, value); break;
- case "PANCHROMATIC_SAMPLES": parseGridSize(PANCHROMATIC, value); break;
- case "REFLECTIVE_LINES": parseGridSize(REFLECTIVE + 1, value); break;
- case "REFLECTIVE_SAMPLES": parseGridSize(REFLECTIVE, value); break;
- case "THERMAL_LINES": parseGridSize(THERMAL + 1, value); break;
- case "THERMAL_SAMPLES": parseGridSize(THERMAL, value); break;
+ case "PANCHROMATIC_LINES": parseGridSize(BandGroup.PANCHROMATIC, false, value); break;
+ case "PANCHROMATIC_SAMPLES": parseGridSize(BandGroup.PANCHROMATIC, true, value); break;
+ case "REFLECTIVE_LINES": parseGridSize(BandGroup.REFLECTIVE, false, value); break;
+ case "REFLECTIVE_SAMPLES": parseGridSize(BandGroup.REFLECTIVE, true, value); break;
+ case "THERMAL_LINES": parseGridSize(BandGroup.THERMAL, false, value); break;
+ case "THERMAL_SAMPLES": parseGridSize(BandGroup.THERMAL, true, value); break;
/*
* The grid cell size in meters used in creating the image for the band, if part of the product.
* This parameter is only included if the corresponding band is included in the product.
@@ -853,18 +789,19 @@ final class LandsatReader extends MetadataBuilder {
* @param key the key without its band number. Used only for formatting warning messages.
* @param index the band index.
*/
- private DefaultBand band(final String key, int index) {
- if (index < 1 || index > BAND_NAMES.length) {
+ private DefaultBand band(final String key, final int index) {
+ if (index < 1 || index > bandEnumerations.length) {
listeners.warning(errors().getString(Errors.Keys.UnexpectedValueInElement_2, key + index, index));
return null;
}
- DefaultBand band = bands[--index];
+ final Band bk = bandEnumerations[index - 1];
+ DefaultBand band = bands.get(bk);
if (band == null) {
band = new DefaultBand();
- band.setDescription(new SimpleInternationalString(BAND_NAMES[index]));
- band.setPeakResponse((double) WAVELENGTHS[index]);
+ band.setDescription(new SimpleInternationalString(bk.name));
+ band.setPeakResponse((double) bk.wavelength);
band.setBoundUnits(Units.NANOMETRE);
- bands[index] = band;
+ bands.put(bk, band);
}
return band;
}
@@ -984,15 +921,13 @@ final class LandsatReader extends MetadataBuilder {
if (toBoundingBox(GEOGRAPHIC)) {
addExtent(corners, GEOGRAPHIC);
}
- for (int i = 0; i < gridSizes.length; i += DIM) {
- final int width = gridSizes[i ];
- final int height = gridSizes[i+1];
- if ((width | height) != 0) {
+ for (final Dimension size : gridSizes.values()) {
+ if ((size.width | size.height) != 0) {
newGridRepresentation(MetadataBuilder.GridType.GEORECTIFIED);
setAxisName(0, DimensionNameType.SAMPLE);
setAxisName(1, DimensionNameType.LINE);
- setAxisSize(0, Integer.toUnsignedLong(width));
- setAxisSize(1, Integer.toUnsignedLong(height));
+ setAxisSize(0, Integer.toUnsignedLong(size.width));
+ setAxisSize(1, Integer.toUnsignedLong(size.height));
}
}
/*
@@ -1005,24 +940,18 @@ final class LandsatReader extends MetadataBuilder {
if (result != null) {
/*
* Set information about all non-null bands. The bands are categorized in three groups:
- * PANCHROMATIC, REFLECTIVE and THERMAL. The group in which each band belong is encoded
- * in the BAND_GROUPS bitmask.
+ * PANCHROMATIC, REFLECTIVE and THERMAL.
*/
final DefaultCoverageDescription content = (DefaultCoverageDescription) singletonOrNull(result.getContentInfo());
if (content != null) {
- final DefaultAttributeGroup[] groups = new DefaultAttributeGroup[NUM_GROUPS];
- for (int i=0; i < bands.length; i++) {
- final DefaultBand band = bands[i];
- if (band != null) {
- final int gi = (BAND_GROUPS >>> 2*i) & 3;
- DefaultAttributeGroup group = groups[gi];
- if (group == null) {
- group = new DefaultAttributeGroup(CoverageContentType.PHYSICAL_MEASUREMENT, null);
- content.getAttributeGroups().add(group);
- groups[gi] = group;
- }
- group.getAttributes().add(band);
- }
+ final EnumMap<BandGroup,DefaultAttributeGroup> groups = new EnumMap<>(BandGroup.class);
+ for (final EnumMap.Entry<Band,DefaultBand> entry : bands.entrySet()) {
+ final DefaultAttributeGroup group = groups.computeIfAbsent(entry.getKey().group, (k) -> {
+ DefaultAttributeGroup g = new DefaultAttributeGroup(CoverageContentType.PHYSICAL_MEASUREMENT, null);
+ content.getAttributeGroups().add(g);
+ return g;
+ });
+ group.getAttributes().add(entry.getValue());
}
}
result.transitionTo(DefaultMetadata.State.FINAL);
diff --git a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
index 5e0567f..15a9ef8 100644
--- a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
+++ b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
@@ -37,9 +37,8 @@ import org.apache.sis.storage.DataStoreException;
import org.apache.sis.test.TestCase;
import org.junit.Test;
-import static org.apache.sis.test.Assert.*;
+import static org.junit.Assert.*;
import static org.apache.sis.test.TestUtilities.date;
-import static org.apache.sis.storage.earthobservation.LandsatReader.DIM;
/**
@@ -64,21 +63,6 @@ public class LandsatReaderTest extends TestCase {
}
/**
- * Verifies the value of the {@link LandsatReader#BAND_GROUPS} mask.
- */
- @Test
- public void verifyBandGroupsMask() {
- final int[] PANCHROMATIC = {8};
- final int[] REFLECTIVE = {1, 2, 3, 4, 5, 6, 7, 9};
- final int[] THERMAL = {10, 11};
- long mask = 0;
- for (int i=0; i < PANCHROMATIC.length; i++) mask |= (LandsatReader.PANCHROMATIC/DIM << 2*(PANCHROMATIC[i] - 1));
- for (int i=0; i < REFLECTIVE.length; i++) mask |= (LandsatReader.REFLECTIVE/DIM << 2*(REFLECTIVE[i] - 1));
- for (int i=0; i < THERMAL.length; i++) mask |= (LandsatReader.THERMAL/DIM << 2*(THERMAL[i] - 1));
- assertEquals("BAND_GROUPS", mask, LandsatReader.BAND_GROUPS);
- }
-
- /**
* Tests {@link LandsatReader#read(BufferedReader)}.
*
* <p><b>NOTE FOR MAINTAINER:</b> if the result of this test changes, consider updating
[sis] 03/03: Allow Landsat file to be opened from its directory,
without requerying to point specifically to the metadata file.
Cleanup in Landsat metadata builder and the JavaFX metadata summary.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 0701a79f3e088f5b814aed30a6a7149067a962dc
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Sep 1 18:27:37 2021 +0200
Allow Landsat file to be opened from its directory, without requerying to point specifically to the metadata file.
Cleanup in Landsat metadata builder and the JavaFX metadata summary.
---
.../sis/gui/metadata/RepresentationInfo.java | 46 +++---
.../storage/earthobservation/LandsatReader.java | 44 +++---
.../sis/storage/earthobservation/LandsatStore.java | 68 ++++++---
.../earthobservation/LandsatStoreProvider.java | 29 ++++
.../earthobservation/LandsatReaderTest.java | 2 +-
.../sis/internal/storage/wkt/FirstKeywordPeek.java | 166 ++++++++++++++-------
.../sis/internal/storage/wkt/package-info.java | 2 +-
7 files changed, 250 insertions(+), 107 deletions(-)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/RepresentationInfo.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/RepresentationInfo.java
index dae8729..1156730 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/RepresentationInfo.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/metadata/RepresentationInfo.java
@@ -59,7 +59,6 @@ final class RepresentationInfo extends Section<SpatialRepresentation> {
@Override
void setInformation(final Metadata metadata) {
referenceSystem = null;
- setInformation(nonNull(metadata == null ? null : metadata.getSpatialRepresentationInfo()), SpatialRepresentation[]::new);
if (metadata != null) {
for (final ReferenceSystem rs : nonNull(metadata.getReferenceSystemInfo())) {
if (rs != null) {
@@ -68,6 +67,7 @@ final class RepresentationInfo extends Section<SpatialRepresentation> {
}
}
}
+ setInformation(nonNull(metadata == null ? null : metadata.getSpatialRepresentationInfo()), SpatialRepresentation[]::new);
}
/**
@@ -93,30 +93,40 @@ final class RepresentationInfo extends Section<SpatialRepresentation> {
* the resolution. Note that VectorSpatialRepresentation would also needs a table.
*/
private void build(final GridSpatialRepresentation info) {
- int dimensionCount = 0;
- final StringBuffer buffer = new StringBuffer();
+ final Integer dimension = info.getNumberOfDimensions();
+ if (dimension != null) {
+ addLine(Vocabulary.Keys.NumberOfDimensions, owner.getNumberFormat().format(dimension));
+ }
+ final StringBuffer gridSize = new StringBuffer(20);
+ final StringBuffer resolution = new StringBuffer(20);
for (final Dimension dim : nonNull(info.getAxisDimensionProperties())) {
final String name = owner.string(Types.getCodeTitle(dim.getDimensionName()));
final Integer size = dim.getDimensionSize();
- if (size != null) {
- owner.getNumberFormat().format(size, buffer, new FieldPosition(0));
- if (name != null) buffer.append(' ');
+ if (name != null || size != null) {
+ if (gridSize.length() != 0) {
+ gridSize.append(" × ");
+ }
+ if (size != null) {
+ owner.getNumberFormat().format(size, gridSize, new FieldPosition(0));
+ }
+ if (name != null) {
+ if (size != null) gridSize.append(' ');
+ gridSize.append(name);
+ }
}
- if (name != null) {
- buffer.append(name);
- } else if (size == null) {
- buffer.append('?');
+ final Double r = dim.getResolution();
+ if (r != null) {
+ if (resolution.length() != 0) {
+ resolution.append(" × ");
+ }
+ owner.getNumberFormat().format(r, resolution, new FieldPosition(0));
}
- buffer.append(" × ");
- dimensionCount++;
}
- if (dimensionCount != 0) {
- buffer.setLength(buffer.length() - 3);
- addLine(Vocabulary.Keys.Dimensions, buffer.toString());
+ if (gridSize.length() != 0) {
+ addLine(Vocabulary.Keys.Dimensions, gridSize.toString());
}
- final Integer nd = info.getNumberOfDimensions();
- if (nd != null && nd != dimensionCount) {
- addLine(Vocabulary.Keys.NumberOfDimensions, owner.getNumberFormat().format(dimensionCount));
+ if (resolution.length() != 0) {
+ addLine(Vocabulary.Keys.Resolution, resolution.toString());
}
addLine(Vocabulary.Keys.CellGeometry, owner.string(Types.getCodeTitle(info.getCellGeometry())));
}
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
index 90a2d1b..d914cb0 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
@@ -109,12 +109,6 @@ final class LandsatReader extends MetadataBuilder {
static final Pattern CREDIT = Pattern.compile("\\bcourtesy\\h+of\\h+(the)?\\b\\s*", Pattern.CASE_INSENSITIVE);
/**
- * Number of spatial dimensions. This is the number of coordinate values to be stored
- * in the {@link #gridSizes} and {@link #corners} arrays for each tuple.
- */
- static final int DIM = 2;
-
- /**
* Suffix of groups that describe the processing done on the data instead of the data themselves.
*/
private static final String LINEAGE_SUFFIX = "_RECORD";
@@ -127,13 +121,6 @@ final class LandsatReader extends MetadataBuilder {
private static final String BAND_SUFFIX = "_BAND";
/**
- * Index of projected and geographic coordinates in the {@link #corners} array.
- * Each kind of coordinates are stored as 4 corners of {@value #DIM} coordinate values.
- */
- private static final int PROJECTED = 0*DIM,
- GEOGRAPHIC = 4*DIM;
-
- /**
* The keyword for end of metadata file.
*/
private static final String END = "END";
@@ -158,7 +145,7 @@ final class LandsatReader extends MetadataBuilder {
/**
* The last group where we fetched lineage (history) information.
- * The same file may contain many lineage group.
+ * The same file may contain many lineage groups.
*
* @see #parseLineage(String, int, String)
*/
@@ -181,13 +168,25 @@ final class LandsatReader extends MetadataBuilder {
private final double[] corners;
/**
+ * Number of coordinates for the corners in a CRS type (geographic or projected).
+ * This is 4 corners multiplied by the number of dimensions, which is 2.
+ */
+ private static final int NUM_COORDINATES = 8;
+
+ /**
+ * Index of projected and geographic coordinates in the {@link #corners} array.
+ * Each kind of coordinates are stored as 4 corners of {@value #DIMENSION} coordinate values.
+ */
+ private static final int PROJECTED = 0, GEOGRAPHIC = NUM_COORDINATES;
+
+ /**
* Image width and hight in pixels, as unsigned integers. Values are (<var>width</var>,<var>height</var>) tuples.
* Tuples in this array are for {@link #PANCHROMATIC}, {@link #REFLECTIVE} or {@link #THERMAL} bands, in that order.
*/
private final EnumMap<BandGroup,Dimension> gridSizes;
/**
- * The bands description. The bands can be, in this exact order:
+ * The bands descriptions. The bands can be, in this exact order:
*
* <ol>
* <li>Coastal Aerosol</li>
@@ -208,6 +207,11 @@ final class LandsatReader extends MetadataBuilder {
private final EnumMap<Band,DefaultBand> bands;
/**
+ * Paths to resources for each band.
+ */
+ final EnumMap<Band,String> resources;
+
+ /**
* {@link Band#values()}, fetched once for avoiding multiple array creations.
*/
private final Band[] bandEnumerations;
@@ -244,11 +248,12 @@ final class LandsatReader extends MetadataBuilder {
this.filename = filename;
this.listeners = listeners;
this.factories = new ReferencingFactoryContainer();
- this.bands = new EnumMap<>(Band.class);
+ this.corners = new double[2*NUM_COORDINATES]; // 2 types of CRS: GEOGRAPHIC and PROJECTED.
this.gridSizes = new EnumMap<>(BandGroup.class);
- this.corners = new double[GEOGRAPHIC + (4*DIM)]; // GEOGRAPHIC is the last group of corners to store.
- Arrays.fill(corners, Double.NaN);
+ this.bands = new EnumMap<>(Band.class);
+ this.resources = new EnumMap<>(Band.class);
bandEnumerations = Band.values();
+ Arrays.fill(corners, Double.NaN);
}
/**
@@ -585,6 +590,7 @@ final class LandsatReader extends MetadataBuilder {
final DefaultBand db = band(key, band);
if (db != null) {
db.getNames().add(new DefaultIdentifier(value));
+ resources.put(bandEnumerations[band-1], value);
}
break;
}
@@ -855,7 +861,7 @@ final class LandsatReader extends MetadataBuilder {
double ymin = Double.POSITIVE_INFINITY;
double xmax = Double.NEGATIVE_INFINITY;
double ymax = Double.NEGATIVE_INFINITY;
- for (int i = base + (4*DIM); --i >= base;) {
+ for (int i = base + NUM_COORDINATES; --i >= base;) {
double v = corners[i];
if (v < ymin) ymin = v;
if (v > ymax) ymax = v;
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
index 6932a16..992afed 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
@@ -16,13 +16,16 @@
*/
package org.apache.sis.storage.earthobservation;
+import java.util.Optional;
import java.io.Reader;
import java.io.BufferedReader;
import java.io.LineNumberReader;
import java.io.IOException;
-import java.nio.file.StandardOpenOption;
import java.net.URI;
-import java.util.Optional;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
import org.opengis.metadata.Metadata;
import org.opengis.util.GenericName;
import org.opengis.util.FactoryException;
@@ -80,6 +83,11 @@ public class LandsatStore extends DataStore {
private Reader source;
/**
+ * The root directory where this file is located, or {@code null} if unknown.
+ */
+ final Path directory;
+
+ /**
* The {@link LandsatStoreProvider#LOCATION} parameter value, or {@code null} if none.
*/
private final URI location;
@@ -105,9 +113,28 @@ public class LandsatStore extends DataStore {
*/
public LandsatStore(final LandsatStoreProvider provider, final StorageConnector connector) throws DataStoreException {
super(provider, connector);
- location = connector.getStorageAs(URI.class);
- source = connector.getStorageAs(Reader.class);
+ Path path = connector.getStorageAs(Path.class);
+ location = connector.getStorageAs(URI.class);
+ source = connector.getStorageAs(Reader.class);
connector.closeAllExcept(source);
+ if (path != null) {
+ if (source != null) {
+ path = path.getParent(); // If the source has been opened, then the path is a file.
+ } else try {
+ final Path file = LandsatStoreProvider.getMetadataFile(path);
+ if (file != null) {
+ final Charset encoding = connector.getOption(OptionKey.ENCODING);
+ if (encoding != null) {
+ source = Files.newBufferedReader(file, encoding);
+ } else {
+ source = Files.newBufferedReader(file);
+ }
+ }
+ } catch (IOException e) {
+ throw new DataStoreException(e);
+ }
+ }
+ directory = path;
if (source == null) {
throw new UnsupportedStorageException(super.getLocale(), LandsatStoreProvider.NAME,
connector.getStorage(), connector.getOption(OptionKey.OPEN_OPTIONS));
@@ -149,6 +176,25 @@ public class LandsatStore extends DataStore {
}
/**
+ * Parses the main Landsat text file.
+ */
+ private void loadMetadata() throws DataStoreException {
+ if (source == null) {
+ throw new DataStoreClosedException(getLocale(), LandsatStoreProvider.NAME, StandardOpenOption.READ);
+ }
+ try (BufferedReader reader = (source instanceof BufferedReader) ? (BufferedReader) source : new LineNumberReader(source)) {
+ source = null; // Will be closed at the end of this try-finally block.
+ final LandsatReader parser = new LandsatReader(getDisplayName(), listeners);
+ parser.read(reader);
+ metadata = parser.getMetadata();
+ } catch (IOException e) {
+ throw new DataStoreException(e);
+ } catch (FactoryException e) {
+ throw new DataStoreReferencingException(e);
+ }
+ }
+
+ /**
* 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.
@@ -159,19 +205,7 @@ public class LandsatStore extends DataStore {
@Override
public synchronized Metadata getMetadata() throws DataStoreException {
if (metadata == null) {
- if (source == null) {
- throw new DataStoreClosedException(getLocale(), LandsatStoreProvider.NAME, StandardOpenOption.READ);
- }
- try (BufferedReader reader = (source instanceof BufferedReader) ? (BufferedReader) source : new LineNumberReader(source)) {
- source = null; // Will be closed at the end of this try-finally block.
- final LandsatReader parser = new LandsatReader(getDisplayName(), listeners);
- parser.read(reader);
- metadata = parser.getMetadata();
- } catch (IOException e) {
- throw new DataStoreException(e);
- } catch (FactoryException e) {
- throw new DataStoreReferencingException(e);
- }
+ loadMetadata();
}
return metadata;
}
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStoreProvider.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStoreProvider.java
index 6db3495..03bf769 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStoreProvider.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStoreProvider.java
@@ -16,6 +16,9 @@
*/
package org.apache.sis.storage.earthobservation;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.Files;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreProvider;
@@ -89,6 +92,16 @@ public class LandsatStoreProvider extends DataStoreProvider {
}
/**
+ * Returns the path to the metadata file relative to the directory specified by user.
+ * This method is invoked if the user gave us the directory containing all Landsat files
+ * instead than the path to the metadata file.
+ */
+ @Override
+ protected Path getAuxiliaryPath(final StorageConnector connector) throws DataStoreException {
+ return getMetadataFile(connector.getStorageAs(Path.class));
+ }
+
+ /**
* Returns {@code ACCEPT} if the given character is valid for a keyword.
*/
@Override
@@ -164,6 +177,22 @@ public class LandsatStoreProvider extends DataStoreProvider {
}
/**
+ * Returns the metadata file inside the given directory if the file exists, or {@code null} otherwise.
+ *
+ * @param directory directory to test, or {@code null} if unknown.
+ * @return metadata file, or {@code null} if it does not exist.
+ */
+ static Path getMetadataFile(final Path directory) {
+ if (directory != null) {
+ final Path file = directory.resolve(Paths.get(directory.getFileName().toString().concat("_MTL.txt")));
+ if (Files.isRegularFile(file)) {
+ return file;
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns {@link ProbeResult#SUPPORTED} if the given storage appears to be supported by {@link LandsatStore}.
* Returning {@code SUPPORTED} from this method does not guarantee that reading or writing will succeed, only
* that there appears to be a reasonable chance of success based on a brief inspection of the storage header.
diff --git a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
index 15a9ef8..f080a2c 100644
--- a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
+++ b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
@@ -46,7 +46,7 @@ import static org.apache.sis.test.TestUtilities.date;
*
* @author Thi Phuong Hao Nguyen (VNSC)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.8
* @module
*/
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/FirstKeywordPeek.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/FirstKeywordPeek.java
index d83319d..5c8c896 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/FirstKeywordPeek.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/FirstKeywordPeek.java
@@ -19,6 +19,9 @@ package org.apache.sis.internal.storage.wkt;
import java.io.Reader;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.file.Path;
+import java.nio.file.Files;
+import java.nio.channels.SeekableByteChannel;
import org.apache.sis.internal.storage.io.IOUtilities;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.StorageConnector;
@@ -30,7 +33,7 @@ import org.apache.sis.util.Characters;
* Inspects the type of a text file based on the first keyword.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.1
* @since 0.8
* @module
*/
@@ -66,6 +69,21 @@ public abstract class FirstKeywordPeek {
}
/**
+ * If the data to read is not provided by the connector by rather by some auxiliary file
+ * relative to the connector, returns the path to that auxiliary file.
+ * The default implementation returns {@code null}.
+ *
+ * @param connector the connector from which to derive the path to auxiliary file to test.
+ * @return path to the auxiliary file to test, or {@code null} if it does not exist.
+ * @throws DataStoreException if an error occurred while determining the auxiliary file.
+ *
+ * @since 1.1
+ */
+ protected Path getAuxiliaryPath(final StorageConnector connector) throws DataStoreException {
+ return null;
+ }
+
+ /**
* Returns the next character which is not a white space, or -1 if the end of stream is reached.
* Exactly one of {@code buffer} and {@code reader} shall be non-null.
*/
@@ -115,19 +133,16 @@ public abstract class FirstKeywordPeek {
}
/**
- * Returns {@link ProbeResult#SUPPORTED} if the given storage appears to begin with an expected keyword
+ * Returns {@link ProbeResult#SUPPORTED} if the given storage appears to begin with an expected keyword.
* Returning {@code SUPPORTED} from this method does not guarantee that reading or writing will succeed,
* only that there appears to be a reasonable chance of success based on a brief inspection of the storage
* header.
*
- * @param connector information about the storage (URL, stream, JDBC connection, <i>etc</i>).
+ * @param connector information about the storage (URL, stream, <i>etc</i>).
* @return {@link ProbeResult#SUPPORTED} if the given storage seems to be readable.
- * @throws DataStoreException if an I/O or SQL error occurred.
+ * @throws DataStoreException if an I/O error occurred.
*/
- @SuppressWarnings("null")
public final ProbeResult probeContent(final StorageConnector connector) throws DataStoreException {
- char[] keyword = null;
- int pos = 0;
try {
final ByteBuffer buffer = connector.getStorageAs(ByteBuffer.class);
final Reader reader;
@@ -135,59 +150,108 @@ public abstract class FirstKeywordPeek {
buffer.mark();
reader = null;
} else {
- // User gave us explicitly a Reader (e.g. a StringReader wrapping a String instance).
+ /*
+ * If we do not have a `ByteBuffer`, maybe the user gave us explicitly a `Reader` wrapping
+ * something else than a stream (e.g. a `StringReader` wrapping a `String` instance).
+ */
reader = connector.getStorageAs(Reader.class);
- if (reader == null) {
- return ProbeResult.UNSUPPORTED_STORAGE;
+ if (reader != null) {
+ reader.mark(READ_AHEAD_LIMIT);
+ } else {
+ final Path file = getAuxiliaryPath(connector);
+ if (file != null) {
+ return probeContent(file);
+ } else {
+ return ProbeResult.UNSUPPORTED_STORAGE;
+ }
}
- reader.mark(READ_AHEAD_LIMIT);
}
- /*
- * Ignore leading spaces and comments if any, then get a keyword no longer than 'maxLength'.
- * That keyword shall be followed by [ or (, ignoring whitespaces.
- */
- int c;
- while ((c = nextAfterSpaces(buffer, reader)) == COMMENT) {
- toEndOfLine(buffer, reader);
+ return probeContent(buffer, reader);
+ } catch (IOException e) {
+ throw new DataStoreException(e);
+ }
+ }
+
+ /**
+ * Returns {@link ProbeResult#SUPPORTED} if the content of given file begins with an expected keyword.
+ * This method can be invoked as an alternative to {@link #probeContent(StorageConnector)} when the file
+ * to test is not the specified storage, but some auxiliary file.
+ *
+ * @param file the file to partially read.
+ * @return {@link ProbeResult#SUPPORTED} if the given file seems to be readable.
+ * @throws DataStoreException if an I/O error occurred.
+ *
+ * @see #getAuxiliaryPath(StorageConnector)
+ *
+ * @since 1.1
+ */
+ public final ProbeResult probeContent(final Path file) throws DataStoreException {
+ final ByteBuffer buffer = ByteBuffer.allocate(maxLength + 100);
+ try {
+ try (SeekableByteChannel channel = Files.newByteChannel(file)) {
+ int n;
+ do n = channel.read(buffer);
+ while (n >= 0 && buffer.hasRemaining());
}
- int s;
- if ((s = isKeywordChar(c)) >= ACCEPT) {
- keyword = new char[maxLength];
- do {
- if (s == ACCEPT) {
- if (pos >= keyword.length) {
- pos = 0; // Keyword too long.
- break;
- }
- keyword[pos++] = (char) c;
- }
- if (buffer != null) {
- c = buffer.hasRemaining() ? (char) buffer.get() : -1;
- } else {
- c = IOUtilities.readCodePoint(reader);
+ return probeContent((ByteBuffer) buffer.flip().mark(), null); // TODO: remove cast in JDK9.
+ } catch (IOException e) {
+ throw new DataStoreException(e);
+ }
+ }
+
+ /**
+ * Tests the first keyword from one of the given inputs.
+ * Exactly one of the two arguments shall be non-null.
+ */
+ private ProbeResult probeContent(final ByteBuffer buffer, final Reader reader) throws IOException {
+ char[] keyword = null;
+ int pos = 0;
+ /*
+ * Ignore leading spaces and comments if any, then get a keyword no longer than `maxLength`.
+ * That keyword should be followed by '[', '(', '=' or ':' character, ignoring whitespaces.
+ */
+ int c;
+ while ((c = nextAfterSpaces(buffer, reader)) == COMMENT) {
+ toEndOfLine(buffer, reader);
+ }
+ int s;
+ if ((s = isKeywordChar(c)) >= ACCEPT) {
+ keyword = new char[maxLength];
+ do {
+ if (s == ACCEPT) {
+ if (pos >= keyword.length) {
+ pos = 0; // Keyword too long.
+ break;
}
- } while ((s = isKeywordChar(c)) >= ACCEPT);
- /*
- * At this point we finished to read and store the keyword.
- * Verify if the keyword is followed by a character that indicate a keyword end.
- */
- if (Character.isWhitespace(c)) {
- c = nextAfterSpaces(buffer, reader);
+ keyword[pos++] = (char) c;
}
- if (!isPostKeyword(c)) {
- pos = 0;
+ if (buffer != null) {
+ c = buffer.hasRemaining() ? (char) buffer.get() : -1;
+ } else {
+ c = IOUtilities.readCodePoint(reader);
}
+ } while ((s = isKeywordChar(c)) >= ACCEPT);
+ /*
+ * At this point we finished to read and store the keyword.
+ * Verify if the keyword is followed by a character that indicate a keyword end.
+ */
+ if (Character.isWhitespace(c)) {
+ c = nextAfterSpaces(buffer, reader);
}
- if (buffer != null) {
- buffer.reset();
- } else {
- reader.reset();
- }
- if (c < 0) {
- return ProbeResult.INSUFFICIENT_BYTES;
+ if (!isPostKeyword(c)) {
+ pos = 0;
}
- } catch (IOException e) {
- throw new DataStoreException(e);
+ }
+ if (buffer != null) {
+ buffer.reset();
+ } else {
+ reader.reset();
+ }
+ if (c < 0) {
+ return ProbeResult.INSUFFICIENT_BYTES;
+ }
+ if (pos == 0) {
+ return ProbeResult.UNSUPPORTED_STORAGE;
}
return forKeyword(keyword, pos);
}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
index 3ccb593..51f03d9 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
@@ -19,7 +19,7 @@
* {@link org.apache.sis.storage.DataStore} implementation for Well Known Text.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.1
* @since 0.7
* @module
*/