sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Add more metadata read from Landsat and netCDF (side-effect of Hao's work on CSW). Relocate Metadata.dataQualityInfo.lineage.statement to Metadata.resourceLineage.statement in netCDF file (https://issues.apache.org/jira/browse/SIS-361). Fix an erroneous use of CSW abbreviation (discovered while integrating above parts of CSW work).
Date Tue, 10 Jul 2018 17:33:59 GMT
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 c5aa9d2  Add more metadata read from Landsat and netCDF (side-effect of Hao's work
on CSW). Relocate Metadata.dataQualityInfo.lineage.statement to Metadata.resourceLineage.statement
in netCDF file (https://issues.apache.org/jira/browse/SIS-361). Fix an erroneous use of CSW
abbreviation (discovered while integrating above parts of CSW work).
c5aa9d2 is described below

commit c5aa9d252c6f431eb444b66fa7b9b9a3578e160b
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Jul 7 18:56:54 2018 +0200

    Add more metadata read from Landsat and netCDF (side-effect of Hao's work on CSW).
    Relocate Metadata.dataQualityInfo.lineage.statement to Metadata.resourceLineage.statement
in netCDF file (https://issues.apache.org/jira/browse/SIS-361).
    Fix an erroneous use of CSW abbreviation (discovered while integrating above parts of
CSW work).
---
 .../iso/maintenance/DefaultScopeDescription.java   |   2 +-
 .../main/java/org/apache/sis/xml/Namespaces.java   |  13 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 .../storage/earthobservation/LandsatReader.java    |  23 ++--
 .../earthobservation/LandsatReaderTest.java        |  10 +-
 .../apache/sis/storage/geotiff/Compression.java    |  28 ++---
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |   1 +
 .../apache/sis/storage/netcdf/MetadataReader.java  |  69 +++++-----
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |   2 +-
 .../sis/storage/netcdf/MetadataReaderTest.java     |   8 +-
 .../sis/internal/storage/MetadataBuilder.java      | 139 ++++++++++++++++++++-
 .../org/apache/sis/internal/storage/csv/Store.java |   4 +-
 14 files changed, 233 insertions(+), 73 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
index d47878f..6b2c2e0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
@@ -271,7 +271,7 @@ public class DefaultScopeDescription extends ISOMetadata implements ScopeDescrip
      * Returns the dataset to which the information applies.
      *
      * <div class="note"><b>Example:</b>
-     * If a geographic data provider is generating vector mapping for thee administrative
areas
+     * If a geographic data provider is generating vector mapping for the administrative
areas
      * and if the data were processed in the same way, then the provider could record the
bulk
      * of initial data at {@link ScopeCode#DATASET} level with a
      * “<cite>Administrative area A, B &amp; C</cite>” description.
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/Namespaces.java b/core/sis-metadata/src/main/java/org/apache/sis/xml/Namespaces.java
index c3ceaee..d0997e6 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/xml/Namespaces.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/Namespaces.java
@@ -505,9 +505,17 @@ public final class Namespaces extends Static {
      * The <code>{@value}</code> URL.
      * The usual prefix for this namespace is {@code "csw"}.
      *
+     * <p>History</p>
+     * <table class="sis">
+     *   <caption>Change log</caption>
+     *   <tr><th>SIS version</th> <th>URL</th></tr>
+     *   <tr><td>0.3</td>         <td>http://www.opengis.net/cat/csw/2.0.2</td></tr>
+     *   <tr><td>Since 1.0</td>   <td>http://www.opengis.net/cat/csw/3.0</td></tr>
+     * </table>
+     *
      * @category OGC
      */
-    public static final String CSW = "http://www.opengis.net/cat/csw/2.0.2";
+    public static final String CSW = "http://www.opengis.net/cat/csw/3.0";
 
     /**
      * The <code>{@value}</code> URL.
@@ -557,7 +565,8 @@ public final class Namespaces extends Static {
         p.put("http://www.opengis.net/sensorML/1.0",                     "sml1");
         p.put("http://www.opengis.net/sensorML/1.0.1",                    "sml");
         p.put("http://www.opengis.net/swe/1.0",                          "swe1");
-        p.put("http://www.opengis.net/cat/csw/2.0.2",                     "csw");
+        p.put("http://www.opengis.net/cat/csw/3.0",                       "csw");
+        p.put("http://www.opengis.net/ows/2.0",                           "ows");
         p.put("http://www.opengis.net/cat/wrs/1.0",                       "wrs");
         p.put("http://www.opengis.net/cat/wrs",                         "wrs09");
         p.put("http://www.opengis.net/ows-6/utds/0.3",                   "utds");
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
index 982badc..a40a766 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
@@ -257,6 +257,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short Details = 33;
 
         /**
+         * Digital elevation model
+         */
+        public static final short DigitalElevationModel = 146;
+
+        /**
          * Dimensions
          */
         public static final short Dimensions = 34;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
index 72250e5..fde71db 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
@@ -54,6 +54,7 @@ Description             = Description
 Designation             = Designation
 Destination             = Destination
 Details                 = Details
+DigitalElevationModel   = Digital elevation model
 Dimensions              = Dimensions
 Directory               = Directory
 DittoMark               = \u2033
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
index 39351ab..f1c9476 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -61,6 +61,7 @@ Description             = Description
 Designation             = D\u00e9signation
 Destination             = Destination
 Details                 = D\u00e9tails
+DigitalElevationModel   = Mod\u00e8le num\u00e9rique de terrain
 Dimensions              = Dimensions
 Directory               = R\u00e9pertoire
 DittoMark               = \u2033
diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
index cb86d8f..45eaea3 100644
--- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
+++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
@@ -33,6 +33,7 @@ import java.time.temporal.Temporal;
 
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.citation.DateType;
+import org.opengis.metadata.identification.TopicCategory;
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.metadata.content.CoverageContentType;
 import org.opengis.metadata.content.TransferFunctionType;
@@ -98,7 +99,7 @@ import static org.apache.sis.internal.util.CollectionsExt.singletonOrNull;
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Rémi Maréchal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.8
  * @module
  */
@@ -497,25 +498,32 @@ final class LandsatReader {
              * The identifier to inform the user of the product type.
              * Value can be "L1T" or "L1GT".
              */
-// TODO     case "DATA_TYPE":
+            case "DATA_TYPE": {
+                metadata.setProcessingLevelCode("Landsat", value);
+                break;
+            }
             /*
              * Indicates the source of the DEM used in the correction process.
              * Value can be "GLS2000", "RAMP" or "GTOPO30".
              */
-// TODO     case "ELEVATION_SOURCE":
+            case "ELEVATION_SOURCE": {
+                metadata.addSource(value, ScopeCode.MODEL,
+                        Vocabulary.formatInternational(Vocabulary.Keys.DigitalElevationModel));
+                break;
+            }
             /*
              * The output format of the image.
              * Value is "GEOTIFF".
              */
             case "OUTPUT_FORMAT": {
-                if (Constants.GEOTIFF.equalsIgnoreCase(value)) {
+                if (Constants.GEOTIFF.equalsIgnoreCase(value)) try {
                     value = Constants.GEOTIFF;              // Because 'metadata.setFormat(…)'
is case-sensitive.
-                }
-                try {
                     metadata.setFormat(value);
+                    break;
                 } catch (MetadataStoreException e) {
                     warning(key, null, e);
                 }
+                metadata.addFormatName(value);
                 break;
             }
             /*
@@ -888,6 +896,7 @@ final class LandsatReader {
     final Metadata getMetadata() throws FactoryException {
         metadata.addLanguage(Locale.ENGLISH, MetadataBuilder.Scope.METADATA);
         metadata.addResourceScope(ScopeCode.COVERAGE, null);
+        metadata.addTopicCategory(TopicCategory.GEOSCIENTIFIC_INFORMATION);
         try {
             flushSceneTime();
         } catch (DateTimeException e) {
@@ -967,7 +976,7 @@ final class LandsatReader {
                 }
             }
             result.setMetadataStandards(Citations.ISO_19115);
-            result.freeze();
+            result.apply(DefaultMetadata.State.FINAL);
         }
         return result;
     }
diff --git a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
index 994d8ac..aa887c0 100644
--- a/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
+++ b/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
@@ -28,6 +28,7 @@ import org.opengis.metadata.citation.DateType;
 import org.opengis.metadata.content.CoverageContentType;
 import org.opengis.metadata.content.TransferFunctionType;
 import org.opengis.metadata.identification.Progress;
+import org.opengis.metadata.identification.TopicCategory;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.util.FactoryException;
@@ -110,6 +111,7 @@ public class LandsatReaderTest extends TestCase {
             "metadataScope[0].resourceScope",                                           
            ScopeCode.COVERAGE,
             "dateInfo[0].date",                                                         
            date("2016-06-27 16:48:12"),
             "dateInfo[0].dateType",                                                     
            DateType.CREATION,
+            "identificationInfo[0].topicCategory[0]",                                   
            TopicCategory.GEOSCIENTIFIC_INFORMATION,
             "identificationInfo[0].citation.date[0].date",                              
            date("2016-06-27 16:48:12"),
             "identificationInfo[0].citation.date[0].dateType",                          
            DateType.CREATION,
             "identificationInfo[0].citation.title",                                     
            "LandsatTest",
@@ -132,6 +134,10 @@ public class LandsatReaderTest extends TestCase {
             "acquisitionInformation[0].operation[0].status",                       Progress.COMPLETED,
             "acquisitionInformation[0].operation[0].type",                         OperationType.REAL,
 
+            "contentInfo[0].processingLevelCode.authority.title",          "Landsat",
+            "contentInfo[0].processingLevelCode.codeSpace",                "Landsat",
+            "contentInfo[0].processingLevelCode.code",                     "Pseudo LT1",
+
             "contentInfo[0].attributeGroup[0].attribute[0].name[0].code",  "TestImage_B1.TIF",
             "contentInfo[0].attributeGroup[0].attribute[1].name[0].code",  "TestImage_B2.TIF",
             "contentInfo[0].attributeGroup[0].attribute[2].name[0].code",  "TestImage_B3.TIF",
@@ -261,6 +267,8 @@ public class LandsatReaderTest extends TestCase {
             "spatialRepresentationInfo[0].transformationParameterAvailability",      false,
             "spatialRepresentationInfo[1].transformationParameterAvailability",      false,
             "spatialRepresentationInfo[0].checkPointAvailability",                   false,
-            "spatialRepresentationInfo[1].checkPointAvailability",                   false);
+            "spatialRepresentationInfo[1].checkPointAvailability",                   false,
+
+            "resourceLineage[0].source[0].description", "Pseudo GLS");
     }
 }
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Compression.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Compression.java
index 119461d..74359c1 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Compression.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Compression.java
@@ -22,11 +22,11 @@ package org.apache.sis.storage.geotiff;
  * Data compression applies only to raster image data. All other TIFF fields are unaffected.
  *
  * <p>Except otherwise noted, field names in this class are upper-case variant of the
names
- * used in Coverage Web Services (CSW) as specified in the following specification:</p>
+ * used in Web Coverage Service (WCS) as specified in the following specification:</p>
  *
  * <blockquote>OGC 12-100: GML Application Schema - Coverages - GeoTIFF Coverage Encoding
Profile</blockquote>
  *
- * The main exception is {@code CCITT}, which has different name in CSW query and response.
+ * The main exception is {@code CCITT}, which has different name in WCS query and response.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -39,8 +39,8 @@ enum Compression {
      * No compression, but pack data into bytes as tightly as possible, leaving no unused
bits except
      * potentially at the end of rows. The component values are stored as an array of type
byte.
      * <ul>
-     *   <li>Name in CSW query:    "None"</li>
-     *   <li>Name in CSW response: "None"</li>
+     *   <li>Name in WCS query:    "None"</li>
+     *   <li>Name in WCS response: "None"</li>
      * </ul>
      */
     NONE(1),
@@ -48,8 +48,8 @@ enum Compression {
     /**
      * CCITT Group 3, 1-Dimensional Modified Huffman run length encoding.
      * <ul>
-     *   <li>Name in CSW query:    "Huffman"</li>
-     *   <li>Name in CSW response: "CCITTRLE"</li>
+     *   <li>Name in WCS query:    "Huffman"</li>
+     *   <li>Name in WCS response: "CCITTRLE"</li>
      * </ul>
      */
     CCITTRLE(2),
@@ -57,8 +57,8 @@ enum Compression {
     /**
      * PackBits compression, a simple byte-oriented run length scheme.
      * <ul>
-     *   <li>Name in CSW query:    "PackBits"</li>
-     *   <li>Name in CSW response: "PackBits"</li>
+     *   <li>Name in WCS query:    "PackBits"</li>
+     *   <li>Name in WCS response: "PackBits"</li>
      * </ul>
      */
     PACKBITS(32773),
@@ -68,8 +68,8 @@ enum Compression {
     /**
      * LZW compression.
      * <ul>
-     *   <li>Name in CSW query:    "LZW"</li>
-     *   <li>Name in CSW response: "LZW"</li>
+     *   <li>Name in WCS query:    "LZW"</li>
+     *   <li>Name in WCS response: "LZW"</li>
      * </ul>
      */
     LZW(5),
@@ -78,8 +78,8 @@ enum Compression {
      * Deflate compression, like ZIP format. This is sometime named {@code "ADOBE_DEFLATE"},
      * withe the {@code "DEFLATE"} name used for another compression method with code 32946.
      * <ul>
-     *   <li>Name in CSW query:    "Deflate"</li>
-     *   <li>Name in CSW response: "Deflate"</li>
+     *   <li>Name in WCS query:    "Deflate"</li>
+     *   <li>Name in WCS response: "Deflate"</li>
      *   <li>Other name:           "ADOBE_DEFLATE"</li>
      * </ul>
      */
@@ -88,8 +88,8 @@ enum Compression {
     /**
      * JPEG compression.
      * <ul>
-     *   <li>Name in CSW query:    "JPEG"</li>
-     *   <li>Name in CSW response: "JPEG"</li>
+     *   <li>Name in WCS query:    "JPEG"</li>
+     *   <li>Name in WCS response: "JPEG"</li>
      *   <li>Name of old JPEG:     "OJPEG" (code 6)</li>
      * </ul>
      */
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
index a14c270..3a1e2c6 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -122,6 +122,7 @@ public class GeoTiffStore extends DataStore {
             try {
                 builder.setFormat(Constants.GEOTIFF);
             } catch (MetadataStoreException e) {
+                builder.addFormatName(Constants.GEOTIFF);
                 warning(null, e);
             }
             builder.addEncoding(encoding, MetadataBuilder.Scope.METADATA);
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
index 4f0a4e2..975692a 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
@@ -49,16 +49,13 @@ import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.crs.VerticalCRS;
 
 import org.apache.sis.util.iso.Types;
-import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.metadata.iso.DefaultMetadata;
 import org.apache.sis.metadata.iso.citation.*;
 import org.apache.sis.metadata.iso.identification.*;
-import org.apache.sis.metadata.iso.lineage.DefaultSource;
-import org.apache.sis.metadata.iso.lineage.DefaultLineage;
-import org.apache.sis.metadata.iso.quality.DefaultDataQuality;
+import org.apache.sis.metadata.sql.MetadataStoreException;
 import org.apache.sis.internal.netcdf.Axis;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.Variable;
@@ -109,6 +106,7 @@ import static org.apache.sis.internal.util.CollectionsExt.first;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
+ * @author  Thi Phuong Hao Nguyen (VNSC)
  * @version 1.0
  * @since   0.3
  * @module
@@ -321,18 +319,6 @@ split:  while ((start = CharSequences.skipLeadingWhitespaces(value, start,
lengt
     }
 
     /**
-     * Adds the given element in the given collection if the element is not already present
in the collection.
-     * We define this method because the metadata API uses collections while the SIS implementation
uses lists.
-     * The lists are usually very short (typically 0 or 1 element), so the call to {@link
List#contains(Object)}
-     * should be cheap.
-     */
-    private static <T> void addIfAbsent(final Collection<T> collection, final
T element) {
-        if (!collection.contains(element)) {
-            collection.add(element);
-        }
-    }
-
-    /**
      * Adds the given element in the given collection if the element is non-null.
      * If the element is non-null and the collection is null, a new collection is
      * created. The given collection, or the new collection if it has been created,
@@ -647,10 +633,11 @@ split:  while ((start = CharSequences.skipLeadingWhitespaces(value,
start, lengt
      *
      * @param  publisher   the publisher names, built by the caller in an opportunist way.
      */
-    private void addIdentificationInfo(final Set<InternationalString> publisher) {
-        boolean     hasExtent = false;
-        Set<String> project   = null;
-        Set<String> standard  = null;
+    private void addIdentificationInfo(final Set<InternationalString> publisher) throws
IOException, DataStoreException {
+        boolean     hasExtent   = false;
+        Set<String> project     = null;
+        Set<String> standard    = null;
+        boolean     hasDataType = false;
         final Set<String> keywords = new LinkedHashSet<>();
         for (final String path : searchPath) {
             decoder.setSearchPath(path);
@@ -661,7 +648,9 @@ split:  while ((start = CharSequences.skipLeadingWhitespaces(value, start,
lengt
                 addAccessConstraint(forCodeName(Restriction.class, keyword));
             }
             addTopicCategory(forEnumName(TopicCategory.class, stringValue(TOPIC_CATEGORY)));
-            addSpatialRepresentation(forCodeName(SpatialRepresentationType.class, stringValue(DATA_TYPE)));
+            SpatialRepresentationType dt = forCodeName(SpatialRepresentationType.class, stringValue(DATA_TYPE));
+            addSpatialRepresentation(dt);
+            hasDataType |= (dt != null);
             if (!hasExtent) {
                 /*
                  * Takes only ONE extent, because a netCDF file may declare many time the
same
@@ -672,6 +661,14 @@ split:  while ((start = CharSequences.skipLeadingWhitespaces(value, start,
lengt
             }
         }
         /*
+         * Add spatial representation type only if it was not explicitly given in the metadata.
+         * The call to getGridGeometries() may be relatively costly, so we don't want to
invoke
+         * it without necessity.
+         */
+        if (!hasDataType && decoder.getGridGeometries().length != 0) {
+            addSpatialRepresentation(SpatialRepresentationType.GRID);
+        }
+        /*
          * For the following properties, use only the first non-empty attribute value found
on the search path.
          */
         decoder.setSearchPath(searchPath);
@@ -694,6 +691,12 @@ split:  while ((start = CharSequences.skipLeadingWhitespaces(value, start,
lengt
             addBoundingPolygon(new StoreFormat(decoder.geomlib, decoder.listeners).parseGeometry(wkt,
                     stringValue(GEOSPATIAL_BOUNDS + "_crs"), stringValue(GEOSPATIAL_BOUNDS
+ "_vertical_crs")));
         }
+        try {
+            setFormat("NetCDF");
+        } catch (MetadataStoreException e) {
+            addFormatName("NetCDF");
+            warning(e);
+        }
     }
 
     /**
@@ -1047,31 +1050,17 @@ split:  while ((start = CharSequences.skipLeadingWhitespaces(value,
start, lengt
         }
         addFileIdentifier();
         /*
-         * Add history in Metadata.dataQualityInfo.lineage.statement as specified by UnidataDD2MI.xsl.
-         * However Metadata.resourceLineage.statement could be a more appropriate place.
+         * Deperture: UnidataDD2MI.xsl puts the source in Metadata.dataQualityInfo.lineage.statement.
+         * However since ISO 19115:2014, Metadata.resourceLineage.statement seems a more
appropriate place.
          * See https://issues.apache.org/jira/browse/SIS-361
          */
-        final DefaultMetadata metadata = build(false);
         for (final String path : searchPath) {
             decoder.setSearchPath(path);
-            DefaultLineage lineage = null;
-            String value = stringValue(HISTORY);
-            if (value != null) {
-                lineage = new DefaultLineage();
-                lineage.setStatement(new SimpleInternationalString(value));
-            }
-            value = stringValue(SOURCE);
-            if (value != null) {
-                if (lineage == null) lineage = new DefaultLineage();
-                addIfAbsent(lineage.getSources(), new DefaultSource(value));
-            }
-            if (lineage != null) {
-                final DefaultDataQuality quality = new DefaultDataQuality(ScopeCode.DATASET);
-                quality.setLineage(lineage);
-                addIfAbsent(metadata.getDataQualityInfo(), quality);
-            }
+            addLineage(stringValue(HISTORY));
+            addSource(stringValue(SOURCE), null, null);
         }
         decoder.setSearchPath(searchPath);
+        final DefaultMetadata metadata = build(false);
         metadata.setMetadataStandards(Citations.ISO_19115);
         addCompleteMetadata(createURI(stringValue(METADATA_LINK)));
         return metadata;
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
index 6de3431..077307c 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
@@ -115,7 +115,7 @@ public class NetcdfStore extends DataStore implements Aggregate {
             final MetadataReader reader = new MetadataReader(decoder);
             metadata = reader.read();
             if (metadata instanceof ModifiableMetadata) {
-                ((ModifiableMetadata) metadata).freeze();
+                ((ModifiableMetadata) metadata).apply(ModifiableMetadata.State.FINAL);
             }
         } catch (IOException e) {
             throw new DataStoreException(e);
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 bea71b2..4ccd6d7 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
@@ -103,6 +103,11 @@ public final strictfp class MetadataReaderTest extends TestCase {
         verifier.addPropertyToIgnore(Metadata.class, "metadataStandard");
         verifier.addMetadataToVerify(actual);
         verifier.assertMetadataEquals(
+            // Hard-coded
+            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.alternateTitle[0]",
"NetCDF",
+            "identificationInfo[0].resourceFormat[0].formatSpecificationCitation.title",
"NetCDF Classic and 64-bit Offset Format",
+
+            // Read from the file
             "dateInfo[0].date",                                                        date("2018-05-15
13:01:00"),
             "dateInfo[0].dateType",                                                    DateType.REVISION,
             "metadataScope[0].resourceScope",                                          ScopeCode.DATASET,
@@ -149,7 +154,6 @@ public final strictfp class MetadataReaderTest extends TestCase {
             "contentInfo[0].attributeGroup[0].attribute[0].offset",                    -1.85,
             "contentInfo[0].attributeGroup[0].attribute[0].units",                     "°C",
 
-            "dataQualityInfo[0].lineage.statement", "Decimated and modified by GeoAPI for
inclusion in conformance test suite.",
-            "dataQualityInfo[0].scope.level",       ScopeCode.DATASET);
+            "resourceLineage[0].statement", "Decimated and modified by GeoAPI for inclusion
in conformance test suite.");
     }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
index f61006b..7b1eac6 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
@@ -110,6 +110,9 @@ import org.apache.sis.metadata.iso.acquisition.DefaultRequirement;
 import org.apache.sis.metadata.iso.lineage.DefaultLineage;
 import org.apache.sis.metadata.iso.lineage.DefaultProcessStep;
 import org.apache.sis.metadata.iso.lineage.DefaultProcessing;
+import org.apache.sis.metadata.iso.lineage.DefaultSource;
+import org.apache.sis.metadata.iso.maintenance.DefaultScope;
+import org.apache.sis.metadata.iso.maintenance.DefaultScopeDescription;
 import org.apache.sis.metadata.sql.MetadataStoreException;
 import org.apache.sis.metadata.sql.MetadataSource;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -136,6 +139,7 @@ import org.opengis.metadata.citation.Responsibility;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
+ * @author  Thi Phuong Hao Nguyen (VNSC)
  * @version 1.0
  * @since   0.8
  * @module
@@ -925,19 +929,36 @@ public class MetadataBuilder {
      * Storage location is:
      *
      * <ul>
-     *   <li>{@code metadata/identificationInfo/resourceFormat/formatSpecificationCitation/alternateTitle}</li>
+     *   <li>{@code metadata/identificationInfo/resourceFormat}</li>
      * </ul>
      *
+     * This method should be invoked <strong>before</strong> any other method
writing in the
+     * {@code identificationInfo/resourceFormat} node. If this exception throws an exception,
+     * than that exception should be reported as a warning. Example:
+     *
+     * {@preformat java
+     *     try {
+     *         metadata.setFormat("MyFormat");
+     *     } catch (MetadataStoreException e) {
+     *         metadata.addFormatName("MyFormat");
+     *         listeners.warning(null, e);
+     *     }
+     *     metadata.addCompression("decompression technique");
+     * }
+     *
      * @param  abbreviation  the format short name or abbreviation, or {@code null} for no-operation.
      * @throws MetadataStoreException  if this method can not connect to the {@code jdbc/SpatialMetadata}
database.
      *         Callers should generally handle this exception as a recoverable one (i.e.
log a warning and continue).
      *
      * @see #addCompression(CharSequence)
+     * @see #addFormatName(CharSequence)
      */
     public final void setFormat(final String abbreviation) throws MetadataStoreException
{
         if (abbreviation != null && abbreviation.length() != 0) {
             if (format == null) {
                 format = MetadataSource.getProvided().lookup(Format.class, abbreviation);
+            } else {
+                addFormatName(abbreviation);
             }
         }
     }
@@ -2404,12 +2425,17 @@ parse:      for (int i = 0; i < length;) {
 
     /**
      * Sets an identifier for the level of processing that has been applied to the coverage.
+     * For image descriptions, this is the image distributor's code that identifies the level
+     * of radiometric and geometric processing that has been applied.
      * Storage location is:
      *
      * <ul>
      *   <li>{@code metadata/contentInfo/processingLevelCode}</li>
      * </ul>
      *
+     * Note that another storage location exists at {@code metadata/identificationInfo/processingLevel}
+     * but is currently not used.
+     *
      * @param  authority        identifies which controlled list of code is used, or {@code
null} if none.
      * @param  processingLevel  identifier for the level of processing that has been applied
to the resource,
      *                          or {@code null} for no-operation.
@@ -2598,6 +2624,64 @@ parse:      for (int i = 0; i < length;) {
     }
 
     /**
+     * Adds a general explanation of the data producer's knowledge about the lineage of a
dataset.
+     * If a statement already exists, the new one will be appended after a new line.
+     * Storage location is:
+     *
+     * <ul>
+     *   <li>{@code metadata/resourceLineage/statement}</li>
+     * </ul>
+     *
+     * @param statement  explanation of the data producer's knowledge about the lineage,
or {@code null} for no-operation.
+     *
+     * @see #addProcessDescription(CharSequence)
+     */
+    public final void addLineage(final CharSequence statement) {
+        final InternationalString i18n = trim(statement);
+        if (i18n != null) {
+            final DefaultLineage lineage = lineage();
+            lineage.setStatement(append(lineage.getStatement(), i18n));
+        }
+    }
+
+    /**
+     * Adds information about a source of data used for producing the resource.
+     * Storage location is:
+     *
+     * <ul>
+     *   <li>{@code metadata/resourceLineage/source/description}</li>
+     *   <li>{@code metadata/resourceLineage/source/scope/level}</li>
+     *   <li>{@code metadata/resourceLineage/source/scope/levelDescription/features}</li>
+     * </ul>
+     *
+     * <div class="note"><b>Example:</b>
+     * if a Landsat image uses the "GTOPO30" digital elevation model, then it can declare
the source
+     * with "GTOPO30" description, {@link ScopeCode#MODEL} and feature "Digital Elevation
Model".</div>
+     *
+     * @param  description  a detailed description of the level of the source data, or {@code
null} if none.
+     * @param  level        hierarchical level of the source (e.g. model), or {@code null} if
unspecified.
+     * @param  feature      more detailed name for {@code level}, or {@code null} if none.
+     *
+     * @see #addProcessing(CharSequence, String)
+     * @see #addProcessDescription(CharSequence)
+     */
+    public final void addSource(final CharSequence description, final ScopeCode level, final
CharSequence feature) {
+        final InternationalString i18n = trim(description);
+        if (i18n != null) {
+            final DefaultSource source = new DefaultSource(description);
+            if (level != null || feature != null) {
+                DefaultScope scope = new DefaultScope(level);
+                if (feature != null) {
+                    final DefaultScopeDescription sd = new DefaultScopeDescription();
+                    sd.getFeatures().add(feature);
+                    scope.getLevelDescription().add(sd);
+                }
+            }
+            addIfNotPresent(lineage().getSources(), source);
+        }
+    }
+
+    /**
      * Adds information about the procedure, process and algorithm applied in a process step.
      * If a processing was already defined with a different identifier, then a new processing
      * instance will be created. Storage location is:
@@ -2611,6 +2695,8 @@ parse:      for (int i = 0; i < length;) {
      *
      * @see #addSoftwareReference(CharSequence)
      * @see #addHostComputer(CharSequence)
+     * @see #addProcessDescription(CharSequence)
+     * @see #addSource(CharSequence, ScopeCode, CharSequence)
      */
     public final void addProcessing(final CharSequence authority, String identifier) {
         if (identifier != null && !(identifier = identifier.trim()).isEmpty()) {
@@ -2640,6 +2726,9 @@ parse:      for (int i = 0; i < length;) {
      * </ul>
      *
      * @param  title  title of the document that describe the software, or {@code null} for
no-operation.
+     *
+     * @see #addProcessing(CharSequence, String)
+     * @see #addSource(CharSequence, ScopeCode, CharSequence)
      */
     public final void addSoftwareReference(final CharSequence title) {
         final InternationalString i18n = trim(title);
@@ -2658,6 +2747,9 @@ parse:      for (int i = 0; i < length;) {
      * </ul>
      *
      * @param  platform  name of the system on which the processing has been executed, or
{@code null} for no-operation.
+     *
+     * @see #addProcessing(CharSequence, String)
+     * @see #addSource(CharSequence, ScopeCode, CharSequence)
      */
     public final void addHostComputer(final CharSequence platform) {
         InternationalString i18n = trim(platform);
@@ -2678,6 +2770,10 @@ parse:      for (int i = 0; i < length;) {
      * </ul>
      *
      * @param  description  additional details about the process step, or {@code null} for
no-operation.
+     *
+     * @see #addProcessing(CharSequence, String)
+     * @see #addSource(CharSequence, ScopeCode, CharSequence)
+     * @see #addLineage(CharSequence)
      */
     public final void addProcessDescription(final CharSequence description) {
         final InternationalString i18n = trim(description);
@@ -2688,6 +2784,36 @@ parse:      for (int i = 0; i < length;) {
     }
 
     /**
+     * Adds a name to the resource format. Note that this method does not add a new format,
+     * but only an alternative name to current format. Storage location is:
+     *
+     * <ul>
+     *   <li>{@code metadata/identificationInfo/resourceFormat/formatSpecificationCitation/alternateTitle}</li>
+     * </ul>
+     *
+     * If this method is used together with {@link #setFormat(String)},
+     * then {@code setFormat} should be invoked <strong>before</strong> this
method.
+     *
+     * @param value  the format name, or {@code null} for no-operation.
+     *
+     * @see #setFormat(String)
+     * @see #addCompression(CharSequence)
+     */
+    public final void addFormatName(final CharSequence value) {
+        final InternationalString i18n = trim(value);
+        if (i18n != null) {
+            final DefaultFormat format = format();
+            DefaultCitation citation = DefaultCitation.castOrCopy(format.getFormatSpecificationCitation());
+            if (citation == null) {
+                citation = new DefaultCitation(i18n);
+            } else {
+                addIfNotPresent(citation.getAlternateTitles(), i18n);
+            }
+            format.setFormatSpecificationCitation(citation);
+        }
+    }
+
+    /**
      * Adds a compression name.
      * Storage location is:
      *
@@ -2695,9 +2821,13 @@ parse:      for (int i = 0; i < length;) {
      *   <li>{@code metadata/identificationInfo/resourceFormat/fileDecompressionTechnique}</li>
      * </ul>
      *
+     * If this method is used together with {@link #setFormat(String)},
+     * then {@code setFormat} should be invoked <strong>before</strong> this
method.
+     *
      * @param value  the compression name, or {@code null} for no-operation.
      *
      * @see #setFormat(String)
+     * @see #addFormatName(CharSequence)
      */
     public final void addCompression(final CharSequence value) {
         final InternationalString i18n = trim(value);
@@ -2731,8 +2861,8 @@ parse:      for (int i = 0; i < length;) {
      * Returns the metadata (optionally as an unmodifiable object), or {@code null} if none.
      * If {@code freeze} is {@code true}, then the returned metadata instance can not be
modified.
      *
-     * @param  freeze  {@code true} if this method should {@linkplain DefaultMetadata#freeze()
freeze}
-     *                 the metadata instance before to return it.
+     * @param  freeze  {@code true} if this method should set the returned metadata to
+     *                 {@link DefaultMetadata.State#FINAL}, or {@code false} for leaving
the metadata editable.
      * @return the metadata, or {@code null} if none.
      */
     public final DefaultMetadata build(final boolean freeze) {
@@ -2742,10 +2872,11 @@ parse:      for (int i = 0; i < length;) {
         newCoverage(false);
         newAcquisition();
         newDistribution();
+        newLineage();
         final DefaultMetadata md = metadata;
         metadata = null;
         if (freeze && md != null) {
-            md.freeze();
+            md.apply(DefaultMetadata.State.FINAL);
         }
         return md;
     }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
index 5d843d5..fe1aff8 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
@@ -627,9 +627,11 @@ final class Store extends URIDataStore implements FeatureSet {
     public synchronized Metadata getMetadata() throws DataStoreException {
         if (metadata == null) {
             final MetadataBuilder builder = new MetadataBuilder();
+            final String format = (timeEncoding != null) && hasTrajectories ? StoreProvider.MOVING
: StoreProvider.NAME;
             try {
-                builder.setFormat(timeEncoding != null && hasTrajectories ? StoreProvider.MOVING
: StoreProvider.NAME);
+                builder.setFormat(format);
             } catch (MetadataStoreException e) {
+                builder.addFormatName(format);
                 listeners.warning(null, e);
             }
             builder.addEncoding(encoding, MetadataBuilder.Scope.ALL);


Mime
View raw message