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

commit b6198d02ceec6d4f4cbbd48dbfd2e982fe9dadf9
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Apr 5 17:02:34 2019 +0200

    Decode Coordinate Reference System from a netCDF file with following 
attributes:
    "EPSG_code", "ESRI_pe_string", "GeoTransform", "spatial_ref".
    Note that those attributes are not part of CF-conventions.
---
 .../main/java/org/apache/sis/io/wkt/Warnings.java  |   2 +-
 .../referencing/j2d/AffineTransform2D.java         |   4 +-
 .../org/apache/sis/internal/netcdf/CRSBuilder.java |   8 +-
 .../org/apache/sis/internal/netcdf/Decoder.java    |  29 +-
 .../java/org/apache/sis/internal/netcdf/Grid.java  |  23 +-
 .../apache/sis/internal/netcdf/GridMapping.java    | 362 +++++++++++++++++++++
 .../org/apache/sis/internal/netcdf/Variable.java   |  12 +
 .../org/apache/sis/internal/netcdf/impl/HYCOM.java |   3 +-
 8 files changed, 432 insertions(+), 11 deletions(-)

diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java 
b/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java
index 4e99508..f7ab6f9 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Warnings.java
@@ -327,7 +327,7 @@ public final class Warnings implements Localized, 
Serializable {
     }
 
     /**
-     * Returns a string representation of the warning messages if the default 
locale.
+     * Returns a string representation of the warning messages in the default 
locale.
      * The locale used by this method is given by {@link #getLocale()}.
      * This is usually the locale given to the {@link WKTFormat} constructor.
      *
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineTransform2D.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineTransform2D.java
index 6455e6a..409a7b6 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineTransform2D.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineTransform2D.java
@@ -104,7 +104,7 @@ public class AffineTransform2D extends 
ImmutableAffineTransform
      * Constructs a new {@code AffineTransform2D} from 6 values representing 
the 6 specifiable
      * entries of the 3×3 transformation matrix. Those values are given 
unchanged to the
      * {@link 
AffineTransform#AffineTransform(double,double,double,double,double,double) super
-     * class constructor}.
+     * class constructor}, except for negative zeros that are replaced by 
positive zeros.
      *
      * @param m00 the X coordinate scaling.
      * @param m10 the Y coordinate shearing.
@@ -126,7 +126,7 @@ public class AffineTransform2D extends 
ImmutableAffineTransform
      * <p>The inconsistency is in the use of {@link 
Double#doubleToLongBits(double)} for hash code and
      * {@code ==} for testing equality. The former is sensitive to the sign of 
0 while the later is not.</p>
      */
-    @Workaround(library="JDK", version="8") // Last verified in 1.8.0_05.
+    @Workaround(library="JDK", version="8")                         // Last 
verified in 1.8.0_05.
     private static double pz(final double value) {
         return (value != 0) ? value : 0;
     }
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
index 4c2579a..f097c65 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
@@ -51,9 +51,11 @@ import org.apache.sis.math.Vector;
 
 /**
  * Temporary object for building a coordinate reference system from the 
variables in a netCDF file.
- * Different instances are required for the geographic, vertical and temporal 
components of a CRS,
- * or if a netCDF file uses different CRS for different variables.
- * This builder is used as below:
+ * This class proceeds by inspecting the coordinate system axes. This is a 
different approach than
+ * {@link GridMapping}, which parses Well Known Text or EPSG codes declared in 
variable attributes.
+ *
+ * <p>Different instances are required for the geographic, vertical and 
temporal components of a CRS,
+ * or if a netCDF file uses different CRS for different variables. This 
builder is used as below:</p>
  *
  * <ol>
  *   <li>Invoke {@link #dispatch(List, Axis)} for all axes in a grid.
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
index ffc4480..2b0b80a 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
@@ -16,9 +16,12 @@
  */
 package org.apache.sis.internal.netcdf;
 
-import java.util.Date;
-import java.util.Objects;
+import java.util.Map;
+import java.util.HashMap;
 import java.util.Collection;
+import java.util.Objects;
+import java.util.Date;
+import java.util.TimeZone;
 import java.io.Closeable;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -29,6 +32,7 @@ import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
 
@@ -88,10 +92,21 @@ public abstract class Decoder extends 
ReferencingFactoryContainer implements Clo
     /**
      * The geodetic datum, created when first needed. The datum are generally 
not specified in netCDF files.
      * To make that clearer, we will build datum with names like "Unknown 
datum presumably based on WGS 84".
+     *
+     * @see CRSBuilder#build(Decoder)
      */
     final Datum[] datumCache;
 
     /**
+     * The CRS and <cite>grid to CRS</cite> transform defined by attributes in 
a variable. For example GDAL uses
+     * {@code "spatial_ref_sys"} and {@code "GeoTransform"} attributes 
associated to a variable having the name
+     * specified by the {@code "grid_mapping"} attribute.
+     *
+     * @see GridMapping#forVariable(Variable)
+     */
+    final Map<String,GridMapping> gridMapping;
+
+    /**
      * Where to send the warnings.
      */
     public final WarningListeners<DataStore> listeners;
@@ -114,6 +129,7 @@ public abstract class Decoder extends 
ReferencingFactoryContainer implements Clo
         this.listeners   = listeners;
         this.nameFactory = DefaultFactories.forBuildin(NameFactory.class);
         this.datumCache  = new Datum[CRSBuilder.DATUM_CACHE_SIZE];
+        this.gridMapping = new HashMap<>();
     }
 
     /**
@@ -244,6 +260,15 @@ public abstract class Decoder extends 
ReferencingFactoryContainer implements Clo
     public abstract Date[] numberToDate(String symbol, Number... values);
 
     /**
+     * Returns the timezone for decoding dates. Currently fixed to UTC.
+     *
+     * @return the timezone for dates.
+     */
+    public TimeZone getTimeZone() {
+        return TimeZone.getTimeZone(StandardDateFormat.UTC);
+    }
+
+    /**
      * Returns the value of the {@code "_Id"} global attribute. The UCAR 
library defines a
      * {@link ucar.nc2.NetcdfFile#getId()} method for that purpose, which we 
will use when
      * possible in case that {@code getId()} method is defined in an other way.
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
index bc9c2b3..6495ccd 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
@@ -104,6 +104,16 @@ public abstract class Grid extends NamedElement {
     private boolean isGeometryDetermined;
 
     /**
+     * Whether we computed a "grid to CRS" transform relative to pixel center 
or pixel corner.
+     * CF-Convention said: "If bounds are not provided, an application might 
reasonably assume
+     * the grid points to be at the centers of the cells, but we do not 
require that in this
+     * standard".
+     *
+     * @see #getAnchor()
+     */
+    private PixelInCell anchor = PixelInCell.CELL_CENTER;
+
+    /**
      * Constructs a new grid geometry information.
      */
     protected Grid() {
@@ -462,7 +472,6 @@ findFree:       for (int srcDim : axis.sourceDimensions) {
              * to be at the centers of the cells, but we do not require that 
in this standard". We nevertheless check
              * if an axis thinks otherwise.
              */
-            PixelInCell anchor = PixelInCell.CELL_CENTER;
             final CoordinateReferenceSystem crs = 
getCoordinateReferenceSystem(decoder);
             if (CRS.getHorizontalComponent(crs) instanceof GeographicCRS) {
                 for (final Axis axis : axes) {
@@ -480,7 +489,19 @@ findFree:       for (int srcDim : axis.sourceDimensions) {
     }
 
     /**
+     * Returns whether we computed a "grid to CRS" transform relative to pixel 
center or pixel corner.
+     * The default value is {@link PixelInCell#CELL_CENTER}, but may be 
modified after invocation of
+     * {@link #getGridGeometry(Decoder)}.
+     */
+    final PixelInCell getAnchor() {
+        return anchor;
+    }
+
+    /**
      * Logs a warning about a CRS or grid geometry that can not be created.
+     *
+     * @param  caller  one of {@code "getCoordinateReferenceSystem"} or {@code 
"getGridGeometry"}.
+     * @param  key     one of {@link Resources.Keys#CanNotCreateCRS_3} or 
{@link Resources.Keys#CanNotCreateGridGeometry_3}.
      */
     private void canNotCreate(final Decoder decoder, final String caller, 
final short key, final Exception ex) {
         warning(decoder.listeners, Grid.class, caller, ex, null, key, 
decoder.getFilename(), getName(), ex.getLocalizedMessage());
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
new file mode 100644
index 0000000..e5a82a8
--- /dev/null
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
@@ -0,0 +1,362 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.netcdf;
+
+import java.util.Map;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.text.ParseException;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.datum.PixelInCell;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.crs.AbstractCRS;
+import org.apache.sis.referencing.cs.AxesConvention;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.operation.transform.TransformSeparator;
+import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.internal.metadata.AxisDirections;
+import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.util.Constants;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.io.wkt.WKTFormat;
+import org.apache.sis.io.wkt.Warnings;
+import ucar.nc2.constants.CF;
+
+
+/**
+ * Temporary objects for creating a {@link GridGeometry} instance defined by 
attributes on a variable.
+ * Those attributes are defined by CF-conventions, but some other non-CF 
attributes are also in usage
+ * (e.g. GDAL or ESRI conventions). This class uses a different approach than 
{@link CRSBuilder},
+ * which creates Coordinate Reference Systems by inspecting coordinate system 
axes.
+ *
+ * <p>Current implementation does not yet parse CF-convention attributes.
+ * Only some GDAL and ESRI custom attributes are currently supported.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+final class GridMapping {
+    /**
+     * The Coordinate Reference System, or {@code null} if none. This CRS can 
be constructed from Well Known Text
+     * or EPSG codes declared in {@code "spatial_ref"}, {@code 
"ESRI_pe_string"} or {@code "EPSG_code"} attributes.
+     *
+     * <div class="note"><b>Note:</b> this come from different information 
than the one used by {@link CRSBuilder},
+     * which creates CRS by inspection of coordinate system axes.</div>
+     */
+    private final CoordinateReferenceSystem crs;
+
+    /**
+     * The <cite>grid to CRS</cite> transform, or {@code null} if none. This 
information is usually not specified
+     * except when using GDAL conventions. If {@code null}, then the transform 
should be inferred by {@link Grid}.
+     */
+    private final MathTransform gridToCRS;
+
+    /**
+     * Whether the {@link #crs} where defined by an EPSG code.
+     */
+    private final boolean isEPSG;
+
+    /**
+     * Creates an instance for the given {@link #crs} and {@link #gridToCRS} 
values.
+     */
+    private GridMapping(final CoordinateReferenceSystem crs, final 
MathTransform gridToCRS, final boolean isEPSG) {
+        this.crs       = crs;
+        this.gridToCRS = gridToCRS;
+        this.isEPSG    = isEPSG;
+    }
+
+    /**
+     * Fetches grid geometry information from attributes associated to the 
given variable.
+     *
+     * @param  variable  the variable for which to create a grid geometry.
+     */
+    static GridMapping forVariable(final Variable variable) {
+        final String name = variable.getAttributeAsString(CF.GRID_MAPPING);
+        if (name != null) {
+            final Map<String,GridMapping> gridMapping = 
variable.decoder.gridMapping;
+            GridMapping gm = gridMapping.get(name);
+            if (gm != null) {
+                return gm;
+            }
+            for (final Variable mapping : variable.decoder.getVariables()) {
+                if (name.equals(mapping.getName())) {
+                    gm = parseGeoTransform(mapping);
+                    if (gm != null) {
+                        gridMapping.put(name, gm);
+                        return gm;
+                    }
+                }
+            }
+        }
+        return parseNonStandard(variable);
+    }
+
+    /**
+     * Tries to parse a CRS and affine transform from GDAL GeoTransform 
coefficients.
+     * Those coefficients are not in the usual order expected by matrix, affine
+     * transforms or TFW files. The relationship from pixel/line (P,L) 
coordinates
+     * to CRS are:
+     *
+     * {@preformat math
+     *     X = c[0] + P*c[1] + L*c[2];
+     *     Y = c[3] + P*c[4] + L*c[5];
+     * }
+     *
+     * @param  mapping  the variable that contains attributes giving CRS 
definition.
+     * @return the mapping, or {@code null} if this method did not found grid 
geometry attributes.
+     */
+    private static GridMapping parseGeoTransform(final Variable mapping) {
+        final String wkt = mapping.getAttributeAsString("spatial_ref");
+        final String gtr = mapping.getAttributeAsString("GeoTransform");
+        if (wkt == null && gtr == null) {
+            return null;
+        }
+        short message = Resources.Keys.CanNotCreateCRS_3;
+        CoordinateReferenceSystem crs = null;
+        MathTransform gridToCRS = null;
+        try {
+            if (wkt != null) {
+                crs = CRS.fromWKT(wkt);
+            }
+            if (gtr != null) {
+                message = Resources.Keys.CanNotCreateGridGeometry_3;
+                final double[] c = CharSequences.parseDoubles(gtr, ' ');
+                if (c.length == 6) {
+                    gridToCRS = new AffineTransform2D(c[1], c[4], c[2], c[5], 
c[0], c[3]);         // X_DIMENSION, Y_DIMENSION
+                } else {
+                    canNotCreate(mapping, message, new 
DataStoreContentException(
+                            Errors.getResources(mapping.getLocale())
+                                  
.getString(Errors.Keys.UnexpectedArrayLength_2, 6, c.length)));
+                }
+            }
+        } catch (FactoryException | NumberFormatException e) {
+            canNotCreate(mapping, message, e);
+        }
+        return new GridMapping(crs, gridToCRS, false);
+    }
+
+    /**
+     * Tries to parse the Coordinate Reference System using ESRI conventions 
or other non-CF conventions.
+     * This method is invoked as a fallback if {@link 
#parseGeoTransform(Variable)} found no grid geometry.
+     *
+     * @param  variable  the variable potentially with attributes to parse.
+     * @return whether this method found grid geometry attributes.
+     */
+    private static GridMapping parseNonStandard(final Variable variable) {
+        boolean isEPSG = false;
+        String code = variable.getAttributeAsString("ESRI_pe_string");
+        if (code == null) {
+            code = variable.getAttributeAsString("EPSG_code");
+            if (code == null) {
+                return null;
+            }
+            isEPSG = true;
+        }
+        /*
+         * The Coordinate Reference System stored in those attributes often 
use the GeoTIFF flavor of EPSG codes,
+         * with (longitude, latitude) axis order instead than the 
authoritative order specified in EPSG database.
+         * Likewise, the "WKT 1" flavor used by ESRI is different than WKT 1 
defined by OGC 01-009 specification.
+         * The CRS parsings below need to take those differences in account, 
except axis order which is tested in
+         * the `adaptGridCRS(…)` method.
+         */
+        CoordinateReferenceSystem crs;
+        try {
+            if (isEPSG) {
+                crs = CRS.forCode(Constants.EPSG + ':' + isEPSG);
+            } else {
+                final WKTFormat f = new WKTFormat(variable.getLocale(), 
variable.decoder.getTimeZone());
+                
f.setConvention(org.apache.sis.io.wkt.Convention.WKT1_COMMON_UNITS);
+                crs = (CoordinateReferenceSystem) f.parseObject(code);
+                final Warnings warnings = f.getWarnings();
+                if (warnings != null) {
+                    final LogRecord record = new LogRecord(Level.WARNING, 
warnings.toString());
+                    record.setLoggerName(Modules.NETCDF);
+                    
record.setSourceClassName(Variable.class.getCanonicalName());
+                    record.setSourceMethodName("getGridGeometry");
+                    variable.decoder.listeners.warning(record);
+                }
+            }
+        } catch (FactoryException | ParseException | ClassCastException e) {
+            canNotCreate(variable, Resources.Keys.CanNotCreateCRS_3, e);
+            crs = null;
+        }
+        return new GridMapping(crs, null, isEPSG);
+    }
+
+    /**
+     * Logs a warning about a CRS or grid geometry that can not be created.
+     * This method presumes that {@link GridMapping} are invoked (indirectly) 
from {@link Variable#getGridGeometry()}.
+     *
+     * @param  key  one of {@link Resources.Keys#CanNotCreateCRS_3} or {@link 
Resources.Keys#CanNotCreateGridGeometry_3}.
+     * @param  ex   the exception that occurred while creating the CRS or grid 
geometry.
+     */
+    private static void canNotCreate(final Variable variable, final short key, 
final Exception ex) {
+        NamedElement.warning(variable.decoder.listeners, Variable.class, 
"getGridGeometry", ex, null,
+                key, variable.decoder.getFilename(), variable.getName(), 
ex.getLocalizedMessage());
+    }
+
+    /**
+     * Creates a new grid geometry for the given extent.
+     * This method should be invoked only when no existing {@link 
GridGeometry} can be used as template.
+     */
+    GridGeometry createGridCRS(final Variable variable) {
+        final List<Dimension> dimensions = variable.getGridDimensions();
+        final long[] upper = new long[dimensions.size()];
+        for (int i=0; i<upper.length; i++) {
+            final int d = (upper.length - 1) - i;           // Convert CRS 
dimension to netCDF dimension.
+            upper[i] = dimensions.get(d).length();
+        }
+        return new GridGeometry(new GridExtent(null, null, upper, false), 
PixelInCell.CELL_CENTER, gridToCRS, crs);
+    }
+
+    /**
+     * Creates the grid geometry from the {@link #crs} and {@link #gridToCRS} 
field,
+     * completing missing information with the given template.
+     *
+     * @param  variable  the variable for which to create a grid geometry.
+     * @param  template  template to use for completing missing information.
+     * @param  anchor    whether we computed "grid to CRS" transform relative 
to pixel center or pixel corner.
+     * @return the grid geometry with modified CRS and "grid to CRS" 
transform, or {@code null} if case of failure.
+     */
+    GridGeometry adaptGridCRS(final Variable variable, final GridGeometry 
template, final PixelInCell anchor) {
+        CoordinateReferenceSystem givenCRS = crs;
+        int firstAffectedCoordinate = 0;
+        boolean isSameGrid = true;
+        if (template.isDefined(GridGeometry.CRS)) {
+            final CoordinateReferenceSystem templateCRS = 
template.getCoordinateReferenceSystem();
+            if (givenCRS == null) {
+                givenCRS = templateCRS;
+                isSameGrid = false;
+            } else {
+                /*
+                 * The CRS built by Grid may have a different axis order than 
the CRS specified by grid mapping attributes.
+                 * Check which axis order seems to fit, then replace grid CRS 
by given CRS (potentially with swapped axes).
+                 * This is where the potential difference between EPSG axis 
order and grid axis order is handled. If we can
+                 * not find where to substitute the CRS, assume that the given 
CRS describes the first dimensions. We have
+                 * no guarantees that this later assumption is right, but it 
seems to match common practice.
+                 */
+                final CoordinateSystem cs = templateCRS.getCoordinateSystem();
+                CoordinateSystem subCS = givenCRS.getCoordinateSystem();
+                firstAffectedCoordinate = AxisDirections.indexOfColinear(cs, 
subCS);
+                if (firstAffectedCoordinate < 0) {
+                    givenCRS = 
AbstractCRS.castOrCopy(givenCRS).forConvention(AxesConvention.RIGHT_HANDED);
+                    subCS = givenCRS.getCoordinateSystem();
+                    firstAffectedCoordinate = 
AxisDirections.indexOfColinear(cs, subCS);
+                    if (firstAffectedCoordinate < 0) {
+                        firstAffectedCoordinate = 0;
+                        if (!isEPSG) {
+                            givenCRS = crs;                             // If 
specified by WKT, use the given CRS verbatim.
+                            subCS = givenCRS.getCoordinateSystem();
+                        }
+                    }
+                }
+                /*
+                 * Replace the grid CRS (or a component of it) by the CRS 
parsed from WKT or EPSG code with same (if possible)
+                 * axis order. If the grid CRS contains more axes (for example 
elevation or time axis), we try to keep them.
+                 */
+                CoordinateReferenceSystem[] components = {
+                    CRS.getComponentAt(templateCRS, 0, 
firstAffectedCoordinate), givenCRS,
+                    CRS.getComponentAt(templateCRS, firstAffectedCoordinate + 
subCS.getDimension(), cs.getDimension())
+                };
+                int count = 0;
+                for (CoordinateReferenceSystem c : components) {
+                    if (c != null) components[count++] = c;
+                }
+                switch (count) {
+                    case 0: break;                                          // 
Should never happen.
+                    case 1: givenCRS = components[0]; break;
+                    default: {
+                        components = ArraysExt.resize(components, count);
+                        Map<String,?> properties = 
IdentifiedObjects.getProperties(templateCRS);
+                        try {
+                            givenCRS = 
variable.decoder.getCRSFactory().createCompoundCRS(properties, components);
+                        } catch (FactoryException e) {
+                            canNotCreate(variable, 
Resources.Keys.CanNotCreateCRS_3, e);
+                            return null;
+                        }
+                        break;
+                    }
+                }
+                isSameGrid = templateCRS.equals(givenCRS);
+                if (isSameGrid) {
+                    givenCRS = templateCRS;                                 // 
Keep using existing instance if appropriate.
+                }
+            }
+        }
+        /*
+         * Perform the same substitution than above, but in the "grid to CRS" 
transform. Note that the "grid to CRS"
+         * is usually not specified, so the block performing substitution will 
rarely be executed. If executed, then
+         * then we need to perform selection in target dimensions (not source 
dimensions) because the first affected
+         * coordinate computed above is in CRS dimension, which is the target 
of "grid to CRS" transform.
+         */
+        MathTransform givenG2C = gridToCRS;
+        if (template.isDefined(GridGeometry.GRID_TO_CRS)) {
+            final MathTransform templateG2C = template.getGridToCRS(anchor);
+            if (givenG2C == null) {
+                givenG2C = templateG2C;
+                isSameGrid = false;
+            } else try {
+                int count = 0;
+                MathTransform[] components = new MathTransform[3];
+                final TransformSeparator sep = new 
TransformSeparator(templateG2C, variable.decoder.getMathTransformFactory());
+                if (firstAffectedCoordinate != 0) {
+                    sep.addTargetDimensionRange(0, firstAffectedCoordinate);
+                    components[count++] = sep.separate();
+                    sep.clear();
+                }
+                components[count++] = givenG2C;
+                final int next = firstAffectedCoordinate + 
givenG2C.getTargetDimensions();
+                final int upper = templateG2C.getTargetDimensions();
+                if (next != upper) {
+                    sep.addTargetDimensionRange(next, upper);
+                    components[count++] = sep.separate();
+                }
+                components = ArraysExt.resize(components, count);
+                givenG2C = MathTransforms.compound(components);
+                if (templateG2C.equals(givenG2C)) {
+                    givenG2C = templateG2C;                                 // 
Keep using existing instance if appropriate.
+                } else {
+                    isSameGrid = false;
+                }
+            } catch (FactoryException e) {
+                canNotCreate(variable, 
Resources.Keys.CanNotCreateGridGeometry_3, e);
+                return null;
+            }
+        }
+        /*
+         * At this point we finished to compute the grid geometry components.
+         * If any of them have changed, create the new grid geometry.
+         */
+        if (isSameGrid) {
+            return template;
+        } else {
+            return new GridGeometry(template.getExtent(), anchor, givenG2C, 
givenCRS);
+        }
+    }
+}
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
index 89eaca0..e81bc7c 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
@@ -656,6 +656,7 @@ public abstract class Variable extends NamedElement {
     public final GridGeometry getGridGeometry() throws IOException, 
DataStoreException {
         if (!gridDetermined) {
             gridDetermined = true;                      // Set first so we 
don't try twice in case of failure.
+            final GridMapping gridMapping = GridMapping.forVariable(this);
             final Adjustment adjustment = new Adjustment();
             final Grid info = getGrid(adjustment);
             if (info != null) {
@@ -730,7 +731,18 @@ public abstract class Variable extends NamedElement {
                         grid = grid.derive().resize(extent, 
dataToGridIndices).build();
                     }
                 }
+                /*
+                 * At this point we finished to build a grid geometry from the 
information provided by axes.
+                 * If there is grid mapping attributes (e.g. "EPSG_code", 
"ESRI_pe_string", "GeoTransform",
+                 * "spatial_ref", etc.), substitute some parts of the grid 
geometry by the parts built from
+                 * those attributes.
+                 */
+                if (gridMapping != null) {
+                    grid = gridMapping.adaptGridCRS(this, grid, 
info.getAnchor());
+                }
                 gridGeometry = grid;
+            } else if (gridMapping != null) {
+                gridGeometry = gridMapping.createGridCRS(this);
             }
         }
         return gridGeometry;
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/HYCOM.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/HYCOM.java
index 3e8ec40..a356aa2 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/HYCOM.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/HYCOM.java
@@ -22,7 +22,6 @@ import java.util.Locale;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.GregorianCalendar;
-import java.util.TimeZone;
 import org.apache.sis.math.Vector;
 import org.apache.sis.measure.Units;
 import org.apache.sis.internal.util.StandardDateFormat;
@@ -107,7 +106,7 @@ final class HYCOM {
                          */
                         Vector values = variable.read();
                         final double[] times = new double[values.size()];
-                        final GregorianCalendar calendar = new 
GregorianCalendar(TimeZone.getTimeZone(StandardDateFormat.UTC), Locale.US);
+                        final GregorianCalendar calendar = new 
GregorianCalendar(decoder.getTimeZone(), Locale.US);
                         calendar.clear();
                         for (int i=0; i<times.length; i++) {
                             double time = values.doubleValue(i);               
             // Date encoded as a double (e.g. 20181017)

Reply via email to