sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/01: Merge branch 'geoapi-3.1'
Date Wed, 13 Feb 2019 18:49:45 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sis.git

commit f460fa53177d9034266ac4896f35d51b65705931
Merge: 15af661 e3e6efa
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Feb 13 19:48:46 2019 +0100

    Merge branch 'geoapi-3.1'

 .../org/apache/sis/internal/gui/Resources.java     |   8 +-
 .../java/org/apache/sis/internal/feature/ESRI.java |  57 +-
 .../apache/sis/internal/feature/Geometries.java    |  44 +-
 .../java/org/apache/sis/internal/feature/JTS.java  |  79 +-
 .../org/apache/sis/internal/feature/Java2D.java    |  78 +-
 .../org/apache/sis/internal/feature/Resources.java |  10 +-
 .../sis/internal/feature/j2d/ShapeProperties.java  | 258 ++++++
 .../sis/internal/feature}/j2d/package-info.java    |  15 +-
 .../feature/jts/GeometryCoordinateTransform.java   |  84 ++
 .../internal/feature/jts/GeometryTransform.java    | 166 ++++
 .../org/apache/sis/internal/feature/jts/JTS.java   | 156 ++++
 .../sis/internal/feature/jts}/package-info.java    |  14 +-
 .../org/apache/sis/internal/feature/ESRITest.java  |  16 +
 .../sis/internal/feature/GeometriesTestCase.java   |  24 +-
 .../org/apache/sis/internal/feature/JTSTest.java   |   1 +
 .../internal/feature/j2d/ShapePropertiesTest.java  |  66 ++
 .../apache/sis/internal/feature/jts/JTSTest.java   | 116 +++
 .../apache/sis/test/suite/FeatureTestSuite.java    |   2 +
 .../sis/internal/jaxb/IdentifierMapAdapter.java    |   4 +-
 .../apache/sis/internal/jaxb/gml/TimeInstant.java  |   3 +-
 .../apache/sis/internal/jaxb/gml/TimePeriod.java   |   3 +-
 .../metadata/EllipsoidalHeightCombiner.java        |   9 +-
 .../sis/internal/metadata/MetadataUtilities.java   |   4 +-
 .../apache/sis/internal/metadata/NameMeaning.java  |   4 +-
 .../sis/internal/metadata/NameToIdentifier.java    |   8 +-
 .../apache/sis/internal/metadata/Resources.java    |   4 +-
 .../sis/internal/metadata/sql/SQLUtilities.java    |  95 ++-
 .../sis/internal/simple/CitationConstant.java      |   3 +-
 .../main/java/org/apache/sis/io/wkt/Formatter.java |  45 +-
 .../org/apache/sis/io/wkt/MathTransformParser.java |   4 +-
 .../main/java/org/apache/sis/io/wkt/Symbols.java   |  17 +-
 .../main/java/org/apache/sis/io/wkt/Warnings.java  |   6 +-
 .../org/apache/sis/metadata/MetadataStandard.java  |   3 +-
 .../org/apache/sis/metadata/sql/Dispatcher.java    |   3 +-
 .../apache/sis/metadata/sql/MetadataSource.java    |   3 +-
 .../apache/sis/util/iso/DefaultNameFactory.java    |   3 +-
 .../java/org/apache/sis/xml/NilObjectHandler.java  |   3 +-
 .../internal/metadata/sql/SQLUtilitiesTest.java    |  23 +-
 .../apache/sis/test/mock/IdentifiedObjectMock.java |   3 +-
 .../java/org/apache/sis/coverage/Category.java     | 195 +++--
 .../java/org/apache/sis/coverage/CategoryList.java | 469 +++++------
 .../org/apache/sis/coverage/ConvertedCategory.java |   4 +-
 .../coverage/MismatchedCoverageRangeException.java |  63 ++
 .../org/apache/sis/coverage/SampleDimension.java   | 135 ++--
 .../org/apache/sis/coverage/SampleRangeFormat.java |  43 +-
 ...ion.java => SubspaceNotSpecifiedException.java} |  37 +-
 .../main/java/org/apache/sis/coverage/ToNaN.java   |  32 +-
 .../org/apache/sis/coverage/grid/GridChange.java   | 476 -----------
 .../org/apache/sis/coverage/grid/GridCoverage.java |  58 +-
 .../apache/sis/coverage/grid/GridDerivation.java   | 893 +++++++++++++++++++++
 .../org/apache/sis/coverage/grid/GridExtent.java   | 541 +++++++++----
 .../org/apache/sis/coverage/grid/GridGeometry.java | 406 +++++-----
 ...tion.java => IllegalGridGeometryException.java} |  38 +-
 .../apache/sis/coverage/grid/ImageRenderer.java    | 374 +++++++++
 .../grid/IncompleteGridGeometryException.java      |  22 +-
 .../java/org/apache/sis/image/DefaultIterator.java |   2 +-
 .../sis/internal/raster/ColorModelFactory.java     |  35 +-
 .../apache/sis/internal/raster/RasterFactory.java  | 119 ++-
 .../org/apache/sis/internal/raster/Resources.java  |  97 ++-
 .../sis/internal/raster/Resources.properties       |  16 +-
 .../sis/internal/raster/Resources_fr.properties    |  16 +-
 .../sis/internal/raster/ScaledColorSpace.java      |   4 +-
 .../org/apache/sis/coverage/CategoryListTest.java  |  73 +-
 .../java/org/apache/sis/coverage/CategoryTest.java |  31 +-
 .../apache/sis/coverage/SampleDimensionTest.java   |  39 +-
 .../apache/sis/coverage/grid/GridChangeTest.java   | 104 ---
 .../sis/coverage/grid/GridDerivationTest.java      | 275 +++++++
 .../apache/sis/coverage/grid/GridExtentTest.java   |  77 +-
 .../apache/sis/coverage/grid/GridGeometryTest.java | 147 +---
 .../org/apache/sis/test/suite/RasterTestSuite.java |   2 +-
 .../apache/sis/internal/gazetteer/Resources.java   |   8 +-
 .../gazetteer/MilitaryGridReferenceSystem.java     |   3 +-
 .../sis/geometry/AbstractDirectPosition.java       |   5 +-
 .../org/apache/sis/geometry/AbstractEnvelope.java  |  10 +-
 .../org/apache/sis/geometry/CoordinateFormat.java  |   9 +-
 .../java/org/apache/sis/geometry/Envelopes.java    |   6 +-
 .../apache/sis/internal/referencing/Formulas.java  |   4 +-
 .../referencing/GeodeticObjectBuilder.java         |   4 +-
 .../internal/referencing/ReferencingUtilities.java |   1 +
 .../apache/sis/internal/referencing/Resources.java |  15 +-
 .../sis/internal/referencing/Resources.properties  |   1 +
 .../internal/referencing/Resources_fr.properties   |   1 +
 .../sis/internal/referencing/WKTUtilities.java     | 103 ++-
 .../referencing/j2d/IntervalRectangle.java         |   8 +-
 .../internal/referencing/j2d/ShapeUtilities.java   |  32 +-
 .../sis/internal/referencing/j2d/package-info.java |   7 +-
 .../provider/DatumShiftGridCompressed.java         |  18 +-
 .../referencing/provider/DatumShiftGridFile.java   |  20 +-
 .../referencing/provider/Equirectangular.java      |   6 +-
 .../org/apache/sis/parameter/ParameterFormat.java  |   2 +-
 .../java/org/apache/sis/parameter/Verifier.java    |   5 +-
 .../main/java/org/apache/sis/referencing/CRS.java  | 101 +++
 .../java/org/apache/sis/referencing/CommonCRS.java |  49 +-
 .../referencing/EllipsoidalHeightSeparator.java    | 136 ++++
 .../org/apache/sis/referencing/NameIterator.java   |   2 +-
 .../org/apache/sis/referencing/cs/AbstractCS.java  |   5 +-
 .../sis/referencing/cs/CoordinateSystems.java      |  10 +-
 .../sis/referencing/cs/DefaultCartesianCS.java     |   1 +
 .../sis/referencing/cs/DefaultEllipsoidalCS.java   |   2 +-
 .../sis/referencing/datum/AbstractDatum.java       |  19 +-
 .../sis/referencing/datum/BursaWolfParameters.java |  28 +-
 .../sis/referencing/datum/DatumShiftGrid.java      | 101 ++-
 .../sis/referencing/datum/DefaultEllipsoid.java    |  22 +-
 .../sis/referencing/datum/TimeDependentBWP.java    |  11 +-
 .../referencing/factory/AuthorityFactoryProxy.java |   3 +-
 .../referencing/factory/sql/EPSGDataAccess.java    | 527 +++++++-----
 .../sis/referencing/factory/sql/EPSGInstaller.java |   2 +-
 .../sis/referencing/factory/sql/SQLTranslator.java | 124 ++-
 .../sis/referencing/factory/sql/TableInfo.java     |  36 +-
 .../operation/DefaultConcatenatedOperation.java    |   3 +-
 .../operation/DefaultPassThroughOperation.java     |  24 +-
 .../operation/builder/LinearTransformBuilder.java  | 169 +++-
 .../operation/builder/LocalizationGridBuilder.java | 145 +++-
 .../operation/builder/ResidualGrid.java            | 163 +++-
 .../sis/referencing/operation/matrix/Matrices.java |  64 +-
 .../referencing/operation/matrix/MatrixSIS.java    |  22 +-
 .../operation/projection/AlbersEqualArea.java      |   8 +-
 .../operation/projection/CylindricalEqualArea.java |  11 +-
 .../operation/projection/Initializer.java          |  35 +-
 .../projection/LambertConicConformal.java          |  13 +-
 .../referencing/operation/projection/Mercator.java |   9 +-
 .../operation/projection/NormalizedProjection.java |   2 +-
 .../operation/projection/ObliqueMercator.java      |   4 +-
 .../operation/projection/PolarStereographic.java   |  10 +-
 .../operation/projection/TransverseMercator.java   |   6 +-
 .../transform/AbstractMathTransform2D.java         |   7 +-
 .../operation/transform/ContextualParameters.java  |   2 +-
 .../transform/CoordinateSystemTransform.java       |   2 +-
 .../transform/DefaultMathTransformFactory.java     |  24 +-
 .../transform/EllipsoidToCentricTransform.java     |   4 +-
 .../transform/InterpolatedGeocentricTransform.java |   2 +-
 .../transform/InterpolatedMolodenskyTransform.java |   2 +-
 .../operation/transform/InterpolatedTransform.java | 108 ++-
 .../operation/transform/MathTransforms.java        |  36 +-
 .../transform/MathTransformsOrFactory.java         |  14 +-
 .../operation/transform/PassThroughTransform.java  | 179 +++--
 .../transform/PassThroughTransform2D.java          |  15 +-
 .../operation/transform/TransformSeparator.java    |  15 +-
 .../sis/referencing/factory/sql/EPSG_Finish.sql    |   1 +
 .../org/apache/sis/geometry/ArrayEnvelopeTest.java |  10 +-
 .../apache/sis/geometry/GeneralEnvelopeTest.java   |  14 +
 .../sis/internal/referencing/WKTUtilitiesTest.java |  29 +-
 .../referencing/j2d/ShapeUtilitiesTest.java        |  13 +-
 .../java/org/apache/sis/referencing/CRSTest.java   |  39 +-
 .../apache/sis/referencing/crs/HardCodedCRS.java   |   8 +-
 .../apache/sis/referencing/cs/HardCodedAxes.java   |   4 +-
 .../sis/referencing/factory/TestFactorySource.java |  23 +-
 .../referencing/factory/sql/EPSGFactoryTest.java   |  12 +-
 .../referencing/factory/sql/TableInfoTest.java}    |  20 +-
 .../operation/builder/ResidualGridTest.java        |   2 +-
 .../transform/MathTransformFactoryBase.java        |   4 +-
 .../transform/MathTransformFactoryMock.java        |   8 +-
 .../transform/PassThroughTransformTest.java        |  20 +-
 .../transform/TransformSeparatorTest.java          |  71 +-
 .../sis/test/suite/ReferencingTestSuite.java       |   1 +
 .../org/apache/sis/internal/converter/Column.java  |   2 +-
 .../org/apache/sis/internal/util/DoubleDouble.java | 188 +++--
 .../org/apache/sis/internal/util/Numerics.java     | 120 ++-
 .../sis/internal/util/StandardDateFormat.java      |  74 +-
 .../internal/util/{Utilities.java => Strings.java} |  64 +-
 .../main/java/org/apache/sis/math/ArrayVector.java |  74 +-
 .../src/main/java/org/apache/sis/math/Line.java    |  23 +-
 .../java/org/apache/sis/math/MathFunctions.java    |   2 +-
 .../java/org/apache/sis/math/PackedVector.java     |   2 +-
 .../src/main/java/org/apache/sis/math/Plane.java   |  41 +-
 .../main/java/org/apache/sis/math/Statistics.java  |  22 +-
 .../java/org/apache/sis/math/StatisticsFormat.java |  29 +-
 .../src/main/java/org/apache/sis/math/Vector.java  |  42 +-
 .../org/apache/sis/measure/AbstractConverter.java  |  58 ++
 .../java/org/apache/sis/measure/AbstractUnit.java  | 139 +++-
 .../main/java/org/apache/sis/measure/Angle.java    |   4 +-
 .../java/org/apache/sis/measure/AngleFormat.java   |   3 +-
 .../org/apache/sis/measure/ConventionalUnit.java   |   6 +-
 .../org/apache/sis/measure/LinearConverter.java    |  10 -
 .../java/org/apache/sis/measure/Longitude.java     |  16 +-
 .../org/apache/sis/measure/MeasurementRange.java   |  52 +-
 .../java/org/apache/sis/measure/NumberRange.java   | 105 ++-
 .../java/org/apache/sis/measure/PowerOf10.java     | 208 +++++
 .../main/java/org/apache/sis/measure/Range.java    |  29 +-
 .../java/org/apache/sis/measure/RangeFormat.java   |  14 +-
 .../apache/sis/measure/SexagesimalConverter.java   |  51 --
 .../java/org/apache/sis/measure/SystemUnit.java    |  79 +-
 .../java/org/apache/sis/measure/UnitRegistry.java  |   2 +-
 .../main/java/org/apache/sis/measure/Units.java    |  21 +-
 .../java/org/apache/sis/util/ArgumentChecks.java   |  32 +-
 .../java/org/apache/sis/util/CharSequences.java    |  12 +-
 .../main/java/org/apache/sis/util/Characters.java  |   3 +-
 .../main/java/org/apache/sis/util/Emptiable.java   |   4 +-
 .../sis/util/iso/AbstractInternationalString.java  |   4 +-
 .../java/org/apache/sis/util/resources/Errors.java |  10 +-
 .../sis/util/resources/IndexedResourceBundle.java  |   7 +-
 .../org/apache/sis/util/resources/Messages.java    |  10 +-
 .../org/apache/sis/measure/UnitAliases.properties  |   2 +
 .../org/apache/sis/measure/UnitNames.properties    |   2 +
 .../org/apache/sis/measure/UnitNames_fr.properties |   1 +
 .../apache/sis/internal/util/DoubleDoubleTest.java |  51 +-
 .../org/apache/sis/internal/util/NumericsTest.java |  14 +-
 .../sis/internal/util/StandardDateFormatTest.java  |   7 +-
 .../util/{UtilitiesTest.java => StringsTest.java}  |  16 +-
 .../test/java/org/apache/sis/math/PlaneTest.java   |   2 +-
 .../test/java/org/apache/sis/math/VectorTest.java  |  39 +
 .../org/apache/sis/measure/NumberRangeTest.java    |  17 +-
 .../org/apache/sis/measure/UnitFormatTest.java     |  16 +-
 .../java/org/apache/sis/measure/UnitsTest.java     |  34 +
 .../apache/sis/test/suite/UtilityTestSuite.java    |   2 +-
 ide-project/NetBeans/nbproject/genfiles.properties |   2 +-
 ide-project/NetBeans/nbproject/project.properties  |  28 +-
 ide-project/NetBeans/nbproject/project.xml         |   4 +
 pom.xml                                            |   6 +-
 .../storage/earthobservation/LandsatReader.java    |  17 +-
 .../org/apache/sis/storage/gdal/MTFactory.java     |   2 +-
 .../org/apache/sis/storage/geotiff/CRSBuilder.java |   4 +-
 .../sis/storage/geotiff/GridGeometryBuilder.java   |   4 +-
 .../java/org/apache/sis/internal/netcdf/Axis.java  | 385 +++++++--
 .../org/apache/sis/internal/netcdf/CRSBuilder.java |   5 +-
 .../org/apache/sis/internal/netcdf/Convention.java | 114 +++
 .../org/apache/sis/internal/netcdf/Decoder.java    |  70 +-
 .../java/org/apache/sis/internal/netcdf/Grid.java  | 175 ++--
 .../org/apache/sis/internal/netcdf/Resources.java  |  20 +-
 .../sis/internal/netcdf/Resources.properties       |   2 +
 .../sis/internal/netcdf/Resources_fr.properties    |   2 +
 .../org/apache/sis/internal/netcdf/Variable.java   | 106 ++-
 .../apache/sis/internal/netcdf/VariableRole.java   |  32 +-
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |  92 ++-
 .../sis/internal/netcdf/impl/FeaturesInfo.java     |   2 +-
 .../apache/sis/internal/netcdf/impl/GridInfo.java  |  63 +-
 .../sis/internal/netcdf/impl/VariableInfo.java     |  56 +-
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   |  29 +-
 .../sis/internal/netcdf/ucar/GridWrapper.java      | 117 ++-
 .../sis/internal/netcdf/ucar/VariableWrapper.java  |  68 +-
 .../apache/sis/storage/netcdf/GridResource.java    | 117 +--
 .../java/org/apache/sis/storage/netcdf/Image.java  |  40 +-
 .../apache/sis/storage/netcdf/MetadataReader.java  |  41 +-
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |   3 +-
 .../sis/storage/netcdf/NetcdfStoreProvider.java    |   4 +-
 .../org/apache/sis/internal/netcdf/GridTest.java   |  36 +-
 .../org/apache/sis/internal/netcdf/TestCase.java   |   7 +
 .../apache/sis/internal/netcdf/VariableTest.java   |  61 +-
 .../apache/sis/internal/sql/feature/Analyzer.java  |  18 +-
 .../apache/sis/internal/sql/feature/Resources.java |   6 +-
 .../sis/internal/storage/AbstractGridResource.java |   7 +-
 .../sis/internal/storage/MetadataBuilder.java      |  40 +-
 .../org/apache/sis/internal/storage/Resources.java |   8 +-
 .../sis/internal/storage/io/ChannelDataOutput.java |   3 +-
 .../internal/storage/io/HyperRectangleReader.java  |   4 +-
 .../org/apache/sis/internal/storage/io/Region.java |   8 +-
 .../java/org/apache/sis/storage/DataStore.java     |   3 +-
 .../java/org/apache/sis/storage/ProbeResult.java   |   8 +-
 .../org/apache/sis/storage/StorageConnector.java   |   4 +-
 .../org/apache/sis/storage/WritableAggregate.java  |   4 +-
 .../storage/io/HyperRectangleReaderTest.java       |  12 +-
 .../internal/storage/xml/stream/StaxDataStore.java |   4 +-
 252 files changed, 9013 insertions(+), 3686 deletions(-)

diff --cc core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index 919c5dc,c54f1c8..e0711b1
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@@ -49,7 -49,19 +49,8 @@@ import org.junit.BeforeClass
      org.apache.sis.feature.EnvelopeOperationTest.class,
      org.apache.sis.feature.FeatureFormatTest.class,
      org.apache.sis.feature.FeaturesTest.class,
 -    org.apache.sis.filter.DefaultLiteralTest.class,
 -    org.apache.sis.filter.DefaultPropertyNameTest.class,
 -    org.apache.sis.filter.DefaultAndTest.class,
 -    org.apache.sis.filter.DefaultOrTest.class,
 -    org.apache.sis.filter.DefaultNotTest.class,
 -    org.apache.sis.filter.DefaultFeatureIdTest.class,
 -    org.apache.sis.filter.DefaultIdTest.class,
 -    org.apache.sis.filter.DefaultAddTest.class,
 -    org.apache.sis.filter.DefaultDivideTest.class,
 -    org.apache.sis.filter.DefaultMultiplyTest.class,
 -    org.apache.sis.filter.DefaultSubtractTest.class,
      org.apache.sis.internal.feature.AttributeConventionTest.class,
+     org.apache.sis.internal.feature.j2d.ShapePropertiesTest.class,
      org.apache.sis.internal.feature.Java2DTest.class,
      org.apache.sis.internal.feature.ESRITest.class,
      org.apache.sis.internal.feature.JTSTest.class,
diff --cc core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
index d6164e4,a18947b..8ab5b48
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimeInstant.java
@@@ -22,12 -22,11 +22,13 @@@ import javax.xml.bind.annotation.XmlEle
  import javax.xml.bind.annotation.XmlRootElement;
  import javax.xml.datatype.XMLGregorianCalendar;
  import javax.xml.datatype.DatatypeConfigurationException;
 -import org.opengis.temporal.Instant;
  import org.apache.sis.internal.jaxb.Context;
+ import org.apache.sis.internal.util.Strings;
  import org.apache.sis.internal.xml.XmlUtilities;
  
 +// Branch-dependent imports
 +import org.apache.sis.internal.geoapi.temporal.Instant;
 +
  
  /**
   * Encapsulates a {@code gml:TimeInstant}. This element may be used alone, or included in a
diff --cc core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
index 0b8bc63,71c4ff8..9c1b63a
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/TimePeriod.java
@@@ -20,8 -20,9 +20,9 @@@ import javax.xml.bind.annotation.XmlTyp
  import javax.xml.bind.annotation.XmlElement;
  import javax.xml.bind.annotation.XmlElements;
  import javax.xml.bind.annotation.XmlRootElement;
 -import org.opengis.temporal.Period;
  import org.apache.sis.internal.jaxb.Context;
 +import org.apache.sis.internal.geoapi.temporal.Period;
+ import org.apache.sis.internal.util.Strings;
  
  import static org.apache.sis.internal.xml.LegacyNamespaces.VERSION_3_0;
  
diff --cc core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java
index 0cef0a8,11f2d8d..996096c
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java
@@@ -31,10 -31,11 +31,11 @@@ import org.apache.sis.metadata.KeyNameP
  import org.apache.sis.metadata.ValueExistencePolicy;
  import org.apache.sis.internal.system.Semaphores;
  import org.apache.sis.internal.metadata.Dependencies;
+ import org.apache.sis.internal.util.Numerics;
  
  // Branch-dependent imports
 -import org.opengis.metadata.citation.Responsibility;
  import org.opengis.metadata.citation.ResponsibleParty;
 +import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
  
  
  /**
diff --cc core/sis-raster/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
index 0ea406c,df8037e..fa89597
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/SubspaceNotSpecifiedException.java
@@@ -14,22 -14,27 +14,25 @@@
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
- package org.apache.sis.coverage.grid;
- 
- import org.apache.sis.internal.raster.Resources;
+ package org.apache.sis.coverage;
  
 -import org.opengis.coverage.CannotEvaluateException;
 -
  
  /**
-  * Thrown by {@link GridGeometry} when a grid geometry can not provide the requested information.
-  * For example this exception is thrown when {@link GridGeometry#getEnvelope()} is invoked while
-  * the grid geometry has been built with a null envelope.
+  * Thrown when an operation can only be applied on a subspace of a multi-dimensional coverage,
+  * but not such subspace has been specified.
+  * For example if a {@link org.apache.sis.coverage.grid.GridCoverage} has three or more dimensions,
+  * then a two-dimensional slice must be specified in order to produce a {@link java.awt.image.RenderedImage}
+  * from that grid coverage.
   *
-  * @author  Martin Desruisseaux (IRD, Geomatys)
+  * @author  Martin Desruisseaux (Geomatys)
   * @version 1.0
-  * @since   1.0
+  *
+  * @see <a href="https://en.wikipedia.org/wiki/Linear_subspace">Linear subspace on Wikipedia</a>
+  *
+  * @since 1.0
   * @module
   */
- public class IncompleteGridGeometryException extends IllegalStateException {
 -public class SubspaceNotSpecifiedException extends CannotEvaluateException {
++public class SubspaceNotSpecifiedException extends RuntimeException {
      /**
       * Serial number for inter-operability with different versions.
       */
diff --cc core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index fc2c60a,7d19398..ae72186
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
@@@ -111,12 -116,27 +112,27 @@@ public abstract class GridCoverage 
      }
  
      /**
-      * Returns a two-dimensional slice of grid data as a rendered image. The given {@code slicePoint} argument specifies
+      * Returns a two-dimensional slice of grid data as a rendered image. The given {@code sliceExtent} argument specifies
       * the coordinates of the slice in all dimensions that are not in the two-dimensional image. For example if this grid
-      * coverage has (<var>x</var>, <var>y</var>, <var>z</var>, <var>t</var>) dimensions and we want to render an image
-      * of data in the (<var>x</var>, <var>y</var>) dimensions, then the given {@code slicePoint} shall contain the
-      * (<var>z</var>, <var>t</var>) coordinates of the desired slice. The two coordinates of the data to be shown
-      * (<var>x</var> and <var>y</var> in our example) shall be excluded from the slice point in one of the following ways:
+      * coverage has <i>(<var>x</var>,<var>y</var>,<var>z</var>,<var>t</var>)</i> dimensions and we want to render an image
+      * of data in the <i>(<var>x</var>,<var>y</var>)</i> dimensions, then the given {@code sliceExtent} shall contain the
+      * <i>(<var>z</var>,<var>t</var>)</i> coordinates of the desired slice. Those coordinates are specified in a grid extent
+      * where {@linkplain GridExtent#getLow(int) low coordinate} = {@linkplain GridExtent#getHigh(int) high coordinate} in the
+      * <var>z</var> and <var>t</var> dimensions. The two dimensions of the data to be shown (<var>x</var> and <var>y</var>
+      * in our example) shall be the only dimensions with a {@linkplain GridExtent#getSize(int) size} greater than 1 cell.
+      *
+      * <p>If the {@code sliceExtent} argument is {@code null}, then the default value is
+      * <code>{@linkplain #getGridGeometry()}.{@linkplain GridGeometry#getExtent() getExtent()}</code>.
+      * This means that {@code gridExtent} is optional for two-dimensional grid coverages or grid coverages where all dimensions
+      * except two have a size of 1 cell. If the grid extent contains more than 2 dimensions with a size greater than one cell,
+      * then a {@link SubspaceNotSpecifiedException} is thrown. If some {@code sliceExtent} coordinates are outside the extent
 -     * of this grid coverage, then a {@link PointOutsideCoverageException} is thrown.</p>
++     * of this grid coverage, then a {@code PointOutsideCoverageException} is thrown.</p>
+      *
+      * <div class="section">Computing a slice extent from a slice point in "real world" coordinates</div>
+      * The {@code sliceExtent} is specified to this method as grid indices. If the <var>z</var> and <var>t</var> values
+      * are not grid indices but are relative to some Coordinate Reference System (CRS) instead, then the slice extent can
+      * be computed as below. First, a <cite>slice point</cite> containing the <var>z</var> and <var>t</var> coordinates
+      * should be constructed as a {@link DirectPosition} in one of the following ways:
       *
       * <ul>
       *   <li>The {@code slicePoint} has a CRS with two dimensions less than this grid coverage CRS.</li>
@@@ -124,18 -144,37 +140,36 @@@
       *       exclude are set to {@link Double#NaN}.</li>
       * </ul>
       *
+      * Then:
+      *
+      * <blockquote><code>sliceExtent = {@linkplain #getGridGeometry()}.{@link GridGeometry#derive()
+      * derive()}.{@linkplain GridDerivation#slice(DirectPosition)
+      * slice}(slicePoint).{@linkplain GridDerivation#build() build()};</code></blockquote>
+      *
       * If the {@code slicePoint} CRS is different than this grid coverage CRS (except for the number of dimensions),
-      * a coordinate transformation will be applied. If the {@code slicePoint} CRS is {@code null}, it is assumed the
-      * same than this grid coverage CRS. If this grid coverage is two-dimensional or can render only one image for
-      * other reason, then the {@code slicePoint} can be null.
+      * a coordinate transformation will be applied as needed.
+      *
+      * <div class="section">Rendered image properties</div>
+      * The {@linkplain RenderedImage#getWidth() image width} and {@linkplain RenderedImage#getHeight() height} will be
+      * the {@code sliceExtent} {@linkplain GridExtent#getSize(int) sizes} in the first and second dimension respectively
+      * of the two-dimensional {@code sliceExtent} {@linkplain GridExtent#getSubspaceDimensions(int) subspace}.
+      * The image location ({@linkplain RenderedImage#getMinX() x}, {@linkplain RenderedImage#getMinY() y}) can be any point;
+      * that location may not be the same as the {@code sliceExtent} {@linkplain GridExtent#getLow(int) low} coordinates
+      * since conversion from {@code long} to {@code int} primitive type may cause lost of precision, and some implementations
+      * like {@link java.awt.image.BufferedImage} restrict that location to (0,0).
       *
-      * <p>Implementations should return a view as much as possible, without copying sample values.</p>
+      * <p>Implementations should return a view as much as possible, without copying sample values.
+      * {@code GridCoverage} subclasses can use the {@link ImageRenderer} class as a helper tool for that purpose.
+      * This method does not mandate any behavior regarding tiling (size of tiles, their numbering system, <i>etc.</i>).
+      * Some implementations may defer data loading until {@linkplain RenderedImage#getTile(int, int) a tile is requested}.</p>
       *
-      * @param  slicePoint  coordinates of the slice in all dimensions other than the two dimensions to be shown on the image.
-      *         May be {@code null} if this coverage can render only one image, for example because its CRS is two-dimensional.
+      * @param  sliceExtent  a subspace of this grid coverage extent where all dimensions except two have a size of 1 cell.
+      *         May be {@code null} if this grid coverage has only two dimensions with a size greater than 1 cell.
       * @return the grid slice as a rendered image.
 -     * @throws PointOutsideCoverageException if the given slice extent contains illegal coordinates.
+      * @throws SubspaceNotSpecifiedException if the given argument is not sufficient for reducing the grid to a two-dimensional slice.
 -     * @throws CannotEvaluateException if this method can not produce the rendered image for another reason.
++     * @throws RuntimeException if this method can not produce the rendered image for another reason.
       */
-     public abstract RenderedImage render(DirectPosition slicePoint);
 -    public abstract RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException;
++    public abstract RenderedImage render(GridExtent sliceExtent);
  
      /**
       * Returns a string representation of this grid coverage for debugging purpose.
diff --cc core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
index 0000000,0e5adff..049f24f
mode 000000,100644..100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
@@@ -1,0 -1,896 +1,893 @@@
+ /*
+  * 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.coverage.grid;
+ 
+ import java.util.Arrays;
+ import java.util.Locale;
+ import org.opengis.geometry.Envelope;
+ import org.opengis.geometry.DirectPosition;
+ import org.opengis.util.FactoryException;
+ import org.opengis.referencing.datum.PixelInCell;
+ import org.opengis.referencing.operation.Matrix;
+ import org.opengis.referencing.operation.MathTransform;
+ import org.opengis.referencing.operation.TransformException;
+ import org.opengis.referencing.operation.CoordinateOperation;
+ import org.opengis.referencing.operation.NoninvertibleTransformException;
+ import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ import org.apache.sis.referencing.operation.transform.MathTransforms;
+ import org.apache.sis.referencing.operation.transform.TransformSeparator;
+ import org.apache.sis.referencing.operation.matrix.Matrices;
+ import org.apache.sis.referencing.CRS;
+ import org.apache.sis.internal.referencing.DirectPositionView;
+ import org.apache.sis.geometry.GeneralDirectPosition;
+ import org.apache.sis.geometry.GeneralEnvelope;
+ import org.apache.sis.geometry.Envelopes;
+ import org.apache.sis.internal.raster.Resources;
+ import org.apache.sis.util.resources.Vocabulary;
+ import org.apache.sis.util.resources.Errors;
+ import org.apache.sis.util.ArgumentChecks;
+ import org.apache.sis.util.ArraysExt;
+ import org.apache.sis.util.CharSequences;
+ import org.apache.sis.util.Classes;
+ import org.apache.sis.util.Debug;
+ import org.apache.sis.util.collection.DefaultTreeTable;
+ import org.apache.sis.util.collection.TableColumn;
+ import org.apache.sis.util.collection.TreeTable;
+ 
 -// Branch-dependent imports
 -import org.opengis.coverage.PointOutsideCoverageException;
 -
+ 
+ /**
+  * Creates a new grid geometry derived from a base grid geometry with different extent or resolution.
+  * {@code GridDerivation} are created by calls to {@link GridGeometry#derive()}.
+  * Properties of the desired grid geometry can be specified by calls to the following methods,
+  * in that order (each method is optional):
+  *
+  * <ol>
+  *   <li>{@link #rounding(GridRoundingMode)} and/or {@link #margin(int...)} in any order</li>
+  *   <li>{@link #subgrid(GridGeometry)} or {@link #subgrid(Envelope, double...)}</li>
+  *   <li>{@link #subsample(int...)}</li>
+  *   <li>{@link #slice(DirectPosition)} and/or {@link #sliceByRatio(double, int...)}</li>
+  * </ol>
+  *
+  * Then the grid geometry is created by a call to {@link #build()}.
+  * Alternatively, {@link #getIntersection()} can be invoked if only the {@link GridExtent} is desired
+  * instead than the full {@link GridGeometry} and no subsampling is applied.
+  *
+  * <p>All methods in this class preserve the number of dimensions. For example the {@link #slice(DirectPosition)} method sets
+  * the {@linkplain GridExtent#getSize(int) grid size} to 1 in all dimensions specified by the <cite>slice point</cite>,
+  * but does not remove those dimensions from the grid geometry.
+  * For dimensionality reduction, see {@link GridGeometry#reduce(int...)}.</p>
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.0
+  *
+  * @see GridGeometry#derive()
+  * @see GridGeometry#reduce(int...)
+  *
+  * @since 1.0
+  * @module
+  */
+ public class GridDerivation {
+     /**
+      * The base grid geometry from which to derive a new grid geometry.
+      */
+     protected final GridGeometry base;
+ 
+     /**
+      * Controls behavior of rounding from floating point values to integers.
+      *
+      * @see #rounding(GridRoundingMode)
+      */
+     private GridRoundingMode rounding;
+ 
+     /**
+      * If non-null, the extent will be expanded by that amount of cells on each grid dimension.
+      *
+      * @see #margin(int...)
+      */
+     private int[] margin;
+ 
+     // ──────── COMPUTED BY METHODS IN THIS CLASS ─────────────────────────────────────────────────────────────────────
+     /**
+      * The sub-extent of {@link #base} grid geometry to use for the new grid geometry. This is the intersection of
+      * {@code base.extent} with any area of interest specified to a {@link #subgrid(Envelope, double...)} method,
+      * potentially with some grid size set to 1 by a {@link #slice(DirectPosition)} method.
+      * This extent is <strong>not</strong> subsampled for a given resolution.
+      *
+      * <p>This extent is initialized to {@code base.extent} if no slice or sub-grid has been requested.
+      * This field may be {@code null} if the base grid geometry does not define any extent.
+      * A successful call to {@link GridGeometry#requireGridToCRS()} guarantees that this field is non-null.</p>
+      */
+     private GridExtent baseExtent;
+ 
+     /**
+      * Same as {@link #baseExtent}, but takes resolution or subsampling in account.
+      * This is {@code null} if no subsampling has been applied.
+      *
+      * @todo if a {@linkplain #margin} has been specified, then we need to perform an additional clipping.
+      */
+     private GridExtent subsampledExtent;
+ 
+     /**
+      * The conversion from the subsampled grid to the original grid, or {@code null} if no subsampling is applied.
+      * This is computed by {@link #subgrid(Envelope, double...)}.
+      */
+     private MathTransform toBase;
+ 
+     /**
+      * List of grid dimensions that are modified by the {@code cornerToCRS} transform, or null for all dimensions.
+      * The length of this array is the number of dimensions of the given Area Of Interest (AOI). Each value in this
+      * array is between 0 inclusive and {@code extent.getDimension()} exclusive.
+      */
+     private int[] modifiedDimensions;
+ 
+     /**
+      * An estimation of the multiplication factors when converting cell coordinates from {@code gridOfInterest} to {@link #base}
+      * grid. Those factors appear in the order of <em>base</em> grid axes. May be {@code null} if the conversion is identity.
+      * This is sometime redundant with {@link #toBase} but not always.
+      *
+      * @see #getSubsamplings()
+      */
+     private double[] scales;
+ 
+     /**
+      * If {@link #subgrid(Envelope, double...)} or {@link #slice(DirectPosition)} has been invoked, the method name.
+      * This is used for preventing those methods to be invoked twice or out-of-order, which is currently not supported.
+      */
+     private String subGridSetter;
+ 
+     /**
+      * Creates a new builder for deriving a grid geometry from the specified base.
+      *
+      * @param  base  the base to use as a template for deriving a new grid geometry.
+      *
+      * @see GridGeometry#derive()
+      */
+     protected GridDerivation(final GridGeometry base) {
+         ArgumentChecks.ensureNonNull("base", base);
+         this.base  = base;
+         baseExtent = base.extent;                    // May be null.
+         rounding   = GridRoundingMode.NEAREST;
+     }
+ 
+     /**
+      * Verifies that a sub-grid has not yet been defined.
+      * This method is invoked for enforcing the method call order defined in javadoc.
+      */
+     private void ensureSubgridNotSet() {
+         if (subGridSetter != null) {
+             throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSetDerivedGridProperty_1, subGridSetter));
+         }
+     }
+ 
+     /**
+      * Controls behavior of rounding from floating point values to integers.
+      * This setting modifies computations performed by the following methods
+      * (it has no effect on other methods in this {@code GridDerivation} class):
+      * <ul>
+      *   <li>{@link #slice(DirectPosition)}</li>
+      *   <li>{@link #subgrid(Envelope, double...)}</li>
+      * </ul>
+      *
+      * If this method is never invoked, the default value is {@link GridRoundingMode#NEAREST}.
+      * If this method is invoked too late, an {@link IllegalStateException} is thrown.
+      *
+      * @param  mode  the new rounding mode.
+      * @return {@code this} for method call chaining.
+      * @throws IllegalStateException if {@link #subgrid(Envelope, double...)} or {@link #slice(DirectPosition)}
+      *         has already been invoked.
+      */
+     public GridDerivation rounding(final GridRoundingMode mode) {
+         ArgumentChecks.ensureNonNull("mode", mode);
+         ensureSubgridNotSet();
+         rounding = mode;
+         return this;
+     }
+ 
+     /**
+      * Specifies an amount of cells by which to expand {@code GridExtent} after rounding.
+      * This setting modifies computations performed by the following methods
+      * (it has no effect on other methods in this {@code GridDerivation} class):
+      * <ul>
+      *   <li>{@link #subgrid(Envelope, double...)}</li>
+      * </ul>
+      *
+      * For each dimension <var>i</var> of the grid computed by above methods, the {@linkplain GridExtent#getLow(int) low} grid
+      * coordinate is subtracted by {@code cellCount[i]} and the {@linkplain GridExtent#getHigh(int) high} grid coordinate is
+      * increased by {@code cellCount[i]}. The result is intersected with the extent of the {@link #base} grid geometry
+      * given to the constructor.
+      *
+      * <div class="note"><b>Use case:</b>
+      * if the caller wants to apply bilinear interpolations in an image, (s)he will need 1 more pixel on each image border.
+      * If the caller wants to apply bi-cubic interpolations, (s)he will need 2 more pixels on each image border.</div>
+      *
+      * If this method is never invoked, the default value is zero for all dimensions.
+      * If this method is invoked too late, an {@link IllegalStateException} is thrown.
+      * If the {@code count} array length is shorter than the grid dimension,
+      * then zero is assumed for all missing dimensions.
+      *
+      * @param  cellCounts  number of cells by which to expand the grid extent.
+      * @return {@code this} for method call chaining.
+      * @throws IllegalArgumentException if a value is negative.
+      * @throws IllegalStateException if {@link #subgrid(Envelope, double...)} or {@link #slice(DirectPosition)}
+      *         has already been invoked.
+      */
+     public GridDerivation margin(final int... cellCounts) {
+         ArgumentChecks.ensureNonNull("cellCounts", cellCounts);
+         ensureSubgridNotSet();
+         int[] margin = null;
+         for (int i=cellCounts.length; --i >= 0;) {
+             final int n = cellCounts[i];
+             ArgumentChecks.ensurePositive("cellCounts", n);
+             if (margin == null) {
+                 margin = new int[i+1];
+             }
+             margin[i] = n;
+         }
+         this.margin = margin;           // Set only on success.
+         return this;
+     }
+ 
+     /**
+      * Adapts the base grid for the geographic area and resolution of the given grid geometry.
+      * After this method invocation, {@code GridDerivation} will hold information about conversion
+      * from the given {@code gridOfInterest} to the {@link #base} grid geometry.
+      * Those information include the {@link MathTransform}s converting cell coordinates
+      * from the {@code gridOfInterest} to cell coordinates in the {@code base} grid,
+      * together with the grid extent that results from this conversion.
+      *
+      * <div class="note"><b>Usage:</b>
+      * This method can be helpful for implementation of
+      * {@link org.apache.sis.storage.GridCoverageResource#read(GridGeometry, int...)}.
+      * Example:
+      *
+      * {@preformat java
+      *     class MyDataStorage extends GridCoverageResource {
+      *         &#64;Override
+      *         public GridCoverage read(GridGeometry domain, int... range) throws DataStoreException {
+      *             GridDerivation change = getGridGeometry().derive().subgrid(domain);
+      *             GridExtent toRead = change.buildExtent();
+      *             int[] subsampling = change.getSubsamplings());
+      *             // Do reading here.
+      *         }
+      *     }
+      * }
+      * </div>
+      *
+      * The following information are mandatory:
+      * <ul>
+      *   <li>{@linkplain GridGeometry#getExtent() Extent} in {@code gridOfInterest}.</li>
+      *   <li>{@linkplain GridGeometry#getGridToCRS(PixelInCell) Grid to CRS} conversion in {@code gridOfInterest}.</li>
+      *   <li>{@linkplain GridGeometry#getGridToCRS(PixelInCell) Grid to CRS} conversion in {@link #base} grid.</li>
+      * </ul>
+      *
+      * The following information are optional but recommended:
+      * <ul>
+      *   <li>{@linkplain GridGeometry#getCoordinateReferenceSystem() Coordinate reference system} in {@code gridOfInterest}.</li>
+      *   <li>{@linkplain GridGeometry#getCoordinateReferenceSystem() Coordinate reference system} in {@link #base} grid.</li>
+      *   <li>{@linkplain GridGeometry#getExtent() Extent} in {@link #base} grid.</li>
+      * </ul>
+      *
+      * An optional {@link #margin(int...) margin} can be specified for increasing the size of the grid extent computed by this method.
+      * For example if the caller wants to apply bilinear interpolations in an image, (s)he will need 1 more pixel on each image border.
+      * If the caller wants to apply bi-cubic interpolations, (s)he will need 2 more pixels on each image border.
+      *
+      * <p>Notes:</p>
+      * <ul>
+      *   <li>This method can be invoked only once.</li>
+      *   <li>This method can not be used together with {@link #subgrid(Envelope, double...)}.</li>
+      *   <li>If a non-default rounding mode is desired, it should be {@linkplain #rounding(GridRoundingMode) specified}
+      *       before to invoke this method.</li>
+      *   <li>This method does not reduce the number of dimensions of the grid geometry.
+      *       For dimensionality reduction, see {@link GridGeometry#reduce(int...)}.</li>
+      * </ul>
+      *
+      * @param  gridOfInterest  the area of interest and desired resolution as a grid geometry.
+      * @return {@code this} for method call chaining.
+      * @throws IncompleteGridGeometryException if a mandatory property of a grid geometry is absent.
+      * @throws IllegalGridGeometryException if an error occurred while converting the envelope coordinates to grid coordinates.
+      * @throws IllegalStateException if a {@link #subgrid(Envelope, double...) subgrid(…)} or {@link #slice(DirectPosition) slice(…)}
+      *         method has already been invoked.
+      *
+      * @see #getSubsamplings()
+      * @see #subsample(int...)
+      */
+     public GridDerivation subgrid(final GridGeometry gridOfInterest) {
+         ArgumentChecks.ensureNonNull("gridOfInterest", gridOfInterest);
+         ensureSubgridNotSet();
+         subGridSetter = "subgrid";
+         if (!base.equals(gridOfInterest)) {
+             final MathTransform mapCorners, mapCenters;
+             final GridExtent domain = gridOfInterest.getExtent();                  // May throw IncompleteGridGeometryException.
+             try {
+                 final CoordinateOperation crsChange;
+                 crsChange  = Envelopes.findOperation(gridOfInterest.envelope, base.envelope);       // Any envelope may be null.
+                 mapCorners = path(gridOfInterest, crsChange, base, PixelInCell.CELL_CORNER);
+                 mapCenters = path(gridOfInterest, crsChange, base, PixelInCell.CELL_CENTER);
+                 clipExtent(domain.toCRS(mapCorners, mapCenters));
+             } catch (FactoryException | TransformException e) {
+                 throw new IllegalGridGeometryException(e, "gridOfInterest");
+             }
+             if (baseExtent != base.extent && baseExtent.equals(gridOfInterest.extent)) {
+                 baseExtent = gridOfInterest.extent;                                                 // Share common instance.
+             }
+             scales = GridGeometry.resolution(mapCenters, domain);
+         }
+         return this;
+     }
+ 
+     /**
+      * Returns the concatenation of all transformation steps from the given source to the given target.
+      *
+      * @param  source     the source grid geometry.
+      * @param  crsChange  the change of coordinate reference system, or {@code null} if none.
+      * @param  target     the target grid geometry.
+      * @param  anchor     whether we want the transform for cell corner or cell center.
+      */
+     private static MathTransform path(final GridGeometry source, final CoordinateOperation crsChange,
+             final GridGeometry target, final PixelInCell anchor) throws NoninvertibleTransformException
+     {
+         MathTransform step1 = source.getGridToCRS(anchor);
+         MathTransform step2 = target.getGridToCRS(anchor);
+         if (crsChange != null) {
+             step1 = MathTransforms.concatenate(step1, crsChange.getMathTransform());
+         }
+         if (step1.equals(step2)) {                                          // Optimization for a common case.
+             return MathTransforms.identity(step1.getSourceDimensions());
+         } else {
+             return MathTransforms.concatenate(step1, step2.inverse());
+         }
+     }
+ 
+     /**
+      * Requests a grid geometry over a sub-region of the base grid geometry and optionally with subsampling.
+      * The given envelope does not need to be expressed in the same coordinate reference system (CRS)
+      * than {@linkplain GridGeometry#getCoordinateReferenceSystem() the CRS of the base grid geometry};
+      * coordinate conversions or transformations will be applied as needed.
+      * That envelope CRS may have fewer dimensions than the base grid geometry CRS,
+      * in which case grid dimensions not mapped to envelope dimensions will be returned unchanged.
+      * The target resolution, if provided, shall be in same units and same order than the given envelope axes.
+      * If the length of {@code resolution} array is less than the number of dimensions of {@code areaOfInterest},
+      * then no subsampling will be applied on the missing dimensions.
+      *
+      * <p>Notes:</p>
+      * <ul>
+      *   <li>This method can be invoked only once.</li>
+      *   <li>This method can not be used together with {@link #subgrid(GridGeometry)}.</li>
+      *   <li>If a non-default rounding mode is desired, it should be {@linkplain #rounding(GridRoundingMode) specified}
+      *       before to invoke this method.</li>
+      *   <li>This method does not reduce the number of dimensions of the grid geometry.
+      *       For dimensionality reduction, see {@link GridGeometry#reduce(int...)}.</li>
+      * </ul>
+      *
+      * @param  areaOfInterest  the desired spatiotemporal region in any CRS (transformations will be applied as needed),
+      *                         or {@code null} for not restricting the sub-grid to a sub-area.
+      * @param  resolution      the desired resolution in the same units and order than the axes of the given envelope,
+      *                         or {@code null} or an empty array if no subsampling is desired. The array length should
+      *                         be equal to the {@code areaOfInterest} dimension, but this is not mandatory
+      *                         (zero or missing values mean no sub-sampling, extraneous values are ignored).
+      * @return {@code this} for method call chaining.
+      * @throws IncompleteGridGeometryException if the base grid geometry has no extent or no "grid to CRS" transform.
+      * @throws IllegalGridGeometryException if an error occurred while converting the envelope coordinates to grid coordinates.
+      * @throws IllegalStateException if a {@link #subgrid(GridGeometry) subgrid(…)} or {@link #slice(DirectPosition) slice(…)}
+      *         method has already been invoked.
+      *
+      * @see GridExtent#subsample(int[])
+      */
+     public GridDerivation subgrid(final Envelope areaOfInterest, double... resolution) {
+         ensureSubgridNotSet();
+         MathTransform cornerToCRS = base.requireGridToCRS();
+         subGridSetter = "subgrid";
+         try {
+             /*
+              * If the envelope CRS is different than the expected CRS, concatenate the envelope transformation
+              * to the 'gridToCRS' transform.  We should not transform the envelope here - only concatenate the
+              * transforms - because transforming envelopes twice would add errors.
+              */
+             final CoordinateOperation operation = Envelopes.findOperation(base.envelope, areaOfInterest);
+             if (operation != null) {
+                 cornerToCRS = MathTransforms.concatenate(cornerToCRS, operation.getMathTransform());
+             }
+             /*
+              * If the envelope dimensions does not encompass all grid dimensions, the envelope is probably non-invertible.
+              * We need to reduce the number of grid dimensions in the transform for having a one-to-one relationship.
+              */
+             int dimension = cornerToCRS.getTargetDimensions();
+             ArgumentChecks.ensureDimensionMatches("areaOfInterest", dimension, areaOfInterest);
+             cornerToCRS = dropUnusedDimensions(cornerToCRS, dimension);
+             /*
+              * Compute the sub-extent for the given Area Of Interest (AOI), ignoring for now the subsampling.
+              * If no area of interest has been specified, or if the result is identical to the original extent,
+              * then we will keep the reference to the original GridExtent (i.e. we share existing instances).
+              */
+             dimension = baseExtent.getDimension();
+             GeneralEnvelope indices = null;
+             if (areaOfInterest != null) {
+                 indices = Envelopes.transform(cornerToCRS.inverse(), areaOfInterest);
+                 clipExtent(indices);
+             }
+             if (indices == null || indices.getDimension() != dimension) {
+                 indices = new GeneralEnvelope(dimension);
+             }
+             for (int i=0; i<dimension; i++) {
+                 indices.setRange(i, baseExtent.getLow(i), baseExtent.getHigh(i) + 1.0);
+             }
+             /*
+              * Convert the target resolutions to grid cell subsamplings and adjust the extent consequently.
+              * We perform this conversion by handling the resolutions as a small translation vector located
+              * at the point of interest, and converting it to a translation vector in grid coordinates. The
+              * conversion is done by a multiplication with the "CRS to grid" derivative at that point.
+              *
+              * The subsampling will be rounded in such a way that the difference in grid size is less than
+              * one half of cell. Demonstration:
+              *
+              *    e = Math.getExponent(span)     →    2^e ≦ span
+              *    a = e+1                        →    2^a > span     →    1/2^a < 1/span
+              *   Δs = (s - round(s)) / 2^a
+              *   (s - round(s)) ≦ 0.5            →    Δs  ≦  0.5/2^a  <  0.5/span
+              *   Δs < 0.5/span                   →    Δs⋅span < 0.5 cell.
+              */
+             if (resolution != null && resolution.length != 0) {
+                 resolution = ArraysExt.resize(resolution, cornerToCRS.getTargetDimensions());
+                 Matrix m = cornerToCRS.derivative(new DirectPositionView.Double(getPointOfInterest()));
+                 resolution = Matrices.inverse(m).multiply(resolution);
+                 final int[] modifiedDimensions = this.modifiedDimensions;                     // Will not change anymore.
+                 boolean modified = false;
+                 for (int k=0; k<resolution.length; k++) {
+                     double s = Math.abs(resolution[k]);
+                     if (s > 1) {                                // Also for skipping NaN values.
+                         final int i = (modifiedDimensions != null) ? modifiedDimensions[k] : k;
+                         final int accuracy = Math.max(0, Math.getExponent(indices.getSpan(i))) + 1;         // Power of 2.
+                         s = Math.scalb(Math.rint(Math.scalb(s, accuracy)), -accuracy);
+                         indices.setRange(i, indices.getLower(i) / s,
+                                             indices.getUpper(i) / s);
+                         modified = true;
+                     }
+                     resolution[k] = s;
+                 }
+                 /*
+                  * If at least one subsampling is effective, build a scale from the old grid coordinates to the new
+                  * grid coordinates. If we had no rounding, the conversion would be only a scale. But because of rounding,
+                  * we need a small translation for the difference between the "real" coordinate and the integer coordinate.
+                  *
+                  * TODO: need to clip to baseExtent, taking in account the difference in resolution.
+                  */
+                 if (modified) {
+                     subsampledExtent = new GridExtent(indices, rounding, null, null, modifiedDimensions);
+                     if (baseExtent.equals(subsampledExtent)) subsampledExtent = baseExtent;
+                     m = Matrices.createIdentity(dimension + 1);
+                     for (int k=0; k<resolution.length; k++) {
+                         final double s = resolution[k];
+                         if (s > 1) {                            // Also for skipping NaN values.
+                             final int i = (modifiedDimensions != null) ? modifiedDimensions[k] : k;
+                             m.setElement(i, i, s);
+                             m.setElement(i, dimension, baseExtent.getLow(i) - subsampledExtent.getLow(i) * s);
+                         }
+                     }
+                     toBase = MathTransforms.linear(m);
+                     scales = resolution;                        // For information purpose only.
+                 }
+             }
+         } catch (FactoryException | TransformException e) {
+             throw new IllegalGridGeometryException(e, "areaOfInterest");
+         }
+         modifiedDimensions = null;                  // Not needed anymore.
+         return this;
+     }
+ 
+     /**
+      * Drops the source dimensions that are not needed for producing the target dimensions.
+      * The retained source dimensions are stored in {@link #modifiedDimensions}.
+      * This method is invoked in an effort to make the transform invertible.
+      *
+      * @param  cornerToCRS  transform from grid coordinates to AOI coordinates.
+      * @param  dimension    value of {@code cornerToCRS.getTargetDimensions()}.
+      */
+     private MathTransform dropUnusedDimensions(MathTransform cornerToCRS, final int dimension)
+             throws FactoryException, TransformException
+     {
+         if (dimension < cornerToCRS.getSourceDimensions()) {
+             final TransformSeparator sep = new TransformSeparator(cornerToCRS);
+             sep.setTrimSourceDimensions(true);
+             cornerToCRS = sep.separate();
+             modifiedDimensions = sep.getSourceDimensions();
+             if (modifiedDimensions.length != dimension) {
+                 throw new TransformException(Resources.format(Resources.Keys.CanNotMapToGridDimensions));
+             }
+         }
+         return cornerToCRS;
+     }
+ 
+     /**
+      * Returns the point of interest of current {@link #baseExtent}, keeping only the remaining
+      * dimensions after {@link #dropUnusedDimensions(MathTransform, int)} execution.
+      * The position is in units of {@link #base} grid coordinates.
+      */
+     private double[] getPointOfInterest() {
+         final double[] pointOfInterest = baseExtent.getPointOfInterest();
+         if (modifiedDimensions == null) {
+             return pointOfInterest;
+         }
+         final double[] filtered = new double[modifiedDimensions.length];
+         for (int i=0; i<filtered.length; i++) {
+             filtered[i] = pointOfInterest[modifiedDimensions[i]];
+         }
+         return filtered;
+     }
+ 
+     /**
+      * Sets {@link #baseExtent} to the given envelope clipped to the previous extent.
+      * This method shall be invoked for clipping only, without any subsampling applied.
+      *
+      * @param  indices  the envelope to intersect in units of {@link #base} grid coordinates.
+      */
+     private void clipExtent(final GeneralEnvelope indices) {
+         final GridExtent sub = new GridExtent(indices, rounding, margin, baseExtent, modifiedDimensions);
+         if (!sub.equals(baseExtent)) {
+             baseExtent = sub;
+         }
+     }
+ 
+     /**
+      * Applies a subsampling on the grid geometry to build.
+      * The {@code subsamplings} argument is often the array returned by {@link #getSubsamplings()}, but not necessarily.
+      * The {@linkplain GridGeometry#getExtent() extent} of the {@linkplain #build() built} grid geometry will be derived
+      * from {@link #getIntersection()} as below for each dimension <var>i</var>:
+      *
+      * <ul>
+      *   <li>The {@linkplain GridExtent#getLow(int)  low}  is divided by {@code subsamplings[i]}, rounded toward zero.</li>
+      *   <li>The {@linkplain GridExtent#getSize(int) size} is divided by {@code subsamplings[i]}, rounded toward zero.</li>
+      *   <li>The {@linkplain GridExtent#getHigh(int) high} is recomputed from above low and size.</li>
+      * </ul>
+      *
+      * The {@linkplain GridGeometry#getGridToCRS(PixelInCell) grid to CRS} transform is scaled accordingly
+      * in order to map approximately to the same {@linkplain GridGeometry#getEnvelope() envelope}.
+      *
+      * @param  subsamplings  the subsampling to apply on each grid dimension. All values shall be greater than zero.
+      *         If the array length is shorter than the number of dimensions, missing values are assumed to be 1.
+      * @return {@code this} for method call chaining.
+      * @throws IllegalStateException if a subsampling has already been set,
+      *         for example by a call to {@link #subgrid(Envelope, double...) subgrid(…)}.
+      *
+      * @see #subgrid(GridGeometry)
+      * @see #getSubsamplings()
+      * @see GridExtent#subsample(int...)
+      */
+     public GridDerivation subsample(final int... subsamplings) {
+         ArgumentChecks.ensureNonNull("subsamplings", subsamplings);
+         if (toBase != null) {
+             throw new IllegalStateException(Errors.format(Errors.Keys.ValueAlreadyDefined_1, "subsamplings"));
+         }
+         final GridExtent extent = (baseExtent != null) ? baseExtent : base.getExtent();
+         Matrix affine = null;
+         scales = null;
+         if (subsamplings != null) {
+             // Validity of the subsamplings values will be verified by GridExtent.subsample(…) invoked below.
+             final int dimension = extent.getDimension();
+             for (int i = Math.min(dimension, subsamplings.length); --i >= 0;) {
+                 final int s = subsamplings[i];
+                 if (s != 1) {
+                     if (scales == null) {
+                         subsampledExtent = extent.subsample(subsamplings);
+                         scales = new double[dimension];
+                         Arrays.fill(scales, 1);
+                         if (!subsampledExtent.startsAtZero()) {
+                             affine = Matrices.createIdentity(dimension + 1);
+                         }
+                     }
+                     final double sd = s;
+                     scales[i] = sd;
+                     if (affine != null) {
+                         affine.setElement(i, i, sd);
+                         affine.setElement(i, dimension, extent.getLow(i) - subsampledExtent.getLow(i) * sd);
+                     }
+                 }
+             }
+         }
+         if (affine != null) {
+             toBase = MathTransforms.linear(affine);
+         } else if (scales != null) {
+             toBase = MathTransforms.scale(scales);
+         }
+         return this;
+     }
+ 
+     /**
+      * Requests a grid geometry for a slice at the given "real world" position.
+      * The given position can be expressed in any coordinate reference system (CRS).
+      * The position should not define a coordinate for all dimensions, otherwise the slice would degenerate
+      * to a single point. Dimensions can be left unspecified either by assigning to {@code slicePoint} a CRS
+      * without those dimensions, or by assigning the NaN value to some coordinates.
+      *
+      * <div class="note"><b>Example:</b>
+      * if the {@linkplain GridGeometry#getCoordinateReferenceSystem() coordinate reference system} of base grid geometry has
+      * (<var>longitude</var>, <var>latitude</var>, <var>time</var>) axes, then a (<var>longitude</var>, <var>latitude</var>)
+      * slice at time <var>t</var> can be created with one of the following two positions:
+      * <ul>
+      *   <li>A three-dimensional position with ({@link Double#NaN}, {@link Double#NaN}, <var>t</var>) coordinates.</li>
+      *   <li>A one-dimensional position with (<var>t</var>) coordinate and the coordinate reference system set to
+      *       {@linkplain org.apache.sis.referencing.CRS#getTemporalComponent(CoordinateReferenceSystem) the temporal component}
+      *       of the grid geometry CRS.</li>
+      * </ul></div>
+      *
+      * <p>Notes:</p>
+      * <ul>
+      *   <li>This method can be invoked after {@link #subgrid(Envelope, double...)}, but not before.</li>
+      *   <li>If a non-default rounding mode is desired, it should be {@linkplain #rounding(GridRoundingMode) specified}
+      *       before to invoke this method.</li>
+      *   <li>This method does not reduce the number of dimensions of the grid geometry.
+      *       For dimensionality reduction, see {@link GridGeometry#reduce(int...)}.</li>
+      * </ul>
+      *
+      * @param  slicePoint   the coordinates where to get a slice.
+      * @return {@code this} for method call chaining.
+      * @throws IncompleteGridGeometryException if the base grid geometry has no extent or no "grid to CRS" transform.
+      * @throws IllegalGridGeometryException if an error occurred while converting the point coordinates to grid coordinates.
 -     * @throws PointOutsideCoverageException if the given point is outside the grid extent.
++     * @throws RuntimeException if the given point is outside the grid extent.
+      */
+     public GridDerivation slice(final DirectPosition slicePoint) {
+         ArgumentChecks.ensureNonNull("slicePoint", slicePoint);
+         MathTransform cornerToCRS = base.requireGridToCRS();
+         subGridSetter = "slice";
+         try {
+             if (toBase != null) {
+                 cornerToCRS = MathTransforms.concatenate(toBase, cornerToCRS);
+             }
+             if (base.envelope != null) {
+                 final CoordinateReferenceSystem sourceCRS = base.envelope.getCoordinateReferenceSystem();
+                 if (sourceCRS != null) {
+                     final CoordinateReferenceSystem targetCRS = slicePoint.getCoordinateReferenceSystem();
+                     if (targetCRS != null) {
+                         final CoordinateOperation operation = CRS.findOperation(sourceCRS, targetCRS, null);
+                         cornerToCRS = MathTransforms.concatenate(cornerToCRS, operation.getMathTransform());
+                     }
+                 }
+             }
+             final int dimension = cornerToCRS.getTargetDimensions();
+             ArgumentChecks.ensureDimensionMatches("slicePoint", dimension, slicePoint);
+             cornerToCRS = dropUnusedDimensions(cornerToCRS, dimension);
+             DirectPosition gridPoint = cornerToCRS.inverse().transform(slicePoint, null);
+             if (subsampledExtent != null) {
+                 subsampledExtent = subsampledExtent.slice(gridPoint, modifiedDimensions);
+             }
+             if (toBase != null) {
+                 gridPoint = toBase.transform(gridPoint, gridPoint);
+             }
+             baseExtent = baseExtent.slice(gridPoint, modifiedDimensions);
+         } catch (FactoryException e) {
+             throw new IllegalGridGeometryException(Resources.format(Resources.Keys.CanNotMapToGridDimensions), e);
+         } catch (TransformException e) {
+             throw new IllegalGridGeometryException(e, "slicePoint");
+         }
+         modifiedDimensions = null;              // Not needed anymore.
+         return this;
+     }
+ 
+     /**
+      * Requests a grid geometry for a slice at the given relative position.
+      * The relative position is specified by a ratio between 0 and 1 where 0 maps to {@linkplain GridExtent#getLow(int) low}
+      * grid coordinates, 1 maps to {@linkplain GridExtent#getHigh(int) high grid coordinates} and 0.5 maps the median point.
+      * The slicing is applied on all dimensions except the specified dimensions to keep.
+      *
+      * @param  sliceRatio        the ratio to apply on all grid dimensions except the ones to keep.
+      * @param  dimensionsToKeep  the grid dimension to keep unchanged.
+      * @return {@code this} for method call chaining.
+      * @throws IncompleteGridGeometryException if the base grid geometry has no extent.
+      * @throws IndexOutOfBoundsException if a {@code dimensionsToKeep} value is out of bounds.
+      */
+     public GridDerivation sliceByRatio(final double sliceRatio, final int... dimensionsToKeep) {
+         ArgumentChecks.ensureBetween("sliceRatio", 0, 1, sliceRatio);
+         ArgumentChecks.ensureNonNull("dimensionsToKeep", dimensionsToKeep);
+         subGridSetter = "sliceByRatio";
+         final GridExtent extent = (baseExtent != null) ? baseExtent : base.getExtent();
+         final GeneralDirectPosition slicePoint = new GeneralDirectPosition(extent.getDimension());
+         baseExtent = extent.sliceByRatio(slicePoint, sliceRatio, dimensionsToKeep);
+         if (subsampledExtent != null) {
+             subsampledExtent = subsampledExtent.sliceByRatio(slicePoint, sliceRatio, dimensionsToKeep);
+         }
+         return this;
+     }
+ 
+     /*
+      * RATIONAL FOR NOT PROVIDING reduce(int... dimensions) METHOD HERE: that method would need to be the last method invoked,
+      * otherwise it makes more complicated to implement other methods in this class.  Forcing users to invoke 'build()' before
+      * (s)he can invoke GridGeometry.reduce(…) makes that clear and avoid the need for more flags in this GridDerivation class.
+      * Furthermore declaring the 'reduce(…)' method in GridGeometry is more consistent with 'GridExtent.reduce(…)'.
+      */
+ 
+     /**
+      * Builds a grid geometry with the configuration specified by the other methods in this {@code GridDerivation} class.
+      *
+      * @return the modified grid geometry. May be the {@link #base} grid geometry if no change apply.
+      */
+     public GridGeometry build() {
+         /*
+          * Assuming:
+          *
+          *   • All low coordinates = 0
+          *   • h₁ the high coordinate before subsampling
+          *   • h₂ the high coordinates after subsampling
+          *   • c  a conversion factor from grid indices to "real world" coordinates
+          *   • s  a subsampling ≧ 1
+          *
+          * Then the envelope upper bounds x is:
+          *
+          *   • x = (h₁ + 1) × c
+          *   • x = (h₂ + f) × c⋅s      which implies       h₂ = h₁/s      and       f = 1/s
+          *
+          * If we modify the later equation for integer division instead than real numbers, we have:
+          *
+          *   • x = (h₂ + f) × c⋅s      where        h₂ = floor(h₁/s)      and       f = ((h₁ mod s) + 1)/s
+          *
+          * Because s ≧ 1, then f ≦ 1. But the f value actually used by GridExtent.toCRS(…) is hard-coded to 1
+          * since it assumes that all cells are whole, i.e. it does not take in account that the last cell may
+          * actually be fraction of a cell. Since 1 ≧ f, the computed envelope may be larger. This explains the
+          * need for envelope clipping performed by GridGeometry constructor.
+          */
+         final GridExtent extent = (subsampledExtent != null) ? subsampledExtent : baseExtent;
+         if (toBase != null || extent != base.extent) try {
+             return new GridGeometry(base, extent, toBase);
+         } catch (TransformException e) {
+             throw new IllegalGridGeometryException(e, "envelope");
+         }
+         return base;
+     }
+ 
+     /**
+      * Returns the extent of the modified grid geometry, ignoring subsamplings or changes in resolution.
+      * This is the intersection of the {@link #base} grid geometry with the (grid or geospatial) envelope
+      * given to a {@link #subgrid(Envelope, double...) subgrid(…)} method,
+      * expanded by the {@linkplain #margin(int...) specified margin} (if any)
+      * and potentially with some {@linkplain GridExtent#getSize(int) grid sizes} set to 1
+      * if a {@link #slice(DirectPosition) slice(…)} method has been invoked.
+      * The returned extent is in units of the {@link #base} grid cells, i.e.
+      * {@linkplain #getSubsamplings() subsamplings} are ignored.
+      *
+      * @return intersection of grid geometry extents in units of {@link #base} grid cells.
+      */
+     public GridExtent getIntersection() {
+         return (baseExtent != null) ? baseExtent : base.getExtent();
+     }
+ 
+     /**
+      * Returns an <em>estimation</em> of the steps for accessing cells along each axis of base grid.
+      * Given a conversion from {@code gridOfInterest} grid coordinates
+      * (<var>x</var>, <var>y</var>, <var>z</var>) to {@link #base} grid coordinates
+      * (<var>x′</var>, <var>y′</var>, <var>z′</var>) defined as below (generalize to as many dimensions as needed):
+      *
+      * <ul>
+      *   <li><var>x′</var> = s₀⋅<var>x</var></li>
+      *   <li><var>y′</var> = s₁⋅<var>y</var></li>
+      *   <li><var>z′</var> = s₂⋅<var>z</var></li>
+      * </ul>
+      *
+      * Then this method returns {|s₀|, |s₁|, |s₂|} rounded toward zero and clamped to 1
+      * (i.e. all values in the returned array are strictly positive, no zero values).
+      * It means that an iteration over {@code gridOfInterest} grid coordinates with a step Δ<var>x</var>=1
+      * corresponds approximately to an iteration in {@link #base} grid coordinates with a step of Δ<var>x′</var>=s₀,
+      * a step Δ<var>y</var>=1 corresponds approximately to a step Δ<var>y′</var>=s₁, <i>etc.</i>
+      * If the conversion changes grid axis order, then the order of elements in the returned array
+      * is the order of axes in the {@link #base} grid.
+      *
+      * @return an <em>estimation</em> of the steps for accessing cells along each axis of {@link #base} grid.
+      *
+      * @see #subgrid(GridGeometry)
+      * @see #subgrid(Envelope, double...)
+      * @see #subsample(int...)
+      */
+     public int[] getSubsamplings() {
+         final int[] subsamplings;
+         if (scales == null) {
+             subsamplings = new int[getIntersection().getDimension()];
+             Arrays.fill(subsamplings, 1);
+         } else {
+             subsamplings = new int[scales.length];
+             for (int i=0; i<subsamplings.length; i++) {
+                 subsamplings[i] = Math.max(1, (int) Math.nextUp(scales[i]));    // Really want rounding toward 0.
+             }
+         }
+         return subsamplings;
+     }
+ 
+     /**
+      * Returns an <em>estimation</em> of the scale factor when converting sub-grid coordinates to {@link #base} grid coordinates.
+      * This is for information purpose only since this method combines potentially different scale factors for all dimensions.
+      *
+      * @return an <em>estimation</em> of the scale factor for all dimensions.
+      *
+      * @see #subgrid(GridGeometry)
+      * @see #subgrid(Envelope, double...)
+      */
+     public double getGlobalScale() {
+         if (scales != null) {
+             double sum = 0;
+             int count = 0;
+             for (final double value : scales) {
+                 if (Double.isFinite(value)) {
+                     sum += value;
+                     count++;
+                 }
+             }
+             if (count != 0) {
+                 return sum / count;
+             }
+         }
+         return 1;
+     }
+ 
+     /**
+      * Returns a tree representation of this {@code GridDerivation}.
+      * The tree representation is for debugging purpose only and may change in any future SIS version.
+      *
+      * @param  locale  the locale to use for textual labels.
+      * @return a tree representation of this {@code GridDerivation}.
+      */
+     @Debug
+     private TreeTable toTree(final Locale locale) {
+         final TableColumn<CharSequence> column = TableColumn.VALUE_AS_TEXT;
+         final TreeTable tree = new DefaultTreeTable(column);
+         final TreeTable.Node root = tree.getRoot();
+         root.setValue(column, Classes.getShortClassName(this));
+         final StringBuilder buffer = new StringBuilder(256);
+         /*
+          * GridDerivation (example)
+          *   └─Intersection
+          *       ├─Dimension 0: [ 2000 … 5475] (3476 cells)
+          *       └─Dimension 1: [-1000 … 7999] (9000 cells)
+          */
+         if (baseExtent != null) {
+             TreeTable.Node section = root.newChild();
+             section.setValue(column, "Intersection");
+             getIntersection().appendTo(buffer, Vocabulary.getResources(locale));
+             for (final CharSequence line : CharSequences.splitOnEOL(buffer)) {
+                 String text = line.toString().trim();
+                 if (!text.isEmpty()) {
+                     section.newChild().setValue(column, text);
+                 }
+             }
+         }
+         /*
+          * GridDerivation (example)
+          *   └─Subsamplings
+          *       ├─{50, 300}
+          *       └─Global ≈ 175.0
+          */
+         if (scales != null) {
+             buffer.setLength(0);
+             buffer.append('{');
+             for (int s : getSubsamplings()) {
+                 if (buffer.length() > 1) buffer.append(", ");
+                 buffer.append(s);
+             }
+             TreeTable.Node section = root.newChild();
+             section.setValue(column, "Subsamplings");
+             section.newChild().setValue(column, buffer.append('}').toString()); buffer.setLength(0);
+             section.newChild().setValue(column, buffer.append("Global ≈ ").append((float) getGlobalScale()).toString());
+         }
+         return tree;
+     }
+ 
+     /**
+      * Returns a string representation of this {@code GridDerivation} for debugging purpose.
+      * The returned string is implementation dependent and may change in any future version.
+      *
+      * @return a string representation of this {@code GridDerivation} for debugging purpose.
+      */
+     @Override
+     public String toString() {
+         return toTree(null).toString();
+     }
+ }
diff --cc core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 1a4e390,b3cc40b..611761c
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@@ -45,8 -48,14 +48,9 @@@ import org.apache.sis.coverage.Subspace
  import org.apache.sis.referencing.operation.transform.MathTransforms;
  import org.apache.sis.referencing.operation.transform.TransformSeparator;
  import org.apache.sis.io.TableAppender;
+ import org.apache.sis.util.ArraysExt;
  import org.apache.sis.util.iso.Types;
  
 -// Branch-dependent imports
 -import org.opengis.coverage.grid.GridEnvelope;
 -import org.opengis.coverage.CannotEvaluateException;
 -import org.opengis.coverage.PointOutsideCoverageException;
 -
  
  /**
   * A range of grid coverage coordinates, also known as "grid envelope".
@@@ -608,6 -618,83 +576,83 @@@ public class GridExtent implements Seri
      }
  
      /**
+      * Returns indices of all dimensions where this grid extent has a size greater than 1.
+      * This method can be used for getting the grid extent of a <var>s</var>-dimensional slice
+      * in a <var>n</var>-dimensional cube where <var>s</var> ≦ <var>n</var>.
+      *
+      * <div class="note"><b>Example:</b>
+      * suppose that we want to get a two-dimensional slice <var>(y,z)</var> in a four-dimensional data cube <var>(x,y,z,t)</var>.
+      * The first step is to specify the <var>x</var> and <var>t</var> coordinates of the slice.
+      * In this example we set <var>x</var> to 5 and <var>t</var> to 8.
+      *
+      * {@preformat java
+      *     GridGeometry grid = ...;             // Geometry of the (x,y,z,t) grid.
+      *     GridGeometry slice4D = grid.slice(new GeneralDirectPosition(5, NaN, NaN, 8));
+      * }
+      *
+      * Above code created a slice at the requested position, but that slice still have 4 dimensions.
+      * It is a "slice" because the <var>x</var> and <var>t</var> dimensions of {@code slice4D} have only one cell.
+      * If a two-dimensional slice is desired, then above operations can be completed as below.
+      * In this example, the result of {@code getSubspaceDimensions(2)} call will be {1,2}.
+      *
+      * {@preformat java
+      *     int[]  subDimensions = slice4D.getExtent().getSubspaceDimensions(2);
+      *     GridGeometry slice2D = slice4D.reduce(subDimensions);
+      * }
+      *
+      * Note that in this particular example, it would have been more efficient to execute {@code grid.reduce(1,2)} directly.
+      * This {@code getSubspaceDimensions(int)} method is more useful for inferring a {@code slice2D} from a {@code slice4D}
+      * which has been created elsewhere, or when we do not really want the {@code slice2D} but only its dimension indices.
+      * </div>
+      *
+      * This method returns exactly <var>s</var> indices. If there is more than <var>s</var> dimensions having a
+      * {@linkplain #getSize(int) size} greater than 1, then a {@link SubspaceNotSpecifiedException} is thrown.
+      * If there is less than <var>s</var> dimensions having a size greater than 1, then the returned list of
+      * dimensions is completed with some dimensions of size 1, starting with the first dimensions in this grid
+      * extent, until there is exactly <var>s</var> dimensions. This this grid extent does not have <var>s</var>
 -     * dimensions, then a {@link CannotEvaluateException} is thrown.
++     * dimensions, then a {@code CannotEvaluateException} is thrown.
+      *
+      * @param  s  number of dimensions of the sub-space.
+      * @return indices of sub-space dimensions, in increasing order in an array of length <var>s</var>.
+      * @throws SubspaceNotSpecifiedException if there is more than <var>s</var> dimensions having a size greater than 1.
 -     * @throws CannotEvaluateException if this grid extent does not have at least <var>s</var> dimensions.
++     * @throws RuntimeException if this grid extent does not have at least <var>s</var> dimensions.
+      */
+     public int[] getSubspaceDimensions(final int s) {
+         ArgumentChecks.ensurePositive("s", s);
+         final int m = getDimension();
+         if (s > m) {
 -            throw new CannotEvaluateException(Resources.format(Resources.Keys.GridEnvelopeMustBeNDimensional_1, s));
++            throw new RuntimeException(Resources.format(Resources.Keys.GridEnvelopeMustBeNDimensional_1, s));
+         }
+         final int[] selected = new int[s];
+         int count = 0;
+         for (int i=0; i<m; i++) {
+             final long low  = coordinates[i];
+             final long high = coordinates[i+m];
+             if (low != high) {
+                 if (count < s) {
+                     selected[count++] = i;
+                 } else {
+                     throw new SubspaceNotSpecifiedException(Resources.format(Resources.Keys.NoNDimensionalSlice_3,
+                                     s, getAxisIdentification(i,i), Numerics.toUnsignedDouble(high - low)));
+                 }
+             }
+         }
+         final int missing = s - count;
+         if (missing != 0) {
+             System.arraycopy(selected, 0, selected, missing, count);
+             count = 0;
+             for (int i=0; ; i++) {                          // An IndexOutOfBoundsException would be a bug in our algorithm.
+                 if (coordinates[i] == coordinates[i+m]) {
+                     selected[count++] = i;
+                     if (count == missing) break;
+                 }
+             }
+             Arrays.sort(selected);
+         }
+         return selected;
+     }
+ 
+     /**
       * Returns the type (vertical, temporal, …) of grid axis at given dimension.
       * This information is provided because the grid axis type can not always be inferred from the context.
       * Some examples are:
@@@ -778,7 -924,114 +882,114 @@@
      }
  
      /**
-      * Returns a hash value for this grid envelope. This value need not remain
+      * Creates a new grid extent subsampled by the given amount of cells along each grid dimensions.
+      * This method divides {@linkplain #getLow(int) low coordinates} and {@linkplain #getSize(int) grid sizes}
+      * by the given periods, rounding toward zero. The {@linkplain #getHigh(int) high coordinates} are adjusted
+      * accordingly (this is often equivalent to dividing high coordinates by the periods too, but a difference
+      * of one cell may exist).
+      *
+      * <div class="note"><b>Note:</b>
+      * The envelope computed from a grid extent may become <em>larger</em> after subsampling, not smaller.
+      * This effect can be understood intuitively if we consider that cells become larger after subsampling,
+      * which implies that accurate representation of the same envelope may require fractional cells on some
+      * grid borders.</div>
+      *
+      * This method does not reduce the number of dimensions of the grid extent.
+      * For dimensionality reduction, see {@link #reduce(int...)}.
+      *
+      * @param  periods  the subsamplings. Length shall be equal to the number of dimension and all values shall be greater than zero.
+      * @return the subsampled extent, or {@code this} is subsampling results in the same extent.
+      * @throws IllegalArgumentException if a period is not greater than zero.
+      *
+      * @see GridDerivation#subsample(int...)
+      */
+     public GridExtent subsample(final int... periods) {
+         ArgumentChecks.ensureNonNull("periods", periods);
+         final int m = getDimension();
+         ArgumentChecks.ensureDimensionMatches("periods", m, periods);
+         final GridExtent sub = new GridExtent(this);
+         for (int i=0; i<m; i++) {
+             final int s = periods[i];
+             if (s > 1) {
+                 final int j = i + m;
+                 long low  = coordinates[i];
+                 long size = coordinates[j] - low + 1;                                             // Result is an unsigned number.
+                 if (size == 0) throw new ArithmeticException("long overflow");
+                 long r = Long.divideUnsigned(size, s);
+                 if (r*s == size) r--;                           // Make inclusive if the division did not already rounded toward 0.
+                 sub.coordinates[i] = low /= s;
+                 sub.coordinates[j] = low + r;
+             } else if (s <= 0) {
+                 throw new IllegalArgumentException(Errors.format(Errors.Keys.ValueNotGreaterThanZero_2, Strings.toIndexed("periods", i), s));
+             }
+         }
+         return Arrays.equals(coordinates, sub.coordinates) ? this : sub;
+     }
+ 
+     /**
+      * Returns a slice of this given grid extent computed by a ratio between 0 and 1 inclusive.
+      * This is a helper method for {@link GridDerivation#sliceByRatio(double, int...)} implementation.
+      *
+      * @param  slicePoint        a pre-allocated direct position to be overwritten by this method.
+      * @param  sliceRatio        the ratio to apply on all grid dimensions except the ones to keep.
+      * @param  dimensionsToKeep  the grid dimension to keep unchanged.
+      */
+     final GridExtent sliceByRatio(final DirectPosition slicePoint, final double sliceRatio, final int[] dimensionsToKeep) {
+         for (int i=slicePoint.getDimension(); --i >= 0;) {
+             slicePoint.setOrdinate(i, sliceRatio * (getSize(i) - 1) + getLow(i));
+         }
+         for (int i=0; i<dimensionsToKeep.length; i++) {
+             slicePoint.setOrdinate(dimensionsToKeep[i], Double.NaN);
+         }
+         return slice(slicePoint, null);
+     }
+ 
+     /**
+      * Creates a new grid extent which represent a slice of this grid at the given point.
+      * The given point may have less dimensions than this grid extent, in which case the
+      * dimensions must be specified in the {@code modifiedDimensions} array. Coordinates
+      * in the given point will be rounded to nearest integer.
+      *
+      * <p>This method does not reduce the number of dimensions of the grid extent.
+      * For dimensionality reduction, see {@link #reduce(int...)}.</p>
+      *
+      * @param  slicePoint           where to take a slice. NaN values are handled as if their dimensions were absent.
+      * @param  modifiedDimensions   mapping from {@code slicePoint} dimensions to this {@code GridExtent} dimensions,
+      *                              or {@code null} if {@code slicePoint} contains all grid dimensions in same order.
+      * @return a grid extent for the specified slice.
 -     * @throws PointOutsideCoverageException if the given point is outside the grid extent.
++     * @throws RuntimeException if the given point is outside the grid extent.
+      */
+     final GridExtent slice(final DirectPosition slicePoint, final int[] modifiedDimensions) {
+         final GridExtent slice = new GridExtent(this);
+         final int n = slicePoint.getDimension();
+         final int m = getDimension();
+         for (int k=0; k<n; k++) {
+             double p = slicePoint.getOrdinate(k);
+             if (!Double.isNaN(p)) {
+                 final long c = Math.round(p);
+                 final int i = (modifiedDimensions != null) ? modifiedDimensions[k] : k;
+                 final long low  = coordinates[i];
+                 final long high = coordinates[i + m];
+                 if (c >= low && c <= high) {
+                     slice.coordinates[i + m] = slice.coordinates[i] = c;
+                 } else {
+                     final StringBuilder b = new StringBuilder();
+                     for (int j=0; j<n; j++) {
+                         if (j != 0) b.append(", ");
+                         p = slicePoint.getOrdinate(j);
+                         if (Double.isNaN(p)) b.append("NaN");
+                         else b.append(Math.round(p));
+                     }
 -                    throw new PointOutsideCoverageException(Resources.format(Resources.Keys.GridCoordinateOutsideCoverage_4,
++                    throw new RuntimeException(Resources.format(Resources.Keys.GridCoordinateOutsideCoverage_4,
+                             getAxisIdentification(i,k), low, high, b.toString()));
+                 }
+             }
+         }
+         return Arrays.equals(coordinates, slice.coordinates) ? this : slice;
+     }
+ 
+     /**
+      * Returns a hash value for this grid envelope. This value needs not to remain
       * consistent between different implementations of the same class.
       *
       * @return a hash value for this grid envelope.
diff --cc core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index a6a4d29,d70678d..7026ce8
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@@ -17,9 -17,13 +17,12 @@@
  package org.apache.sis.coverage.grid;
  
  import java.util.Locale;
+ import org.opengis.geometry.DirectPosition;
  import org.opengis.metadata.spatial.DimensionNameType;
 -import org.opengis.coverage.PointOutsideCoverageException;
  import org.apache.sis.geometry.AbstractEnvelope;
  import org.apache.sis.geometry.GeneralEnvelope;
+ import org.apache.sis.geometry.GeneralDirectPosition;
+ import org.apache.sis.coverage.SubspaceNotSpecifiedException;
  import org.apache.sis.referencing.crs.HardCodedCRS;
  import org.apache.sis.util.resources.Vocabulary;
  import org.apache.sis.test.TestCase;
@@@ -119,17 -123,67 +122,67 @@@ public final strictfp class GridExtentT
      }
  
      /**
-      * Tests {@link GridExtent#subExtent(int, int)}.
+      * Tests {@link GridExtent#reduce(int...)}.
       */
      @Test
-     public void testSubExtent() {
-         GridExtent extent = create3D();
-         extent = extent.subExtent(0, 2);
-         assertEquals("dimension", 2, extent.getDimension());
-         assertExtentEquals(extent, 0, 100, 499);
-         assertExtentEquals(extent, 1, 200, 799);
-         assertEquals(DimensionNameType.COLUMN, extent.getAxisType(0).get());
-         assertEquals(DimensionNameType.ROW,    extent.getAxisType(1).get());
+     public void testReduce() {
+         final GridExtent extent = create3D();
+         GridExtent reduced = extent.reduce(0, 1);
+         assertEquals("dimension", 2, reduced.getDimension());
+         assertExtentEquals(reduced, 0, 100, 499);
+         assertExtentEquals(reduced, 1, 200, 799);
+         assertEquals(DimensionNameType.COLUMN, reduced.getAxisType(0).get());
+         assertEquals(DimensionNameType.ROW,    reduced.getAxisType(1).get());
+ 
+         reduced = extent.reduce(2);
+         assertEquals("dimension", 1, reduced.getDimension());
+         assertExtentEquals(reduced, 0, 40, 49);
+         assertEquals(DimensionNameType.TIME, reduced.getAxisType(0).get());
+     }
+ 
+     /**
+      * Tests {@link GridExtent#slice(DirectPosition, int[])}.
+      */
+     @Test
+     public void testSlice() {
+         final GeneralDirectPosition slicePoint = new GeneralDirectPosition(226.7, 47.2);
+         final GridExtent extent = create3D();
+         final GridExtent slice  = extent.slice(slicePoint, new int[] {1, 2});
+         assertEquals("dimension", 3, slice.getDimension());
+         assertExtentEquals(slice, 0, 100, 499);
+         assertExtentEquals(slice, 1, 227, 227);
+         assertExtentEquals(slice, 2,  47,  47);
+         /*
+          * Verify that point outside the GridExtent causes an exception to be thrown.
+          * The message is localized but the grid coordinates "(900, 47)" are currently
+          * unlocalized, so the check below should work in any locale (note that it may
+          * change in future SIS version).
+          */
+         slicePoint.setOrdinate(0, 900);
+         try {
+             extent.slice(slicePoint, new int[] {1, 2});
+             fail("Expected PointOutsideCoverageException");
 -        } catch (PointOutsideCoverageException e) {
++        } catch (RuntimeException e) {
+             final String message = e.getLocalizedMessage();
+             assertTrue(message, message.contains("(900, 47)"));     // See above comment.
+         }
+     }
+ 
+     /**
+      * Tests {@link GridExtent#getSubspaceDimensions(int)}.
+      */
+     @Test
+     public void testGetSubspaceDimensions() {
+         final GridExtent extent = new GridExtent(null, new long[] {100, 5, 200, 40}, new long[] {500, 5, 800, 40}, true);
+         assertArrayEquals(new int[] {0,  2  }, extent.getSubspaceDimensions(2));
+         assertArrayEquals(new int[] {0,1,2  }, extent.getSubspaceDimensions(3));
+         assertArrayEquals(new int[] {0,1,2,3}, extent.getSubspaceDimensions(4));
+         try {
+             extent.getSubspaceDimensions(1);
+             fail("Should not reduce to 1 dimension.");
+         } catch (SubspaceNotSpecifiedException e) {
+             assertNotNull(e.getMessage());
+         }
      }
  
      /**
diff --cc core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
index c96e4aa,1d6a6e8..1ef6fa7
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
@@@ -62,6 -65,8 +62,7 @@@ import org.apache.sis.geometry.Shapes2D
  import org.apache.sis.geometry.Envelopes;
  import org.apache.sis.geometry.Envelope2D;
  import org.apache.sis.geometry.DirectPosition2D;
 -import org.apache.sis.internal.system.Modules;
+ import org.apache.sis.internal.util.Strings;
  import org.apache.sis.math.DecimalFunctions;
  import org.apache.sis.measure.Longitude;
  import org.apache.sis.measure.Latitude;
diff --cc core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 0605267,3fdec31..76c15ec
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@@ -121,12 -122,8 +121,13 @@@ import org.apache.sis.measure.Units
  import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
  import static org.apache.sis.internal.util.StandardDateFormat.UTC;
  import static org.apache.sis.internal.referencing.ServicesForMetadata.CONNECTION;
+ import static org.apache.sis.internal.metadata.NameToIdentifier.Simplifier.ESRI_DATUM_PREFIX;
  
 +// Branch-dependent imports
 +import org.apache.sis.internal.util.StandardDateFormat;
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +
  
  /**
   * <cite>Data Access Object</cite> (DAO) creating geodetic objects from a JDBC connection to an EPSG database.
diff --cc core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
index 54f278b,53b991f..33bdf31
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
@@@ -23,12 -23,8 +23,13 @@@ import org.opengis.referencing.datum.*
  import org.opengis.referencing.operation.*;
  import org.opengis.parameter.ParameterDescriptor;
  import org.apache.sis.internal.metadata.WKTKeywords;
+ import org.apache.sis.util.CharSequences;
  
 +// Branch-dependent imports
 +import org.apache.sis.referencing.crs.DefaultParametricCRS;
 +import org.apache.sis.referencing.cs.DefaultParametricCS;
 +import org.apache.sis.referencing.datum.DefaultParametricDatum;
 +
  
  /**
   * Information about a specific table. The MS-Access dialect of SQL is assumed;
diff --cc core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java
index a379022,ed08d7e..f2e333b
--- a/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java
@@@ -20,8 -20,9 +20,8 @@@ import java.util.Locale
  import java.util.Formatter;
  import java.util.Formattable;
  import java.util.FormattableFlags;
 -import org.opengis.util.ControlledVocabulary;
  import org.opengis.util.InternationalString;
- import org.apache.sis.internal.util.Utilities;
+ import org.apache.sis.internal.util.Strings;
  import org.apache.sis.util.CharSequences;
  
  
diff --cc ide-project/NetBeans/nbproject/genfiles.properties
index f2edd02,3f06e92..8263145
--- a/ide-project/NetBeans/nbproject/genfiles.properties
+++ b/ide-project/NetBeans/nbproject/genfiles.properties
@@@ -3,6 -3,6 +3,6 @@@
  build.xml.data.CRC32=58e6b21c
  build.xml.script.CRC32=462eaba0
  build.xml.stylesheet.CRC32=28e38971@1.53.1.46
- nbproject/build-impl.xml.data.CRC32=c09a569b
 -nbproject/build-impl.xml.data.CRC32=f8afd8eb
 -nbproject/build-impl.xml.script.CRC32=aa8f5386
++nbproject/build-impl.xml.data.CRC32=22126788
 +nbproject/build-impl.xml.script.CRC32=3a1dc6ad
  nbproject/build-impl.xml.stylesheet.CRC32=3a2fa800@1.89.1.48
diff --cc ide-project/NetBeans/nbproject/project.properties
index 676b527,6551144..1b8ac41
--- a/ide-project/NetBeans/nbproject/project.properties
+++ b/ide-project/NetBeans/nbproject/project.properties
@@@ -98,11 -98,12 +98,12 @@@ test.fra-profile.dir = ${project.root}/
  # Those dependencies must exist in the local Maven repository.
  # Those numbers should match the ones declared in the pom.xml files.
  #
 -geoapi.version       = 3.1-SNAPSHOT
 +geoapi.version       = 3.0.1
  jsr363.version       = 1.0
  jama.version         = 1.0.3
- esri.api.version     = 2.1.0
- jts.version          = 1.15.0
+ guava.version        = 18.0
+ esri.api.version     = 2.2.2
+ jts.version          = 1.16.0
  georss.version       = 0.9.8
  rome.version         = 0.9
  jdom1.version        = 1.0
diff --cc storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
index c63e18f,4190b49..37a6be3
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
@@@ -16,14 -16,11 +16,10 @@@
   */
  package org.apache.sis.storage.netcdf;
  
- import java.awt.Color;
  import java.util.List;
  import java.awt.image.DataBuffer;
- import java.awt.image.ColorModel;
- import java.awt.image.BufferedImage;
  import java.awt.image.RenderedImage;
- import java.awt.image.WritableRaster;
- import org.opengis.geometry.DirectPosition;
+ import java.awt.image.RasterFormatException;
 -import org.opengis.coverage.CannotEvaluateException;
  import org.apache.sis.coverage.SampleDimension;
  import org.apache.sis.coverage.grid.GridCoverage;
  import org.apache.sis.coverage.grid.GridGeometry;
@@@ -64,14 -62,13 +61,13 @@@ final class Image extends GridCoverage 
       * This returns a view as much as possible; sample values are not copied.
       */
      @Override
-     public RenderedImage render(final DirectPosition slicePoint) {
-         // TODO: use slicePoint.
-         final GridExtent extent = getGridGeometry().getExtent();
-         final int width  = Math.toIntExact(extent.getSize(0));
-         final int height = Math.toIntExact(extent.getSize(1));
-         final WritableRaster raster = RasterFactory.createBandedRaster(data, width, height, width, null, null, null);
-         final ColorModel colors = ColorModelFactory.createColorModel(getSampleDimensions(), VISIBLE_BAND, data.getDataType(),
-                 (category) -> category.isQuantitative() ? new Color[] {Color.BLACK, Color.WHITE} : null);
-         return new BufferedImage(colors, raster, false, null);
+     public RenderedImage render(final GridExtent target) {
+         try {
+             final ImageRenderer renderer = new ImageRenderer(this, target);
+             renderer.setData(data);
+             return renderer.image();
+         } catch (IllegalArgumentException | ArithmeticException | RasterFormatException e) {
 -            throw new CannotEvaluateException(Resources.format(Resources.Keys.CanNotRender_2, label, e), e);
++            throw new RuntimeException(Resources.format(Resources.Keys.CanNotRender_2, label, e), e);
+         }
      }
  }
diff --cc storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
index f33a648,019750c..6acd4cd
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
@@@ -21,7 -22,9 +22,8 @@@ import org.apache.sis.math.Vector
  import org.apache.sis.util.Workaround;
  import org.apache.sis.storage.DataStoreException;
  import org.apache.sis.internal.netcdf.ucar.DecoderWrapper;
+ import org.apache.sis.measure.Units;
  import org.apache.sis.test.DependsOn;
 -import org.opengis.test.dataset.TestData;
  import org.junit.Test;
  
  import static org.opengis.test.Assert.*;
diff --cc storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
index 156f6d7,7a23148..2d1b59b
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
@@@ -33,11 -33,9 +33,12 @@@ import org.apache.sis.util.logging.Warn
  import org.apache.sis.util.logging.WarningListeners;
  import org.apache.sis.internal.storage.StoreUtilities;
  import org.apache.sis.internal.storage.Resources;
+ import org.apache.sis.internal.util.Strings;
  import org.apache.sis.referencing.NamedIdentifier;
  
 +// Branch-specific imports
 +import org.opengis.referencing.ReferenceIdentifier;
 +
  
  /**
   * Manages a series of features, coverages or sensor data.


Mime
View raw message