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 f071bb2911 If a TIFF `DateTime` tag exists, use it as the temporal
coordinate of the grid geometry. This behavior is specified by the Defense
Geospatial Information Working Group (DGIWG).
f071bb2911 is described below
commit f071bb291137857db4b409f5cf42e505901b2848
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Oct 16 15:10:40 2025 +0200
If a TIFF `DateTime` tag exists, use it as the temporal coordinate of the
grid geometry.
This behavior is specified by the Defense Geospatial Information Working
Group (DGIWG).
---
.../sis/coverage/grid/GridCoverageProcessor.java | 2 +-
.../org/apache/sis/coverage/grid/GridGeometry.java | 15 +--
.../apache/sis/coverage/grid/GridOrientation.java | 34 ++++--
.../org/apache/sis/coverage/grid/package-info.java | 2 +-
.../org.apache.sis.metadata/main/module-info.java | 2 +-
.../org/apache/sis/metadata/sql/package-info.java | 2 +-
.../org/apache/sis/temporal/LenientDateFormat.java | 5 +-
.../main/module-info.java | 2 +-
.../main/org/apache/sis/referencing/CommonCRS.java | 25 ++++-
.../referencing/factory/sql/EPSGDataAccess.java | 2 +-
.../sis/referencing/factory/sql/package-info.java | 2 +-
.../shared/ReferencingFactoryContainer.java | 5 -
.../internal/shared/TemporalAccessor.java | 2 +-
.../main/module-info.java | 2 +-
.../apache/sis/storage/geotiff/GeoTiffStore.java | 30 ------
.../sis/storage/geotiff/ImageFileDirectory.java | 95 +++++++++++------
.../sis/storage/geotiff/MultiResolutionImage.java | 27 ++---
.../org/apache/sis/storage/geotiff/Writer.java | 20 ++--
.../org/apache/sis/storage/geotiff/base/Tags.java | 2 +-
.../apache/sis/storage/geotiff/package-info.java | 2 +-
.../sis/storage/geotiff/reader/CRSBuilder.java | 6 +-
.../geotiff/reader/GridGeometryBuilder.java | 118 +++++++++++++++------
.../apache/sis/storage/geotiff/reader/Type.java | 2 +
.../sis/storage/geotiff/reader/XMLMetadata.java | 2 +-
.../sis/storage/geotiff/writer/GeoEncoder.java | 72 ++++++++++++-
.../main/module-info.java | 2 +-
.../org/apache/sis/storage/sql/package-info.java | 2 +-
.../org.apache.sis.storage/main/module-info.java | 2 +-
.../sis/storage/aggregate/CoverageAggregator.java | 2 +-
.../apache/sis/storage/base/MetadataFetcher.java | 28 ++---
.../main/org/apache/sis/storage/csv/Store.java | 2 +-
.../org/apache/sis/storage/csv/TimeEncoding.java | 7 +-
.../src/org.apache.sis.util/main/module-info.java | 2 +-
.../main/org/apache/sis/math/Statistics.java | 2 +-
34 files changed, 333 insertions(+), 194 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
index c4648c90a4..b4b2f9a674 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -720,7 +720,7 @@ public class GridCoverageProcessor implements Cloneable {
* @since 1.5
*/
public GridCoverage appendDimension(final GridCoverage source, final
Instant lower, final Duration span) {
- final DefaultTemporalCRS crs =
DefaultTemporalCRS.castOrCopy(CommonCRS.Temporal.TRUNCATED_JULIAN.crs());
+ final DefaultTemporalCRS crs =
DefaultTemporalCRS.castOrCopy(CommonCRS.defaultTemporal());
double scale = crs.toValue(span);
double offset = crs.toValue(lower);
long index = Numerics.roundAndClamp(offset / scale); //
See comment in above method.
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
index 7a73ef7192..ea203379e0 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
@@ -134,7 +134,7 @@ import org.opengis.coordinate.MismatchedDimensionException;
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Johann Sorel (Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 1.0
*/
public class GridGeometry implements LenientComparable, Serializable {
@@ -222,9 +222,10 @@ public class GridGeometry implements LenientComparable,
Serializable {
protected final GridExtent extent;
/**
- * The geodetic envelope, or {@code null} if unknown. If non-null, this
envelope is usually the grid {@link #extent}
- * {@linkplain #gridToCRS transformed} to real world coordinates. The
Coordinate Reference System} (CRS) of this
- * envelope defines the "real world" CRS of this grid geometry.
+ * The spatiotemporal extent in units of the <abbr>CRS</abbr>, or {@code
null} if unknown.
+ * If non-null, this envelope is usually the grid {@link #extent}
{@linkplain #gridToCRS transformed}
+ * to real world coordinates, with the lower coordinates inclusive and the
upper coordinates exclusive.
+ * The Coordinate Reference System (CRS) of this envelope defines the
"real world" CRS of this grid geometry.
*
* @see #ENVELOPE
* @see #getEnvelope()
@@ -683,7 +684,7 @@ public class GridGeometry implements LenientComparable,
Serializable {
this.envelope = null;
} else {
this.envelope = target;
- if (extent != null) {
+ if (extent != null && orientation != GridOrientation.UNKNOWN) {
// A non-null `sourceDimensions` implies non-null
`orientation`.
if (sourceDimensions != null &&
orientation.canReorderGridAxis) {
if (!ArraysExt.isRange(0, sourceDimensions)) {
@@ -1138,8 +1139,8 @@ public class GridGeometry implements LenientComparable,
Serializable {
* Returns the start time and end time of coordinates of the grid.
* If the grid has no temporal dimension, then this method returns an
empty array.
* If only the start time or end time is defined, then returns an array of
length 1.
- * Otherwise this method returns an array of length 2 with the start time
in the first element
- * and the end time in the last element.
+ * Otherwise this method returns an array of length 2 with the start time
(inclusive)
+ * in the first element and the end time (exclusive) in the last element.
*
* @return time range as an array of length 0 (if none), 1 or 2.
*/
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
index 6fc1594eb3..d5c31f9b56 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridOrientation.java
@@ -30,10 +30,15 @@ import org.apache.sis.util.resources.Errors;
* For example, the conversion from grid coordinates to CRS coordinates may
flip the <var>y</var> axis
* (grid coordinates increasing toward down on screen), or may swap
<var>x</var> and <var>y</var> axes, <i>etc.</i>
* The constants enumerated in this class cover only a few common cases where
the grid is
- * <a
href="https://en.wikipedia.org/wiki/Axis-aligned_object">axis-aligned</a> with
the CRS.
+ * <a
href="https://en.wikipedia.org/wiki/Axis-aligned_object">axis-aligned</a> with
the <abbr>CRS</abbr>.
+ *
+ * <h4>Custom orientations</h4>
+ * For creating a custom orientations, one of the constants defined in this
class can be used as a starting point.
+ * Then, the {@link #flipGridAxis(int)}, {@link
#useVariantOfCRS(AxesConvention)} or {@link #canReorderGridAxis(boolean)}
+ * methods can be invoked.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.6
*
* @see GridGeometry#GridGeometry(GridExtent, Envelope, GridOrientation)
*
@@ -143,6 +148,15 @@ public final class GridOrientation implements Serializable
{
*/
public static final GridOrientation DISPLAY = new GridOrientation(2,
AxesConvention.DISPLAY_ORIENTED, false);
+ /**
+ * Unknown image orientation. The {@linkplain
GridGeometry#getGridToCRS(PixelInCell) grid to CRS}
+ * transforms inferred from this orientation will be null.
+ * All methods in this class invoked on the {@code UNKNOWN} instance will
return {@code UNKNOWN}.
+ *
+ * @since 1.6
+ */
+ public static final GridOrientation UNKNOWN = new GridOrientation(0, null,
true);
+
/**
* Set of grid axes to reverse, as a bit mask. For any dimension
<var>i</var>, the bit
* at {@code 1L << i} is set to 1 if the grid axis at that dimension
should be flipped.
@@ -163,7 +177,7 @@ public final class GridOrientation implements Serializable {
/**
* Whether {@link GridExtent} can be rewritten with a different axis order
- * for matching the CRS axis order specified by {@link #crsVariant}.
+ * for matching the <abbr>CRS</abbr> axis order specified by {@link
#crsVariant}.
* If {@code false}, then axis order changes will be handled in the {@code
gridToCRS} transform instead.
*
* @see #canReorderGridAxis(boolean)
@@ -196,6 +210,9 @@ public final class GridOrientation implements Serializable {
if (dimension >= Long.SIZE) {
throw new
ArithmeticException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1,
dimension + 1));
}
+ if (this == UNKNOWN) {
+ return this;
+ }
return new GridOrientation(flippedAxes ^ (1L << dimension),
crsVariant, canReorderGridAxis);
}
@@ -227,7 +244,7 @@ public final class GridOrientation implements Serializable {
* @see #DISPLAY
*/
public GridOrientation useVariantOfCRS(final AxesConvention variant) {
- if (variant == crsVariant) {
+ if (variant == crsVariant || this == UNKNOWN) {
return this;
}
if (variant == AxesConvention.NORMALIZED || variant ==
AxesConvention.ORIGINAL) {
@@ -246,7 +263,7 @@ public final class GridOrientation implements Serializable {
* @return a grid orientation equals to this one except that it has the
specified flag.
*/
public GridOrientation canReorderGridAxis(final boolean enabled) {
- if (enabled == canReorderGridAxis) {
+ if (enabled == canReorderGridAxis || this == UNKNOWN) {
return this;
}
return new GridOrientation(flippedAxes, crsVariant, enabled);
@@ -261,7 +278,7 @@ public final class GridOrientation implements Serializable {
@Override
public boolean equals(final Object other) {
if (other instanceof GridOrientation) {
- final GridOrientation that = (GridOrientation) other;
+ final var that = (GridOrientation) other;
return flippedAxes == that.flippedAxes &&
crsVariant == that.crsVariant &&
canReorderGridAxis == that.canReorderGridAxis;
@@ -283,7 +300,10 @@ public final class GridOrientation implements Serializable
{
*/
@Override
public String toString() {
- final StringBuilder buffer = new
StringBuilder(getClass().getSimpleName()).append('[');
+ if (this == UNKNOWN) {
+ return "UNKNOWN";
+ }
+ final var buffer = new
StringBuilder(getClass().getSimpleName()).append('[');
String separator = "";
if (flippedAxes != 0) {
buffer.append("flip={");
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/package-info.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/package-info.java
index 8fde1c33e5..81b06aaef5 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/package-info.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/package-info.java
@@ -41,7 +41,7 @@
* @author Martin Desruisseaux (Geomatys)
* @author Johann Sorel (Geomatys)
* @author Alexis Manin (Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 1.0
*/
package org.apache.sis.coverage.grid;
diff --git a/endorsed/src/org.apache.sis.metadata/main/module-info.java
b/endorsed/src/org.apache.sis.metadata/main/module-info.java
index e66e26c614..e76f65657f 100644
--- a/endorsed/src/org.apache.sis.metadata/main/module-info.java
+++ b/endorsed/src/org.apache.sis.metadata/main/module-info.java
@@ -23,7 +23,7 @@
* @author Martin Desruisseaux (Geomatys)
* @author Cédric Briançon (Geomatys)
* @author Cullen Rombach (Image Matters)
- * @version 1.5
+ * @version 1.6
* @since 0.3
*/
module org.apache.sis.metadata {
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
index d6396799d0..30d5b7ae9b 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/package-info.java
@@ -42,7 +42,7 @@
*
* @author Touraïvane (IRD)
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.6
*
* @see org.apache.sis.referencing.factory.sql
*
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
index c3672d4178..09176ac3da 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/LenientDateFormat.java
@@ -124,7 +124,7 @@ public final class LenientDateFormat extends DateFormat {
}
/**
- * Parses the given date as an instant, assuming UTC timezone if
unspecified.
+ * Parses the given date as an instant, assuming <abbr>UTC</abbr> timezone
if unspecified.
*
* @param text the text to parse as an instant in UTC timezone by
default, or {@code null}.
* @return the instant for the given text, or {@code null} if the given
text was null.
@@ -135,7 +135,8 @@ public final class LenientDateFormat extends DateFormat {
}
/**
- * Parses the given date as an instant, assuming UTC timezone if
unspecified.
+ * Parses the given date as an instant, assuming <abbr>UTC</abbr> timezone
if unspecified.
+ * This method is tolerant to date and time separated by a space instead
of the {@code 'T'} character.
*
* @param text the text to parse as an instant in UTC timezone by
default.
* @param lower index of the first character to parse.
diff --git a/endorsed/src/org.apache.sis.referencing/main/module-info.java
b/endorsed/src/org.apache.sis.referencing/main/module-info.java
index 27146fb6ca..2457ee6ede 100644
--- a/endorsed/src/org.apache.sis.referencing/main/module-info.java
+++ b/endorsed/src/org.apache.sis.referencing/main/module-info.java
@@ -21,7 +21,7 @@
* @author Martin Desruisseaux (Geomatys)
* @author Rémi Maréchal (Geomatys)
* @author Maxime Gavens (Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 0.3
*/
module org.apache.sis.referencing {
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
index ddd75f1fed..d446fd47cb 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
@@ -572,11 +572,33 @@ public enum CommonCRS {
* their datum can be arbitrary.</p>
*
* @return the default two-dimensional geographic CRS with
(<var>longitude</var>, <var>latitude</var>) axis order.
+ *
+ * @see #defaultTemporal()
*/
public static GeographicCRS defaultGeographic() {
return DEFAULT.normalizedGeographic();
}
+ /**
+ * Returns the default temporal <abbr>CRS</abbr> used by the Apache
<abbr>SIS</abbr> library.
+ * The current implementation uses {@linkplain Temporal#TRUNCATED_JULIAN
truncated Julian days},
+ * which are the number of days elapsed since May 24, 1968 at 00:00
<abbr>UTC</abbr>.
+ * However, this default <abbr>CRS</abbr> may change in any future
<abbr>SIS</abbr> version.
+ * For handling the coordinate values in a way independent of
<abbr>CRS</abbr> changes,
+ * the can be converted with {@link DefaultTemporalCRS#toInstant(double)}.
+ *
+ * @return the default temporal <abbr>CRS</abbr>.
+ *
+ * @see #defaultGeographic()
+ * @see Temporal#TRUNCATED_JULIAN
+ * @see DefaultTemporalCRS#toInstant(double)
+ *
+ * @since 1.6
+ */
+ public static TemporalCRS defaultTemporal() {
+ return Temporal.TRUNCATED_JULIAN.crs();
+ }
+
/**
* Returns a two-dimensional geographic CRS with axes in the non-standard
but computationally convenient
* (<var>longitude</var>, <var>latitude</var>) order. The coordinate
system axes will be oriented toward
@@ -1553,6 +1575,7 @@ public enum CommonCRS {
* @author Martin Desruisseaux (Geomatys)
* @version 1.5
*
+ * @see #defaultTemporal()
* @see Engineering#TIME
*
* @since 0.4
@@ -1618,7 +1641,7 @@ public enum CommonCRS {
* (by contrast, the IUGS definition is only about duration).
*
* <h4>Application to geodesy</h4>
- * The tropical year is the unit of measurement used in EPSG geodetic
database for year duration.
+ * The tropical year is the unit of measurement used in the
<abbr>EPSG</abbr> geodetic dataset for year duration.
* It it used for rate of changes such as "centimeters per year". Its
identifier is EPSG:1029.
*
* @see Units#TROPICAL_YEAR
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index b0a8fc0bc9..8ab167b668 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -160,7 +160,7 @@ import org.opengis.referencing.ObjectDomain;
* @author Matthias Basler
* @author Andrea Aime (TOPP)
* @author Johann Sorel (Geomatys)
- * @version 1.5
+ * @version 1.6
*
* @see <a
href="https://sis.apache.org/tables/CoordinateReferenceSystems.html">List of
authority codes</a>
*
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/package-info.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/package-info.java
index bde70bffc3..3df5fafcea 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/package-info.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/package-info.java
@@ -83,7 +83,7 @@
* @author Jody Garnett (Refractions)
* @author Didier Richard (IGN)
* @author John Grange
- * @version 1.5
+ * @version 1.6
*
* @see org.apache.sis.metadata.sql
*
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingFactoryContainer.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingFactoryContainer.java
index 05d07eccc0..e745d32268 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingFactoryContainer.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/ReferencingFactoryContainer.java
@@ -51,14 +51,9 @@ import org.opengis.util.Factory;
* A container of factories frequently used together.
* Provides also some utility methods working with factories.
*
- * This class may be temporary until we choose a dependency injection framework
- * See <a href="https://issues.apache.org/jira/browse/SIS-102">SIS-102</a>.
- *
* <p>This class is not thread safe. Synchronization, if needed, is caller's
responsibility.</p>
*
* @author Martin Desruisseaux (IRD, Geomatys)
- *
- * @see <a href="https://issues.apache.org/jira/browse/SIS-102">SIS-102</a>
*/
public class ReferencingFactoryContainer implements Localized {
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
index e348b50eba..bab0c03bf3 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/TemporalAccessor.java
@@ -101,7 +101,7 @@ public final class TemporalAccessor {
startTime = endTime;
endTime = null;
}
- final Instant[] times = new Instant[(endTime != null) ? 2 : 1];
+ final var times = new Instant[(endTime != null) ? 2 : 1];
switch (times.length) {
default: times[1] = endTime; // Fall through.
case 1: times[0] = startTime; // Fall through.
diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java
index d65fe43c7f..3e4d1b77c9 100644
--- a/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage.geotiff/main/module-info.java
@@ -22,7 +22,7 @@
* @author Thi Phuong Hao Nguyen (VNSC)
* @author Minh Chinh Vu (VNSC)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 0.8
*/
module org.apache.sis.storage.geotiff {
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
index 3ffe5083f8..1152138fe2 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -19,11 +19,7 @@ package org.apache.sis.storage.geotiff;
import java.util.Set;
import java.util.List;
import java.util.Locale;
-import java.util.TimeZone;
import java.util.Optional;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.time.ZoneId;
import java.net.URI;
import java.io.IOException;
import java.nio.charset.Charset;
@@ -121,18 +117,6 @@ public class GeoTiffStore extends DataStore implements
Aggregate {
*/
final Locale dataLocale;
- /**
- * The timezone for the date and time parsing, or {@code null} for the
default.
- */
- private final ZoneId timezone;
-
- /**
- * The object to use for parsing and formatting dates. Created when first
needed.
- *
- * @see #getDateFormat()
- */
- private transient DateFormat dateFormat;
-
/**
* The {@link GeoTiffStoreProvider#LOCATION} parameter value, or {@code
null} if none.
* This is used for information purpose only, not for actual reading
operations.
@@ -261,7 +245,6 @@ public class GeoTiffStore extends DataStore implements
Aggregate {
compression = connector.getOption(Compression.OPTION_KEY);
dataLocale = connector.getOption(OptionKey.LOCALE);
- timezone = connector.getOption(OptionKey.TIMEZONE);
location = connector.getStorageAs(URI.class);
path = connector.getStorageAs(Path.class);
try {
@@ -498,19 +481,6 @@ public class GeoTiffStore extends DataStore implements
Aggregate {
return (path != null) ? Optional.of(new FileSet(path)) :
Optional.empty();
}
- /**
- * Returns the object to use for parsing and formatting dates.
- */
- final DateFormat getDateFormat() {
- if (dateFormat == null) {
- dateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss",
Locale.US);
- if (timezone != null) {
- dateFormat.setTimeZone(TimeZone.getTimeZone(timezone));
- }
- }
- return dateFormat;
- }
-
/**
* Returns the reader if it is not closed, or throws an exception
otherwise.
*
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index ab44a85017..c8368e99a2 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -17,7 +17,8 @@
package org.apache.sis.storage.geotiff;
import java.io.IOException;
-import java.text.ParseException;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Arrays;
import java.util.Optional;
@@ -34,6 +35,7 @@ import static javax.imageio.plugins.tiff.GeoTIFFTagSet.*;
import static javax.imageio.plugins.tiff.BaselineTIFFTagSet.*;
import org.opengis.metadata.Metadata;
import org.opengis.metadata.citation.DateType;
+import org.opengis.metadata.spatial.DimensionNameType;
import org.opengis.util.GenericName;
import org.opengis.util.NameSpace;
import org.opengis.util.FactoryException;
@@ -65,13 +67,14 @@ import
org.apache.sis.util.internal.shared.UnmodifiableArrayList;
import org.apache.sis.util.internal.shared.Numerics;
import org.apache.sis.util.internal.shared.Strings;
import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.temporal.LenientDateFormat;
import org.apache.sis.math.Vector;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.pending.jdk.JDK18;
/**
- * An Image File Directory (FID) in a TIFF image.
+ * An Image File Directory (FID) in a <abbr>TIFF</abbr> image.
*
* <h2>Thread-safety</h2>
* Public methods should be synchronized because they can be invoked directly
by users.
@@ -160,8 +163,6 @@ final class ImageFileDirectory extends DataCube {
*
* <p><b>Note:</b>
* the {@link #imageHeight} attribute is named {@code ImageLength} in TIFF
specification.</p>
- *
- * @see #getExtent()
*/
private long imageWidth = -1, imageHeight = -1;
@@ -386,6 +387,15 @@ final class ImageFileDirectory extends DataCube {
*/
private Predictor predictor;
+ /**
+ * The date/time found in the {@code DATE_TIME} tag, or {@code null} if
none.
+ * According the <abbr>TIFF</abbr> specification, this is the image
creation date.
+ * But the <abbr>DGIWG</abbr> specification reinterprets that information
as the time when
+ * the imagery values were collected, which is a point on the
<abbr>CRS</abbr> temporal axis.
+ * However, we will use this information that way only if {@link
#referencing} is non-null.
+ */
+ public Instant imageDate;
+
/**
* A helper class for building Coordinate Reference System and complete
related metadata.
* Contains the following information:
@@ -415,7 +425,7 @@ final class ImageFileDirectory extends DataCube {
}
/**
- * The grid geometry created by {@link GridGeometryBuilder#build(Reader,
long, long)}.
+ * The grid geometry created by {@link
GridGeometryBuilder#build(StoreListeners, long, long, Instant)}.
* It has 2 or 3 dimensions, depending on whether the CRS declares a
vertical axis or not.
*
* @see #getGridGeometry()
@@ -524,7 +534,7 @@ final class ImageFileDirectory extends DataCube {
* @param count the number of values to read.
* @return {@code null} on success, or the unrecognized value otherwise.
* @throws IOException if an error occurred while reading the stream.
- * @throws ParseException if the value need to be parsed as date and the
parsing failed.
+ * @throws DateTimeParseException if the value need to be parsed as date
and the parsing failed.
* @throws NumberFormatException if the value need to be parsed as number
and the parsing failed.
* @throws ArithmeticException if the value cannot be represented in the
expected Java type.
* @throws IllegalArgumentException if a value which was expected to be a
singleton is not.
@@ -1009,13 +1019,25 @@ final class ImageFileDirectory extends DataCube {
}
/*
* Date and time of image creation. The format is: "YYYY:MM:DD
HH:MM:SS" with 24-hour clock.
+ * The <abbr>DGIWG</abbr> specification requires that all
date/time stamps are expressed in
+ * Coordinated Universal Time (UTC), and that they represent the
date/time when the imagery
+ * values were collected.
*
* Destination: metadata/identificationInfo/citation/date
+ * Also: gridGeometry/gridToCRS
*/
case TAG_DATE_TIME: {
- for (final String value : type.readAsStrings(input(), count,
encoding())) {
-
metadata.addCitationDate(reader.store.getDateFormat().parse(value).toInstant(),
- DateType.CREATION,
ImageMetadataBuilder.Scope.RESOURCE);
+ for (String value : type.readAsStrings(input(), count,
encoding())) {
+ // The presence of a letter would be an invalid date
according TIFF and DGIWG specifications.
+ // If present anyway, parse as an ISO date. If failure,
warning will be logged by the caller.
+ if (!CharSequences.isUpperCase(value)) {
+ final String[] parts = (String[])
CharSequences.split(value, ' ');
+ if (parts.length == 2) {
+ value = parts[0].replace(':', '-') + 'T' +
parts[1] + 'Z';
+ }
+ }
+ imageDate = LenientDateFormat.parseInstantUTC(value);
+ metadata.addCitationDate(imageDate, DateType.CREATION,
ImageMetadataBuilder.Scope.RESOURCE);
}
break;
}
@@ -1450,22 +1472,45 @@ final class ImageFileDirectory extends DataCube {
}
/**
- * If this IFD has no grid geometry information, derives a grid geometry
by applying a scale factor
- * on the grid geometry of another IFD. Information about bands are also
copied if compatible.
+ * If this <abbr>IFD</abbr> has no grid geometry, derives this information
+ * by scaling the grid geometry of the specified image at full resolution.
+ * Information about bands are also copied if compatible. The scale
factors are returned,
+ * with the scale of the temporal dimension defined to 1 for telling that
the time does not change.
+ *
+ * <h4>Conditions</h4>
* This method should be invoked only when {@link #isReducedResolution()}
is {@code true}.
*
* @param fullResolution the full-resolution image.
- * @param scales <var>size of full resolution image</var> / <var>size of
this image</var> for each grid axis.
- */
- final void initReducedResolution(final ImageFileDirectory fullResolution,
final double[] scales)
- throws DataStoreException, TransformException
- {
+ * @return <var>size of full resolution image</var> / <var>size of this
image</var> for each grid axis.
+ */
+ final double[] initReducedResolution(final ImageFileDirectory
fullResolution) throws DataStoreException, TransformException {
+ final GridGeometry geometry = fullResolution.getGridGeometry();
+ final GridExtent fullExtent = geometry.getExtent();
+ final int dimension = fullExtent.getDimension();
+ final var axisTypes = new DimensionNameType[dimension];
+ final var scales = new double[dimension];
+ final var high = new long[dimension];
+ for (int i=0; i<dimension; i++) {
+ axisTypes[i] = fullExtent.getAxisType(i).orElse(null);
+ final long size;
+ switch (i) {
+ case 0: size = imageWidth; break;
+ case 1: size = imageHeight; break;
+ default: scales[i] = 1; continue;
+ }
+ scales[i] = fullExtent.getSize(i, false) / size;
+ high[i] = size - 1;
+ }
if (referencing == null) {
- gridGeometry = new GridGeometry(fullResolution.getGridGeometry(),
getExtent(), MathTransforms.scale(scales));
+ gridGeometry = new GridGeometry(
+ geometry,
+ new GridExtent(axisTypes, null, high, true),
+ MathTransforms.scale(scales));
}
if (samplesPerPixel == fullResolution.samplesPerPixel) {
sampleDimensions = fullResolution.getSampleDimensions();
}
+ return scales;
}
/**
@@ -1483,7 +1528,6 @@ final class ImageFileDirectory extends DataCube {
* <h4>Thread-safety</h4>
* This method must be thread-safe because it can be invoked directly by
the user.
*
- * @see #getExtent()
* @see #getTileSize()
*/
@Override
@@ -1492,12 +1536,12 @@ final class ImageFileDirectory extends DataCube {
GridGeometry domain = gridGeometry;
if (domain == null) {
if (referencing != null) try {
- domain = referencing.build(reader.store.listeners(),
imageWidth, imageHeight);
+ domain = referencing.build(reader.store.listeners(),
imageWidth, imageHeight, imageDate);
} catch (FactoryException e) {
throw new
DataStoreContentException(reader.resources().getString(Resources.Keys.CanNotComputeGridGeometry_1,
filename()), e);
} else {
// Fallback if the TIFF file has no GeoKeys.
- domain = new GridGeometry(getExtent(), null, null);
+ domain = new GridGeometry(new GridExtent(imageWidth,
imageHeight), null, null);
}
final CoverageModifier.Source source = source();
gridGeometry = (source != null) ?
reader.store.customizer.customize(source, domain) : domain;
@@ -1506,16 +1550,6 @@ final class ImageFileDirectory extends DataCube {
}
}
- /**
- * Returns the image width and height without building the full grid
geometry.
- *
- * @see #getTileSize()
- * @see #getGridGeometry()
- */
- final GridExtent getExtent() {
- return new GridExtent(imageWidth, imageHeight);
- }
-
/**
* Returns the minimum and maximum non-fill values in the specified band.
* This is the values explicitly defined in <abbr>TIFF</abbr> tags if
present,
@@ -1709,7 +1743,6 @@ final class ImageFileDirectory extends DataCube {
* Returns the size of tiles. This is also the size of the image sample
model.
* The number of dimensions is always 2 for {@code ImageFileDirectory}.
*
- * @see #getExtent()
* @see #getSampleModel(int[])
*/
@Override
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
index 097b4bf978..75792a41a8 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
@@ -40,6 +40,7 @@ import org.apache.sis.storage.base.GridResourceWrapper;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.internal.shared.DirectPositionView;
import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import static
org.apache.sis.storage.geotiff.reader.GridGeometryBuilder.BIDIMENSIONAL;
/**
@@ -150,7 +151,9 @@ final class MultiResolutionImage extends
GridResourceWrapper implements StoreRes
}
/**
- * Returns the resolution (in units of CRS axes) for the given level.
+ * Returns the resolution (in units of <abbr>CRS</abbr> axes) for the
given level.
+ * If there is a temporal dimension, its resolution is set to NaN because
we don't
+ * know the duration.
*
* @param level the desired resolution level, numbered from finest to
coarsest resolution.
* @return resolution at the specified level, not cloned (caller shall not
modify).
@@ -158,17 +161,12 @@ final class MultiResolutionImage extends
GridResourceWrapper implements StoreRes
private double[] resolution(final int level) throws DataStoreException {
double[] resolution = resolutions[level];
if (resolution == null) try {
- final ImageFileDirectory image = getImageFileDirectory(level);
- final ImageFileDirectory base = getImageFileDirectory(0);
- final GridGeometry geometry = base.getGridGeometry();
- final GridExtent fullExtent = geometry.getExtent();
- final GridExtent subExtent = image.getExtent();
- final double[] scales = new double[fullExtent.getDimension()];
- for (int i=0; i<scales.length; i++) {
- scales[i] = fullExtent.getSize(i, false) /
subExtent.getSize(i, false);
- }
- image.initReducedResolution(base, scales);
+ final ImageFileDirectory image = getImageFileDirectory(level);
+ final ImageFileDirectory base = getImageFileDirectory(0);
+ final double[] scales = image.initReducedResolution(base);
+ final GridGeometry geometry = base.getGridGeometry();
if (geometry.isDefined(GridGeometry.GRID_TO_CRS)) {
+ final GridExtent fullExtent = geometry.getExtent();
DirectPosition poi = new
DirectPositionView.Double(fullExtent.getPointOfInterest(PixelInCell.CELL_CENTER));
MatrixSIS gridToCRS =
MatrixSIS.castOrCopy(geometry.getGridToCRS(PixelInCell.CELL_CENTER).derivative(poi));
resolution = gridToCRS.multiply(scales);
@@ -176,7 +174,10 @@ final class MultiResolutionImage extends
GridResourceWrapper implements StoreRes
// Assume an identity transform for the `gridToCRS` of full
resolution image.
resolution = scales;
}
- for (int i=0; i<resolution.length; i++) {
+ // Set to NaN only after all matrix multiplications are done.
+ int i = Math.min(BIDIMENSIONAL, resolution.length);
+ Arrays.fill(scales, BIDIMENSIONAL, i, Double.NaN);
+ while (--i >= 0) {
resolution[i] = Math.abs(resolution[i]);
}
resolutions[level] = resolution;
@@ -257,7 +258,7 @@ final class MultiResolutionImage extends
GridResourceWrapper implements StoreRes
synchronized (getSynchronizationLock()) {
finer: while (--level > 0) {
final double[] resolution = resolution(level);
- for (int i=0; i<request.length; i++) {
+ for (int i = Math.min(request.length, BIDIMENSIONAL); --i >=
0;) {
if (!(request[i] >= resolution[i])) { // Use
`!` for catching NaN.
continue finer;
}
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
index 3926b00baf..f3aba2e396 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java
@@ -21,7 +21,6 @@ import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
-import java.util.Date;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Deque;
@@ -38,7 +37,6 @@ import static javax.imageio.plugins.tiff.GeoTIFFTagSet.*;
import javax.measure.IncommensurableException;
import org.opengis.util.FactoryException;
import org.opengis.metadata.Metadata;
-import org.opengis.metadata.citation.CitationDate;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.image.ImageProcessor;
import org.apache.sis.image.DataType;
@@ -55,8 +53,8 @@ import org.apache.sis.storage.geotiff.writer.GeoEncoder;
import org.apache.sis.storage.geotiff.writer.ReformattedImage;
import org.apache.sis.io.stream.ChannelDataOutput;
import org.apache.sis.io.stream.UpdatableWrite;
-import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.CharSequences;
import org.apache.sis.util.internal.shared.Numerics;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.math.Fraction;
@@ -298,6 +296,7 @@ final class Writer extends IOBase implements Flushable {
* @throws DataStoreException if the given {@code image} has a property
* which is not supported by TIFF specification or by this writer.
*/
+ @SuppressWarnings("UseSpecificCatch")
public final long append(final RenderedImage image, final GridGeometry
grid, final Metadata metadata)
throws IOException, DataStoreException
{
@@ -395,23 +394,18 @@ final class Writer extends IOBase implements Flushable {
}
/*
* Metadata (optional) and GeoTIFF. They are managed by separated
classes.
+ * Note that `TAG_DATE_TIME` has slightly different interpretation
depending
+ * on whether we are writing GeoTIFF (`geoKeys != null`) or plain TIFF.
*/
final double[][] statistics = image.statistics(numBands);
final short[][] shortStats = toShorts(statistics, sampleFormat);
- final MetadataFetcher<String> mf = new
MetadataFetcher<>(store.dataLocale) {
- @Override protected boolean accept(final CitationDate info) {
- return super.accept(info) || creationDate != null; //
Limit to a singleton.
- }
-
- @Override protected String convertDate(final Date date) {
- return store.getDateFormat().format(date);
- }
- };
+ final var mf = new MetadataFetcher(store.dataLocale);
mf.accept(metadata);
GeoEncoder geoKeys = null;
if (grid != null) try {
geoKeys = new GeoEncoder(store.listeners());
geoKeys.write(grid, mf);
+ mf.creationDate = geoKeys.imageDate(); // Unconditional, even if
the list of empty.
} catch (IncompleteGridGeometryException | CannotEvaluateException |
TransformException e) {
throw new IncompatibleResourceException(e.getMessage(),
e).addAspect("gridGeometry");
} catch (FactoryException | IncommensurableException |
RuntimeException e) {
@@ -460,7 +454,7 @@ final class Writer extends IOBase implements Flushable {
writeTag((short) TAG_PLANAR_CONFIGURATION, (short)
TIFFTag.TIFF_SHORT, planarConfiguration);
writeTag((short) TAG_RESOLUTION_UNIT, (short)
TIFFTag.TIFF_SHORT, RESOLUTION_UNIT_NONE);
writeTag((short) TAG_SOFTWARE, /* TIFF_ASCII */
mf.software);
- writeTag((short) TAG_DATE_TIME, /* TIFF_ASCII */
mf.creationDate);
+ writeTag((short) TAG_DATE_TIME, /* TIFF_ASCII */
GeoEncoder.creationDates(mf.creationDate));
writeTag((short) TAG_ARTIST, /* TIFF_ASCII */
mf.party);
writeTag((short) TAG_HOST_COMPUTER, /* TIFF_ASCII */
mf.procedure);
if (compression.usePredictor()) {
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Tags.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Tags.java
index 50eb9c9f38..426a5588eb 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Tags.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/base/Tags.java
@@ -52,7 +52,7 @@ public final class Tags {
/**
* Embedded XML-encoded instance documents prepared using 19139-based
schema.
- * This is an OGC DGIWG extension tag.
+ * This is an <abbr>OGC</abbr> <abbr>DGIWG</abbr> extension tag.
*/
public static final short GEO_METADATA = (short) 0xC6DD;
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/package-info.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/package-info.java
index d5d2f35134..603b212edc 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/package-info.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/package-info.java
@@ -33,7 +33,7 @@
* @author Thi Phuong Hao Nguyen (VNSC)
* @author Minh Chinh Vu (VNSC)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 0.8
*/
package org.apache.sis.storage.geotiff;
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
index 7900e1de85..4604c24a3a 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/CRSBuilder.java
@@ -82,7 +82,6 @@ import org.apache.sis.util.internal.shared.Strings;
import org.apache.sis.util.internal.shared.Numerics;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.resources.Errors;
-import org.apache.sis.math.Vector;
import org.apache.sis.measure.Units;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.storage.event.StoreListeners;
@@ -179,14 +178,14 @@ public final class CRSBuilder extends
ReferencingFactoryContainer {
/**
* Suggested value for a general description of the transformation form
grid coordinates to "real world" coordinates.
- * This is computed by {@link #build(Vector, Vector, String)} and made
available as additional information to the caller.
+ * This is computed by {@link #build(GeoKeysLoader)} and made available as
additional information to the caller.
*/
public String description;
/**
* {@code POINT} if {@link GeoKeys#RasterType} is {@link
GeoCodes#RasterPixelIsPoint},
* {@code AREA} if it is {@link GeoCodes#RasterPixelIsArea}, or null if
unspecified.
- * This is computed by {@link #build(Vector, Vector, String)} and made
available to the caller.
+ * This is computed by {@link #build(GeoKeysLoader)} and made available to
the caller.
*/
public CellGeometry cellGeometry;
@@ -512,6 +511,7 @@ public final class CRSBuilder extends
ReferencingFactoryContainer {
* @throws ClassCastException if an object defined by an EPSG code is not
of the expected type.
* @throws FactoryException if an error occurred during objects creation
with the factories.
*/
+ @SuppressWarnings("UseSpecificCatch")
public CoordinateReferenceSystem build(final GeoKeysLoader source) throws
FactoryException {
try {
source.logger = this;
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
index 68d704b4f8..ec1d0d93ed 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/GridGeometryBuilder.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.storage.geotiff.reader;
+import java.time.Instant;
import java.util.NoSuchElementException;
import org.opengis.util.FactoryException;
import org.opengis.util.NoSuchIdentifierException;
@@ -25,12 +26,17 @@ import org.opengis.metadata.spatial.DimensionNameType;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.crs.DefaultTemporalCRS;
import org.apache.sis.referencing.operation.matrix.MatrixSIS;
import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
import
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
import org.apache.sis.storage.base.MetadataBuilder;
import org.apache.sis.storage.event.StoreListeners;
@@ -78,13 +84,9 @@ import org.apache.sis.math.Vector;
*/
public final class GridGeometryBuilder extends GeoKeysLoader {
/**
- * Default scale factory to apply if a row in the model transformation
contains only zero values.
- * The matrix in a GeoTIFF file is always of size 4×4 even if the
<abbr>CRS</abbr> is two-dimensional.
- * In the latter case, the matrix row for the third dimension has only
zero values. That row should be
- * discarded when building the final {@link GridGeometry}, but there is
sometime inconsistency between
- * the number of <abbr>CRS</abbr> dimensions and which matrix rows have
been assigned non-zero values.
+ * Number of dimensions of the horizontal part.
*/
- private static final double DEFAULT_SCALE_FACTOR = 1;
+ public static final int BIDIMENSIONAL = 2;
//
╔════════════════════════════════════════════════════════════════════════════════╗
// ║
║
@@ -108,7 +110,7 @@ public final class GridGeometryBuilder extends
GeoKeysLoader {
public Vector modelTiePoints;
/**
- * The conversion from grid coordinates to CRS coordinates as an affine
transform.
+ * The conversion from grid coordinates to <abbr>CRS</abbr> coordinates as
an affine transform.
* The "grid to CRS" transform can be determined in different ways, from
simpler to more complex:
*
* <ul>
@@ -174,14 +176,18 @@ public final class GridGeometryBuilder extends
GeoKeysLoader {
/**
* Suggested value for a general description of the transformation form
grid coordinates to "real world" coordinates.
- * This information is obtained as a side-effect of {@link
#build(StoreListeners, long, long)} call.
+ * This information is obtained as a side-effect of {@link
#build(StoreListeners, long, long, Instant)} call.
+ *
+ * @see #completeMetadata(GridGeometry, MetadataBuilder)
*/
private String description;
/**
* {@code POINT} if {@link GeoKeys#RasterType} is {@link
GeoCodes#RasterPixelIsPoint},
* {@code AREA} if it is {@link GeoCodes#RasterPixelIsArea}, or null if
unspecified.
- * This information is obtained as a side-effect of {@link
#build(StoreListeners, long, long)} call.
+ * This information is obtained as a side-effect of {@link
#build(StoreListeners, long, long, Instant)} call.
+ *
+ * @see #completeMetadata(GridGeometry, MetadataBuilder)
*/
private CellGeometry cellGeometry;
@@ -236,7 +242,7 @@ public final class GridGeometryBuilder extends
GeoKeysLoader {
* Grid to CRS conversion: crs = grid × scale + translation
* We rearrange as: translation = crs - grid × scale
* where: grid = modelTiePoints[i]
- * crs = modelTiePoints[i +
RECORD_LENGTH/2]
+ * crs = modelTiePoints[i +
RECORD_LENGTH / BIDIMENSIONAL]
* scale = affine(i,i) — on the
diagonal
*/
if (distance != Double.POSITIVE_INFINITY) {
@@ -245,7 +251,7 @@ public final class GridGeometryBuilder extends
GeoKeysLoader {
final int trCol = affine.getNumCol() - 1;
for (int j=0; j<numDim; j++) {
final double src = -modelTiePoints.doubleValue(nearest +
j);
- final double tgt = modelTiePoints.doubleValue(nearest + j
+ Localization.RECORD_LENGTH / 2);
+ final double tgt = modelTiePoints.doubleValue(nearest + j
+ Localization.RECORD_LENGTH / BIDIMENSIONAL);
var t = DoubleDouble.of(src,
decimal).multiply(affine.getNumber(j,j), decimal).add(tgt, decimal);
affine.setNumber(j, trCol, t);
}
@@ -264,14 +270,17 @@ public final class GridGeometryBuilder extends
GeoKeysLoader {
* @param listeners the listeners where to report warnings.
* @param width the image width in pixels.
* @param height the image height in pixels.
+ * @param imageDate the date/time found in the {@code DATE_TIME} tag, or
{@code null} if none.
* @return the grid geometry, guaranteed non-null.
* @throws FactoryException if an error occurred while creating a CRS or a
transform.
*/
@SuppressWarnings("fallthrough")
- public GridGeometry build(final StoreListeners listeners, final long
width, final long height) throws FactoryException {
+ public GridGeometry build(final StoreListeners listeners, final long
width, final long height, final Instant imageDate)
+ throws FactoryException
+ {
CoordinateReferenceSystem crs = null;
if (keyDirectory != null) {
- final CRSBuilder helper = new CRSBuilder(listeners);
+ final var helper = new CRSBuilder(listeners);
try {
crs = helper.build(this);
description = helper.description;
@@ -289,47 +298,90 @@ public final class GridGeometryBuilder extends
GeoKeysLoader {
}
}
/*
- * If the CRS is non-null, then it is either two- or three-dimensional.
- * The `affine` matrix may be for a greater number of dimensions, so it
- * may need to be reduced.
+ * If the CRS is non-null, then the spatial part is either two- or
three-dimensional.
+ * A temporal axis may be added to the non-null CRS.
*/
- int n = (crs != null) ? crs.getCoordinateSystem().getDimension() : 2;
- final var axisTypes = new DimensionNameType[n];
- final var high = new long[n];
- switch (n) {
+ final double timeCoordinate;
+ final TemporalCRS temporalCRS;
+ final int spatialDimension = (crs != null) ?
crs.getCoordinateSystem().getDimension() : BIDIMENSIONAL;
+ final int dimension;
+ if (imageDate != null) {
+ dimension = spatialDimension + 1;
+ temporalCRS = CommonCRS.defaultTemporal();
+ timeCoordinate =
DefaultTemporalCRS.castOrCopy(temporalCRS).toValue(imageDate);
+ if (crs != null) {
+ crs = CRS.compound(crs, temporalCRS);
+ }
+ } else {
+ dimension = spatialDimension;
+ timeCoordinate = Double.NaN;
+ temporalCRS = null;
+ }
+ final var axisTypes = new DimensionNameType[dimension];
+ final var high = new long[dimension];
+ if (temporalCRS != null) {
+ axisTypes[spatialDimension] = DimensionNameType.TIME;
+ }
+ switch (spatialDimension) {
default: axisTypes[2] = DimensionNameType.VERTICAL; // Fallthrough
everywhere.
case 2: axisTypes[1] = DimensionNameType.ROW; high[1] =
height - 1;
case 1: axisTypes[0] = DimensionNameType.COLUMN; high[0] =
width - 1;
case 0: break;
}
final var extent = new GridExtent(axisTypes, null, high, true);
- boolean pixelIsPoint = (cellGeometry == CellGeometry.POINT);
final MathTransformFactory factory =
DefaultMathTransformFactory.provider();
+ PixelInCell anchor = (cellGeometry == CellGeometry.POINT) ?
PixelInCell.CELL_CENTER : PixelInCell.CELL_CORNER;
GridGeometry gridGeometry;
try {
MathTransform gridToCRS = null;
if (affine != null) {
- final Matrix m = Matrices.resizeAffine(affine, ++n, n);
- Matrices.forceNonZeroScales(m, DEFAULT_SCALE_FACTOR);
+ /*
+ * The `affine` matrix is always 4×4 in a GeoTIFF file, which
may be larger than requested.
+ * Resize the matrix to the size that we need. Maybe the last
dimension, initially ignored,
+ * become used by the temporal dimension, so we need to clear
that dimension for safety.
+ */
+ final Matrix m = Matrices.resizeAffine(affine, dimension + 1,
dimension + 1);
+ if (temporalCRS != null) {
+ for (int i=0; i <= dimension; i++) {
+ m.setElement(spatialDimension, i, 0);
+ m.setElement(i, spatialDimension, 0);
+ }
+ m.setElement(spatialDimension, dimension, timeCoordinate);
+ m.setElement(spatialDimension, spatialDimension,
Double.NaN); // Unknown duration.
+ }
+ /*
+ * If the CRS has no vertical component, then the matrix row
and column for the vertical coordinates
+ * should be ignored. However, we observed inconsistency in
some GeoTIFF files between the number of
+ * CRS dimensions and the matrix rows which have been assigned
non-zero values. Because rows of only
+ * zero values cause problems, we assign a NaN value in one of
their columns.
+ */
+ Matrices.forceNonZeroScales(m, Double.NaN);
gridToCRS = factory.createAffineTransform(m);
} else if (modelTiePoints != null) {
- pixelIsPoint = true;
+ anchor = PixelInCell.CELL_CENTER;
gridToCRS = Localization.nonLinear(modelTiePoints);
- gridToCRS = factory.createPassThroughTransform(0, gridToCRS, n
- 2);
+ gridToCRS = factory.createPassThroughTransform(0, gridToCRS,
spatialDimension - BIDIMENSIONAL);
+ if (temporalCRS != null) {
+ gridToCRS = MathTransforms.compound(gridToCRS,
MathTransforms.linear(Double.NaN, timeCoordinate));
+ }
}
- gridGeometry = new GridGeometry(extent, pixelIsPoint ?
PixelInCell.CELL_CENTER : PixelInCell.CELL_CORNER, gridToCRS, crs);
+ gridGeometry = new GridGeometry(extent, anchor, gridToCRS, crs);
} catch (TransformException e) {
+ /*
+ * Note: we catch TransformExceptions because they may be caused
by erroneous data in the GeoTIFF file,
+ * but let FactoryExceptions propagate because they are more
likely to be a SIS configuration problem.
+ */
GeneralEnvelope envelope = null;
if (crs != null) {
envelope = new GeneralEnvelope(crs);
envelope.setToNaN();
+ if (temporalCRS != null && anchor == PixelInCell.CELL_CENTER) {
+ // The coordinate is the lower value (start) of the time
range.
+ envelope.setRange(spatialDimension, timeCoordinate,
Double.NaN);
+ }
}
- gridGeometry = new GridGeometry(extent, envelope,
GridOrientation.HOMOTHETY);
+ gridGeometry = new GridGeometry(extent, envelope,
GridOrientation.UNKNOWN);
canNotCreate(listeners, e);
- /*
- * Note: we catch TransformExceptions because they may be caused
by erroneous data in the GeoTIFF file,
- * but let FactoryExceptions propagate because they are more
likely to be a SIS configuration problem.
- */
}
keyDirectory = null; // Not needed anymore, so let GC
do its work.
numericParameters = null;
@@ -344,7 +396,7 @@ public final class GridGeometryBuilder extends
GeoKeysLoader {
*
* <h4>Prerequisite</h4>
* <ul>
- * <li>{@link #build(StoreListeners, long, long)} must have been invoked
successfully before this method.</li>
+ * <li>{@link #build(StoreListeners, long, long, Instant)} must have
been invoked successfully before this method.</li>
* <li>{@link ImageFileDirectory} must have filled its part of metadata
before to invoke this method.</li>
* </ul>
*
@@ -358,7 +410,7 @@ public final class GridGeometryBuilder extends
GeoKeysLoader {
* <li>{@code metadata/referenceSystemInfo}</li>
* </ul>
*
- * @param gridGeometry the grid geometry computed by {@link
#build(StoreListeners, long, long)}.
+ * @param gridGeometry the grid geometry computed by {@link
#build(StoreListeners, long, long, Instant)}.
* @param metadata the helper class where to write metadata values.
* @throws NumberFormatException if a numeric value was stored as a string
and cannot be parsed.
*/
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Type.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Type.java
index 093f27c106..89532b5401 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Type.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/Type.java
@@ -615,6 +615,8 @@ public enum Type {
/**
* Reads the value as strings. There is usually exactly one string, but an
arbitrary amount is allowed.
+ * The default implementation assumes that the vector contains numerical
data, which is the case of all
+ * types except {@link #ASCII}. This method is overridden for handling any
text in the {@code ASCII} case.
*
* @param input the input from where to read the value.
* @param length the string length, including the final NUL byte.
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
index 7a5a64e482..9daaeb1969 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/reader/XMLMetadata.java
@@ -76,7 +76,7 @@ public final class XMLMetadata implements Filter {
/**
* The bytes to decode as an XML document.
- * DGIWG specification mandates UTF-8 encoding.
+ * <abbr>DGIWG</abbr> specification mandates UTF-8 encoding.
*/
private byte[] bytes;
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
index 03b2f6b0d7..12f9f057e7 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/writer/GeoEncoder.java
@@ -20,6 +20,10 @@ import java.util.Arrays;
import java.util.List;
import java.util.EnumMap;
import java.util.logging.Level;
+import java.time.Instant;
+import java.time.Duration;
+import java.time.temporal.Temporal;
+import java.time.format.DateTimeFormatter;
import static javax.imageio.plugins.tiff.GeoTIFFTagSet.TAG_GEO_ASCII_PARAMS;
import static javax.imageio.plugins.tiff.GeoTIFFTagSet.TAG_GEO_DOUBLE_PARAMS;
import javax.measure.Unit;
@@ -55,6 +59,7 @@ import org.opengis.parameter.ParameterValue;
import org.apache.sis.measure.Units;
import org.apache.sis.measure.Longitude;
import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.StringBuilders;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.internal.shared.Strings;
import org.apache.sis.util.internal.shared.CollectionsExt;
@@ -120,6 +125,14 @@ public final class GeoEncoder {
*/
private String citation;
+ /**
+ * The temporal coordinate of the image, or {@code null} if none.
+ * This is extracted from the temporal dimension of the grid geometry.
+ *
+ * @see #imageDate()
+ */
+ private Instant imageDate;
+
/**
* Whether the map projection is a pseudo-projection. The latter has no
GeoTIFF code.
* Therefore, they need to be replaced by the non-pseudo variant with
adjustments of
@@ -257,7 +270,7 @@ public final class GeoEncoder {
* @throws IncompleteGridGeometryException if the grid geometry is
incomplete.
* @throws IncompatibleResourceException if the grid geometry cannot be
encoded.
*/
- public void write(GridGeometry grid, final MetadataFetcher<?> metadata)
+ public void write(GridGeometry grid, final MetadataFetcher metadata)
throws FactoryException, TransformException,
IncommensurableException, IncompatibleResourceException
{
grid = grid.shiftGridToZeros();
@@ -265,8 +278,8 @@ public final class GeoEncoder {
isPoint = CollectionsExt.first(metadata.cellGeometry) ==
CellGeometry.POINT;
final var anchor = isPoint ? PixelInCell.CELL_CENTER :
PixelInCell.CELL_CORNER;
/*
- * Get the dimension indices of the two-dimensional slice to write.
They should be the first dimensions,
- * but we allow those dimensions to appear elsewhere.
+ * Get the dimension indices of the two-dimensional slice to write.
+ * They should be the first dimensions, but we allow those dimensions
to appear elsewhere.
*/
final int[] dimensions =
grid.getExtent().getSubspaceDimensions(BIDIMENSIONAL);
final GridGeometry horizontal = grid.selectDimensions(dimensions);
@@ -276,6 +289,20 @@ public final class GeoEncoder {
String message =
resources().getString(Resources.Keys.CanNotEncodeNonLinearModel);
throw new
IncompatibleResourceException(message).addAspect("gridToCRS");
}
+ /*
+ * Extract the temporal coordinate. This information is stored for
restitution by the
+ * `imageDate()` method. This information, even absent, shall
unconditionally replace
+ * the information obtained from metadata for avoiding
misinterpretation at read time.
+ */
+ final Instant[] time = grid.getTemporalExtent();
+ if (time.length != 0) {
+ imageDate = time[0];
+ if (isPoint && time.length > 1) {
+ // TODO: replace by the following when allowed to compile
for JDK23:
+ // imageDate =
imageDate.plus(imageDate.until(t[1]).dividedBy(2));
+ imageDate = imageDate.plus(Duration.between(imageDate,
time[1]).dividedBy(2));
+ }
+ }
}
/*
* Write the horizontal component of the CRS. We need to take the CRS
@@ -861,6 +888,45 @@ public final class GeoEncoder {
return JDK15.isEmpty(asciiParams) ? null :
List.of(asciiParams.toString());
}
+ /**
+ * Formats the given date in the way that it should be encoded in the
{@code DATE_TIME} tag.
+ * According the <abbr>TIFF</abbr> specification, this tag contains the
image creation date.
+ * But the <abbr>DGIWG</abbr> specification reinterprets that information
as the time when
+ * the imagery values were collected, which is a point on the
<abbr>CRS</abbr> temporal axis.
+ *
+ * <p>The list given to this method should be the list returned by {@link
#imageDate()} when
+ * the image contains spatiotemporal referencing information. But the
argument may also be a
+ * list obtained from other source when the image to write has no
spatiotemporal coordinates,
+ * in which case we are writing a plain <abbr>TIFF</abbr> image and the
date may be obtained
+ * from ISO 19115 metadata.</p>
+ *
+ * @param imageDate the date to encode, or {@code null} or empty if none.
+ * @return the first date encoded as a string, or {@code null} if none.
+ */
+ public static List<String> creationDates(final List<Temporal> imageDate) {
+ if (imageDate == null || imageDate.isEmpty()) {
+ return null;
+ }
+ var value = new
StringBuilder(DateTimeFormatter.ISO_INSTANT.format(imageDate.get(0)));
+ int s = value.lastIndexOf(".");
+ if (s >= 0) value.setLength(s);
+ StringBuilders.replace(value, "-", ":");
+ StringBuilders.replace(value, "T", " ");
+ StringBuilders.replace(value, "Z", "");
+ return List.of(value.toString());
+ }
+
+ /**
+ * Returns the temporal coordinate of the image, or an empty list if none.
+ * This information shall unconditionally replace the information obtained
from metadata, even when
+ * the list is empty, for avoiding misinterpretation of the spatiotemporal
location at reading time.
+ *
+ * @return the temporal coordinate of the image as a list of 0 or 1
element.
+ */
+ public List<Temporal> imageDate() {
+ return (imageDate != null) ? List.of(imageDate) : List.of();
+ }
+
/**
* Returns the coefficients of the affine transform, or {@code null} if
none.
* Array length is fixed to 16 elements, for a 4×4 matrix in row-major
order.
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
b/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
index c34df3d4f4..bb0104a6d9 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
@@ -42,7 +42,7 @@
* @author Martin Desruisseaux (Geomatys)
* @author Alexis Manin (Geomatys)
* @author Guilhem Legal (Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 1.0
*/
module org.apache.sis.storage.sql {
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
index ec2b149779..c764390369 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java
@@ -56,7 +56,7 @@
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @author Alexis Manin (Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 1.0
*/
package org.apache.sis.storage.sql;
diff --git a/endorsed/src/org.apache.sis.storage/main/module-info.java
b/endorsed/src/org.apache.sis.storage/main/module-info.java
index 527a1e2d5e..8b7fe8b1c5 100644
--- a/endorsed/src/org.apache.sis.storage/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage/main/module-info.java
@@ -20,7 +20,7 @@
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 0.3
*/
module org.apache.sis.storage {
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
index 22b3c11e6b..5de1f71862 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
@@ -370,7 +370,7 @@ search: synchronized (members) {
* but a future version may use the state of this
`CoverageAggregator`, for example making a better
* effort to align the resources on the same "gridToCRS" transform.
*/
- final var crs =
DefaultTemporalCRS.castOrCopy(CommonCRS.Temporal.TRUNCATED_JULIAN.crs());
+ final var crs =
DefaultTemporalCRS.castOrCopy(CommonCRS.defaultTemporal());
double scale = crs.toValue(span);
double offset = crs.toValue(lower);
long index = Numerics.roundAndClamp(offset / scale); //
See comment in above method.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
index b9d249cb25..02d0a6715a 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataFetcher.java
@@ -17,7 +17,6 @@
package org.apache.sis.storage.base;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -60,10 +59,8 @@ import org.opengis.metadata.citation.Responsibility;
* API of this class may change in any future SIS versions.
*
* @author Martin Desruisseaux (Geomatys)
- *
- * @param <T> type of temporal objects.
*/
-public abstract class MetadataFetcher<T> {
+public class MetadataFetcher {
/**
* Types of date to accept as a date of last update, in preference order.
*/
@@ -112,7 +109,7 @@ public abstract class MetadataFetcher<T> {
*
* <p>Path: {@code metadata/identificationInfo/citation/date}</p>
*/
- public List<T> creationDate;
+ public List<Temporal> creationDate;
/**
* Dates of the last update, or {@code null} if none.
@@ -121,7 +118,7 @@ public abstract class MetadataFetcher<T> {
*
* @see #lastUpdate(Metadata)
*/
- public List<T> lastUpdate;
+ public List<Temporal> lastUpdate;
/**
* Type of the {@link #lastUpdate} values as an index in the {@link
#LAST_UPDATE_TYPES} array.
@@ -186,7 +183,7 @@ public abstract class MetadataFetcher<T> {
* @param accept the method to invoke for each element.
* @param elements the collection of elements, or {@code null} if none.
*/
- private <E> void forEach(final BiPredicate<MetadataFetcher<T>,E> accept,
final Iterable<? extends E> elements) {
+ private <E> void forEach(final BiPredicate<MetadataFetcher, E> accept,
final Iterable<? extends E> elements) {
if (elements != null) {
for (final E info : elements) {
if (info != null && accept.test(this, info)) break;
@@ -434,30 +431,19 @@ public abstract class MetadataFetcher<T> {
* @param clear whether to clear the list before to add the date.
* @return the collection where the date was added.
*/
- private List<T> addDate(List<T> target, final CitationDate value, final
boolean clear) {
- @SuppressWarnings("deprecation")
- final Date date = value.getDate();
+ private List<Temporal> addDate(List<Temporal> target, final CitationDate
value, final boolean clear) {
+ final Temporal date = value.getReferenceDate();
if (date != null) {
if (target == null) {
target = new ArrayList<>(2); // We will usually have
only one element.
} else if (clear) {
target.clear();
}
- target.add(convertDate(date));
+ target.add(date);
}
return target;
}
- /**
- * Converts the given date into the object to store.
- * The {@code <T>} type may be for example {@code <String>}
- * with a string representation specified by the format implemented by the
store.
- *
- * @param date the date to convert.
- * @return subclass-dependent object representing the given date.
- */
- protected abstract T convertDate(final Date date);
-
/**
* Returns the first date of type {@link DateType#LAST_UPDATE}.
* If there is no last update, then this method fallbacks on {@link
DateType#LAST_REVISION}.
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
index 4d190f4c48..f0b43e0d6d 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java
@@ -441,7 +441,7 @@ final class Store extends URIDataStore implements
FeatureSet {
if (startTime != null) {
final TemporalCRS temporal;
if (isTimeAbsolute) {
- temporal = TimeEncoding.DEFAULT.crs();
+ temporal = CommonCRS.defaultTemporal();
timeEncoding = TimeEncoding.ABSOLUTE;
} else {
temporal = builder.createTemporalCRS(startTime, timeUnit);
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
index d48618192a..bcf7fe7164 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/TimeEncoding.java
@@ -36,15 +36,10 @@ import org.apache.sis.measure.Units;
* @author Martin Desruisseaux (Geomatys)
*/
class TimeEncoding extends SurjectiveConverter<String,Instant> {
- /**
- * The temporal coordinate reference system to use for {@link #ABSOLUTE}
time encoding.
- */
- static final CommonCRS.Temporal DEFAULT =
CommonCRS.Temporal.TRUNCATED_JULIAN;
-
/**
* Times are formatted as ISO dates.
*/
- static final TimeEncoding ABSOLUTE = new TimeEncoding(DEFAULT.datum(),
Units.DAY) {
+ static final TimeEncoding ABSOLUTE = new
TimeEncoding(CommonCRS.defaultTemporal().getDatum(), Units.DAY) {
@Override public Instant apply(final String time) {
return LenientDateFormat.parseInstantUTC(time);
}
diff --git a/endorsed/src/org.apache.sis.util/main/module-info.java
b/endorsed/src/org.apache.sis.util/main/module-info.java
index 23e4d4def6..84025a789f 100644
--- a/endorsed/src/org.apache.sis.util/main/module-info.java
+++ b/endorsed/src/org.apache.sis.util/main/module-info.java
@@ -22,7 +22,7 @@
*
* @author Martin Desruisseaux (MPO, IRD, Geomatys)
* @author Alexis Manin (Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 0.3
*/
module org.apache.sis.util {
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
index e9e287a54e..677458deaf 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
@@ -391,7 +391,7 @@ public class Statistics implements DoubleConsumer,
LongConsumer, Cloneable, Seri
}
/**
- * Multiplies the statistics by the given factor. The given scale factory
is also applied
+ * Multiplies the statistics by the given factor. The given scale factor
is also applied
* recursively on the {@linkplain #differences() differences} statistics,
if any.
* Invoking this method transforms the statistics as if every values given
to the
* {@code accept(…)} had been first multiplied by the given factor.