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 a18611b  Initial support of `GEO_METADATA` and `GDAL_METADATA` tags 
when reading GeoTIFF image.
a18611b is described below

commit a18611b19198329b8f19a3a77310484c44f6b2ac
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Jan 7 06:34:39 2022 +0100

    Initial support of `GEO_METADATA` and `GDAL_METADATA` tags when reading 
GeoTIFF image.
---
 .../src/test/java/org/apache/sis/test/Assert.java  |   2 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    |  12 +-
 .../org/apache/sis/storage/geotiff/Reader.java     |   9 +-
 .../apache/sis/storage/geotiff/XMLMetadata.java    | 195 ++++++++++++++++++++-
 .../sis/storage/geotiff/XMLMetadataTest.java       | 106 +++++++++++
 .../apache/sis/test/suite/GeoTiffTestSuite.java    |   3 +-
 .../sis/internal/storage/MetadataBuilder.java      | 183 +++++++++----------
 7 files changed, 410 insertions(+), 100 deletions(-)

diff --git a/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java 
b/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java
index 53c3482..9a1b6d9 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java
@@ -130,7 +130,7 @@ public strictfp class Assert extends 
org.opengis.test.Assert {
 
     /**
      * Asserts that two strings are equal, ignoring the differences in EOL 
characters.
-     * The comparisons is performed one a line-by-line basis. For each line, 
trailing
+     * The comparisons are performed on a line-by-line basis. For each line, 
trailing
      * spaces (but not leading spaces) are ignored.
      *
      * @param  expected  the expected string.
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 3e7a3f2..ba9bd62 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -526,9 +526,7 @@ final class ImageFileDirectory extends DataCube {
      * @throws UnsupportedOperationException if the given type is {@link 
Type#UNDEFINED}.
      * @throws DataStoreException if a logical error is found or an 
unsupported TIFF feature is used.
      */
-    Object addEntry(final short tag, final Type type, final long count)
-            throws IOException, ParseException, DataStoreException
-    {
+    Object addEntry(final short tag, final Type type, final long count) throws 
Exception {
         switch (tag) {
 
             
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1116,10 +1114,16 @@ final class ImageFileDirectory extends DataCube {
 
             
////////////////////////////////////////////////////////////////////////////////////////////////
             ////                                                               
                         ////
-            ////    Extensions defined by GDAL.                                
                         ////
+            ////    Extensions defined by DGIWG or GDAL.                       
                         ////
             ////                                                               
                         ////
             
////////////////////////////////////////////////////////////////////////////////////////////////
 
+            case Tags.GEO_METADATA:
+            case Tags.GDAL_METADATA: {
+                final XMLMetadata parser = new XMLMetadata(reader, type, 
count, tag == Tags.GDAL_METADATA);
+                parser.appendTo(metadata);
+                break;
+            }
             case Tags.GDAL_NODATA: {
                 noData = type.readDouble(input(), count);
                 break;
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
index 770836f..3af73e0 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
@@ -24,7 +24,6 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.io.IOException;
 import java.nio.ByteOrder;
-import java.text.ParseException;
 import org.opengis.util.NameFactory;
 import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.storage.DataStoreException;
@@ -289,7 +288,9 @@ final class Reader extends GeoTIFF {
                          * without value (count = 0) - in principle illegal 
but we make this reader tolerant.
                          */
                         error = dir.addEntry(tag, type, count);
-                    } catch (ParseException | RuntimeException e) {
+                    } catch (IOException | DataStoreException e) {
+                        throw e;
+                    } catch (Exception e) {
                         error = e;
                     }
                     if (error != null) {
@@ -361,7 +362,9 @@ final class Reader extends GeoTIFF {
                 Object error;
                 try {
                     error = entry.owner.addEntry(entry.tag, entry.type, 
entry.count);
-                } catch (ParseException | RuntimeException e) {
+                } catch (IOException | DataStoreException e) {
+                    throw e;
+                } catch (Exception e) {
                     error = e;
                 }
                 if (error != null) {
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/XMLMetadata.java
 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/XMLMetadata.java
index 8e7465d..299163f 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/XMLMetadata.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/XMLMetadata.java
@@ -16,13 +16,18 @@
  */
 package org.apache.sis.storage.geotiff;
 
+import java.util.Locale;
 import java.util.Iterator;
+import java.util.Collections;
 import java.util.StringJoiner;
+import java.util.logging.Filter;
+import java.util.logging.LogRecord;
 import java.io.IOException;
 import java.io.StringReader;
 import java.io.ByteArrayInputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.time.Instant;
 import javax.xml.stream.XMLEventReader;
 import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLStreamException;
@@ -30,10 +35,19 @@ import javax.xml.stream.events.XMLEvent;
 import javax.xml.stream.events.Attribute;
 import javax.xml.stream.events.Characters;
 import javax.xml.stream.events.StartElement;
+import javax.xml.transform.stax.StAXSource;
+import javax.xml.bind.JAXBException;
+import javax.xml.namespace.QName;
+import org.apache.sis.internal.util.StandardDateFormat;
+import org.apache.sis.internal.storage.MetadataBuilder;
+import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.util.collection.DefaultTreeTable;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.xml.XML;
+
+import static org.apache.sis.internal.util.TemporalUtilities.toDate;
 
 
 /**
@@ -57,7 +71,12 @@ import org.apache.sis.util.resources.Errors;
  * @since 1.2
  * @module
  */
-final class XMLMetadata {
+final class XMLMetadata implements Filter {
+    /**
+     * The {@value} string, used in GDAL metadata.
+     */
+    private static final String ITEM = "Item", NAME = "name";
+
     /**
      * The bytes to decode as an XML document.
      * DGIWG specification mandates UTF-8 encoding.
@@ -76,6 +95,11 @@ final class XMLMetadata {
     private String currentElement;
 
     /**
+     * Where to report non-fatal warnings.
+     */
+    private final StoreListeners listeners;
+
+    /**
      * {@code true} if the XML is GDAL metadata. Example:
      *
      * {@preformat xml
@@ -88,6 +112,15 @@ final class XMLMetadata {
     private final boolean isGDAL;
 
     /**
+     * Creates a new instance with the given XML. Used for testing purposes.
+     */
+    XMLMetadata(final String xml, final boolean isGDAL) {
+        this.isGDAL = isGDAL;
+        string = xml;
+        listeners = null;
+    }
+
+    /**
      * Creates new metadata which will decode the given vector of bytes.
      *
      * @param  reader  the TIFF reader.
@@ -97,6 +130,7 @@ final class XMLMetadata {
      */
     XMLMetadata(final Reader reader, final Type type, final long count, final 
boolean isGDAL) throws IOException {
         this.isGDAL = isGDAL;
+        listeners = reader.store.listeners();
         switch (type) {
             case ASCII: {
                 final String[] cs = type.readString(reader.input, count, 
reader.store.encoding);
@@ -196,6 +230,7 @@ final class XMLMetadata {
          *
          * @param  source  the XML document to represent as a tree table.
          * @param  target  where to append this root node.
+         * @param  name    name to assign to this root node.
          * @return {@code true} on success, or {@code false} if the XML 
document could not be decoded.
          */
         Root(final XMLMetadata source, final DefaultTreeTable.Node parent, 
final String name) {
@@ -239,14 +274,14 @@ final class XMLMetadata {
         final String previous = currentElement;
         currentElement = element.getName().getLocalPart();
         node.setValue(Root.NAME, currentElement);
-        final boolean isItem = isGDAL && currentElement.equals("Item");
+        final boolean isItem = isGDAL && currentElement.equals(ITEM);
         final Iterator<Attribute> attributes = element.getAttributes();
         while (attributes.hasNext()) {
             final Attribute attribute = attributes.next();
             if (attribute.isSpecified()) {
                 final String name  = attribute.getName().getLocalPart();
                 final String value = attribute.getValue();
-                if (isItem && name.equals("name")) {
+                if (isItem && name.equals(NAME)) {
                     /*
                      * GDAL metadata does not really use of XML schema.
                      * Instead, it is a collection of lines like below:
@@ -286,4 +321,158 @@ final class XMLMetadata {
         }
         currentElement = previous;
     }
+
+    /**
+     * Appends the content of this object to the given metadata builder.
+     *
+     * @param  metadata  the builder where to append the content of this 
{@code XMLMetadata}.
+     * @throws XMLStreamException if an error occurred while parsing the XML.
+     * @throws JAXBException if an error occurred while parsing the XML.
+     */
+    public void appendTo(final MetadataBuilder metadata) throws 
XMLStreamException, JAXBException {
+        final XMLEventReader reader = toXML();
+        if (reader != null) {
+            if (isGDAL) {
+                /*
+                 * We expect a list of XML elements as below:
+                 *
+                 *   <Item 
name="acquisitionEndDate">2016-09-08T15:53:00+05:00</Item>
+                 *
+                 * Those items should be children of <GDALMetadata> node. That 
node should be the root node,
+                 * but current implementation searches <GDALMetadata> 
recursively if not found at the root.
+                 */
+                final Parser parser = new Parser(reader, metadata);
+                while (reader.hasNext()) {
+                    final XMLEvent event = reader.nextEvent();
+                    if (event.isStartElement()) {
+                        parser.root(event.asStartElement());
+                    }
+                }
+                parser.flush();
+            } else {
+                /*
+                 * Parse as an ISO 19115 document and get the content as a 
`Metadata` object.
+                 * Some other types are accepted as well (e.g. 
`IdentificationInfo`).
+                 * The `mergeMetadata` method applies heuristic rules for 
adding components.
+                 */
+                metadata.mergeMetadata(XML.unmarshal(new StAXSource(reader),
+                        Collections.singletonMap(XML.WARNING_FILTER, this)), 
listeners.getLocale());
+            }
+            reader.close();     // No need to close the underlying input 
stream.
+        }
+    }
+
+    /**
+     * Parser of GDAL metadata.
+     */
+    private static final class Parser {
+        /** The XML reader from which to get XML elements. */
+        private final XMLEventReader reader;
+
+        /** A qualified name with the {@value #NAME} local part, used for 
searching attributes. */
+        private final QName name;
+
+        /** A value increased for each level of nested {@code <Item>} element. 
*/
+        private int depth;
+
+        /** Where to write metadata. */
+        private final MetadataBuilder metadata;
+
+        /** Temporary storage for metadata values that need a little 
processing. */
+        private Instant startTime, endTime;
+
+        /**
+         * Creates a new reader.
+         *
+         * @param reader     the source of XML elements.
+         * @param metadata   the target of metadata elements.
+         */
+        Parser(final XMLEventReader reader, final MetadataBuilder metadata) {
+            this.reader = reader;
+            this.metadata = metadata;
+            name = new QName(NAME);
+        }
+
+        /**
+         * Parses a {@code <GDALMetadata>} element and its children. After 
this method returns,
+         * the reader is positioned after the closing {@code </GDALMetadata>} 
tag.
+         *
+         * @param  start  the {@code <GDALMetadata>} element.
+         */
+        void root(final StartElement start) throws XMLStreamException {
+            final boolean parse = 
start.getName().getLocalPart().equals("GDALMetadata");
+            while (reader.hasNext()) {
+                final XMLEvent event = reader.nextEvent();
+                if (event.isStartElement()) {
+                    if (parse) {
+                        item(event.asStartElement());       // Parse <Item> 
elements.
+                    } else {
+                        root(event.asStartElement());       // Search a nested 
<GDALMetadata>.
+                    }
+                } else if (event.isEndElement()) {
+                    break;
+                }
+            }
+        }
+
+        /**
+         * Parses a {@code <Item>} element and its children. After this method 
returns,
+         * the reader is positioned after the closing {@code </Item>} tag.
+         *
+         * @param  start  the {@code <Item>} element.
+         */
+        private void item(final StartElement start) throws XMLStreamException {
+            String attribute = null;
+            if (depth == 0 && start.getName().getLocalPart().equals(ITEM)) {
+                final Attribute a = start.getAttributeByName(name);
+                if (a != null) attribute = a.getValue();
+            }
+            final StringJoiner buffer = new StringJoiner("");
+            while (reader.hasNext()) {
+                final XMLEvent event = reader.nextEvent();
+                if (event.isEndElement()) {
+                    break;
+                } else if (event.isCharacters()) {
+                    buffer.add(event.asCharacters().getData());
+                } else if (event.isStartElement()) {
+                    depth++;
+                    item(event.asStartElement());
+                    depth--;
+                }
+            }
+            if (attribute != null) {
+                if (Character.isUpperCase(attribute.codePointAt(0))) {
+                    attribute = attribute.toLowerCase(Locale.US);
+                }
+                final String content = buffer.toString();
+                if (!content.isEmpty()) {
+                    switch (attribute) {
+                        case "acquisitionStartDate": startTime = 
StandardDateFormat.parseInstantUTC(content); break;
+                        case "acquisitionEndDate":   endTime   = 
StandardDateFormat.parseInstantUTC(content); break;
+                        case "title": metadata.addTitle(content); break;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Writes to {@link MetadataBuilder} all information that were pending 
parsing completion.
+         */
+        void flush() {
+            try {
+                metadata.addTemporalExtent(toDate(startTime), toDate(endTime));
+            } catch (UnsupportedOperationException e) {
+                // Ignore (this operation requires "sis-temporal" module).
+            }
+        }
+    }
+
+    /**
+     * Invoked when a non-fatal warning occurs during the parsing of XML 
document.
+     */
+    @Override
+    public boolean isLoggable(final LogRecord warning) {
+        listeners.warning(warning);
+        return false;
+    }
 }
diff --git 
a/storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/XMLMetadataTest.java
 
b/storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/XMLMetadataTest.java
new file mode 100644
index 0000000..cb21eba
--- /dev/null
+++ 
b/storage/sis-geotiff/src/test/java/org/apache/sis/storage/geotiff/XMLMetadataTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.geotiff;
+
+import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.internal.storage.MetadataBuilder;
+import org.apache.sis.util.collection.DefaultTreeTable;
+import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Tests the {@link XMLMetadata} enumeration.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+public final strictfp class XMLMetadataTest extends TestCase {
+    /**
+     * A GDAL metadata.
+     */
+    private static final String GDAL_METADATA =
+            "<GDALMetadata>\n" +
+            "  <Item name=\"TITLE\">My image</Item>\n" +
+            "  <Item name=\"SCALE\" sample=\"-3\">0.015</Item>" +
+            "  <Item 
name=\"acquisitionStartDate\">2018-02-28T04:48:00</Item>\n" +
+            "  <Item name=\"acquisitionEndDate\">2018-02-28T03:04:00</Item>\n" 
+
+            "  <Foo>bar</Foo>\n" +
+            "</GDALMetadata>\n";
+
+    /**
+     * Tests parsing of GDAL metadata and formatting as a tree table.
+     * THe XML document is like below:
+     *
+     * {@preformat xml
+     *   <GDALMetadata>
+     *     <Item name="SCALE" sample="-3">0.015</Item>
+     *     <Item name="acquisitionStartDate">2018-02-28T04:48:00</Item>
+     *     <Item name="acquisitionEndDate">2018-02-28T03:04:00</Item>
+     *   </GDALMetadata>
+     * }
+     *
+     * The tree table output is expected to <em>not</em> contains the "Item" 
word,
+     * because this is redundancy repeated for all nodes. Instead "Item" 
should be
+     * replaced by the value of the "name" attribute.
+     */
+    @Test
+    public void testTreeGDAL() {
+        XMLMetadata xml = new XMLMetadata(GDAL_METADATA, true);
+        assertSame(GDAL_METADATA, xml.toString());
+        assertFalse(xml.isEmpty());
+        DefaultTreeTable     table = new DefaultTreeTable(TableColumn.NAME, 
TableColumn.VALUE);
+        DefaultTreeTable.Node root = (DefaultTreeTable.Node ) table.getRoot();
+        DefaultTreeTable.Node node = new XMLMetadata.Root(xml, root, "Test");
+        assertEquals("Test", node.getValue(TableColumn.NAME));
+        root.setValue(TableColumn.NAME, "Root");
+        assertMultilinesEquals(
+                "Root\n" +
+                "  └─Test\n" +
+                "      └─GDALMetadata\n" +
+                "          ├─TITLE…………………………………………… My image\n" +
+                "          ├─SCALE…………………………………………… 0.015\n" +
+                "          │   └─sample……………………………… -3\n" +
+                "          ├─acquisitionStartDate…… 2018-02-28T04:48:00\n" +
+                "          ├─acquisitionEndDate………… 2018-02-28T03:04:00\n" +
+                "          └─Foo………………………………………………… bar\n",
+                table.toString());
+    }
+
+    /**
+     * Tests parsing GDAL metadata and conversion to ISO 19115 metadata.
+     *
+     * @throws Exception if an error occurred during XML parsing.
+     */
+    @Test
+    public void testMetadataGDAL() throws Exception {
+        XMLMetadata xml = new XMLMetadata(GDAL_METADATA, true);
+        MetadataBuilder builder = new MetadataBuilder();
+        xml.appendTo(builder);
+        DefaultMetadata metadata = builder.build(false);
+        assertMultilinesEquals(
+                "Metadata\n" +
+                "  └─Identification info\n" +
+                "      └─Citation………………… My image\n",
+                metadata.toString());
+    }
+}
diff --git 
a/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java
 
b/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java
index 69f2ba6..7540ccb 100644
--- 
a/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java
+++ 
b/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java
@@ -25,7 +25,7 @@ import org.junit.BeforeClass;
  * All tests from the {@code sis-geotiff} module, in rough dependency order.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.8
  * @module
  */
@@ -36,6 +36,7 @@ import org.junit.BeforeClass;
     org.apache.sis.storage.geotiff.TypeTest.class,
     org.apache.sis.storage.geotiff.GeoKeysTest.class,
     org.apache.sis.storage.geotiff.CRSBuilderTest.class,
+    org.apache.sis.storage.geotiff.XMLMetadataTest.class,
     org.apache.sis.storage.geotiff.SelfConsistencyTest.class
 })
 public final strictfp class GeoTiffTestSuite extends TestSuite {
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 a969c23..5e498bf 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
@@ -30,6 +30,7 @@ import java.util.List;
 import java.util.Map;
 import java.net.URI;
 import java.nio.charset.Charset;
+import java.time.Instant;
 import javax.measure.Unit;
 import javax.measure.quantity.Length;
 import org.opengis.util.MemberName;
@@ -37,41 +38,18 @@ import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.Geometry;
-import org.opengis.metadata.Metadata;
-import org.opengis.metadata.Identifier;
-import org.opengis.metadata.citation.Role;
-import org.opengis.metadata.citation.DateType;
-import org.opengis.metadata.citation.Citation;
-import org.opengis.metadata.citation.CitationDate;
-import org.opengis.metadata.citation.OnLineFunction;
-import org.opengis.metadata.citation.OnlineResource;
-import org.opengis.metadata.identification.Identification;
-import org.opengis.metadata.extent.Extent;
-import org.opengis.metadata.spatial.GCP;
-import org.opengis.metadata.spatial.Dimension;
-import org.opengis.metadata.spatial.DimensionNameType;
-import org.opengis.metadata.spatial.CellGeometry;
-import org.opengis.metadata.spatial.PixelOrientation;
-import org.opengis.metadata.spatial.GeolocationInformation;
-import org.opengis.metadata.spatial.SpatialRepresentation;
-import org.opengis.metadata.spatial.SpatialRepresentationType;
-import org.opengis.metadata.constraint.Constraints;
-import org.opengis.metadata.constraint.Restriction;
-import org.opengis.metadata.content.ContentInformation;
-import org.opengis.metadata.content.CoverageContentType;
-import org.opengis.metadata.content.TransferFunctionType;
-import org.opengis.metadata.maintenance.ScopeCode;
-import org.opengis.metadata.acquisition.Context;
-import org.opengis.metadata.acquisition.OperationType;
-import org.opengis.metadata.identification.Progress;
-import org.opengis.metadata.identification.KeywordType;
-import org.opengis.metadata.identification.Resolution;
-import org.opengis.metadata.identification.TopicCategory;
-import org.opengis.metadata.distribution.Distribution;
-import org.opengis.metadata.distribution.Distributor;
-import org.opengis.metadata.distribution.Format;
-import org.opengis.metadata.lineage.Lineage;
+import org.opengis.metadata.*;
+import org.opengis.metadata.acquisition.*;
+import org.opengis.metadata.citation.*;
+import org.opengis.metadata.constraint.*;
+import org.opengis.metadata.content.*;
+import org.opengis.metadata.distribution.*;
+import org.opengis.metadata.extent.*;
+import org.opengis.metadata.identification.*;
+import org.opengis.metadata.lineage.*;
+import org.opengis.metadata.maintenance.*;
 import org.opengis.metadata.quality.Element;
+import org.opengis.metadata.spatial.*;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
@@ -81,62 +59,24 @@ import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.simple.SimpleDuration;
 import org.apache.sis.geometry.AbstractEnvelope;
-import org.apache.sis.metadata.iso.DefaultMetadata;
-import org.apache.sis.metadata.iso.DefaultIdentifier;
-import org.apache.sis.metadata.iso.DefaultMetadataScope;
-import org.apache.sis.metadata.iso.extent.DefaultExtent;
-import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
-import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
-import org.apache.sis.metadata.iso.extent.DefaultBoundingPolygon;
-import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
-import org.apache.sis.metadata.iso.extent.DefaultGeographicDescription;
-import org.apache.sis.metadata.iso.spatial.DefaultGridSpatialRepresentation;
-import org.apache.sis.metadata.iso.spatial.DefaultDimension;
-import org.apache.sis.metadata.iso.spatial.DefaultGeorectified;
-import org.apache.sis.metadata.iso.spatial.DefaultGeoreferenceable;
-import org.apache.sis.metadata.iso.spatial.DefaultGCPCollection;
-import org.apache.sis.metadata.iso.spatial.DefaultGCP;
-import org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
-import org.apache.sis.metadata.iso.content.DefaultSampleDimension;
-import org.apache.sis.metadata.iso.content.DefaultBand;
-import org.apache.sis.metadata.iso.content.DefaultCoverageDescription;
-import org.apache.sis.metadata.iso.content.DefaultFeatureCatalogueDescription;
-import org.apache.sis.metadata.iso.content.DefaultRangeElementDescription;
-import org.apache.sis.metadata.iso.content.DefaultImageDescription;
-import org.apache.sis.metadata.iso.content.DefaultFeatureTypeInfo;
-import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.metadata.iso.citation.AbstractParty;
-import org.apache.sis.metadata.iso.citation.DefaultSeries;
-import org.apache.sis.metadata.iso.citation.DefaultCitation;
-import org.apache.sis.metadata.iso.citation.DefaultCitationDate;
-import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
-import org.apache.sis.metadata.iso.citation.DefaultIndividual;
-import org.apache.sis.metadata.iso.citation.DefaultOrganisation;
-import org.apache.sis.metadata.iso.citation.DefaultOnlineResource;
-import org.apache.sis.metadata.iso.constraint.DefaultLegalConstraints;
-import org.apache.sis.metadata.iso.identification.DefaultKeywords;
-import org.apache.sis.metadata.iso.identification.DefaultResolution;
-import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
-import org.apache.sis.metadata.iso.distribution.DefaultFormat;
-import org.apache.sis.metadata.iso.distribution.DefaultDistributor;
-import org.apache.sis.metadata.iso.distribution.DefaultDistribution;
-import org.apache.sis.metadata.iso.acquisition.DefaultAcquisitionInformation;
-import org.apache.sis.metadata.iso.acquisition.DefaultEvent;
-import org.apache.sis.metadata.iso.acquisition.DefaultInstrument;
-import org.apache.sis.metadata.iso.acquisition.DefaultOperation;
-import org.apache.sis.metadata.iso.acquisition.DefaultPlatform;
-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.ModifiableMetadata;
+import org.apache.sis.metadata.iso.*;
+import org.apache.sis.metadata.iso.acquisition.*;
+import org.apache.sis.metadata.iso.citation.*;
+import org.apache.sis.metadata.iso.constraint.*;
+import org.apache.sis.metadata.iso.content.*;
+import org.apache.sis.metadata.iso.distribution.*;
+import org.apache.sis.metadata.iso.extent.*;
+import org.apache.sis.metadata.iso.identification.*;
+import org.apache.sis.metadata.iso.lineage.*;
+import org.apache.sis.metadata.iso.maintenance.*;
+import org.apache.sis.metadata.iso.spatial.*;
 import org.apache.sis.metadata.sql.MetadataStoreException;
 import org.apache.sis.metadata.sql.MetadataSource;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.internal.metadata.Merger;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.util.Strings;
 import org.apache.sis.util.resources.Vocabulary;
@@ -152,8 +92,6 @@ import static 
org.apache.sis.internal.util.StandardDateFormat.MILLISECONDS_PER_D
 // Branch-dependent imports
 import org.opengis.temporal.Duration;
 import org.opengis.feature.FeatureType;
-import org.opengis.metadata.acquisition.AcquisitionInformation;
-import org.opengis.metadata.citation.Responsibility;
 
 
 /**
@@ -166,7 +104,7 @@ import org.opengis.metadata.citation.Responsibility;
  * @author  Rémi Maréchal (Geomatys)
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Alexis Manin (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.8
  * @module
  */
@@ -2770,6 +2708,32 @@ parse:      for (int i = 0; i < length;) {
     }
 
     /**
+     * Adds an event that describe the range of time at which data were 
acquired.
+     * Current implementation computes the average of given instants.
+     * Storage location is:
+     *
+     * <ul>
+     *   <li>{@code 
metadata/acquisitionInformation/operation/significantEvent/time}</li>
+     * </ul>
+     *
+     * @param  startTime  start time, or {@code null} if unknown.
+     * @param  endTime    end time, or {@code null} if unknown.
+     */
+    public final void addAcquisitionTime(final Instant startTime, final 
Instant endTime) {
+        final Date time;
+        if (startTime == null) {
+            if (endTime == null) return;
+            time = Date.from(endTime);
+        } else if (endTime == null) {
+            time = Date.from(startTime);
+        } else {
+            // Divide by 2 before to add in order to avoid overflow.
+            time = new Date((startTime.toEpochMilli() >> 1) + 
(endTime.toEpochMilli() >> 1));
+        }
+        addAcquisitionTime(time);
+    }
+
+    /**
      * Adds the identifier of the operation used to acquire the dataset.
      * Examples: "GHRSST", "NOAA CDR", "NASA EOS", "JPSS", "GOES-R".
      * Storage location is:
@@ -3240,6 +3204,49 @@ parse:      for (int i = 0; i < length;) {
     }
 
     /**
+     * Merge the given metadata into the metadata created by this builder.
+     * The given source should be an instance of {@link Metadata},
+     * but some types of metadata components are accepted as well.
+     *
+     * @param  source  the source metadata to merge. Will never be modified.
+     * @param  locale  the locale to use for error message in exceptions.
+     * @return {@code true} if the given source has been merged,
+     *         or {@code false} if its type is not managed by this builder.
+     * @throws RuntimeException if the merge failed (may be {@link 
IllegalArgumentException},
+     *         {@link ClassCastException}, {@link 
org.apache.sis.metadata.InvalidMetadataException}…)
+     *
+     * @see Merger
+     */
+    public boolean mergeMetadata(final Object source, final Locale locale) {
+        final ModifiableMetadata target;
+             if (source instanceof Metadata)                    target = 
metadata();
+        else if (source instanceof DataIdentification)          target = 
identification();
+        else if (source instanceof Citation)                    target = 
citation();
+        else if (source instanceof Series)                      target = 
series();
+        else if (source instanceof Responsibility)              target = 
responsibility();
+        else if (source instanceof Party)                       target = 
party();
+        else if (source instanceof LegalConstraints)            target = 
constraints();
+        else if (source instanceof Extent)                      target = 
extent();
+        else if (source instanceof AcquisitionInformation)      target = 
acquisition();
+        else if (source instanceof Platform)                    target = 
platform();
+        else if (source instanceof FeatureCatalogueDescription) target = 
featureDescription();
+        else if (source instanceof CoverageDescription)         target = 
coverageDescription();
+        else if (source instanceof AttributeGroup)              target = 
attributeGroup();
+        else if (source instanceof SampleDimension)             target = 
sampleDimension();
+        else if (source instanceof GridSpatialRepresentation)   target = 
gridRepresentation();
+        else if (source instanceof GCPCollection)               target = 
groundControlPoints();
+        else if (source instanceof Distribution)                target = 
distribution();
+        else if (source instanceof Format)                      target = 
format();
+        else if (source instanceof Lineage)                     target = 
lineage();
+        else if (source instanceof ProcessStep)                 target = 
processStep();
+        else if (source instanceof Processing)                  target = 
processing();
+        else return false;
+        final Merger merger = new Merger(locale);
+        merger.copy(source, target);
+        return true;
+    }
+
+    /**
      * 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.
      *

Reply via email to