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 <martin.desruisse...@geomatys.com> 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. */