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)
