You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/01/07 05:35:16 UTC
[sis] branch geoapi-4.0 updated: Initial support of `GEO_METADATA` and `GDAL_METADATA` tags when reading GeoTIFF image.
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 <ma...@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.
*