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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 9647286 Initial implementation of ESRI ASCII Grid reader.
9647286 is described below
commit 96472863b7cdefa32fe5549afbf260a668ed43f9
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Apr 1 19:03:53 2022 +0200
Initial implementation of ESRI ASCII Grid reader.
https://issues.apache.org/jira/browse/SIS-540
---
.../org/apache/sis/metadata/sql/Contents.sql | 16 +-
.../java/org/apache/sis/util/resources/Errors.java | 10 +
.../apache/sis/util/resources/Errors.properties | 4 +-
.../apache/sis/util/resources/Errors_fr.properties | 4 +-
.../sis/storage/landsat/MetadataReaderTest.java | 3 +
.../sis/storage/netcdf/MetadataReaderTest.java | 2 +
.../org/apache/sis/internal/storage/Resources.java | 5 +
.../sis/internal/storage/Resources.properties | 1 +
.../sis/internal/storage/Resources_fr.properties | 1 +
.../sis/internal/storage/ascii/CharactersView.java | 233 ++++++++++++
.../apache/sis/internal/storage/ascii/Store.java | 420 +++++++++++++++++++++
.../sis/internal/storage/ascii/StoreProvider.java | 92 +++++
.../sis/internal/storage/ascii/package-info.java | 95 +++++
.../apache/sis/storage/event/StoreListeners.java | 2 +-
14 files changed, 878 insertions(+), 10 deletions(-)
diff --git
a/core/sis-metadata/src/main/resources/org/apache/sis/metadata/sql/Contents.sql
b/core/sis-metadata/src/main/resources/org/apache/sis/metadata/sql/Contents.sql
index 8eb153c..a3c5c87 100644
---
a/core/sis-metadata/src/main/resources/org/apache/sis/metadata/sql/Contents.sql
+++
b/core/sis-metadata/src/main/resources/org/apache/sis/metadata/sql/Contents.sql
@@ -13,18 +13,20 @@ CREATE TABLE metadata."Format" (
"amendmentNumber" VARCHAR(120),
"fileDecompressionTechnique" VARCHAR(120));
-INSERT INTO metadata."Citation" ("ID", "alternateTitle", "title") VALUES
- ('GeoTIFF', 'GeoTIFF', 'GeoTIFF Coverage Encoding Profile'),
- ('NetCDF', 'NetCDF', 'NetCDF Classic and 64-bit Offset Format'),
- ('PNG', 'PNG', 'PNG (Portable Network Graphics) Specification'),
- ('CSV', 'CSV', 'Common Format and MIME Type for Comma-Separated
Values (CSV) Files'),
- ('CSV-MF', 'CSV', 'OGC Moving Features Encoding Extension: Simple
Comma-Separated Values (CSV)'),
- ('GPX', 'GPX', 'GPS Exchange Format');
+INSERT INTO metadata."Citation" ("ID", "alternateTitle",
"citedResponsibleParty", "title") VALUES
+ ('GeoTIFF', 'GeoTIFF', 'OGC', 'GeoTIFF Coverage Encoding Profile'),
+ ('NetCDF', 'NetCDF', 'OGC', 'NetCDF Classic and 64-bit Offset Format'),
+ ('PNG', 'PNG', NULL, 'PNG (Portable Network Graphics)
Specification'),
+ ('ASCGRD', 'ASCII Grid', 'ESRI', 'ESRI ArcInfo ASCII Grid format'),
+ ('CSV', 'CSV', NULL, 'Common Format and MIME Type for
Comma-Separated Values (CSV) Files'),
+ ('CSV-MF', 'CSV', 'OGC', 'OGC Moving Features Encoding Extension:
Simple Comma-Separated Values (CSV)'),
+ ('GPX', 'GPX', NULL, 'GPS Exchange Format');
INSERT INTO metadata."Format" ("ID", "formatSpecificationCitation") VALUES
('GeoTIFF', 'GeoTIFF'),
('NetCDF', 'NetCDF'),
('PNG', 'PNG'),
+ ('ASCGRD', 'ASCGRD'),
('CSV', 'CSV'),
('CSV-MF', 'CSV-MF'),
('GPX', 'GPX');
diff --git
a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
index 030b59e..d906781 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
@@ -436,6 +436,11 @@ public final class Errors extends IndexedResourceBundle {
public static final short IllegalUnicodeCodePoint_2 = 61;
/**
+ * Illegal value for property “{1}” in “{0}”.
+ */
+ public static final short IllegalValueForProperty_2 = 196;
+
+ /**
* Can not use the {1} format with “{0}”.
*/
public static final short IncompatibleFormat_2 = 62;
@@ -599,6 +604,11 @@ public final class Errors extends IndexedResourceBundle {
public static final short MissingValueForProperty_1 = 89;
/**
+ * Missing value for “{1}” property in “{0}”.
+ */
+ public static final short MissingValueForProperty_2 = 197;
+
+ /**
* Missing value in the “{0}” column.
*/
public static final short MissingValueInColumn_1 = 90;
diff --git
a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
index 5b2d938..c052d47 100644
---
a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
+++
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
@@ -85,6 +85,7 @@ IllegalBitsPattern_1 = Illegal bits pattern: {0}.
IllegalCharacter_2 = The \u201c{1}\u201d character can not be
used for \u201c{0}\u201d.
IllegalCharacterForFormat_3 = The \u201c{2}\u201d character in
\u201c{1}\u201d is not permitted by the \u201c{0}\u201d format.
IllegalClass_2 = Class \u2018{1}\u2019 is illegal. It must
be \u2018{0}\u2019 or a derived class.
+IllegalCoordinateRange_3 = The [{0} \u2026 {1}] range of coordinate
values is not valid for the \u201c{2}\u201d axis.
IllegalCoordinateSystem_1 = Coordinate system can not be
\u201c{0}\u201d.
IllegalCRSType_1 = Coordinate reference system can not be of
type \u2018{0}\u2019.
IllegalFormatPatternForClass_2 = The \u201c{1}\u201d pattern can not be
applied to formatting of objects of type \u2018{0}\u2019.
@@ -93,11 +94,11 @@ IllegalLanguageCode_1 = The \u201c{0}\u201d
language is not recogniz
IllegalMapping_2 = Illegal mapping: {0} \u2192 {1}.
IllegalMemberType_2 = Member \u201c{0}\u201d can not be
associated to type \u201c{1}\u201d.
IllegalOptionValue_2 = Option \u2018{0}\u2019 can not take the
\u201c{1}\u201d value.
-IllegalCoordinateRange_3 = The [{0} \u2026 {1}] range of coordinate
values is not valid for the \u201c{2}\u201d axis.
IllegalPropertyValueClass_2 = Property \u201c{0}\u201d does not accept
instances of \u2018{1}\u2019.
IllegalPropertyValueClass_3 = Expected an instance of \u2018{1}\u2019
for the \u201c{0}\u201d property, but got an instance of \u2018{2}\u2019.
IllegalRange_2 = Range [{0} \u2026 {1}] is not valid.
IllegalUnicodeCodePoint_2 = Value {1} for \u201c{0}\u201d is not a
valid Unicode code point.
+IllegalValueForProperty_2 = Illegal value for property \u201c{1}\u201d
in \u201c{0}\u201d.
IncompatibleFormat_2 = Can not use the {1} format with
\u201c{0}\u201d.
IncompatiblePropertyValue_1 = Property \u201c{0}\u201d has an
incompatible value.
IncompatibleUnit_1 = Unit \u201c{0}\u201d is incompatible with
current value.
@@ -130,6 +131,7 @@ MissingOrEmptyAttribute_2 = Missing or empty
\u2018{1}\u2019 attribute i
MissingRequiredModule_1 = This operation requires the
\u201c{0}\u201d module.
MissingValueForOption_1 = Missing value for \u201c{0}\u201d option.
MissingValueForProperty_1 = Missing value for \u201c{0}\u201d property.
+MissingValueForProperty_2 = Missing value for \u201c{1}\u201d property
in \u201c{0}\u201d.
MissingValueInColumn_1 = Missing value in the \u201c{0}\u201d
column.
MultiOccurenceValueAtIndices_3 = Can not return a single value for
\u201c{0}\u201d because there is at least two occurrences, at indices {1} and
{2}.
MutuallyExclusiveOptions_2 = Options \u201c{0}\u201d and
\u201c{1}\u201d are mutually exclusive.
diff --git
a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
index 6e99108..9c1e1db 100644
---
a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
+++
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
@@ -82,6 +82,7 @@ IllegalBitsPattern_1 = Pattern de bits
invalide\u00a0: {0}.
IllegalClass_2 = La classe \u2018{1}\u2019 est
ill\u00e9gale. Il doit s\u2019agir d\u2019une classe \u2018{0}\u2019 ou
d\u00e9riv\u00e9e.
IllegalCharacter_2 = Le caract\u00e8re
\u00ab\u202f{1}\u202f\u00bb ne peut pas \u00eatre utilis\u00e9 dans
\u00ab\u202f{0}\u202f\u00bb.
IllegalCharacterForFormat_3 = Le caract\u00e8re
\u00ab\u202f{2}\u202f\u00bb dans \u00ab\u202f{1}\u202f\u00bb n\u2019est pas
permis par le format \u00ab\u202f{0}\u202f\u00bb.
+IllegalCoordinateRange_3 = La plage de valeurs de coordonn\u00e9es
[{0} \u2026 {1}] n\u2019est pas valide pour l\u2019axe
\u00ab\u202f{2}\u202f\u00bb.
IllegalCoordinateSystem_1 = Le syst\u00e8me de coordonn\u00e9es ne
peut pas \u00eatre \u00ab\u202f{0}\u202f\u00bb.
IllegalCRSType_1 = Le syst\u00e8me de r\u00e9f\u00e9rence des
coordonn\u00e9es ne peut pas \u00eatre de type \u2018{0}\u2019.
IllegalFormatPatternForClass_2 = Le mod\u00e8le \u00ab\u202f{1}\u202f\u00bb
ne peut pas \u00eatre appliqu\u00e9 au formatage d\u2019objets de type
\u2018{0}\u2019.
@@ -90,11 +91,11 @@ IllegalLanguageCode_1 = Le code de langue
\u00ab\u202f{0}\u202f\u00b
IllegalMapping_2 = Correspondance ill\u00e9gale: {0} \u2192
{1}.
IllegalMemberType_2 = Le membre \u00ab\u202f{0}\u202f\u00bb ne
peut pas \u00eatre associ\u00e9 au type \u00ab\u202f{1}\u202f\u00bb.
IllegalOptionValue_2 = L\u2019option \u2018{0}\u2019
n\u2019accepte pas la valeur \u00ab\u202f{1}\u202f\u00bb.
-IllegalCoordinateRange_3 = La plage de valeurs de coordonn\u00e9es
[{0} \u2026 {1}] n\u2019est pas valide pour l\u2019axe
\u00ab\u202f{2}\u202f\u00bb.
IllegalPropertyValueClass_2 = La propri\u00e9t\u00e9
\u00ab\u202f{0}\u202f\u00bb n\u2019accepte pas les valeurs de type
\u2018{1}\u2019.
IllegalPropertyValueClass_3 = Une instance \u2018{1}\u2019 \u00e9tait
attendue pour la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb, mais la
valeur donn\u00e9e est une instance de \u2018{2}\u2019.
IllegalRange_2 = La plage [{0} \u2026 {1}] n\u2019est pas
valide.
IllegalUnicodeCodePoint_2 = La valeur {1} de
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas un code Unicode valide.
+IllegalValueForProperty_2 = Valeur ill\u00e9gale pour la
propri\u00e9t\u00e9 \u00ab\u202f{1}\u202f\u00bb dans
\u00ab\u202f{0}\u202f\u00bb.
IncompatibleFormat_2 = Le format {1} ne s\u2019applique pas
\u00e0 \u00ab\u202f{0}\u202f\u00bb.
IncompatiblePropertyValue_1 = La valeur de la propri\u00e9t\u00e9
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas compatible.
IncompatibleUnit_1 = L\u2019unit\u00e9
\u00ab\u202f{0}\u202f\u00bb n\u2019est pas compatible avec la valeur actuelle.
@@ -127,6 +128,7 @@ MissingOrEmptyAttribute_2 = L\u2019attribut
\u2018{1}\u2019 de \u00ab\u2
MissingRequiredModule_1 = Cette op\u00e9ration requiert le module
\u00ab\u202f{0}\u202f\u00bb.
MissingValueForOption_1 = Aucune valeur n\u2019a \u00e9t\u00e9
d\u00e9finie pour l\u2019option \u00ab\u202f{0}\u202f\u00bb.
MissingValueForProperty_1 = Aucune valeur n\u2019a \u00e9t\u00e9
d\u00e9finie pour la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb.
+MissingValueForProperty_2 = Aucune valeur n\u2019a \u00e9t\u00e9
d\u00e9finie pour la propri\u00e9t\u00e9 \u00ab\u202f{1}\u202f\u00bb dans
\u00ab\u202f{0}\u202f\u00bb.
MissingValueInColumn_1 = Il manque une valeur dans la colonne
\u00ab\u202f{0}\u202f\u00bb.
MultiOccurenceValueAtIndices_3 = Ne peut pas retourner une valeur unique
pour \u00ab\u202f{0}\u202f\u00bb parce qu\u2019il y a au moins deux
occurrences, aux index {1} et {2}.
MutuallyExclusiveOptions_2 = Les options \u00ab\u202f{0}\u202f\u00bb et
\u00ab\u202f{1}\u202f\u00bb sont mutuellement exclusives.
diff --git
a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
index c7843af..a02db92 100644
---
a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
+++
b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/landsat/MetadataReaderTest.java
@@ -23,6 +23,7 @@ import java.io.InputStreamReader;
import org.opengis.metadata.Metadata;
import org.opengis.metadata.acquisition.Context;
import org.opengis.metadata.acquisition.OperationType;
+import org.opengis.metadata.citation.Role;
import org.opengis.metadata.citation.DateType;
import org.opengis.metadata.content.CoverageContentType;
import org.opengis.metadata.content.TransferFunctionType;
@@ -102,6 +103,8 @@ public class MetadataReaderTest extends TestCase {
"identificationInfo[0].credit[0]",
"Derived from U.S. Geological Survey data",
"identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title",
"GeoTIFF Coverage Encoding Profile",
"identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]",
"GeoTIFF",
+
"identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].party[0].name",
"Open Geospatial Consortium",
+
"identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].role",
Role.PRINCIPAL_INVESTIGATOR,
"identificationInfo[0].extent[0].geographicElement[0].extentTypeCode",
true,
"identificationInfo[0].extent[0].geographicElement[0].westBoundLongitude",
108.34,
"identificationInfo[0].extent[0].geographicElement[0].eastBoundLongitude",
110.44,
diff --git
a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
index 339ae0d..5ad546a 100644
---
a/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
+++
b/storage/sis-netcdf/src/test/java/org/apache/sis/storage/netcdf/MetadataReaderTest.java
@@ -111,6 +111,8 @@ public final strictfp class MetadataReaderTest extends
TestCase {
// Hard-coded
"identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]",
"NetCDF",
"identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title",
"NetCDF Classic and 64-bit Offset Format",
+
"identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].party[0].name",
"Open Geospatial Consortium",
+
"identificationInfo[0].resourceFormat[0].formatSpecificationCitation.citedResponsibleParty[0].role",
Role.PRINCIPAL_INVESTIGATOR,
// Read from the file
"dateInfo[0].date",
date("2018-05-15 13:01:00"),
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
index 5bc9bef..fdfa50c 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
@@ -200,6 +200,11 @@ public final class Resources extends IndexedResourceBundle
{
public static final short DuplicatedSampleDimensionIndex_1 = 53;
/**
+ * Header in the “{0}” file is too large.
+ */
+ public static final short ExcessiveHeaderSize_1 = 67;
+
+ /**
* Character string in the “{0}” file is too long. The string has {2}
characters while the
* limit is {1}.
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
index da72a2a..7a50e42 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
@@ -47,6 +47,7 @@ DirectoryContent_1 = Content of
\u201c{0}\u201d directory.
DirectoryContentFormatName = Name of the format to use for reading or
writing the directory content.
DuplicatedQueryProperty_3 = Query property \u201c{0}\u201d is
duplicated at indices {1} and {2}.
DuplicatedSampleDimensionIndex_1 = Sample dimension index {0} is duplicated.
+ExcessiveHeaderSize_1 = Header in the \u201c{0}\u201d file is too
large.
ExcessiveStringSize_3 = Character string in the \u201c{0}\u201d
file is too long. The string has {2} characters while the limit is {1}.
FeatureAlreadyPresent_2 = A feature named \u201c{1}\u201d is already
present in the \u201c{0}\u201d data store.
FeatureNotFound_2 = Feature \u201c{1}\u201d has not been found
in the \u201c{0}\u201d data store.
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
index 4d3b918..0b19b1f 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
@@ -52,6 +52,7 @@ DirectoryContent_1 = Contenu du
r\u00e9pertoire \u00ab\u202f{0}\u
DirectoryContentFormatName = Nom du format ou de la source de
donn\u00e9es \u00e0 utiliser pour lire ou \u00e9crire le contenu du
r\u00e9pertoire.
DuplicatedQueryProperty_3 = La propri\u00e9t\u00e9
\u00ab\u202f{0}\u202f\u00bb de la requ\u00eate est dupliqu\u00e9e aux indices
{1} et {2}.
DuplicatedSampleDimensionIndex_1 = L\u2019index de dimension
d\u2019\u00e9chantillonnage {0} est r\u00e9p\u00e9t\u00e9.
+ExcessiveHeaderSize_1 = L\u2019en-t\u00eate dans le fichier
\u00ab\u202f{0}\u202f\u00bb est trop grand.
ExcessiveStringSize_3 = La cha\u00eene de caract\u00e8res dans le
fichier \u00ab\u202f{0}\u202f\u00bb est trop longue. La cha\u00eene fait {2}
caract\u00e8res alors que la limite est {1}.
FeatureAlreadyPresent_2 = Une entit\u00e9 nomm\u00e9e
\u00ab\u202f{1}\u202f\u00bb est d\u00e9j\u00e0 pr\u00e9sente dans les
donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb.
FeatureNotFound_2 = L\u2019entit\u00e9
\u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e dans les
donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb.
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/CharactersView.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/CharactersView.java
new file mode 100644
index 0000000..ef5cfa6
--- /dev/null
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/CharactersView.java
@@ -0,0 +1,233 @@
+/*
+ * 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.internal.storage.ascii;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Locale;
+import java.io.IOException;
+import java.io.EOFException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import org.apache.sis.internal.jdk9.JDK9;
+import org.apache.sis.internal.storage.Resources;
+import org.apache.sis.internal.storage.io.ChannelDataInput;
+import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * Character sequences as a view over a buffer of bytes interpreted as
US-ASCII characters.
+ * The character sequence starts always at zero and its length is the buffer
limit.
+ * The intent is to allow the use of {@link Integer#parseInt(CharSequence,
int, int, int)}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since 1.2
+ * @module
+ */
+final class CharactersView implements CharSequence {
+ /**
+ * The space character used as sample value separator.
+ */
+ private static final char SPACE = ' ';
+
+ /**
+ * The object to use for reading data, or {@code null} if this store has
been closed.
+ */
+ final ChannelDataInput input;
+
+ /**
+ * The buffer of bytes to wrap. This is the same reference as {@link
ChannelDataInput#buffer},
+ * copied here because frequently used.
+ */
+ private final ByteBuffer buffer;
+
+ /**
+ * The buffer array if the buffer is allocated on the heap,
+ * or a temporary array of arbitrary length otherwise.
+ */
+ private final byte[] array;
+
+ /**
+ * Whether {@link #array} is the array backing the {@link #buffer}.
+ * If {@code true}, then we do not need some copy operations.
+ */
+ private final boolean direct;
+
+ /**
+ * Creates a new sequence of characters.
+ *
+ * @param input the source of bytes.
+ */
+ CharactersView(final ChannelDataInput input) {
+ this.input = input;
+ this.buffer = input.buffer;
+ this.direct = buffer.hasArray();
+ this.array = direct ? buffer.array() : new byte[80];
+ }
+
+ /**
+ * Returns the number of bytes in the buffer.
+ */
+ @Override
+ public int length() {
+ return buffer.limit();
+ }
+
+ /**
+ * Returns the bytes at the given index, converted to a character.
+ */
+ @Override
+ public char charAt(final int index) {
+ return (char) Byte.toUnsignedInt(buffer.get(index));
+ }
+
+ /**
+ * Read all (key, value) pairs from the header. All keys are converted to
upper-case letters.
+ * The map may contain null values if a key was declared with no value.
+ *
+ * @return the (key, value) pairs, with keys in upper-case letters.
+ * @throws IOException if an error occurred while reading the header.
+ * @throws DataStoreContentException if a duplicated key is found.
+ */
+ final Map<String,String> readHeader() throws IOException,
DataStoreContentException {
+ final Map<String,String> header = new HashMap<>();
+ for (;;) {
+ String key = readToken(); // Never empty.
+ final char c = key.charAt(0);
+ if (c != '#') {
+ if (!Character.isJavaIdentifierStart(c)) {
+ buffer.position(buffer.position() - key.length() - 1);
+ return header;
+ }
+ String value = null;
+ while (!skipLine(true)) {
+ buffer.position(buffer.position() - 1);
+ final String next = readToken();
+ if (value == null) value = next;
+ else value = value + SPACE + next;
+ }
+ key = key.toUpperCase(Locale.US);
+ final String old = header.put(key, value);
+ if (old != null && !old.equals(value)) {
+ if (value == null) header.put(key, old);
+ else throw new
DataStoreContentException(Errors.format(Errors.Keys.DuplicatedElement_1, key));
+ }
+ }
+ skipLine(false);
+ }
+ }
+
+ /**
+ * Skips all character until the end of line.
+ * This is used for skipping a comment line in the header.
+ * This method can be invoked after {@link #readToken()}.
+ *
+ * @param stopAtToken whether to stop at the first non-white character.
+ * @return whether end of line has been reached.
+ * @throws EOFException if the channel has reached the end of stream.
+ * @throws IOException if an other kind of error occurred while reading.
+ */
+ private boolean skipLine(final boolean stopAtToken) throws IOException {
+ buffer.position(buffer.position() - 1); // For checking if the
space that we skipped was CR/LF.
+ boolean eol = false;
+ byte c;
+ do {
+ c = input.readByte();
+ eol = (c == '\r' || c == '\n');
+ }
+ while (!(eol || (stopAtToken && c > SPACE)));
+ return eol;
+ }
+
+ /**
+ * Skips leading white spaces, carriage returns or control characters,
then skips the non-white characters.
+ * This method is used for skipping a sample value without parsing the
number when a subsampling is applied.
+ *
+ * @throws EOFException if the channel has reached the end of stream.
+ * @throws IOException if an other kind of error occurred while reading.
+ */
+ @SuppressWarnings("empty-statement")
+ final void skipToken() throws IOException {
+ while (input.readByte() <= SPACE);
+ while (input.readByte() > SPACE);
+ }
+
+ /**
+ * Skips leading white spaces, carriage returns or control characters,
then reads and returns the next
+ * sequence of non-white characters. After this method call, the buffer
position is on the first white
+ * character after the token.
+ *
+ * @return the next token, never empty and without leading or trailing
white spaces.
+ * @throws EOFException if the channel has reached the end of stream.
+ * @throws IOException if an other kind of error occurred while reading.
+ * @throws DataStoreContentException if the content does not seem to
comply with ASCII Grid format.
+ */
+ @SuppressWarnings("empty-statement")
+ final String readToken() throws IOException, DataStoreContentException {
+ while (input.readByte() <= SPACE);
+ int start = buffer.position() - 1;
+ int c;
+ do {
+ if (!buffer.hasRemaining()) {
+ buffer.position(start);
+ final int current = buffer.limit() - start;
+ if (current >= buffer.capacity()) {
+ throw new
DataStoreContentException(Resources.format(Resources.Keys.ExcessiveHeaderSize_1,
input.filename));
+ }
+ input.ensureBufferContains(current + 1);
+ buffer.position(current);
+ start = 0;
+ }
+ c = Byte.toUnsignedInt(buffer.get());
+ } while (c > SPACE);
+ return subSequence(start, buffer.position() - 1);
+ }
+
+ /**
+ * Returns a copy of the buffer content over the given range of bytes.
+ * This method should be invoked only for small ranges (e.g. less than 80
characters).
+ * We use it for parsing floating point numbers.
+ *
+ * @param start the start index, inclusive.
+ * @param end the end index, exclusive.
+ * @return the specified subsequence
+ */
+ @Override
+ public String subSequence(final int start, final int end) {
+ final int length = end - start;
+ if (direct) {
+ return new String(array, start, length, StandardCharsets.US_ASCII);
+ } else if (length <= array.length) {
+ JDK9.get(buffer, start, array, 0, length);
+ return new String(array, 0, length, StandardCharsets.US_ASCII);
+ } else {
+ final byte[] data = new byte[length];
+ JDK9.get(buffer, start, data);
+ return new String(data, StandardCharsets.US_ASCII);
+ }
+ }
+
+ /**
+ * Returns a string representation of the buffer content.
+ * Note that it represents only a truncated view of the file content.
+ */
+ public String toString() {
+ return subSequence(0, length());
+ }
+}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java
new file mode 100644
index 0000000..68d4178
--- /dev/null
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java
@@ -0,0 +1,420 @@
+/*
+ * 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.internal.storage.ascii;
+
+import java.util.Map;
+import java.util.List;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.StringJoiner;
+import java.io.IOException;
+import java.nio.file.StandardOpenOption;
+import java.awt.image.DataBufferDouble;
+import org.opengis.geometry.Envelope;
+import org.opengis.metadata.Metadata;
+import org.opengis.metadata.maintenance.ScopeCode;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.datum.PixelInCell;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridCoverageBuilder;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreClosedException;
+import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.storage.DataStoreReferencingException;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.internal.storage.MetadataBuilder;
+import org.apache.sis.internal.storage.PRJDataStore;
+import org.apache.sis.internal.storage.RangeArgument;
+import org.apache.sis.internal.storage.io.ChannelDataInput;
+import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.metadata.sql.MetadataStoreException;
+import org.apache.sis.referencing.operation.matrix.Matrix3;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.resources.Vocabulary;
+
+
+/**
+ * A data store which creates grid coverages from an ESRI ASCII grid file.
+ * The header contains (<var>key</var> <var>value</var>) pairs,
+ * one pair per line and using spaces as separator between keys and values.
+ * The package javadoc lists the recognized keywords.
+ *
+ * If we allow subclasses in a future version,
+ * subclasses can add their own (<var>key</var>, <var>value</var>) pairs or
modify
+ * the existing ones by overriding the {@link #processHeader(Map)} method.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since 1.2
+ * @module
+ */
+final class Store extends PRJDataStore implements GridCoverageResource {
+ /**
+ * The object to use for reading data, or {@code null} if this store has
been closed.
+ */
+ private CharactersView input;
+
+ /**
+ * The {@code NCOLS} and {@code NROWS} attributes read from the header.
+ * Those values are valid only if {@link #gridGeometry} is non-null.
+ */
+ private int width, height;
+
+ /**
+ * The optional {@code NODATA_VALUE} attribute, or {@code NaN} if none.
+ * This value is valid only if {@link #gridGeometry} is non-null.
+ */
+ private double fillValue;
+
+ /**
+ * The {@link #fillValue} as a text. This is useful when the fill value
+ * can not be parsed as a {@code double} value, for example {@code "N/A"}.
+ */
+ private String fillText;
+
+ /**
+ * The image size together with the "grid to CRS" transform.
+ * This is also used as a flag for checking whether the
+ * {@code "*.prj"} file and the header have been read.
+ */
+ private GridGeometry gridGeometry;
+
+ /**
+ * Description of the single band contained in the ASCII Grid file.
+ */
+ private SampleDimension band;
+
+ /**
+ * The metadata object, or {@code null} if not yet created.
+ */
+ private DefaultMetadata metadata;
+
+ /**
+ * The full coverage, read when first requested then cached.
+ * We cache the full coverage on the assumption that the
+ * ASCII Grid format is not used for very large images.
+ */
+ private GridCoverage coverage;
+
+ /**
+ * Creates a new ASCII Grid store from the given file, URL or stream.
+ *
+ * @param provider the factory that created this {@code DataStore}
instance, or {@code null} if unspecified.
+ * @param connector information about the storage (URL, stream,
<i>etc</i>).
+ * @throws DataStoreException if an error occurred while opening the
stream.
+ */
+ public Store(final StoreProvider provider, final StorageConnector
connector) throws DataStoreException {
+ super(provider, connector);
+ fillValue = Double.NaN;
+ input = new CharactersView(connector.commit(ChannelDataInput.class,
StoreProvider.NAME));
+ listeners.useWarningEventsOnly();
+ }
+
+ /**
+ * Reads the {@code "*.prj"} file and the header if not already done.
+ * This method does nothing if the data store is already initialized.
+ * After a successful return, {@link #gridGeometry} is guaranteed non-null.
+ *
+ * <p>Note: we don't do this initialization in the constructor
+ * for giving a chance for users to register listeners first.</p>
+ */
+ private void readHeader() throws DataStoreException {
+ if (gridGeometry == null) try {
+ final Map<String,String> header = input().readHeader();
+ final Matrix3 gridToCRS = new Matrix3();
+ PixelInCell anchor = PixelInCell.CELL_CORNER;
+ String key = null; // Used for error message if an exception
is thrown.
+ try {
+ width = Integer.parseInt(headerValue(header, key = "NCOLS"));
+ height = Integer.parseInt(headerValue(header, key = "NROWS"));
+ /*
+ * The ESRI ASCII Grid format has only a "CELLSIZE" property
for both axes.
+ * The "DX" and "DY" properties are GDAL extensions and
considered optional.
+ * If the de-facto standard "CELLSIZE" property exists, "DX"
and "DY" will
+ * be considered unexpected.
+ */
+ String value = header.remove(key = "CELLSIZE");
+ if (value != null) {
+ gridToCRS.m00 = gridToCRS.m11 = Double.parseDouble(value);
+ } else {
+ int def = 0;
+ value = header.remove(key = "DX"); if (value != null)
{gridToCRS.m00 = Double.parseDouble(value); def |= 1;}
+ value = header.remove(key = "DY"); if (value != null)
{gridToCRS.m11 = Double.parseDouble(value); def |= 2;}
+ if (def != 3) {
+ // Report "CELLSIZE" as the missing property because
it is the de-facto standard one.
+ throw new
DataStoreContentException(illegalValue(Errors.Keys.MissingValueForProperty_2,
"CELLSIZE"));
+ }
+ }
+ /*
+ * Lower-left coordinates is specified either by CENTER or
CORNER property.
+ * If both are missing, the error message reports that CORNER
is missing.
+ */
+ value = header.remove(key = "XLLCENTER");
+ final boolean xCenter = (value != null);
+ if (!xCenter) {
+ value = headerValue(header, key = "XLLCORNER");
+ }
+ gridToCRS.m02 = Double.parseDouble(value);
+ value = header.remove(key = "YLLCENTER");
+ final boolean yCenter = (value != null);
+ if (!yCenter) {
+ value = headerValue(header, key = "YLLCORNER");
+ }
+ gridToCRS.m12 = Double.parseDouble(value);
+ if (xCenter & yCenter) {
+ anchor = PixelInCell.CELL_CENTER;
+ } else if (xCenter != yCenter) {
+ gridToCRS.convertBefore(xCenter ? 0 : 1, null, 0.5);
+ }
+ /*
+ * "No data" value is an optional property. Default value is
NaN.
+ * This reader accepts a value specified as text.
+ */
+ fillText = header.remove(key = "NODATA_VALUE");
+ if (fillText != null) try {
+ fillValue = Double.parseDouble(fillText);
+ } catch (NumberFormatException e) {
+
listeners.warning(illegalValue(Errors.Keys.IllegalValueForProperty_2, key), e);
+ }
+ } catch (NumberFormatException e) {
+ throw new
DataStoreContentException(illegalValue(Errors.Keys.IllegalValueForProperty_2,
key), e);
+ }
+ /*
+ * Read the auxiliary PRJ file after we finished parsing the
header file.
+ * A future version could skip this step if we add a non-standard
"CRS" property in the header.
+ */
+ readPRJ();
+ gridGeometry = new GridGeometry(new GridExtent(width, height),
anchor, MathTransforms.linear(gridToCRS), crs);
+ /*
+ * If there is any unprocessed properties, log warnings about them.
+ */
+ if (!header.isEmpty()) {
+ final StringJoiner joiner = new StringJoiner(", ");
+ header.keySet().forEach(joiner::add);
+ listeners.warning(Errors.getResources(getLocale()).getString(
+ Errors.Keys.UnexpectedProperty_2,
input.input.filename, joiner.toString()));
+ }
+ } catch (DataStoreException e) {
+ closeOnError(e);
+ throw e;
+ } catch (Exception e) {
+ closeOnError(e);
+ throw new DataStoreException(e);
+ }
+ }
+
+ /**
+ * Returns the error message for an exception or log record.
+ *
+ * @param rk {@link Errors.Keys#IllegalValueForProperty_2} or {@link
Errors.Keys#MissingValueForProperty_2}.
+ * @param key key of the header property which was requested.
+ * @return the message to use in the exception to be thrown or the warning
to be logged.
+ */
+ private String illegalValue(final short rk, final String key) {
+ return Errors.getResources(getLocale()).getString(rk,
input.input.filename, key);
+ }
+
+ /**
+ * Gets a value from the header map and ensures that it is non-null.
+ *
+ * @param header map of (key, value) pair from the header.
+ * @param key the name of the properties to get.
+ * @return the value, guaranteed to be non-null.
+ * @throws DataStoreException if the value was null.
+ */
+ private String headerValue(final Map<String,String> header, final String
key) throws DataStoreException {
+ final String value = header.remove(key);
+ if (value == null) {
+ throw new
DataStoreContentException(illegalValue(Errors.Keys.MissingValueForProperty_2,
key));
+ }
+ return value;
+ }
+
+ /**
+ * Returns the metadata associated to the ASII grid file, or {@code null}
if none.
+ *
+ * @return the metadata associated to the CSV file, or {@code null} if
none.
+ * @throws DataStoreException if an error occurred during the parsing
process.
+ */
+ @Override
+ public synchronized Metadata getMetadata() throws DataStoreException {
+ if (metadata == null) {
+ readHeader();
+ final MetadataBuilder builder = new MetadataBuilder();
+ try {
+ builder.setFormat("ASCGRD");
+ } catch (MetadataStoreException e) {
+ builder.addFormatName(StoreProvider.NAME);
+ listeners.warning(e);
+ }
+ builder.addEncoding(encoding, MetadataBuilder.Scope.METADATA);
+ builder.addResourceScope(ScopeCode.COVERAGE, null);
+ try {
+ builder.addExtent(gridGeometry.getEnvelope());
+ } catch (TransformException e) {
+ throw new DataStoreReferencingException(getLocale(),
StoreProvider.NAME, getDisplayName(), null).initCause(e);
+ }
+ addTitleOrIdentifier(builder);
+ builder.setISOStandards(false);
+ metadata = builder.buildAndFreeze();
+ }
+ return metadata;
+ }
+
+ /**
+ * Returns the spatiotemporal extent of CSV data in coordinate reference
system of the CSV file.
+ *
+ * @return the spatiotemporal resource extent.
+ * @throws DataStoreException if an error occurred while computing the
envelope.
+ */
+ @Override
+ public Optional<Envelope> getEnvelope() throws DataStoreException {
+ return Optional.ofNullable(getGridGeometry().getEnvelope());
+ }
+
+ /**
+ * Returns the valid extent of grid coordinates together with the
conversion from those grid
+ * coordinates to real world coordinates.
+ *
+ * @return extent of grid coordinates together with their mapping to "real
world" coordinates.
+ * @throws DataStoreException if an error occurred while reading
definitions from the underlying data store.
+ */
+ @Override
+ public synchronized GridGeometry getGridGeometry() throws
DataStoreException {
+ readHeader();
+ return gridGeometry;
+ }
+
+ /**
+ * Returns the ranges of sample values together with the conversion from
samples to real values.
+ * ASCII Grid files always contain a single band.
+ *
+ * @return ranges of sample values together with their mapping to "real
values".
+ * @throws DataStoreException if an error occurred while reading
definitions from the underlying data store.
+ */
+ @Override
+ public synchronized List<SampleDimension> getSampleDimensions() throws
DataStoreException {
+ readHeader();
+ if (band == null) {
+ read(null, null);
+ }
+ return Collections.singletonList(band);
+ }
+
+ /**
+ * Loads the data. If a non-null grid geometry is specified, then this
method may return a sub-sampled image.
+ *
+ * @param domain desired grid extent and resolution, or {@code null} for
reading the whole domain.
+ * @param range shall be either 0 or an containing only 0.
+ * @return the grid coverage for the specified domain.
+ * @throws DataStoreException if an error occurred while reading the grid
coverage data.
+ */
+ @Override
+ public synchronized GridCoverage read(final GridGeometry domain, final
int... range) throws DataStoreException {
+ RangeArgument.validate(1, range, listeners);
+ if (coverage == null) try {
+ readHeader();
+ final CharactersView input = input();
+ final double[] data = new double[width * height];
+ double minimum = Double.POSITIVE_INFINITY;
+ double maximum = Double.NEGATIVE_INFINITY;
+ for (int i=0; i < data.length; i++) {
+ final String token = input.readToken();
+ double value;
+ try {
+ value = Double.parseDouble(token);
+ if (value == fillValue) {
+ value = Double.NaN;
+ } else {
+ if (value < minimum) minimum = value;
+ if (value > maximum) maximum = value;
+ }
+ } catch (NumberFormatException e) {
+ if (token.equals(fillText)) {
+ value = Double.NaN;
+ } else {
+ throw new DataStoreContentException(e);
+ }
+ }
+ data[i] = value;
+ }
+ if (!(minimum <= maximum)) {
+ minimum = 0;
+ maximum = 1;
+ }
+ final SampleDimension.Builder b = new SampleDimension.Builder();
+ if (!Double.isNaN(fillValue)) {
+ b.setBackground(null, fillValue);
+ }
+
b.addQuantitative(Vocabulary.formatInternational(Vocabulary.Keys.Values),
minimum, maximum, null);
+ band = b.build();
+ coverage = new GridCoverageBuilder()
+ .addRange(band)
+ .setDomain(gridGeometry)
+ .setValues(new DataBufferDouble(data, data.length), null)
+ .build();
+ } catch (IOException e) {
+ closeOnError(e);
+ throw new DataStoreException(e);
+ }
+ return coverage;
+ }
+
+ /**
+ * Returns the input if it has not been closed.
+ */
+ private CharactersView input() throws DataStoreException {
+ final CharactersView in = input;
+ if (in == null) {
+ throw new DataStoreClosedException(getLocale(),
StoreProvider.NAME, StandardOpenOption.READ);
+ }
+ return in;
+ }
+
+ /**
+ * Closes this data store and releases any underlying resources.
+ *
+ * @throws DataStoreException if an error occurred while closing this data
store.
+ */
+ @Override
+ public synchronized void close() throws DataStoreException {
+ final CharactersView view = input;
+ input = null; // Cleared first in case of failure.
+ if (view != null) try {
+ view.input.channel.close();
+ } catch (IOException e) {
+ throw new DataStoreException(e);
+ }
+ }
+
+ /**
+ * Closes this data store after an unrecoverable error occurred.
+ * The caller is expected to throw the given exception after this method
call.
+ */
+ private void closeOnError(final Throwable e) {
+ try {
+ close();
+ } catch (Throwable s) {
+ e.addSuppressed(s);
+ }
+ }
+}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/StoreProvider.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/StoreProvider.java
new file mode 100644
index 0000000..8dc8957
--- /dev/null
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/StoreProvider.java
@@ -0,0 +1,92 @@
+/*
+ * 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.internal.storage.ascii;
+
+import org.apache.sis.storage.DataStore;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.ProbeResult;
+import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.internal.storage.Capability;
+import org.apache.sis.internal.storage.StoreMetadata;
+import org.apache.sis.internal.storage.PRJDataStore;
+
+
+/**
+ * The provider of {@link Store} instances.
+ * Given a {@link StorageConnector} input, this class tries to instantiate an
ESRI ASCII Grid {@code Store}.
+ *
+ * <h2>Thread safety</h2>
+ * The same {@code StoreProvider} instance can be safely used by many threads
without synchronization on
+ * the part of the caller. However the {@link Store} instances created by this
factory are not thread-safe.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since 1.2
+ * @module
+ */
+@StoreMetadata(formatName = StoreProvider.NAME,
+ fileSuffixes = {"asc", "grd", "agr"},
+ capabilities = Capability.READ,
+ resourceTypes = GridCoverageResource.class)
+public final class StoreProvider extends PRJDataStore.Provider {
+ /**
+ * The format names for ESRI ASCII grid files.
+ */
+ static final String NAME = "ASCII Grid";
+
+ /**
+ * Creates a new provider.
+ */
+ public StoreProvider() {
+ }
+
+ /**
+ * Returns a generic name for this data store, used mostly in warnings or
error messages.
+ *
+ * @return a short name or abbreviation for the data format.
+ */
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ /**
+ * Returns {@link ProbeResult#SUPPORTED} if the given storage appears to
be supported by ASCII Grid {@link Store}.
+ * 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.
+ *
+ * @return {@link ProbeResult#SUPPORTED} if the given storage seems to be
readable as an ASCII Grid file.
+ * @throws DataStoreException if an I/O or SQL error occurred.
+ */
+ @Override
+ public ProbeResult probeContent(StorageConnector connector) throws
DataStoreException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ /**
+ * Returns a CSV {@link Store} implementation associated with this
provider.
+ *
+ * @param connector information about the storage (URL, stream,
<i>etc</i>).
+ * @return a data store implementation associated with this provider for
the given storage.
+ * @throws DataStoreException if an error occurred while creating the data
store instance.
+ */
+ @Override
+ public DataStore open(final StorageConnector connector) throws
DataStoreException {
+ return new Store(this, connector);
+ }
+}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/package-info.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/package-info.java
new file mode 100644
index 0000000..a20a161
--- /dev/null
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/package-info.java
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+/**
+ * {@link org.apache.sis.storage.DataStore} implementation for ESRI ASCII grid
format.
+ * This is a very simple format for reading and writing single-banded raster
data.
+ * As the "ASCII" name implies, files are text files in US-ASCII character
encoding
+ * no matter what the {@link org.apache.sis.setup.OptionKey#ENCODING} value is,
+ * and numbers are parsed or formatted according the US locale no matter
+ * what the {@link org.apache.sis.setup.OptionKey#LOCALE} value is.
+ *
+ * <p>ASCII grid files contains a header before the actual data.
+ * The header contains (<var>key</var> <var>value</var>) pairs,
+ * one pair per line and using spaces as separator between keys and values
+ * (Apache SIS accepts also {@code '='} and {@code ':'} as separators).
+ * The valid keys are listed in the table below
+ * (note that some of them are extensions to the ESRI ASCII Grid format).</p>
+ *
+ * <table class="sis">
+ * <caption>Recognized keywords in ASCII Grid header</caption>
+ * <tr>
+ * <th>Keyword</th>
+ * <th>Value type</th>
+ * <th>Obligation</th>
+ * </tr>
+ * <tr>
+ * <td>{@code NCOLS}</td>
+ * <td>Integer</td>
+ * <td>Mandatory</td>
+ * </tr>
+ * <tr>
+ * <td>{@code NROWS}</td>
+ * <td>Integer</td>
+ * <td>Mandatory</td>
+ * </tr>
+ * <tr>
+ * <td>{@code XLLCORNER} or {@code XLLCENTER}</td>
+ * <td>Floating point</td>
+ * <td>Mandatory</td>
+ * </tr>
+ * <tr>
+ * <td>{@code YLLCORNER} or {@code YLLCENTER}</td>
+ * <td>Floating point</td>
+ * <td>Mandatory</td>
+ * </tr>
+ * <tr>
+ * <td>{@code CELLSIZE}</td>
+ * <td>Floating point</td>
+ * <td>Mandatory, unless {@code DX} and {@code DY} are present</td>
+ * </tr>
+ * <tr>
+ * <td>{@code DX} and {@code DY}</td>
+ * <td>Floating point</td>
+ * <td>Accepted but non-standard</td>
+ * </tr>
+ * <tr>
+ * <td>{@code NODATA_VALUE}</td>
+ * <td>Floating point</td>
+ * <td>Optional</td>
+ * </tr>
+ * </table>
+ *
+ * <h2>Extensions</h2>
+ * The implementation in this package adds the following extensions
+ * (some of them are taken from GDAL):
+ *
+ * <ul class="verbose">
+ * <li>Coordinate reference system specified by auxiliary {@code *.prj} file.
+ * If the format is WKT 1, the GDAL variant is used (that variant
differs from
+ * the OGC 01-009 standard in their interpretation of units of
measurement).</li>
+ * <li>{@code DX} and {@code DY} parameters in the header are used instead
of {@code CELLSIZE}
+ * if the pixels are non-square.</li>
+ * <li>Lines in the header starting with {@code '#'} are ignored as comment
lines.</li>
+ * </ul>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since 1.2
+ * @module
+ */
+package org.apache.sis.internal.storage.ascii;
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
index 48d6eea..c0396df 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
@@ -394,7 +394,7 @@ public class StoreListeners implements Localized {
* Reports a warning described by the given message.
*
* <p>This method is a shortcut for <code>{@linkplain #warning(Level,
String, Exception)
- * warning}({@linkplain Level#WARNING}, null, exception)</code>.</p>
+ * warning}({@linkplain Level#WARNING}, message, null)</code>.</p>
*
* @param message the warning message to report.
*/