You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2018/12/28 20:03:50 UTC

[sis] branch master updated (475ebea -> 15af661)

This is an automated email from the ASF dual-hosted git repository.

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


    from 475ebea  Merge branch 'geoapi-3.1'.
     add 5aab518  Accept exponents in unit name (e.g. "metre2").
     add 544e733  Change the abbreviations used for spherical coordinates axes for matching latest revision of ISO 19111: Radius "R" become "r" and spherical latitude φ′ become Ω.
     add d777342  First draft of a netCDF Axis.toISO() method.
     add 7655964  Use Level.CONFIG instead of Level.WARNING if Derby driver is not on the classpath since Derby is optional.
     add c52bfcf  Remove usage of PACK200 in OpenOffice add-in (https://issues.apache.org/jira/browse/SIS-430).
     add 6759f5c  Fix the insufficient amount of digits in some error messages.
     add 0c69ecc  Documentation update: replace "ISO-19115" by "ISO 19115".
     add 77c040a  More progress toward decoding the CoordinateSystem from a netCDF file. A side effect of this work is the introduction of ReferencingFactoryContainer, to be needed by various data store (not just netCDF).
     add 1c8d11d  Rename methods in ReferencingFactoryContainer and use them in GeodeticObjectBuilder.
     add e86bb56  More work toward the creation of CoordinateReferenceSystem from netCDF variables.
     add ee56b82  First draft of GridGeometry.getCoordinateReferenceSystem() method, except for the temporal axis and projected CRS.
     add 3613eef  Support time axis and leverage pre-defined coordinate system when possible.
     add feec75a  Deprecate the DefaultTemporalCRS methods working on java.util.Date. Replaced by java.time.Instant.
     add 108d1f3  When using the UCAR library for reading netCDF file, also use that library for parsing units.
     add e2519af  Adjustment in handling of offset in units.
     add e303b6f  First part of building a "grid to CRS" transform for netCDF file, including only the linear parts for now. https://issues.apache.org/jira/browse/SIS-316
     add f4a9e49  Rename the netCDF internal GridGeometry class as "Grid" for avoiding confusion with the public GridGeometry class.
     add 8e76ba4  Modify the policy about default implementation in AbstractResource: construct metadata from getIdentifier() and getEnvelope() instead than providing a default implementation of those methods based on metadata. A side effect of this work is to provide an implementation of JoinFeatureSet.getEnvelope() based on newly added Envelopes.union(Envelope...) ùethod.
     add 16c8dca  NetcdfStore.components() now include GridCoverageResources.
     add d0a7fc7  Fix the 'gridToCRS' coefficients for non-linear transform and compute an envelope even if a grid range is [0 … 0].
     add bff02e8  Implement a mechanism for detecting repetitions in a vector. This is needed for simplifying grid geometries found in some netCDF files, for example HYCOM data.
     add 2e1fb8e  NetCDF grid now uses the new Vector methods for calculating the grid geometry.
     add 9f76515  Improve performance of Vector.repetitions(). It make a big difference for large grids like HYCOM.
     add 367df41  Partial decoding of HYCOM netCDF files.
     add c0f83ed  First attempt to take in account the two-dimensional localization grid found in HYCOM files.
     add 027eaaa  Added three convenience methods: - MatrixSIS.translate(double[]) - MathTransforms.getMatrix(MathTransform, DirectPosition) - GridExtent.append(DimentionNameType, long, long, boolean)
     add 278310d  Add a MathTransforms.linear(MathTransform, DirectPosition) convenience method.
     add 68b153a  Bug fix: where reading metadata from PostgreSQL, enumeration types need to be read as strings. Opportunistic documentation improvement.
     add 648d58a  Add a GridGeometry.getExtent(Envelope) method. This require an improvement in TransformSeparator class.
     add a9a26ab  Move the special case handled by ConcatenatedTransform for PassThroughTransform as an implementation of PassThroughTransform.tryConcatenate(...) instead. The intent is to improve the handling of a chain of transforms in a way that allow GridExtent to reduce the "gridToCRS" transform to the dimensions of interest. Prior this work, we had an issue if the chain of operations included a PassThroughTransform.
     add 48dbf45  Add more tests for PassThroughTransform. This forced us to revisit the 'transform' implementation for overlapping arrays. This commit may make https://issues.apache.org/jira/browse/SIS-318 irrelevant.
     add 1c11912  Trivial method renaming for consistency with https://issues.apache.org/jira/browse/SIS-318.
     add 576d9a2  Move the special case as an implementation of tryConcatenate(…).
     add 29e6779  Reduce the amount of transform to inverse when ConcatenatedTransform verify if two transforms are the inverse of each other.
     add f248e6b  Add a test verifying more deeply PassThroughTransform.tryConcatenate(…) work.
     add 616a963  Initial draft of SampleDimension class for coverages.
     add de9772b  MathFunctions.toNanFloat(int) should always map to quiet NaN. Previous version was mapping to signaling NaN when the given ordinal was negative.
     add 9e8a1d5  Initial port of CategoryList and skeleton of SampleDimension.Builder.
     add 04cf24e  More complete port of SampleDimension built from a list of Categories.
     add 214e2c3  Port more tests and update documentation (replace "geophysics values" by "real values").
     add ae6fb90  Typo: replaced "approximative" by "approximated", "approximation" or "rough".
     add 66a0c87  Add more tests.
     add 420aacc  More verbose implementation of SampleDimension.toString().
     add 8482f7a  Initial implementation of netCDF GridResource.getSampleDimensions().
     add f2bee90  Parse missing values and fill values in netCDF files.
     add df1cc0b  Make Category class public. Constructors are accessible for allowing customization, but most common usages should use SampleDimension.Builder instead.
     add 54ae4c4  Add SampleDimension.forConvertedValues(boolean) method.
     add d287b83  Add more tests.
     add 6c31398  Add SampleDimension.getBackground() method.
     add 7462355  Replace the 'padValues' argument by a 'toNaN' mapping function in Category constructor. This give more control, for example for selecting a more efficient background value.
     add 000b6e0  Implements GridCoverageResource.getSampleDimension() in GeoTIFF reader too. It provides us more tests.
     add 5328290  Reimplement GeoTiffStore.components() as a List instead than a Set.
     add 5a18b91  First draft of a GridResource.read(…) method returning a GridCoverage.
     add f6bafe1  Ported ColorModelFactory from geotk. The previous skeleton was insufficient for netCDF reader needs.
     add 5d1392b  Mave Vector.buffer() optional.
     add 7742963  Spelling fix: explicitely -> explicitly.
     add 30ea394  Change the API in the GridCoverage method returning a RenderedImage. We need a way to specify which two-dimensional slice is desired.
     add 2d186a8  Better filtering of netCDF variable to consider as GridCoverage.
     add a72bc6b  When the netCDF variable does not specify explicitly the valid value range, infer a default range based on the data type.
     add 3ebb551  Add a GridGeometry(GridGeometry, GridExtent) constructor, un-deprecate DefaultTemporalCRS methods working on java.util.Date and adjust javadoc.
     add fa22bec  First draft of handling of GridGeometry argument in the 'read' method.
     add c60e904  Add ArraysExt.sequence(int, int) for a common operation.
     add 054ae86  Reduce the occurrence of NaN in transform matrix.
     add ea9b376  Add a MathTransforms.translation(double...) method, together with a TranslationTransform internal implementation.
     add 91ad710  Add an Envelope.findOperation(Envelope, Envelope) which take in a coount the area of interest. This is an incomplete feature - see https://issues.apache.org/jira/browse/SIS-442
     add f928dce  Spelling fixes.
     add 5b26997  Reduce the amount of exception that may be thrown when converting an Envelope to a GeographicBoundingBox for Envelopes.findOperation(…) purpose.
     add afae70f  Addition of GridChang as a helper class for implementation of GridResource.read(GridGeometry, int...).
     add 9fac261  Documentation updates.
     add c97b206  Avoid formatting too long parameter values in WKT. In particular interpolation tables can have thousands of values.
     add 04c4a7a  ConcatenatedTransform should check sooner if two transforma are inverse, since matrix multiplication will not tell us if matrices contain NaN values.
     add 679796f  Complete (for now) the implementation of GridCoverage.toString(). This tasks required improvement in GridGeometry.toString(), SampleDimension.toString(), TreeTableFormat and TableAppender.
     add 9ef5c6c  Rename a misnamed test class (after renaming of the actual class).
     add e882a60  Normalize GeographicBoundingBox longitude values to [-180 … 180] range.
     add 65f8e67  Add test.
     add 58634c6  Rename GridExtent.getCentroid() as GridExtent.getPointOfInterest() and change its return value for making more obvious that this is not in the unit of a spatio-temporal CRS.
     add e006851  Make a better guess when 'gridToCRS' seems to be mapping pixel corner instead than pixel center.
     add 0bc82ea  More effort in estimating a tolerance threshold for vector compression.
     add c2065de  If a netCDF file contains two variables that are components of wind vectors or current vectors, they should appear as sample dimensions of the same grid coverage.
     add 0574eb2  Share the netCDF coordinate axes accross netCDF files.
     add 67d85bd  Remove references to Geotk in comments.
     add 2020033  GridChange should round to the enclosing GridExtent instead than nearest. We added a GridRoundingMode enumeration for giving control.
     add 15f1424  MathTransforms.translation(double...) shall return a MathTransform2D instance when vector length is 2. Add author name in GridResource for the part about grouping related sample dimensions together.
     add b09bc6a  Make a bigger effort to use the EPSG definitions for CoordinateSystem with different axis order and for CoordinateReferenceSystem with unknown datum.
     add e18babb  Consolidation of the code adding or removing EPSG codes after changes of axis order. More tests have been added.
     add 74d5c0a  Rename AxesConvention.CONVENTIONALLY_ORIENTED as DISPLAY_ORIENTED.
     add 7f43f52  Defines coordinate system with [0 … 360]° longitude range instead of [-180 … +180]° when the coordinate values in the netCDF are in that range.
     add 9d2431d  Remove the package-private AbstractMathTransform.inverseOf(…) method. It has been retrofitted in tryConcatenate(…) instead.
     add 3ebe785  Replace fill/missing values by NaN in netCDF files when there is no offset and scale factor. The intent is to make easier to build a SampleDimension for such data.
     add bafe80e  Consolidation of creation of SampleDimension with NaN values.
     add b3b819d  First draft of a netCDF reader implementation taking in account the sub-sampling and sub-set of bands specified in the read(GridGeometry domain, int... range) arguments.
     add a5ad10a  Add tests and a GridGeometry.subgrid(areaOfInterest, targetResolution) method.
     add 2caa9ba  Merge branch 'geoapi-4.0' into geoapi-3.1
     new 15af661  Merge branch 'geoapi-3.1'

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 application/pom.xml                                |  12 +-
 .../java/org/apache/sis/console/AboutCommand.java  |   2 +-
 .../org/apache/sis/console/TransformCommand.java   |   2 +-
 .../apache/sis/test/suite/ConsoleTestSuite.java    |   2 +-
 application/sis-openoffice/pom.xml                 |  10 +-
 .../org/apache/sis/openoffice/Registration.java    | 130 +--
 .../org/apache/sis/openoffice/Transformer.java     |   2 +-
 .../sis-openoffice/src/main/unopkg/description.xml |   2 +-
 .../apache/sis/test/suite/OpenOfficeTestSuite.java |   2 +-
 .../sis/internal/unopkg/FilteredJarFile.java       | 120 ---
 .../org/apache/sis/internal/unopkg/JavaMaker.java  |  89 +-
 .../org/apache/sis/internal/unopkg/UnoPkg.java     | 198 ++---
 .../apache/sis/internal/unopkg/package-info.java   |   9 +-
 .../org/apache/sis/feature/AbstractAttribute.java  |   4 +-
 .../org/apache/sis/feature/CharacteristicMap.java  |   2 +-
 .../org/apache/sis/feature/DefaultFeatureType.java |   2 +-
 .../org/apache/sis/feature/EnvelopeOperation.java  |   2 +-
 .../java/org/apache/sis/feature/FeatureFormat.java |   2 +-
 .../main/java/org/apache/sis/feature/Features.java |   2 +-
 .../java/org/apache/sis/feature/SparseFeature.java |   2 +-
 .../java/org/apache/sis/feature/Validator.java     |   2 +-
 .../java/org/apache/sis/feature/benchmarks.html    |   2 +-
 .../sis/feature/builder/FeatureTypeBuilder.java    |   8 +-
 .../apache/sis/feature/builder/TypeBuilder.java    |   2 +-
 .../sis/internal/feature/AttributeConvention.java  |   2 +-
 .../java/org/apache/sis/internal/feature/ESRI.java |   2 +-
 .../apache/sis/feature/CharacteristicMapTest.java  |   2 +-
 .../apache/sis/feature/DefaultFeatureTypeTest.java |   2 +-
 .../apache/sis/test/suite/FeatureTestSuite.java    |   2 +-
 .../java/org/apache/sis/internal/jaxb/Context.java |   6 +-
 .../sis/internal/jaxb/SpecializedIdentifier.java   |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_DateTime.java  |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_Distance.java  |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_Measure.java   |   2 +-
 .../apache/sis/internal/jaxb/gco/package-info.java |   2 +-
 .../apache/sis/internal/jaxb/gml/DateAdapter.java  |   2 +-
 .../apache/sis/internal/jaxb/gml/GMLAdapter.java   |   2 +-
 .../org/apache/sis/internal/jaxb/gml/Measure.java  |   7 +-
 .../internal/jaxb/gml/UniversalTimeAdapter.java    |   2 +-
 .../apache/sis/internal/jaxb/lan/PT_FreeText.java  |   3 +-
 .../sis/internal/metadata/AxisDirections.java      |  76 +-
 .../apache/sis/internal/metadata/AxisNames.java    |  20 +-
 .../internal/metadata/LegacyPropertyAdapter.java   |   2 +-
 .../org/apache/sis/internal/metadata/Merger.java   |   2 +-
 .../sis/internal/metadata/ReferencingServices.java |  20 +-
 .../apache/sis/internal/metadata/Resources.java    |   5 +
 .../sis/internal/metadata/Resources.properties     |   1 +
 .../sis/internal/metadata/Resources_fr.properties  |   1 +
 .../sis/internal/metadata/ServicesForUtility.java  |   2 +-
 .../sis/internal/metadata/VerticalDatumTypes.java  |   2 -
 .../sis/internal/metadata/sql/Initializer.java     |   2 +-
 .../org/apache/sis/internal/xml/XmlUtilities.java  |   2 +-
 .../org/apache/sis/io/wkt/FormattableObject.java   |   3 +-
 .../main/java/org/apache/sis/io/wkt/Formatter.java |  71 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    |  10 +-
 .../org/apache/sis/io/wkt/MathTransformParser.java |   2 +-
 .../java/org/apache/sis/io/wkt/Transliterator.java |  16 +-
 .../main/java/org/apache/sis/io/wkt/WKTFormat.java |  51 +-
 .../main/java/org/apache/sis/io/wkt/Warnings.java  |   2 +-
 .../org/apache/sis/metadata/PropertyAccessor.java  |   8 +-
 .../apache/sis/metadata/ValueExistencePolicy.java  |   2 +-
 .../org/apache/sis/metadata/iso/ISOMetadata.java   |   8 +-
 .../sis/metadata/iso/ImmutableIdentifier.java      |   2 +-
 .../metadata/iso/acquisition/DefaultObjective.java |   2 +-
 .../sis/metadata/iso/acquisition/package-info.java |   2 +-
 .../sis/metadata/iso/citation/Citations.java       |   4 +-
 .../sis/metadata/iso/citation/DefaultCitation.java |   2 +-
 .../sis/metadata/iso/citation/package-info.java    |   2 +-
 .../sis/metadata/iso/constraint/package-info.java  |   2 +-
 .../sis/metadata/iso/content/package-info.java     |   2 +-
 .../metadata/iso/distribution/package-info.java    |   2 +-
 .../iso/extent/DefaultGeographicBoundingBox.java   |   9 +-
 .../metadata/iso/extent/DefaultTemporalExtent.java |   2 +-
 .../apache/sis/metadata/iso/extent/Extents.java    |   6 +-
 .../sis/metadata/iso/extent/package-info.java      |   2 +-
 .../metadata/iso/identification/package-info.java  |   2 +-
 .../sis/metadata/iso/lineage/package-info.java     |   2 +-
 .../sis/metadata/iso/maintenance/package-info.java |   2 +-
 .../org/apache/sis/metadata/iso/package-info.java  |   2 +-
 .../sis/metadata/iso/quality/package-info.java     |   2 +-
 .../sis/metadata/iso/spatial/package-info.java     |   2 +-
 .../apache/sis/metadata/sql/CachedStatement.java   |  15 +-
 .../apache/sis/metadata/sql/MetadataSource.java    |   6 +-
 .../apache/sis/util/iso/DefaultRecordSchema.java   |   2 +-
 .../org/apache/sis/util/iso/DefaultTypeName.java   |   4 +-
 .../org/apache/sis/util/iso/GlobalNameSpace.java   |   2 +-
 .../main/java/org/apache/sis/util/iso/Names.java   |  44 +
 .../java/org/apache/sis/xml/MarshalContext.java    |   2 +-
 .../java/org/apache/sis/xml/MarshallerPool.java    |   2 +-
 .../main/java/org/apache/sis/xml/NilReason.java    |   2 +-
 .../java/org/apache/sis/xml/ReferenceResolver.java |   2 +-
 .../src/main/java/org/apache/sis/xml/XLink.java    |  18 +-
 .../src/main/java/org/apache/sis/xml/XML.java      |   4 +-
 .../internal/jaxb/ModifiableIdentifierMapTest.java |   2 +-
 .../org/apache/sis/io/wkt/TransliteratorTest.java  |   8 +-
 .../sis/metadata/iso/extent/ExtentsTest.java       |   2 +-
 .../java/org/apache/sis/test/MetadataAssert.java   |  10 +-
 .../apache/sis/test/suite/MetadataTestSuite.java   |   2 +-
 .../sis/test/xml/AnnotationConsistencyCheck.java   |   2 +-
 .../apache/sis/test/xml/DocumentComparator.java    |   4 +-
 .../java/org/apache/sis/test/xml/TestCase.java     |   6 +-
 .../java/org/apache/sis/coverage/Category.java     | 488 +++++++++++
 .../java/org/apache/sis/coverage/CategoryList.java | 691 +++++++++++++++
 .../org/apache/sis/coverage/ConvertedCategory.java |  74 ++
 .../org/apache/sis/coverage/ConvertedRange.java    | 105 +++
 .../org/apache/sis/coverage/SampleDimension.java   | 967 +++++++++++++++++++++
 .../org/apache/sis/coverage/SampleRangeFormat.java | 321 +++++++
 .../main/java/org/apache/sis/coverage/ToNaN.java   | 102 +++
 .../org/apache/sis/coverage/grid/GridChange.java   | 476 ++++++++++
 .../org/apache/sis/coverage/grid/GridCoverage.java | 185 ++++
 .../org/apache/sis/coverage/grid/GridExtent.java   | 441 +++++++---
 .../org/apache/sis/coverage/grid/GridGeometry.java | 501 ++++++++---
 .../apache/sis/coverage/grid/GridRoundingMode.java |  64 ++
 .../apache/sis/coverage/grid/PixelTranslation.java |  19 +-
 .../sis/coverage/{grid => }/package-info.java      |   8 +-
 .../sis/internal/raster/ColorModelFactory.java     | 499 +++++++++++
 .../sis/internal/raster/ColorModelPatch.java       | 121 +++
 .../internal/raster/MultiBandsIndexColorModel.java | 236 +++++
 .../apache/sis/internal/raster/RasterFactory.java  | 140 +++
 .../org/apache/sis/internal/raster/Resources.java  |  45 +
 .../sis/internal/raster/Resources.properties       |   9 +
 .../sis/internal/raster/Resources_fr.properties    |   9 +
 .../sis/internal/raster/ScaledColorSpace.java      | 171 ++++
 .../org/apache/sis/coverage/CategoryListTest.java  | 344 ++++++++
 .../java/org/apache/sis/coverage/CategoryTest.java | 242 ++++++
 .../apache/sis/coverage/SampleDimensionTest.java   | 116 +++
 .../apache/sis/coverage/grid/GridChangeTest.java   | 104 +++
 .../apache/sis/coverage/grid/GridExtentTest.java   |  86 +-
 .../apache/sis/coverage/grid/GridGeometryTest.java | 146 +++-
 .../org/apache/sis/image/DefaultIteratorTest.java  |   5 +-
 .../java/org/apache/sis/image/ImageTestCase.java   | 172 ++++
 .../test/java/org/apache/sis/image/TestViewer.java | 237 +++++
 .../image/{TiledImage.java => TiledImageMock.java} |  26 +-
 .../sis/internal/raster/ScaledColorSpaceTest.java  | 104 +++
 .../org/apache/sis/test/suite/RasterTestSuite.java |   9 +-
 .../sis/referencing/gazetteer/LocationFormat.java  |   2 +-
 .../gazetteer/MilitaryGridReferenceSystem.java     |   6 +-
 .../gazetteer/ModifiableLocationType.java          |  12 +-
 .../referencing/gazetteer/LocationTypeTest.java    |   2 +-
 .../gazetteer/ReferencingByIdentifiersTest.java    |   2 +-
 .../suite/ReferencingByIdentifiersTestSuite.java   |   2 +-
 .../sis/geometry/AbstractDirectPosition.java       |   8 +-
 .../org/apache/sis/geometry/AbstractEnvelope.java  |   6 +-
 .../org/apache/sis/geometry/ArrayEnvelope.java     |   3 +-
 .../org/apache/sis/geometry/CoordinateFormat.java  |   4 +-
 .../org/apache/sis/geometry/DirectPosition2D.java  |   4 +-
 .../org/apache/sis/geometry/EnvelopeReducer.java   | 165 ++++
 .../java/org/apache/sis/geometry/Envelopes.java    | 121 ++-
 .../apache/sis/geometry/GeneralDirectPosition.java |   4 +-
 .../org/apache/sis/geometry/GeneralEnvelope.java   |   2 +
 .../java/org/apache/sis/geometry/Shapes2D.java     |   6 +-
 .../referencing/CC_GeneralOperationParameter.java  |   2 +-
 .../referencing/CC_OperationParameterGroup.java    |   4 +-
 .../internal/referencing/CoordinateOperations.java |   5 +-
 .../internal/referencing/DefinitionVerifier.java   |   4 +-
 .../internal/referencing/DirectPositionView.java   |  10 +
 .../referencing/GeodeticObjectBuilder.java         |  86 +-
 .../apache/sis/internal/referencing/LazySet.java   |   2 +-
 .../referencing/PositionalAccuracyConstant.java    |   2 +-
 .../referencing/ReferencingFactoryContainer.java   | 215 +++++
 .../apache/sis/internal/referencing/Resources.java |  11 +
 .../sis/internal/referencing/Resources.properties  |   2 +
 .../internal/referencing/Resources_fr.properties   |   2 +
 .../internal/referencing/ServicesForMetadata.java  |  66 +-
 .../sis/internal/referencing/provider/Affine.java  |   2 +-
 .../referencing/provider/GeographicOffsets.java    |  12 +-
 .../provider/GeographicToGeocentric.java           |   2 +-
 .../internal/referencing/provider/Molodensky.java  |   4 +-
 .../referencing/provider/VerticalOffset.java       |   7 +-
 .../sis/parameter/DefaultParameterValue.java       |   4 +-
 .../sis/parameter/MapProjectionParameters.java     |   8 +-
 .../org/apache/sis/parameter/ParameterBuilder.java |   2 +-
 .../org/apache/sis/parameter/ParameterFormat.java  |   7 +-
 .../org/apache/sis/parameter/Parameterized.java    |   2 +-
 .../org/apache/sis/parameter/TensorValues.java     |   2 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |   2 +-
 .../main/java/org/apache/sis/referencing/CRS.java  |  14 +-
 .../java/org/apache/sis/referencing/CommonCRS.java |  41 +-
 .../sis/referencing/PropertiesConverter.java       |   2 +-
 .../sis/referencing/StandardDefinitions.java       |   4 +-
 .../sis/referencing/crs/AbstractDerivedCRS.java    |   2 +-
 .../sis/referencing/crs/DefaultCompoundCRS.java    |   4 +-
 .../sis/referencing/crs/DefaultProjectedCRS.java   |   2 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |  96 +-
 .../org/apache/sis/referencing/cs/AbstractCS.java  |  66 +-
 .../apache/sis/referencing/cs/AxesConvention.java  | 104 ++-
 .../java/org/apache/sis/referencing/cs/Codes.java  |  58 +-
 .../sis/referencing/cs/CoordinateSystems.java      | 161 +++-
 .../cs/DefaultCoordinateSystemAxis.java            |   2 +-
 .../org/apache/sis/referencing/cs/Normalizer.java  |  98 ++-
 .../sis/referencing/datum/DefaultEllipsoid.java    |   2 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |   4 +-
 .../factory/CommonAuthorityFactory.java            |   2 +-
 .../factory/ConcurrentAuthorityFactory.java        |   2 +-
 .../factory/GeodeticAuthorityFactory.java          |   4 +-
 .../referencing/factory/sql/AuthorityCodes.java    |   2 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |  12 +-
 .../sis/referencing/factory/sql/EPSGFactory.java   |   6 +-
 .../operation/AbstractSingleOperation.java         |   6 +-
 .../operation/CoordinateOperationFinder.java       |   4 +-
 .../operation/CoordinateOperationRegistry.java     |  10 +-
 .../operation/DefaultConcatenatedOperation.java    |   2 +-
 .../referencing/operation/DefaultConversion.java   |   6 +-
 .../operation/DefaultOperationMethod.java          |   2 +-
 .../operation/InverseOperationMethod.java          |   2 +-
 .../operation/builder/LinearTransformBuilder.java  |   3 +-
 .../operation/matrix/GeneralMatrix.java            |  16 +-
 .../referencing/operation/matrix/MatrixSIS.java    |  53 +-
 .../sis/referencing/operation/matrix/Solver.java   |   5 +-
 .../referencing/operation/projection/Mercator.java |   2 +-
 .../operation/projection/NormalizedProjection.java |   2 +-
 .../transform/AbstractLinearTransform.java         |  21 +-
 .../operation/transform/AbstractMathTransform.java |  49 +-
 .../operation/transform/ConcatenatedTransform.java | 218 ++---
 .../operation/transform/ConstantTransform1D.java   |   4 +
 .../transform/DefaultMathTransformFactory.java     |   4 +-
 .../transform/InterpolatedGeocentricTransform.java |   2 +-
 .../operation/transform/LinearInterpolator1D.java  |   2 +-
 .../operation/transform/MathTransformProvider.java |   2 +-
 .../operation/transform/MathTransforms.java        | 130 ++-
 .../transform/MathTransformsOrFactory.java         | 129 +++
 .../operation/transform/PassThroughTransform.java  | 473 +++++++---
 .../operation/transform/ProjectiveTransform.java   |  25 +-
 .../operation/transform/ScaleTransform.java        |  23 +-
 .../transform/SpecializableTransform.java          |   2 +-
 .../transform/SpecializableTransform1D.txt         |   6 +-
 .../operation/transform/SphericalToCartesian.java  |   2 +-
 .../operation/transform/TransferFunction.java      |  16 +-
 .../operation/transform/TransformSeparator.java    | 201 ++++-
 ...aleTransform.java => TranslationTransform.java} | 136 ++-
 .../operation/transform/package-info.java          |   2 +-
 .../apache/sis/geometry/EnvelopeReducerTest.java   |  95 ++
 .../org/apache/sis/geometry/EnvelopesTest.java     |  22 +
 .../sis/internal/metadata/AxisDirectionsTest.java  |  46 +-
 .../referencing/ServicesForMetadataTest.java       |  31 +-
 .../internal/referencing/provider/NADCONTest.java  |   2 +-
 .../internal/referencing/provider/NTv2Test.java    |   2 +-
 .../sis/io/wkt/GeodeticObjectParserTest.java       |   4 +-
 .../java/org/apache/sis/io/wkt/WKTFormatTest.java  |   2 +-
 .../sis/parameter/MapProjectionParametersTest.java |   2 +-
 .../org/apache/sis/referencing/CommonCRSTest.java  |  21 +-
 .../sis/referencing/crs/AbstractCRSTest.java       |   4 +-
 .../referencing/crs/DefaultEngineeringCRSTest.java |  14 +-
 .../referencing/crs/DefaultGeographicCRSTest.java  |  12 +-
 .../apache/sis/referencing/cs/AbstractCSTest.java  |   8 +-
 .../org/apache/sis/referencing/cs/CodesTest.java   |  10 +-
 .../sis/referencing/cs/CoordinateSystemsTest.java  |  38 +-
 .../sis/referencing/cs/DefaultCartesianCSTest.java |   4 +-
 .../cs/DefaultCoordinateSystemAxisTest.java        |   6 +-
 .../referencing/cs/DefaultCylindricalCSTest.java   |   2 +-
 .../referencing/cs/DefaultEllipsoidalCSTest.java   |   4 +-
 .../sis/referencing/cs/DefaultPolarCSTest.java     |   2 +-
 .../sis/referencing/cs/DefaultSphericalCSTest.java |   6 +-
 .../apache/sis/referencing/cs/HardCodedAxes.java   |  40 +-
 .../org/apache/sis/referencing/cs/HardCodedCS.java |   2 +-
 .../apache/sis/referencing/cs/NormalizerTest.java  | 176 +++-
 .../builder/LinearTransformBuilderTest.java        |   2 +-
 .../operation/matrix/GeneralMatrixTest.java        |  17 +-
 .../referencing/operation/matrix/Matrix2Test.java  |   2 +-
 .../referencing/operation/matrix/Matrix3Test.java  |  13 +-
 .../referencing/operation/matrix/Matrix4Test.java  |  42 +-
 .../operation/matrix/MatrixTestCase.java           |  60 +-
 .../operation/matrix/NonSquareMatrixTest.java      |  17 +-
 .../operation/projection/Benchmark.java            |   2 +-
 .../operation/projection/InitializerTest.java      |   2 +-
 .../projection/MercatorMethodComparison.java       |   5 +-
 .../projection/ObliqueStereographicTest.java       |   2 +-
 .../transform/AbridgedMolodenskyTransformTest.java |   4 +-
 .../transform/ConcatenatedTransformTest.java       |  10 +-
 .../operation/transform/MathTransformTestCase.java |  13 +-
 .../operation/transform/MathTransformsTest.java    |  96 +-
 .../transform/PassThroughTransformTest.java        | 141 ++-
 .../operation/transform/PseudoTransform.java       |  11 +-
 .../transform/TransformSeparatorTest.java          | 169 +++-
 .../transform/TranslationTransformTest.java        | 113 +++
 .../report/CoordinateOperationMethods.java         |   2 +-
 .../sis/test/suite/ReferencingTestSuite.java       |   4 +-
 .../org/apache/sis/referencing/crs/DerivedCRS.xml  |   2 +-
 .../sis/internal/converter/ConverterRegistry.java  |   2 +-
 .../sis/internal/converter/SystemRegistry.java     |   2 +-
 .../java/org/apache/sis/internal/jdk9/JDK9.java    |  25 +-
 .../apache/sis/internal/system/DaemonThread.java   |   4 +-
 .../org/apache/sis/internal/system/Threads.java    |   4 +-
 .../sis/internal/util/AutoMessageFormat.java       | 114 +++
 .../apache/sis/internal/util/CollectionsExt.java   |   2 +
 .../org/apache/sis/internal/util/Constants.java    |  16 +-
 .../org/apache/sis/internal/util/DoubleDouble.java |   4 +-
 .../sis/internal/util/ListOfUnknownSize.java       | 263 ++++++
 .../org/apache/sis/internal/util/Numerics.java     |  49 --
 .../apache/sis/internal/util/SetOfUnknownSize.java |  24 +-
 .../sis/internal/util/StandardDateFormat.java      |  95 +-
 .../sis/internal/util/TemporalUtilities.java       |  10 +
 .../sis/internal/util/UnmodifiableArrayList.java   |   4 +-
 .../java/org/apache/sis/internal/util/X364.java    |   3 +-
 .../main/java/org/apache/sis/io/TableAppender.java |  23 +-
 .../main/java/org/apache/sis/io/TabularFormat.java |   2 +-
 .../main/java/org/apache/sis/math/ArrayVector.java | 220 ++++-
 .../java/org/apache/sis/math/MathFunctions.java    |  59 +-
 .../src/main/java/org/apache/sis/math/Plane.java   |   2 +-
 .../java/org/apache/sis/math/RepeatedVector.java   | 270 ++++++
 .../java/org/apache/sis/math/SequenceVector.java   |  66 +-
 .../src/main/java/org/apache/sis/math/Vector.java  | 475 +++++++++-
 .../java/org/apache/sis/measure/AbstractUnit.java  |  32 +-
 .../main/java/org/apache/sis/measure/Angle.java    |   2 +-
 .../org/apache/sis/measure/MeasurementRange.java   |  11 +
 .../java/org/apache/sis/measure/NumberRange.java   |  77 +-
 .../main/java/org/apache/sis/measure/Range.java    |  21 +-
 .../java/org/apache/sis/measure/RangeFormat.java   |  26 +-
 .../main/java/org/apache/sis/measure/Salinity.java |   2 +-
 .../java/org/apache/sis/measure/UnitDimension.java |   3 +-
 .../java/org/apache/sis/measure/UnitFormat.java    |  26 +-
 .../main/java/org/apache/sis/measure/Units.java    |  11 +-
 .../java/org/apache/sis/measure/ValueRange.java    |   4 +-
 .../main/java/org/apache/sis/setup/OptionKey.java  |   2 +-
 .../java/org/apache/sis/setup/package-info.java    |   2 +-
 .../main/java/org/apache/sis/util/ArraysExt.java   | 252 +++++-
 .../src/main/java/org/apache/sis/util/Classes.java |   6 +-
 .../src/main/java/org/apache/sis/util/Numbers.java |   9 +-
 .../java/org/apache/sis/util/collection/Cache.java |   2 +-
 .../org/apache/sis/util/collection/Containers.java |  16 +-
 .../sis/util/collection/TreeTableFormat.java       |  35 +-
 .../sis/util/collection/WeakValueHashMap.java      |   2 +-
 .../sis/util/iso/ResourceInternationalString.java  |   2 +-
 .../org/apache/sis/util/logging/LoggerAdapter.java |   2 +-
 .../apache/sis/util/logging/MonolineFormatter.java |  11 +-
 .../apache/sis/util/logging/QuietLogRecord.java    |   2 +-
 .../java/org/apache/sis/util/resources/Errors.java |   4 +-
 .../apache/sis/util/resources/Errors.properties    |   4 +-
 .../apache/sis/util/resources/Errors_fr.properties |   4 +-
 .../sis/util/resources/IndexedResourceBundle.java  |  15 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |  45 +
 .../sis/util/resources/Vocabulary.properties       |   9 +
 .../sis/util/resources/Vocabulary_fr.properties    |   9 +
 .../apache/sis/internal/util/DoubleDoubleTest.java |   4 +-
 ...ilitiesTest.java => ListOfUnknownSizeTest.java} |  43 +-
 .../org/apache/sis/internal/util/NumericsTest.java |   9 -
 .../sis/internal/util/StandardDateFormatTest.java  |  39 +-
 .../org/apache/sis/math/MathFunctionsTest.java     |  25 +-
 .../org/apache/sis/math/RepeatedVectorTest.java    | 172 ++++
 .../test/java/org/apache/sis/math/VectorTest.java  |  32 +-
 .../apache/sis/measure/ConventionalUnitTest.java   |  49 +-
 .../org/apache/sis/measure/UnitFormatTest.java     |  14 +-
 .../org/apache/sis/test/TestConfiguration.java     |   7 +-
 .../test/java/org/apache/sis/test/TestStep.java    |   2 +-
 .../test/java/org/apache/sis/test/TestSuite.java   |   9 +-
 .../apache/sis/test/suite/UtilityTestSuite.java    |   4 +-
 .../java/org/apache/sis/util/ArraysExtTest.java    |  24 +-
 .../org/apache/sis/util/collection/CacheTest.java  |   3 +-
 .../sis/util/collection/TreeTableFormatTest.java   |  33 +-
 ide-project/NetBeans/nbproject/genfiles.properties |   2 +-
 ide-project/NetBeans/nbproject/project.properties  |   2 +
 ide-project/NetBeans/nbproject/project.xml         |   7 +
 pom.xml                                            |   6 +-
 .../sis/test/suite/FrenchProfileTestSuite.java     |   2 +-
 storage/pom.xml                                    |   1 +
 .../storage/earthobservation/LandsatReader.java    |   2 +-
 .../sis/storage/earthobservation/LandsatStore.java |   2 +-
 .../doc-files/LandsatMetadata.html                 |   8 +-
 .../sis/test/suite/EarthObservationTestSuite.java  |   2 +-
 .../src/main/c/org_apache_sis_storage_gdal_PJ.c    |   2 +-
 .../org/apache/sis/storage/gdal/Proj4Factory.java  |   6 +-
 .../org/apache/sis/storage/gdal/package-info.java  |   2 +-
 .../org/apache/sis/test/suite/GDALTestSuite.java   |   2 +-
 .../org/apache/sis/storage/geotiff/CRSBuilder.java | 142 +--
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |  92 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    |  61 +-
 .../apache/sis/test/suite/GeoTiffTestSuite.java    |   2 +-
 .../java/org/apache/sis/internal/netcdf/Axis.java  | 381 +++++++-
 .../org/apache/sis/internal/netcdf/CRSBuilder.java | 751 ++++++++++++++++
 .../org/apache/sis/internal/netcdf/Decoder.java    |  27 +-
 .../java/org/apache/sis/internal/netcdf/Grid.java  | 412 +++++++++
 .../apache/sis/internal/netcdf/GridGeometry.java   |  87 --
 .../apache/sis/internal/netcdf/NamedElement.java   |  96 +-
 .../org/apache/sis/internal/netcdf/Resources.java  |  23 +
 .../sis/internal/netcdf/Resources.properties       |   4 +
 .../sis/internal/netcdf/Resources_fr.properties    |   4 +
 .../org/apache/sis/internal/netcdf/Variable.java   | 604 ++++++++++++-
 .../sis/internal/netcdf/impl/ChannelDecoder.java   | 149 +++-
 .../sis/internal/netcdf/impl/FeaturesInfo.java     |  34 +-
 .../impl/{GridGeometryInfo.java => GridInfo.java}  | 161 +++-
 .../org/apache/sis/internal/netcdf/impl/HYCOM.java | 129 +++
 .../sis/internal/netcdf/impl/VariableInfo.java     | 279 ++++--
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   |  32 +-
 .../sis/internal/netcdf/ucar/FeaturesWrapper.java  |   6 -
 .../{GridGeometryWrapper.java => GridWrapper.java} |  83 +-
 .../sis/internal/netcdf/ucar/VariableWrapper.java  | 235 ++++-
 .../apache/sis/storage/netcdf/AttributeNames.java  |   4 +-
 .../apache/sis/storage/netcdf/GridResource.java    | 416 +++++++++
 .../java/org/apache/sis/storage/netcdf/Image.java  |  77 ++
 .../apache/sis/storage/netcdf/MetadataReader.java  | 128 +--
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |  15 +-
 .../sis/storage/netcdf/NetcdfStoreProvider.java    |   4 +-
 .../{GridGeometryTest.java => GridTest.java}       |  27 +-
 .../apache/sis/internal/netcdf/VariableTest.java   |  16 +-
 ...GridGeometryInfoTest.java => GridInfoTest.java} |  22 +-
 .../sis/storage/netcdf/MetadataReaderTest.java     |   1 +
 .../org/apache/sis/test/suite/NetcdfTestSuite.java |   6 +-
 .../apache/sis/test/suite/ShapefileTestSuite.java  |   2 +-
 .../org/apache/sis/internal/sql/feature/Table.java |   4 +-
 .../org/apache/sis/test/suite/SQLTestSuite.java    |   2 +-
 .../sis/internal/storage/AbstractFeatureSet.java   |  82 +-
 .../sis/internal/storage/AbstractGridResource.java | 116 +++
 .../sis/internal/storage/AbstractResource.java     | 150 ++--
 .../sis/internal/storage/MemoryFeatureSet.java     |  28 +-
 .../sis/internal/storage/MetadataBuilder.java      |  75 +-
 .../org/apache/sis/internal/storage/Resources.java |   5 +
 .../sis/internal/storage/Resources.properties      |   1 +
 .../sis/internal/storage/Resources_fr.properties   |   1 +
 .../sis/internal/storage/StoreUtilities.java       |  41 +-
 .../sis/internal/storage/csv/package-info.java     |   2 +-
 .../apache/sis/internal/storage/folder/Store.java  |   3 +
 .../sis/internal/storage/io/ChannelFactory.java    |  15 +-
 .../sis/internal/storage/query/FeatureSubset.java  |   9 -
 .../sis/internal/storage/wkt/FirstKeywordPeek.java |   2 +-
 .../sis/internal/storage/xml/AbstractProvider.java |   2 +-
 .../internal/storage/xml/GeographicEnvelope.java   |   2 +-
 .../java/org/apache/sis/storage/Aggregate.java     |   2 +-
 .../main/java/org/apache/sis/storage/DataSet.java  |   5 +-
 .../java/org/apache/sis/storage/DataStore.java     |  34 +-
 .../java/org/apache/sis/storage/FeatureNaming.java |   2 +-
 .../apache/sis/storage/GridCoverageResource.java   |  60 +-
 .../org/apache/sis/storage/StorageConnector.java   |  17 +-
 .../org/apache/sis/storage/WritableAggregate.java  |   2 +-
 .../internal/storage/query/SimpleQueryTest.java    |   2 +-
 .../apache/sis/test/suite/StorageTestSuite.java    |   2 +-
 .../apache/sis/internal/storage/gpx/Metadata.java  |   3 +-
 .../org/apache/sis/internal/storage/gpx/Store.java |   4 +-
 .../sis/internal/storage/gpx/WriterTest.java       |   3 +-
 .../org/apache/sis/test/suite/GPXTestSuite.java    |   2 +-
 429 files changed, 17760 insertions(+), 3090 deletions(-)
 delete mode 100644 core/sis-build-helper/src/main/java/org/apache/sis/internal/unopkg/FilteredJarFile.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/coverage/Category.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/coverage/ConvertedCategory.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/coverage/ConvertedRange.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/coverage/ToNaN.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridChange.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridRoundingMode.java
 copy core/sis-raster/src/main/java/org/apache/sis/coverage/{grid => }/package-info.java (71%)
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelFactory.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/internal/raster/ColorModelPatch.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/internal/raster/MultiBandsIndexColorModel.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/internal/raster/RasterFactory.java
 create mode 100644 core/sis-raster/src/main/java/org/apache/sis/internal/raster/ScaledColorSpace.java
 create mode 100644 core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryListTest.java
 create mode 100644 core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryTest.java
 create mode 100644 core/sis-raster/src/test/java/org/apache/sis/coverage/SampleDimensionTest.java
 create mode 100644 core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridChangeTest.java
 create mode 100644 core/sis-raster/src/test/java/org/apache/sis/image/ImageTestCase.java
 create mode 100644 core/sis-raster/src/test/java/org/apache/sis/image/TestViewer.java
 rename core/sis-raster/src/test/java/org/apache/sis/image/{TiledImage.java => TiledImageMock.java} (93%)
 create mode 100644 core/sis-raster/src/test/java/org/apache/sis/internal/raster/ScaledColorSpaceTest.java
 create mode 100644 core/sis-referencing/src/main/java/org/apache/sis/geometry/EnvelopeReducer.java
 create mode 100644 core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java
 create mode 100644 core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MathTransformsOrFactory.java
 copy core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/{ScaleTransform.java => TranslationTransform.java} (67%)
 create mode 100644 core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopeReducerTest.java
 create mode 100644 core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
 create mode 100644 core/sis-utility/src/main/java/org/apache/sis/internal/util/AutoMessageFormat.java
 create mode 100644 core/sis-utility/src/main/java/org/apache/sis/internal/util/ListOfUnknownSize.java
 create mode 100644 core/sis-utility/src/main/java/org/apache/sis/math/RepeatedVector.java
 copy core/sis-utility/src/test/java/org/apache/sis/internal/util/{UtilitiesTest.java => ListOfUnknownSizeTest.java} (53%)
 create mode 100644 core/sis-utility/src/test/java/org/apache/sis/math/RepeatedVectorTest.java
 create mode 100644 storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
 create mode 100644 storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Grid.java
 delete mode 100644 storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridGeometry.java
 rename storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/{GridGeometryInfo.java => GridInfo.java} (58%)
 create mode 100644 storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/HYCOM.java
 rename storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/{GridGeometryWrapper.java => GridWrapper.java} (67%)
 create mode 100644 storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
 create mode 100644 storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/Image.java
 rename storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/{GridGeometryTest.java => GridTest.java} (81%)
 rename storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/{GridGeometryInfoTest.java => GridInfoTest.java} (77%)
 create mode 100644 storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java


[sis] 01/01: Merge branch 'geoapi-3.1'

Posted by de...@apache.org.
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 15af6618a728e005c54fbb3ae92c50e9d4842a0b
Merge: 475ebea 2caa9ba
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Dec 28 21:02:56 2018 +0100

    Merge branch 'geoapi-3.1'

 application/pom.xml                                |  12 +-
 .../java/org/apache/sis/console/AboutCommand.java  |   2 +-
 .../org/apache/sis/console/TransformCommand.java   |   2 +-
 .../apache/sis/test/suite/ConsoleTestSuite.java    |   2 +-
 application/sis-openoffice/pom.xml                 |  10 +-
 .../org/apache/sis/openoffice/Registration.java    | 130 +--
 .../org/apache/sis/openoffice/Transformer.java     |   2 +-
 .../sis-openoffice/src/main/unopkg/description.xml |   2 +-
 .../apache/sis/test/suite/OpenOfficeTestSuite.java |   2 +-
 .../sis/internal/unopkg/FilteredJarFile.java       | 120 ---
 .../org/apache/sis/internal/unopkg/JavaMaker.java  |  89 +-
 .../org/apache/sis/internal/unopkg/UnoPkg.java     | 198 ++---
 .../apache/sis/internal/unopkg/package-info.java   |   9 +-
 .../org/apache/sis/feature/AbstractAttribute.java  |   4 +-
 .../org/apache/sis/feature/CharacteristicMap.java  |   2 +-
 .../org/apache/sis/feature/DefaultFeatureType.java |   2 +-
 .../org/apache/sis/feature/EnvelopeOperation.java  |   2 +-
 .../java/org/apache/sis/feature/FeatureFormat.java |   2 +-
 .../main/java/org/apache/sis/feature/Features.java |   2 +-
 .../java/org/apache/sis/feature/SparseFeature.java |   2 +-
 .../java/org/apache/sis/feature/Validator.java     |   2 +-
 .../java/org/apache/sis/feature/benchmarks.html    |   2 +-
 .../sis/feature/builder/FeatureTypeBuilder.java    |   8 +-
 .../apache/sis/feature/builder/TypeBuilder.java    |   2 +-
 .../sis/internal/feature/AttributeConvention.java  |   2 +-
 .../java/org/apache/sis/internal/feature/ESRI.java |   2 +-
 .../apache/sis/feature/CharacteristicMapTest.java  |   2 +-
 .../apache/sis/feature/DefaultFeatureTypeTest.java |   2 +-
 .../apache/sis/test/suite/FeatureTestSuite.java    |   2 +-
 .../java/org/apache/sis/internal/jaxb/Context.java |   6 +-
 .../sis/internal/jaxb/SpecializedIdentifier.java   |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_DateTime.java  |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_Distance.java  |   2 +-
 .../apache/sis/internal/jaxb/gco/GO_Measure.java   |   2 +-
 .../apache/sis/internal/jaxb/gco/package-info.java |   2 +-
 .../apache/sis/internal/jaxb/gml/DateAdapter.java  |   2 +-
 .../apache/sis/internal/jaxb/gml/GMLAdapter.java   |   2 +-
 .../org/apache/sis/internal/jaxb/gml/Measure.java  |   7 +-
 .../internal/jaxb/gml/UniversalTimeAdapter.java    |   2 +-
 .../apache/sis/internal/jaxb/lan/PT_FreeText.java  |   3 +-
 .../sis/internal/metadata/AxisDirections.java      |  76 +-
 .../apache/sis/internal/metadata/AxisNames.java    |  20 +-
 .../internal/metadata/LegacyPropertyAdapter.java   |   2 +-
 .../org/apache/sis/internal/metadata/Merger.java   |   2 +-
 .../sis/internal/metadata/ReferencingServices.java |  20 +-
 .../apache/sis/internal/metadata/Resources.java    |   5 +
 .../sis/internal/metadata/Resources.properties     |   1 +
 .../sis/internal/metadata/Resources_fr.properties  |   1 +
 .../sis/internal/metadata/ServicesForUtility.java  |   2 +-
 .../sis/internal/metadata/VerticalDatumTypes.java  |   2 -
 .../sis/internal/metadata/sql/Initializer.java     |   2 +-
 .../org/apache/sis/internal/xml/XmlUtilities.java  |   2 +-
 .../org/apache/sis/io/wkt/FormattableObject.java   |   3 +-
 .../main/java/org/apache/sis/io/wkt/Formatter.java |  71 +-
 .../apache/sis/io/wkt/GeodeticObjectParser.java    |  10 +-
 .../org/apache/sis/io/wkt/MathTransformParser.java |   2 +-
 .../java/org/apache/sis/io/wkt/Transliterator.java |  16 +-
 .../main/java/org/apache/sis/io/wkt/WKTFormat.java |  51 +-
 .../main/java/org/apache/sis/io/wkt/Warnings.java  |   2 +-
 .../org/apache/sis/metadata/PropertyAccessor.java  |   8 +-
 .../apache/sis/metadata/ValueExistencePolicy.java  |   2 +-
 .../org/apache/sis/metadata/iso/ISOMetadata.java   |   8 +-
 .../sis/metadata/iso/ImmutableIdentifier.java      |   2 +-
 .../metadata/iso/acquisition/DefaultObjective.java |   2 +-
 .../sis/metadata/iso/acquisition/package-info.java |   2 +-
 .../sis/metadata/iso/citation/Citations.java       |   4 +-
 .../sis/metadata/iso/citation/DefaultCitation.java |   2 +-
 .../sis/metadata/iso/citation/package-info.java    |   2 +-
 .../sis/metadata/iso/constraint/package-info.java  |   2 +-
 .../sis/metadata/iso/content/package-info.java     |   2 +-
 .../metadata/iso/distribution/package-info.java    |   2 +-
 .../iso/extent/DefaultGeographicBoundingBox.java   |   9 +-
 .../metadata/iso/extent/DefaultTemporalExtent.java |   2 +-
 .../apache/sis/metadata/iso/extent/Extents.java    |   6 +-
 .../sis/metadata/iso/extent/package-info.java      |   2 +-
 .../metadata/iso/identification/package-info.java  |   2 +-
 .../sis/metadata/iso/lineage/package-info.java     |   2 +-
 .../sis/metadata/iso/maintenance/package-info.java |   2 +-
 .../org/apache/sis/metadata/iso/package-info.java  |   2 +-
 .../sis/metadata/iso/quality/package-info.java     |   2 +-
 .../sis/metadata/iso/spatial/package-info.java     |   2 +-
 .../apache/sis/metadata/sql/CachedStatement.java   |  15 +-
 .../apache/sis/metadata/sql/MetadataSource.java    |   6 +-
 .../apache/sis/util/iso/DefaultRecordSchema.java   |   2 +-
 .../org/apache/sis/util/iso/DefaultTypeName.java   |   4 +-
 .../org/apache/sis/util/iso/GlobalNameSpace.java   |   2 +-
 .../main/java/org/apache/sis/util/iso/Names.java   |  44 +
 .../java/org/apache/sis/xml/MarshalContext.java    |   2 +-
 .../java/org/apache/sis/xml/MarshallerPool.java    |   2 +-
 .../main/java/org/apache/sis/xml/NilReason.java    |   2 +-
 .../java/org/apache/sis/xml/ReferenceResolver.java |   2 +-
 .../src/main/java/org/apache/sis/xml/XLink.java    |  18 +-
 .../src/main/java/org/apache/sis/xml/XML.java      |   4 +-
 .../internal/jaxb/ModifiableIdentifierMapTest.java |   2 +-
 .../org/apache/sis/io/wkt/TransliteratorTest.java  |   8 +-
 .../sis/metadata/iso/extent/ExtentsTest.java       |   2 +-
 .../java/org/apache/sis/test/MetadataAssert.java   |  10 +-
 .../apache/sis/test/suite/MetadataTestSuite.java   |   2 +-
 .../sis/test/xml/AnnotationConsistencyCheck.java   |   2 +-
 .../apache/sis/test/xml/DocumentComparator.java    |   4 +-
 .../java/org/apache/sis/test/xml/TestCase.java     |   6 +-
 .../java/org/apache/sis/coverage/Category.java     | 488 +++++++++++
 .../java/org/apache/sis/coverage/CategoryList.java | 691 +++++++++++++++
 .../org/apache/sis/coverage/ConvertedCategory.java |  74 ++
 .../org/apache/sis/coverage/ConvertedRange.java    | 105 +++
 .../org/apache/sis/coverage/SampleDimension.java   | 967 +++++++++++++++++++++
 .../org/apache/sis/coverage/SampleRangeFormat.java | 321 +++++++
 .../main/java/org/apache/sis/coverage/ToNaN.java   | 102 +++
 .../org/apache/sis/coverage/grid/GridChange.java   | 476 ++++++++++
 .../org/apache/sis/coverage/grid/GridCoverage.java | 185 ++++
 .../org/apache/sis/coverage/grid/GridExtent.java   | 441 +++++++---
 .../org/apache/sis/coverage/grid/GridGeometry.java | 501 ++++++++---
 .../apache/sis/coverage/grid/GridRoundingMode.java |  64 ++
 .../apache/sis/coverage/grid/PixelTranslation.java |  19 +-
 .../org/apache/sis/coverage}/package-info.java     |  14 +-
 .../sis/internal/raster/ColorModelFactory.java     | 499 +++++++++++
 .../sis/internal/raster/ColorModelPatch.java       | 121 +++
 .../internal/raster/MultiBandsIndexColorModel.java | 236 +++++
 .../apache/sis/internal/raster/RasterFactory.java  | 140 +++
 .../org/apache/sis/internal/raster/Resources.java  |  45 +
 .../sis/internal/raster/Resources.properties       |   9 +
 .../sis/internal/raster/Resources_fr.properties    |   9 +
 .../sis/internal/raster/ScaledColorSpace.java      | 171 ++++
 .../org/apache/sis/coverage/CategoryListTest.java  | 344 ++++++++
 .../java/org/apache/sis/coverage/CategoryTest.java | 242 ++++++
 .../apache/sis/coverage/SampleDimensionTest.java   | 116 +++
 .../apache/sis/coverage/grid/GridChangeTest.java   | 104 +++
 .../apache/sis/coverage/grid/GridExtentTest.java   |  86 +-
 .../apache/sis/coverage/grid/GridGeometryTest.java | 146 +++-
 .../org/apache/sis/image/DefaultIteratorTest.java  |   5 +-
 .../java/org/apache/sis/image/ImageTestCase.java   | 172 ++++
 .../test/java/org/apache/sis/image/TestViewer.java | 237 +++++
 .../image/{TiledImage.java => TiledImageMock.java} |  26 +-
 .../sis/internal/raster/ScaledColorSpaceTest.java  | 104 +++
 .../org/apache/sis/test/suite/RasterTestSuite.java |   9 +-
 .../sis/referencing/gazetteer/LocationFormat.java  |   2 +-
 .../gazetteer/MilitaryGridReferenceSystem.java     |   6 +-
 .../gazetteer/ModifiableLocationType.java          |  12 +-
 .../referencing/gazetteer/LocationTypeTest.java    |   2 +-
 .../gazetteer/ReferencingByIdentifiersTest.java    |   2 +-
 .../suite/ReferencingByIdentifiersTestSuite.java   |   2 +-
 .../sis/geometry/AbstractDirectPosition.java       |   8 +-
 .../org/apache/sis/geometry/AbstractEnvelope.java  |   6 +-
 .../org/apache/sis/geometry/ArrayEnvelope.java     |   3 +-
 .../org/apache/sis/geometry/CoordinateFormat.java  |   4 +-
 .../org/apache/sis/geometry/DirectPosition2D.java  |   4 +-
 .../org/apache/sis/geometry/EnvelopeReducer.java   | 165 ++++
 .../java/org/apache/sis/geometry/Envelopes.java    | 121 ++-
 .../apache/sis/geometry/GeneralDirectPosition.java |   4 +-
 .../org/apache/sis/geometry/GeneralEnvelope.java   |   2 +
 .../java/org/apache/sis/geometry/Shapes2D.java     |   6 +-
 .../referencing/CC_GeneralOperationParameter.java  |   2 +-
 .../referencing/CC_OperationParameterGroup.java    |   4 +-
 .../internal/referencing/CoordinateOperations.java |   5 +-
 .../internal/referencing/DefinitionVerifier.java   |   4 +-
 .../internal/referencing/DirectPositionView.java   |  10 +
 .../referencing/GeodeticObjectBuilder.java         |  86 +-
 .../apache/sis/internal/referencing/LazySet.java   |   2 +-
 .../referencing/PositionalAccuracyConstant.java    |   2 +-
 .../referencing/ReferencingFactoryContainer.java   | 215 +++++
 .../apache/sis/internal/referencing/Resources.java |  11 +
 .../sis/internal/referencing/Resources.properties  |   2 +
 .../internal/referencing/Resources_fr.properties   |   2 +
 .../internal/referencing/ServicesForMetadata.java  |  66 +-
 .../sis/internal/referencing/provider/Affine.java  |   2 +-
 .../referencing/provider/GeographicOffsets.java    |  12 +-
 .../provider/GeographicToGeocentric.java           |   2 +-
 .../internal/referencing/provider/Molodensky.java  |   4 +-
 .../referencing/provider/VerticalOffset.java       |   7 +-
 .../sis/parameter/DefaultParameterValue.java       |   4 +-
 .../sis/parameter/MapProjectionParameters.java     |   8 +-
 .../org/apache/sis/parameter/ParameterBuilder.java |   2 +-
 .../org/apache/sis/parameter/ParameterFormat.java  |   7 +-
 .../org/apache/sis/parameter/Parameterized.java    |   2 +-
 .../org/apache/sis/parameter/TensorValues.java     |   2 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |   2 +-
 .../main/java/org/apache/sis/referencing/CRS.java  |  14 +-
 .../java/org/apache/sis/referencing/CommonCRS.java |  41 +-
 .../sis/referencing/PropertiesConverter.java       |   2 +-
 .../sis/referencing/StandardDefinitions.java       |   4 +-
 .../sis/referencing/crs/AbstractDerivedCRS.java    |   2 +-
 .../sis/referencing/crs/DefaultCompoundCRS.java    |   4 +-
 .../sis/referencing/crs/DefaultProjectedCRS.java   |   2 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |  96 +-
 .../org/apache/sis/referencing/cs/AbstractCS.java  |  66 +-
 .../apache/sis/referencing/cs/AxesConvention.java  | 104 ++-
 .../java/org/apache/sis/referencing/cs/Codes.java  |  58 +-
 .../sis/referencing/cs/CoordinateSystems.java      | 161 +++-
 .../cs/DefaultCoordinateSystemAxis.java            |   2 +-
 .../org/apache/sis/referencing/cs/Normalizer.java  |  98 ++-
 .../sis/referencing/datum/DefaultEllipsoid.java    |   2 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |   4 +-
 .../factory/CommonAuthorityFactory.java            |   2 +-
 .../factory/ConcurrentAuthorityFactory.java        |   2 +-
 .../factory/GeodeticAuthorityFactory.java          |   4 +-
 .../referencing/factory/sql/AuthorityCodes.java    |   2 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |  12 +-
 .../sis/referencing/factory/sql/EPSGFactory.java   |   6 +-
 .../operation/AbstractSingleOperation.java         |   6 +-
 .../operation/CoordinateOperationFinder.java       |   4 +-
 .../operation/CoordinateOperationRegistry.java     |  10 +-
 .../operation/DefaultConcatenatedOperation.java    |   2 +-
 .../referencing/operation/DefaultConversion.java   |   6 +-
 .../operation/DefaultOperationMethod.java          |   2 +-
 .../operation/InverseOperationMethod.java          |   2 +-
 .../operation/builder/LinearTransformBuilder.java  |   3 +-
 .../operation/matrix/GeneralMatrix.java            |  16 +-
 .../referencing/operation/matrix/MatrixSIS.java    |  53 +-
 .../sis/referencing/operation/matrix/Solver.java   |   5 +-
 .../referencing/operation/projection/Mercator.java |   2 +-
 .../operation/projection/NormalizedProjection.java |   2 +-
 .../transform/AbstractLinearTransform.java         |  21 +-
 .../operation/transform/AbstractMathTransform.java |  49 +-
 .../operation/transform/ConcatenatedTransform.java | 218 ++---
 .../operation/transform/ConstantTransform1D.java   |   4 +
 .../transform/DefaultMathTransformFactory.java     |   4 +-
 .../transform/InterpolatedGeocentricTransform.java |   2 +-
 .../operation/transform/LinearInterpolator1D.java  |   2 +-
 .../operation/transform/MathTransformProvider.java |   2 +-
 .../operation/transform/MathTransforms.java        | 130 ++-
 .../transform/MathTransformsOrFactory.java         | 129 +++
 .../operation/transform/PassThroughTransform.java  | 473 +++++++---
 .../operation/transform/ProjectiveTransform.java   |  25 +-
 .../operation/transform/ScaleTransform.java        |  23 +-
 .../transform/SpecializableTransform.java          |   2 +-
 .../transform/SpecializableTransform1D.txt         |   6 +-
 .../operation/transform/SphericalToCartesian.java  |   2 +-
 .../operation/transform/TransferFunction.java      |  16 +-
 .../operation/transform/TransformSeparator.java    | 201 ++++-
 ...aleTransform.java => TranslationTransform.java} | 136 ++-
 .../operation/transform/package-info.java          |   2 +-
 .../apache/sis/geometry/EnvelopeReducerTest.java   |  95 ++
 .../org/apache/sis/geometry/EnvelopesTest.java     |  22 +
 .../sis/internal/metadata/AxisDirectionsTest.java  |  46 +-
 .../referencing/ServicesForMetadataTest.java       |  31 +-
 .../internal/referencing/provider/NADCONTest.java  |   2 +-
 .../internal/referencing/provider/NTv2Test.java    |   2 +-
 .../sis/io/wkt/GeodeticObjectParserTest.java       |   4 +-
 .../java/org/apache/sis/io/wkt/WKTFormatTest.java  |   2 +-
 .../sis/parameter/MapProjectionParametersTest.java |   2 +-
 .../org/apache/sis/referencing/CommonCRSTest.java  |  21 +-
 .../sis/referencing/crs/AbstractCRSTest.java       |   4 +-
 .../referencing/crs/DefaultEngineeringCRSTest.java |  14 +-
 .../referencing/crs/DefaultGeographicCRSTest.java  |  12 +-
 .../apache/sis/referencing/cs/AbstractCSTest.java  |   8 +-
 .../org/apache/sis/referencing/cs/CodesTest.java   |  10 +-
 .../sis/referencing/cs/CoordinateSystemsTest.java  |  38 +-
 .../sis/referencing/cs/DefaultCartesianCSTest.java |   4 +-
 .../cs/DefaultCoordinateSystemAxisTest.java        |   6 +-
 .../referencing/cs/DefaultCylindricalCSTest.java   |   2 +-
 .../referencing/cs/DefaultEllipsoidalCSTest.java   |   4 +-
 .../sis/referencing/cs/DefaultPolarCSTest.java     |   2 +-
 .../sis/referencing/cs/DefaultSphericalCSTest.java |   6 +-
 .../apache/sis/referencing/cs/HardCodedAxes.java   |  40 +-
 .../org/apache/sis/referencing/cs/HardCodedCS.java |   2 +-
 .../apache/sis/referencing/cs/NormalizerTest.java  | 176 +++-
 .../builder/LinearTransformBuilderTest.java        |   2 +-
 .../operation/matrix/GeneralMatrixTest.java        |  17 +-
 .../referencing/operation/matrix/Matrix2Test.java  |   2 +-
 .../referencing/operation/matrix/Matrix3Test.java  |  13 +-
 .../referencing/operation/matrix/Matrix4Test.java  |  42 +-
 .../operation/matrix/MatrixTestCase.java           |  60 +-
 .../operation/matrix/NonSquareMatrixTest.java      |  17 +-
 .../operation/projection/Benchmark.java            |   2 +-
 .../operation/projection/InitializerTest.java      |   2 +-
 .../projection/MercatorMethodComparison.java       |   5 +-
 .../projection/ObliqueStereographicTest.java       |   2 +-
 .../transform/AbridgedMolodenskyTransformTest.java |   4 +-
 .../transform/ConcatenatedTransformTest.java       |  10 +-
 .../operation/transform/MathTransformTestCase.java |  13 +-
 .../operation/transform/MathTransformsTest.java    |  96 +-
 .../transform/PassThroughTransformTest.java        | 141 ++-
 .../operation/transform/PseudoTransform.java       |  11 +-
 .../transform/TransformSeparatorTest.java          | 169 +++-
 .../transform/TranslationTransformTest.java        | 113 +++
 .../report/CoordinateOperationMethods.java         |   2 +-
 .../sis/test/suite/ReferencingTestSuite.java       |   4 +-
 .../org/apache/sis/referencing/crs/DerivedCRS.xml  |   2 +-
 .../sis/internal/converter/ConverterRegistry.java  |   2 +-
 .../sis/internal/converter/SystemRegistry.java     |   2 +-
 .../java/org/apache/sis/internal/jdk9/JDK9.java    |  25 +-
 .../apache/sis/internal/system/DaemonThread.java   |   4 +-
 .../org/apache/sis/internal/system/Threads.java    |   4 +-
 .../sis/internal/util/AutoMessageFormat.java       | 114 +++
 .../apache/sis/internal/util/CollectionsExt.java   |   2 +
 .../org/apache/sis/internal/util/Constants.java    |  16 +-
 .../org/apache/sis/internal/util/DoubleDouble.java |   4 +-
 .../sis/internal/util/ListOfUnknownSize.java       | 263 ++++++
 .../org/apache/sis/internal/util/Numerics.java     |  49 --
 .../apache/sis/internal/util/SetOfUnknownSize.java |  24 +-
 .../sis/internal/util/StandardDateFormat.java      |  95 +-
 .../sis/internal/util/TemporalUtilities.java       |  10 +
 .../sis/internal/util/UnmodifiableArrayList.java   |   4 +-
 .../java/org/apache/sis/internal/util/X364.java    |   3 +-
 .../main/java/org/apache/sis/io/TableAppender.java |  23 +-
 .../main/java/org/apache/sis/io/TabularFormat.java |   2 +-
 .../main/java/org/apache/sis/math/ArrayVector.java | 220 ++++-
 .../java/org/apache/sis/math/MathFunctions.java    |  59 +-
 .../src/main/java/org/apache/sis/math/Plane.java   |   2 +-
 .../java/org/apache/sis/math/RepeatedVector.java   | 270 ++++++
 .../java/org/apache/sis/math/SequenceVector.java   |  66 +-
 .../src/main/java/org/apache/sis/math/Vector.java  | 475 +++++++++-
 .../java/org/apache/sis/measure/AbstractUnit.java  |  32 +-
 .../main/java/org/apache/sis/measure/Angle.java    |   2 +-
 .../org/apache/sis/measure/MeasurementRange.java   |  11 +
 .../java/org/apache/sis/measure/NumberRange.java   |  77 +-
 .../main/java/org/apache/sis/measure/Range.java    |  21 +-
 .../java/org/apache/sis/measure/RangeFormat.java   |  26 +-
 .../main/java/org/apache/sis/measure/Salinity.java |   2 +-
 .../java/org/apache/sis/measure/UnitDimension.java |   3 +-
 .../java/org/apache/sis/measure/UnitFormat.java    |  26 +-
 .../main/java/org/apache/sis/measure/Units.java    |  11 +-
 .../java/org/apache/sis/measure/ValueRange.java    |   4 +-
 .../main/java/org/apache/sis/setup/OptionKey.java  |   2 +-
 .../java/org/apache/sis/setup/package-info.java    |   2 +-
 .../main/java/org/apache/sis/util/ArraysExt.java   | 252 +++++-
 .../src/main/java/org/apache/sis/util/Classes.java |   6 +-
 .../src/main/java/org/apache/sis/util/Numbers.java |   9 +-
 .../java/org/apache/sis/util/collection/Cache.java |   2 +-
 .../org/apache/sis/util/collection/Containers.java |  16 +-
 .../sis/util/collection/TreeTableFormat.java       |  35 +-
 .../sis/util/collection/WeakValueHashMap.java      |   2 +-
 .../sis/util/iso/ResourceInternationalString.java  |   2 +-
 .../org/apache/sis/util/logging/LoggerAdapter.java |   2 +-
 .../apache/sis/util/logging/MonolineFormatter.java |  11 +-
 .../apache/sis/util/logging/QuietLogRecord.java    |   2 +-
 .../java/org/apache/sis/util/resources/Errors.java |   4 +-
 .../apache/sis/util/resources/Errors.properties    |   4 +-
 .../apache/sis/util/resources/Errors_fr.properties |   4 +-
 .../sis/util/resources/IndexedResourceBundle.java  |  15 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |  45 +
 .../sis/util/resources/Vocabulary.properties       |   9 +
 .../sis/util/resources/Vocabulary_fr.properties    |   9 +
 .../apache/sis/internal/util/DoubleDoubleTest.java |   4 +-
 .../sis/internal/util/ListOfUnknownSizeTest.java   |  60 ++
 .../org/apache/sis/internal/util/NumericsTest.java |   9 -
 .../sis/internal/util/StandardDateFormatTest.java  |  39 +-
 .../org/apache/sis/math/MathFunctionsTest.java     |  25 +-
 .../org/apache/sis/math/RepeatedVectorTest.java    | 172 ++++
 .../test/java/org/apache/sis/math/VectorTest.java  |  32 +-
 .../apache/sis/measure/ConventionalUnitTest.java   |  49 +-
 .../org/apache/sis/measure/UnitFormatTest.java     |  14 +-
 .../org/apache/sis/test/TestConfiguration.java     |   7 +-
 .../test/java/org/apache/sis/test/TestStep.java    |   2 +-
 .../test/java/org/apache/sis/test/TestSuite.java   |   9 +-
 .../apache/sis/test/suite/UtilityTestSuite.java    |   4 +-
 .../java/org/apache/sis/util/ArraysExtTest.java    |  24 +-
 .../org/apache/sis/util/collection/CacheTest.java  |   3 +-
 .../sis/util/collection/TreeTableFormatTest.java   |  33 +-
 ide-project/NetBeans/nbproject/genfiles.properties |   2 +-
 ide-project/NetBeans/nbproject/project.properties  |   2 +
 ide-project/NetBeans/nbproject/project.xml         |   7 +
 pom.xml                                            |   6 +-
 .../sis/test/suite/FrenchProfileTestSuite.java     |   2 +-
 storage/pom.xml                                    |   1 +
 .../storage/earthobservation/LandsatReader.java    |   2 +-
 .../sis/storage/earthobservation/LandsatStore.java |   2 +-
 .../doc-files/LandsatMetadata.html                 |   8 +-
 .../sis/test/suite/EarthObservationTestSuite.java  |   2 +-
 .../src/main/c/org_apache_sis_storage_gdal_PJ.c    |   2 +-
 .../org/apache/sis/storage/gdal/Proj4Factory.java  |   6 +-
 .../org/apache/sis/storage/gdal/package-info.java  |   2 +-
 .../org/apache/sis/test/suite/GDALTestSuite.java   |   2 +-
 .../org/apache/sis/storage/geotiff/CRSBuilder.java | 142 +--
 .../apache/sis/storage/geotiff/GeoTiffStore.java   |  92 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    |  61 +-
 .../apache/sis/test/suite/GeoTiffTestSuite.java    |   2 +-
 .../java/org/apache/sis/internal/netcdf/Axis.java  | 381 +++++++-
 .../org/apache/sis/internal/netcdf/CRSBuilder.java | 751 ++++++++++++++++
 .../org/apache/sis/internal/netcdf/Decoder.java    |  27 +-
 .../java/org/apache/sis/internal/netcdf/Grid.java  | 412 +++++++++
 .../apache/sis/internal/netcdf/GridGeometry.java   |  87 --
 .../apache/sis/internal/netcdf/NamedElement.java   |  96 +-
 .../org/apache/sis/internal/netcdf/Resources.java  |  23 +
 .../sis/internal/netcdf/Resources.properties       |   4 +
 .../sis/internal/netcdf/Resources_fr.properties    |   4 +
 .../org/apache/sis/internal/netcdf/Variable.java   | 604 ++++++++++++-
 .../sis/internal/netcdf/impl/ChannelDecoder.java   | 149 +++-
 .../sis/internal/netcdf/impl/FeaturesInfo.java     |  34 +-
 .../impl/{GridGeometryInfo.java => GridInfo.java}  | 161 +++-
 .../org/apache/sis/internal/netcdf/impl/HYCOM.java | 129 +++
 .../sis/internal/netcdf/impl/VariableInfo.java     | 279 ++++--
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   |  32 +-
 .../sis/internal/netcdf/ucar/FeaturesWrapper.java  |   6 -
 .../{GridGeometryWrapper.java => GridWrapper.java} |  83 +-
 .../sis/internal/netcdf/ucar/VariableWrapper.java  | 235 ++++-
 .../apache/sis/storage/netcdf/AttributeNames.java  |   4 +-
 .../apache/sis/storage/netcdf/GridResource.java    | 416 +++++++++
 .../java/org/apache/sis/storage/netcdf/Image.java  |  77 ++
 .../apache/sis/storage/netcdf/MetadataReader.java  | 128 +--
 .../org/apache/sis/storage/netcdf/NetcdfStore.java |  15 +-
 .../sis/storage/netcdf/NetcdfStoreProvider.java    |   4 +-
 .../{GridGeometryTest.java => GridTest.java}       |  27 +-
 .../apache/sis/internal/netcdf/VariableTest.java   |  16 +-
 ...GridGeometryInfoTest.java => GridInfoTest.java} |  22 +-
 .../sis/storage/netcdf/MetadataReaderTest.java     |   1 +
 .../org/apache/sis/test/suite/NetcdfTestSuite.java |   6 +-
 .../apache/sis/test/suite/ShapefileTestSuite.java  |   2 +-
 .../org/apache/sis/internal/sql/feature/Table.java |   4 +-
 .../org/apache/sis/test/suite/SQLTestSuite.java    |   2 +-
 .../sis/internal/storage/AbstractFeatureSet.java   |  82 +-
 .../sis/internal/storage/AbstractGridResource.java | 116 +++
 .../sis/internal/storage/AbstractResource.java     | 150 ++--
 .../sis/internal/storage/MemoryFeatureSet.java     |  28 +-
 .../sis/internal/storage/MetadataBuilder.java      |  75 +-
 .../org/apache/sis/internal/storage/Resources.java |   5 +
 .../sis/internal/storage/Resources.properties      |   1 +
 .../sis/internal/storage/Resources_fr.properties   |   1 +
 .../sis/internal/storage/StoreUtilities.java       |  41 +-
 .../sis/internal/storage/csv/package-info.java     |   2 +-
 .../apache/sis/internal/storage/folder/Store.java  |   3 +
 .../sis/internal/storage/io/ChannelFactory.java    |  15 +-
 .../sis/internal/storage/query/FeatureSubset.java  |   9 -
 .../sis/internal/storage/wkt/FirstKeywordPeek.java |   2 +-
 .../sis/internal/storage/xml/AbstractProvider.java |   2 +-
 .../internal/storage/xml/GeographicEnvelope.java   |   2 +-
 .../java/org/apache/sis/storage/Aggregate.java     |   2 +-
 .../main/java/org/apache/sis/storage/DataSet.java  |   5 +-
 .../java/org/apache/sis/storage/DataStore.java     |  34 +-
 .../java/org/apache/sis/storage/FeatureNaming.java |   2 +-
 .../apache/sis/storage/GridCoverageResource.java   |  60 +-
 .../org/apache/sis/storage/StorageConnector.java   |  17 +-
 .../org/apache/sis/storage/WritableAggregate.java  |   2 +-
 .../internal/storage/query/SimpleQueryTest.java    |   2 +-
 .../apache/sis/test/suite/StorageTestSuite.java    |   2 +-
 .../apache/sis/internal/storage/gpx/Metadata.java  |   3 +-
 .../org/apache/sis/internal/storage/gpx/Store.java |   4 +-
 .../sis/internal/storage/gpx/WriterTest.java       |   3 +-
 .../org/apache/sis/test/suite/GPXTestSuite.java    |   2 +-
 429 files changed, 17797 insertions(+), 3076 deletions(-)

diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
index e017104,a1007cf..6d44383
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
@@@ -831,12 -839,8 +831,12 @@@ public class DefaultFeatureType extend
       * inherited from the {@linkplain #getSuperTypes() super-types} only if {@code includeSuperTypes}
       * is {@code true}.
       *
 +     * <div class="warning"><b>Warning:</b>
 +     * The type of list elements will be changed to {@code PropertyType} if and when such interface
 +     * will be defined in GeoAPI.</div>
 +     *
       * @param  includeSuperTypes  {@code true} for including the properties inherited from the super-types,
-      *         or {@code false} for returning only the properties defined explicitely in this type.
+      *         or {@code false} for returning only the properties defined explicitly in this type.
       * @return feature operation, attribute type and association role that carries characteristics of this
       *         feature type (not including parent types).
       */
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
index 87dc23b,20697e8..bb8933e
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
@@@ -106,12 -116,12 +106,12 @@@ final class Validator 
      /**
       * Implementation of {@link AbstractFeature#quality()}, also shared by {@link Features} static method.
       *
-      * @param type     the type of the {@code feature} argument, provided explicitely for protecting from user overriding.
+      * @param type     the type of the {@code feature} argument, provided explicitly for protecting from user overriding.
       * @param feature  the feature to validate.
       */
 -    void validate(final FeatureType type, final Feature feature) {
 -        for (final PropertyType pt : type.getProperties(true)) {
 -            final Property property = feature.getProperty(pt.getName().toString());
 +    void validate(final FeatureType type, final AbstractFeature feature) {
 +        for (final AbstractIdentifiedType pt : type.getProperties(true)) {
 +            final Object property = feature.getProperty(pt.getName().toString());
              final DataQuality pq;
              if (property instanceof AbstractAttribute<?>) {
                  pq = ((AbstractAttribute<?>) property).quality();
diff --cc core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
index fc5c6d7,bb227ba..359c68c
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
@@@ -203,15 -205,13 +203,15 @@@ public class FeatureTypeBuilder extend
       * This constructor initializes the list of {@linkplain #properties() properties}, the
       * {@linkplain #getSuperTypes() super types} and {@link #isAbstract() isAbstract} flag
       * to values inferred from the given template. The properties list will contain properties
-      * declared explicitely in the given template, not including properties inherited from super types.
+      * declared explicitly in the given template, not including properties inherited from super types.
       *
 -     * @param template  an existing feature type to use as a template, or {@code null} if none.
 +     * <div class="warning"><b>Warning:</b>
 +     * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
 +     * will be defined in GeoAPI.</div>
       *
 -     * @see #setAll(FeatureType)
 +     * @param template  an existing feature type to use as a template, or {@code null} if none.
       */
 -    public FeatureTypeBuilder(final FeatureType template) {
 +    public FeatureTypeBuilder(final DefaultFeatureType template) {
          this(null, null, null);
          if (template != null) {
              initialize(template);
diff --cc core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java
index dd75f3e,841275e..c40f504
--- a/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/feature/CharacteristicMapTest.java
@@@ -58,7 -61,7 +58,7 @@@ public final strictfp class Characteris
      }
  
      /**
-      * Tests adding explicitely a characteristic with {@code CharacteristicMap.put(String, Attribute)}.
 -     * Tests adding explicitly a characteristic with {@link CharacteristicMap#put(String, Attribute)}.
++     * Tests adding explicitly a characteristic with {@code CharacteristicMap.put(String, Attribute)}.
       */
      @Test
      public void testPut() {
diff --cc core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
index baabd2c,2089d58..05e160a
--- a/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/io/wkt/Formatter.java
@@@ -1485,13 -1488,44 +1489,42 @@@ public class Formatter implements Local
              } else {
                  append(number.doubleValue());
              }
 -        } else if (value instanceof ControlledVocabulary) {
 -            append((ControlledVocabulary) value);
 -        } else if (value instanceof Date) {
 -            append((Date) value);
 -        } else if (value instanceof Boolean) {
 -            append((Boolean) value);
 -        } else if (value instanceof CharSequence) {
 +        }
 +        else if (value instanceof CodeList<?>) append((CodeList<?>) value);
 +        else if (value instanceof Date)        append((Date)        value);
 +        else if (value instanceof Boolean)     append((Boolean)     value);
 +        else if (value instanceof CharSequence) {
              append((value instanceof InternationalString) ?
                      ((InternationalString) value).toString(locale) : value.toString(), null);
+         } else if (value.getClass().isArray()) {
+             /*
+              * All above cases delegated to another method which invoke 'appendSeparator()'.
+              * Since the following block is writing itself a new element, we need to invoke
+              * 'appendSeparator()' here. This block invokes (indirectly) this 'appendValue'
+              * method recursively for some or all elements in the list.
+              */
+             appendSeparator();
+             elementStart = buffer.appendCodePoint(symbols.getOpenSequence()).length();
+             final int length = Array.getLength(value);
+             final int cut = (length <= listSizeLimit) ? length : Math.max(listSizeLimit/2 - 1, 1);
+             for (int i=0; i<length; i++) {
+                 if (i == cut) {
+                     /*
+                      * Skip elements in the middle if the list is too long. The 'cut' index has been computed
+                      * in such a way that the number of elements to skip should be greater than 1, otherwise
+                      * formatting the single missing element would often have been shorter.
+                      */
+                     final int skip = length - Math.min(2*cut, listSizeLimit);
+                     buffer.append(symbols.getSeparator());
+                     setColor(ElementKind.REMARKS);
+                     buffer.append(Resources.forLocale(locale).getString(Resources.Keys.ElementsOmitted_1, skip));
+                     resetColor();
+                     i += skip;
+                     setInvalidWKT(value.getClass().getSimpleName(), null);
+                 }
+                 appendAny(Array.get(value, i));
+             }
+             buffer.appendCodePoint(symbols.getCloseSequence());
          } else {
              return false;
          }
diff --cc core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
index 0000000,6294919..09b2507
mode 000000,100644..100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@@ -1,0 -1,970 +1,967 @@@
+ /*
+  * 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;
+ 
+ import java.util.List;
+ import java.util.ArrayList;
+ import java.util.Set;
+ import java.util.TreeSet;
+ import java.util.Optional;
+ import java.util.Collection;
+ import java.util.Collections;
+ import java.util.Locale;
+ import java.util.Objects;
+ import java.io.Serializable;
+ import javax.measure.Unit;
+ import org.opengis.util.GenericName;
+ import org.opengis.util.InternationalString;
+ import org.opengis.referencing.operation.MathTransform1D;
+ import org.apache.sis.referencing.operation.transform.TransferFunction;
+ import org.apache.sis.internal.raster.Resources;
+ import org.apache.sis.measure.MeasurementRange;
+ import org.apache.sis.measure.NumberRange;
+ import org.apache.sis.util.resources.Vocabulary;
+ import org.apache.sis.util.ArgumentChecks;
+ import org.apache.sis.util.iso.Names;
+ import org.apache.sis.util.Numbers;
+ import org.apache.sis.util.Debug;
+ 
+ 
+ /**
+  * Describes the data values in a coverage (the range). For a raster, a sample dimension is a band.
+  * A sample dimension can reserve some values for <cite>qualitative</cite> information like  “this
+  * is a forest” and some other values for <cite>quantitative</cite> information like a temperature
+  * measurements.
+  *
+  * <div class="note"><b>Example:</b>
+  * an image of sea surface temperature (SST) could define the following categories:
+  * <table class="sis">
+  *   <caption>Example of categories in a sample dimension</caption>
+  *   <tr><th>Values range</th> <th>Meaning</th></tr>
+  *   <tr><td>[0]</td>          <td>No data</td></tr>
+  *   <tr><td>[1]</td>          <td>Cloud</td></tr>
+  *   <tr><td>[2]</td>          <td>Land</td></tr>
+  *   <tr><td>[10…210]</td>     <td>Temperature to be converted into Celsius degrees through a linear equation</td></tr>
+  * </table>
+  * In this example, sample values in range [10…210] define a quantitative category, while all others categories are qualitative.
+  * </div>
+  *
+  * <div class="section">Relationship with metadata</div>
 - * This class provides the same information than ISO 19115 {@link org.opengis.metadata.content.SampleDimension},
++ * This class provides the same information than ISO 19115 {@code org.opengis.metadata.content.SampleDimension},
+  * but organized in a different way. The use of the same name may seem a risk, but those two types are typically
+  * not used in same time.
+  *
+  * @author  Martin Desruisseaux (IRD, Geomatys)
+  * @version 1.0
 - *
 - * @see org.opengis.metadata.content.SampleDimension
 - *
+  * @since 1.0
+  * @module
+  */
+ public class SampleDimension implements Serializable {
+     /**
+      * Serial number for inter-operability with different versions.
+      */
+     private static final long serialVersionUID = 6026936545776852758L;
+ 
+     /**
+      * Identification for this sample dimension. Typically used as a way to perform a band select by
+      * using human comprehensible descriptions instead of just numbers. Web Coverage Service (WCS)
+      * can use this name in order to perform band sub-setting as directed from a user request.
+      *
+      * @see #getName()
+      */
+     private final GenericName name;
+ 
+     /**
+      * The background value, or {@code null} if unspecified. Should be a sample value of
+      * a qualitative category in the {@link #categories} list, but this is not mandatory.
+      *
+      * @see #getBackground()
+      */
+     private final Number background;
+ 
+     /**
+      * The list of categories making this sample dimension. May be empty but shall never be null.
+      */
+     private final CategoryList categories;
+ 
+     /**
+      * The transform from samples to real values. May be {@code null} if this sample dimension
+      * does not define any transform (which is not the same that defining an identity transform).
+      *
+      * @see #getTransferFunction()
+      */
+     private final MathTransform1D transferFunction;
+ 
+     /**
+      * The {@code SampleDimension} that describes values after {@linkplain #getTransferFunction() transfer function}
+      * has been applied, or if this {@code SampleDimension} is already converted then the original sample dimension.
+      * May be {@code null} if this sample dimension has no transfer function, or {@code this} if the transfer function
+      * is the identity function.
+      *
+      * <p>This field establishes a bidirectional navigation between sample values and real values.
+      * This is in contrast with methods named {@link #converted()}, which establish a unidirectional
+      * navigation from sample values to real values.</p>
+      *
+      * @see #converted()
+      * @see Category#converse
+      * @see CategoryList#converse
+      */
+     private final SampleDimension converse;
+ 
+     /**
+      * Creates a new sample dimension for values that are already converted to real values.
+      * This transfer function is set to identity, which implies that this constructor should
+      * be invoked only for sample dimensions having at least one quantitative category.
+      *
+      * @param  original  the original sample dimension for packed values.
+      * @param  bc        category of the background value in original sample dimension, or {@code null}.
+      */
+     private SampleDimension(final SampleDimension original, Category bc) {
+         converse         = original;
+         name             = original.name;
+         categories       = original.categories.converse;
+         transferFunction = Category.identity();
+         assert hasQuantitative();
+         if (bc == null) {
+             background = null;
+         } else {
+             bc = bc.converse;
+             final NumberRange<?> range = bc.range;
+             if (range != null) {
+                 background = range.getMinValue();
+             } else {
+                 background = (float) bc.minimum;
+             }
+         }
+     }
+ 
+     /**
+      * Creates a sample dimension with the specified name and categories.
+      * The sample dimension name is used as a way to perform a band select
+      * by using human comprehensible descriptions instead of numbers.
+      * The background value is used for filling empty space in map reprojections.
+      * The background value (if specified) should be the value of a qualitative category
+      * present in the {@code categories} collection, but this is not mandatory.
+      *
+      * <p>Note that {@link Builder} provides a more convenient way to create sample dimensions.</p>
+      *
+      * @param name        an identification for the sample dimension.
+      * @param background  the background value, or {@code null} if none.
+      * @param categories  the list of categories. May be empty if none.
+      */
+     public SampleDimension(final GenericName name, final Number background, final Collection<? extends Category> categories) {
+         ArgumentChecks.ensureNonNull("name", name);
+         ArgumentChecks.ensureNonNull("categories", categories);
+         final CategoryList list;
+         if (categories.isEmpty()) {
+             list = CategoryList.EMPTY;
+         } else {
+             list = new CategoryList(categories.toArray(new Category[categories.size()]), null);
+         }
+         this.name       = name;
+         this.background = background;
+         this.categories = list;
+         if (list.converse.range == null) {      // !hasQuantitative() inlined since we can not yet invoke that method.
+             transferFunction = null;
+             converse = null;
+         } else if (list == list.converse) {
+             transferFunction = Category.identity();
+             converse = this;
+         } else {
+             assert !list.isEmpty();             // Verified by inlined !hasQuantitative() above.
+             transferFunction = list.getTransferFunction();
+             converse = new SampleDimension(this, (background != null) ? list.search(background.doubleValue()) : null);
+         }
+     }
+ 
+     /**
+      * Returns the sample dimension that describes real values. This method establishes a unidirectional navigation
+      * from sample values to real values. This is in contrast to {@link #converse}, which establish a bidirectional
+      * navigation.
+      *
+      * @see #forConvertedValues(boolean)
+      */
+     private SampleDimension converted() {
+         // Transfer function shall never be null if 'converse' is non-null.
+         return (converse != null && !transferFunction.isIdentity()) ? converse : this;
+     }
+ 
+     /**
+      * Returns an identification for this sample dimension. This is typically used as a way to perform a band select
+      * by using human comprehensible descriptions instead of just numbers. Web Coverage Service (WCS) can use this name
+      * in order to perform band sub-setting as directed from a user request.
+      *
+      * @return an identification of this sample dimension.
+      *
+      * @see org.opengis.metadata.content.RangeDimension#getSequenceIdentifier()
+      */
+     public GenericName getName() {
+         return name;
+     }
+ 
+     /**
+      * Returns all categories in this sample dimension. Note that a {@link Category} object may apply to an arbitrary range
+      * of sample values. Consequently, the first element in this collection may not be directly related to the sample value
+      * {@code 0}.
+      *
+      * @return the list of categories in this sample dimension, or an empty list if none.
+      */
+     @SuppressWarnings("ReturnOfCollectionOrArrayField")
+     public List<Category> getCategories() {
+         return categories;                      // Safe to return because immutable.
+     }
+ 
+     /**
+      * Returns the background value. If this sample dimensions has quantitative categories, then the background
+      * value should be one of the value returned by {@link #getNoDataValues()}. However this is not mandatory.
+      *
+      * @return the background value.
+      */
+     public Optional<Number> getBackground() {
+         return Optional.ofNullable(background);
+     }
+ 
+     /**
+      * Returns {@code true} if this list contains at least one quantitative category.
+      * We use the converted range has a criterion, since it shall be null if the result
+      * of all conversions is NaN.
+      *
+      * @see Category#isQuantitative()
+      */
+     private boolean hasQuantitative() {
+         return converted().categories.range != null;
+     }
+ 
+     /**
+      * Returns the values to indicate "no data" for this sample dimension.
+      *
+      * @return the values to indicate no data values for this sample dimension, or an empty set if none.
+      * @throws IllegalStateException if this method can not expand the range of no data values, for example
+      *         because some ranges contain an infinite amount of values.
+      */
+     public Set<Number> getNoDataValues() {
+         if (hasQuantitative()) {
+             final NumberRange<?>[] ranges = new NumberRange<?>[categories.size()];
+             Class<? extends Number> widestClass = Byte.class;
+             int count = 0;
+             for (final Category category : categories) {
+                 final NumberRange<?> range = category.range;
+                 if (range != null && !category.isQuantitative()) {
+                     if (!range.isBounded()) {
+                         throw new IllegalStateException(Resources.format(Resources.Keys.CanNotEnumerateValuesInRange_1, range));
+                     }
+                     widestClass = Numbers.widestClass(widestClass, range.getElementType());
+                     ranges[count++] = range;
+                 }
+             }
+             if (count != 0) {
+                 final Set<Number> noDataValues = new TreeSet<>();
+                 for (int i=0; i<count; i++) {
+                     final NumberRange<?> range = ranges[i];
+                     final Number minimum = range.getMinValue();
+                     final Number maximum = range.getMaxValue();
+                     if (range.isMinIncluded()) noDataValues.add(Numbers.cast(minimum, widestClass));
+                     if (range.isMaxIncluded()) noDataValues.add(Numbers.cast(maximum, widestClass));
+                     if (Numbers.isInteger(range.getElementType())) {
+                         long value = minimum.longValue() + 1;       // If value was inclusive, then it has already been added to the set.
+                         long stop  = maximum.longValue() - 1;
+                         while (value <= stop) {
+                             noDataValues.add(Numbers.wrap(value, widestClass));
+                         }
+                     } else if (!minimum.equals(maximum)) {
+                         throw new IllegalStateException(Resources.format(Resources.Keys.CanNotEnumerateValuesInRange_1, range));
+                     }
+                 }
+                 return noDataValues;
+             }
+         }
+         return Collections.emptySet();
+     }
+ 
+     /**
+      * Returns the range of values occurring in this sample dimension. The range delimits sample values that
+      * can be converted into real values using the {@linkplain #getTransferFunction() transfer function}.
+      * If that function is {@linkplain MathTransform1D#isIdentity() identity}, then the values are already
+      * real values and the range may be an instance of {@link MeasurementRange}
+      * (i.e. a number range with units of measurement).
+      *
+      * @return the range of sample values in this sample dimension.
+      */
+     public Optional<NumberRange<?>> getSampleRange() {
+         return Optional.ofNullable(categories.range);
+     }
+ 
+     /**
+      * Returns the range of values after conversions by the transfer function.
+      * This range is absent if there is no transfer function.
+      *
+      * @return the range of values after conversion by the transfer function.
+      *
+      * @see #getUnits()
+      */
+     public Optional<MeasurementRange<?>> getMeasurementRange() {
+         // A ClassCastException below would be a bug in our constructors.
+         return Optional.ofNullable((MeasurementRange<?>) converted().categories.range);
+     }
+ 
+     /**
+      * Returns the <cite>transfer function</cite> from sample values to real values.
+      * This method returns a transform expecting sample values as input and computing real values as output.
+      * The output units of measurement is given by {@link #getUnits()}.
+      *
+      * <p>This transform takes care of converting all "{@linkplain #getNoDataValues() no data values}" into {@code NaN} values.
+      * The <code>transferFunction.{@linkplain MathTransform1D#inverse() inverse()}</code> transform is capable to differentiate
+      * those {@code NaN} values and get back the original sample value.</p>
+      *
+      * @return the <cite>transfer function</cite> from sample to real values. May be absent if this sample dimension
+      *         does not define any transform (which is not the same that defining an identity transform).
+      */
+     public Optional<MathTransform1D> getTransferFunction() {
+         return Optional.ofNullable(transferFunction);
+     }
+ 
+     /**
+      * Returns the scale factor and offset of the transfer function.
+      * The formula returned by this method does <strong>not</strong> take
+      * "{@linkplain #getNoDataValues() no data values}" in account.
+      * For a more generic transfer function, see {@link #getTransferFunction()}.
+      *
+      * @return a description of the part of the transfer function working on real numbers.
+      * @throws IllegalStateException if the transfer function can not be simplified in a form representable
+      *         by {@link TransferFunction}.
+      */
+     public Optional<TransferFunction> getTransferFunctionFormula() {
+         MathTransform1D tr = null;
+         for (final Category category : categories) {
+             final Optional<MathTransform1D> c = category.getTransferFunction();
+             if (c.isPresent()) {
+                 if (tr == null) {
+                     tr = c.get();
+                 } else if (!tr.equals(c.get())) {
+                     throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSimplifyTransferFunction_1));
+                 }
+             }
+         }
+         if (tr == null) {
+             return Optional.empty();
+         }
+         final TransferFunction f = new TransferFunction();
+         try {
+             f.setTransform(tr);
+         } catch (IllegalArgumentException e) {
+             throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSimplifyTransferFunction_1, e));
+         }
+         return Optional.of(f);
+     }
+ 
+     /**
+      * Returns the units of measurement for this sample dimension.
+      * This unit applies to values obtained after the {@linkplain #getTransferFunction() transfer function}.
+      * May be absent if not applicable.
+      *
+      * @return the units of measurement.
+      * @throws IllegalStateException if this sample dimension use different units.
+      *
+      * @see #getMeasurementRange()
+      */
+     public Optional<Unit<?>> getUnits() {
+         Unit<?> unit = null;
+         final SampleDimension converted = converted();
+         for (final Category category : converted.categories) {
+             final NumberRange<?> r = category.range;
+             if (r instanceof MeasurementRange<?>) {
+                 final Unit<?> c = ((MeasurementRange<?>) r).unit();
+                 if (c != null) {
+                     if (unit == null) {
+                         unit = c;
+                     } else if (!unit.equals(c)) {
+                         throw new IllegalStateException();
+                     }
+                 }
+             }
+         }
+         return Optional.ofNullable(unit);
+     }
+ 
+     /**
+      * Returns a sample dimension that describes real values or sample values, depending if {@code converted} is {@code true}
+      * or {@code false} respectively.  If there is no {@linkplain #getTransferFunction() transfer function}, then this method
+      * returns {@code this}.
+      *
+      * @param  converted  {@code true} for a sample dimension representing converted values,
+      *                    or {@code false} for a sample dimension representing sample values.
+      * @return a sample dimension representing converted or sample values, depending on {@code converted} argument value.
+      *         May be {@code this} but never {@code null}.
+      */
+     public SampleDimension forConvertedValues(final boolean converted) {
+         // Transfer function shall never be null if 'converse' is non-null.
+         if (converse != null && transferFunction.isIdentity() != converted) {
+             return converse;
+         }
+         return this;
+     }
+ 
+     /**
+      * Returns a hash value for this sample dimension.
+      */
+     @Override
+     public int hashCode() {
+         return categories.hashCode() + 31*name.hashCode();
+     }
+ 
+     /**
+      * Compares the specified object with this sample dimension for equality.
+      *
+      * @param  object  the object to compare with.
+      * @return {@code true} if the given object is equals to this sample dimension.
+      */
+     @Override
+     public boolean equals(final Object object) {
+         if (object == this) {
+             return true;
+         }
+         if (object instanceof SampleDimension) {
+             final SampleDimension that = (SampleDimension) object;
+             return name.equals(that.name) && Objects.equals(background, that.background) && categories.equals(that.categories);
+         }
+         return false;
+     }
+ 
+     /**
+      * Returns a string representation of this sample dimension.
+      * This string is for debugging purpose only and may change in future version.
+      *
+      * @return a string representation of this sample dimension for debugging purpose.
+      */
+     @Override
+     public String toString() {
+         return new SampleRangeFormat(Locale.getDefault()).write(new SampleDimension[] {this});
+     }
+ 
+     /**
+      * Returns a string representation of the given sample dimensions.
+      * This string is for debugging purpose only and may change in future version.
+      *
+      * @param  locale      the locale to use for formatting texts.
+      * @param  dimensions  the sample dimensions to format.
+      * @return a string representation of the given sample dimensions for debugging purpose.
+      */
+     @Debug
+     public static String toString(final Locale locale, SampleDimension... dimensions) {
+         ArgumentChecks.ensureNonNull("dimensions", dimensions);
+         return new SampleRangeFormat(locale).write(dimensions);
+     }
+ 
+ 
+ 
+ 
+     /**
+      * A mutable builder for creating an immutable {@link SampleDimension}.
+      * The following properties can be set:
+      *
+      * <ul>
+      *   <li>An optional name for the {@code SampleDimension}.</li>
+      *   <li>A single optional category for the background value.</li>
+      *   <li>An arbitrary amount of <cite>qualitative</cite> categories.</li>
+      *   <li>An arbitrary amount of <cite>quantitative</cite> categories.</li>
+      * </ul>
+      *
+      * A <cite>qualitative category</cite> is a range of sample values associated to a label (not numbers).
+      * For example 0 = cloud, 1 = sea, 2 = land, <i>etc</i>.
+      * A <cite>quantitative category</cite> is a range of sample values associated to numbers with units of measurement.
+      * For example 10 = 1.0°C, 11 = 1.1°C, 12 = 1.2°C, <i>etc</i>.
+      * Those three kinds of category are created by the following methods:
+      *
+      * <ul>
+      *   <li>{@link #setBackground(CharSequence, Number)}</li>
+      *   <li>{@link #addQualitative(CharSequence, NumberRange)}</li>
+      *   <li>{@link #addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}</li>
+      * </ul>
+      *
+      * All other {@code addQualitative(…)} and {@code addQuantitative(…)} methods are convenience methods delegating
+      * to above-cited methods. Qualitative and quantitative categories can be mixed in the same {@link SampleDimension},
+      * provided that their ranges do not overlap.
+      * After properties have been set, the sample dimension is created by invoking {@link #build()}.
+      *
+      * @author  Martin Desruisseaux (IRD, Geomatys)
+      * @version 1.0
+      * @since   1.0
+      * @module
+      */
+     public static class Builder {
+         /**
+          * Identification for this sample dimension.
+          */
+         private GenericName dimensionName;
+ 
+         /**
+          * The background value, or {@code null} if unspecified.
+          */
+         private Number background;
+ 
+         /**
+          * The categories for this sample dimension.
+          */
+         private final List<Category> categories;
+ 
+         /**
+          * The ordinal NaN values used for this sample dimension.
+          * The {@link Category} constructor uses this set for avoiding collisions.
+          */
+         private final ToNaN toNaN;
+ 
+         /**
+          * Creates an initially empty builder for a sample dimension.
+          * Callers shall invoke at least one {@code addFoo(…)} method before {@link #build()}.
+          */
+         public Builder() {
+             categories = new ArrayList<>();
+             toNaN      = new ToNaN();
+         }
+ 
+         /**
+          * Sets an identification of the sample dimension.
+          * This is the value to be returned by {@link SampleDimension#getName()}.
+          * If this method is invoked more than once, then the last specified name prevails
+          * (previous sample dimension names are discarded).
+          *
+          * @param  name  identification of the sample dimension.
+          * @return {@code this}, for method call chaining.
+          */
+         public Builder setName(final GenericName name) {
+             dimensionName = name;
+             return this;
+         }
+ 
+         /**
+          * Sets an identification of the sample dimension as a character sequence.
+          * This is a convenience method for creating a {@link GenericName} from the given characters.
+          *
+          * @param  name  identification of the sample dimension.
+          * @return {@code this}, for method call chaining.
+          */
+         public Builder setName(final CharSequence name) {
+             dimensionName = createLocalName(name);
+             return this;
+         }
+ 
+         /**
+          * Sets an identification of the sample dimension as a band number.
+          * This method should be used only when no more descriptive name is available.
+          *
+          * @param  band  sequence identifier of the sample dimension to create.
+          * @return {@code this}, for method call chaining.
+          */
+         public Builder setName(final int band) {
+             dimensionName = Names.createMemberName(null, null, band);
+             return this;
+         }
+ 
+         /**
+          * A common place where are created local names from character string.
+          * For making easier to revisit if we want to add a namespace.
+          */
+         private static GenericName createLocalName(final CharSequence name) {
+             return Names.createLocalName(null, null, name);
+         }
+ 
+         /**
+          * Creates a range for the given minimum and maximum values. We use the static factory methods instead
+          * than the {@link NumberRange} constructor for sharing existing range instances. This is also a way
+          * to ensure that the number type is one of the primitive wrappers.
+          *
+          * <p>This method is invoked for qualitative categories only. For that reason, it accepts NaN values.</p>
+          */
+         private static NumberRange<?> range(final Class<?> type, Number minimum, Number maximum) {
+             switch (Numbers.getEnumConstant(type)) {
+                 case Numbers.BYTE:    return NumberRange.create(minimum.byteValue(),  true, maximum.byteValue(),   true);
+                 case Numbers.SHORT:   return NumberRange.create(minimum.shortValue(), true, maximum.shortValue(),  true);
+                 case Numbers.INTEGER: return NumberRange.create(minimum.intValue(),   true, maximum.intValue(),    true);
+                 case Numbers.LONG:    return NumberRange.create(minimum.longValue(),  true, maximum.longValue(),   true);
+                 case Numbers.FLOAT: {
+                     final float min = minimum.floatValue();
+                     final float max = maximum.floatValue();
+                     if (!Float.isNaN(min) || !Float.isNaN(max)) {       // Let 'create' throws an exception if only one value is NaN.
+                         return NumberRange.create(min, true, max, true);
+                     }
+                     if (minimum.getClass() != Float.class) minimum = min;
+                     if (maximum.getClass() != Float.class) maximum = max;
+                     break;
+                 }
+                 default: {
+                     final double min = minimum.doubleValue();
+                     final double max = maximum.doubleValue();
+                     if (!Double.isNaN(min) || !Double.isNaN(max)) {     // Let 'create' throws an exception if only one value is NaN.
+                         return NumberRange.create(min, true, max, true);
+                     }
+                     if (minimum.getClass() != Double.class) minimum = min;
+                     if (maximum.getClass() != Double.class) maximum = max;
+                     break;
+                 }
+             }
+             @SuppressWarnings({"unchecked", "rawtypes"})
+             final NumberRange<?> samples = new NumberRange(type, minimum, true, maximum, true);
+             return samples;
+         }
+ 
+         /**
+          * Adds a qualitative category and marks that category as the background value.
+          * This is the value to be returned by {@link SampleDimension#getBackground()}.
+          * If this method is invoked more than once, then the last specified value prevails
+          * (previous values become ordinary qualitative categories).
+          *
+          * @param  name    the category name as a {@link String} or {@link InternationalString} object,
+          *                 or {@code null} for a default "fill value" name.
+          * @param  sample  the background value.
+          * @return {@code this}, for method call chaining.
+          */
+         public Builder setBackground(CharSequence name, Number sample) {
+             ArgumentChecks.ensureNonNull("sample", sample);
+             if (name == null) {
+                 name = Vocabulary.formatInternational(Vocabulary.Keys.FillValue);
+             }
+             final NumberRange<?> samples = range(sample.getClass(), sample, sample);
+             background = samples.getMinValue();
+             toNaN.background = background.doubleValue();
+             categories.add(new Category(name, samples, null, null, toNaN));
+             return this;
+         }
+ 
+         /**
+          * Adds a qualitative category for samples of the given boolean value.
+          * The {@code true} value is represented by 1 and the {@code false} value is represented by 0.
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQualitative(CharSequence, NumberRange)}.</div>
+          *
+          * @param  name    the category name as a {@link String} or {@link InternationalString} object,
+          *                 or {@code null} for a default "no data" name.
+          * @param  sample  the sample value as a boolean.
+          * @return {@code this}, for method call chaining.
+          */
+         public Builder addQualitative(final CharSequence name, final boolean sample) {
+             final byte value = sample ? (byte) 1 : 0;
+             return addQualitative(name, NumberRange.create(value, true, value, true));
+         }
+ 
+         /**
+          * Adds a qualitative category for samples of the given tiny (8 bits) integer value.
+          * The argument is treated as a signed integer ({@value Byte#MIN_VALUE} to {@value Byte#MAX_VALUE}).
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQualitative(CharSequence, NumberRange)}.</div>
+          *
+          * @param  name    the category name as a {@link String} or {@link InternationalString} object,
+          *                 or {@code null} for a default "no data" name.
+          * @param  sample  the sample value as an integer.
+          * @return {@code this}, for method call chaining.
+          */
+         public Builder addQualitative(final CharSequence name, final byte sample) {
+             return addQualitative(name, NumberRange.create(sample, true, sample, true));
+         }
+ 
+         /**
+          * Adds a qualitative category for samples of the given short (16 bits) integer value.
+          * The argument is treated as a signed integer ({@value Short#MIN_VALUE} to {@value Short#MAX_VALUE}).
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQualitative(CharSequence, NumberRange)}.</div>
+          *
+          * @param  name    the category name as a {@link String} or {@link InternationalString} object,
+          *                 or {@code null} for a default "no data" name.
+          * @param  sample  the sample value as an integer.
+          * @return {@code this}, for method call chaining.
+          */
+         public Builder addQualitative(final CharSequence name, final short sample) {
+             return addQualitative(name, NumberRange.create(sample, true, sample, true));
+         }
+ 
+         /**
+          * Adds a qualitative category for samples of the given integer value.
+          * The argument is treated as a signed integer ({@value Integer#MIN_VALUE} to {@value Integer#MAX_VALUE}).
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQualitative(CharSequence, NumberRange)}.</div>
+          *
+          * @param  name    the category name as a {@link String} or {@link InternationalString} object,
+          *                 or {@code null} for a default "no data" name.
+          * @param  sample  the sample value as an integer.
+          * @return {@code this}, for method call chaining.
+          */
+         public Builder addQualitative(final CharSequence name, final int sample) {
+             return addQualitative(name, NumberRange.create(sample, true, sample, true));
+         }
+ 
+         /**
+          * Adds a qualitative category for samples of the given floating-point value.
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQualitative(CharSequence, NumberRange)}.</div>
+          *
+          * @param  name    the category name as a {@link String} or {@link InternationalString} object,
+          *                 or {@code null} for a default "no data" name.
+          * @param  sample  the sample value as a real number.
+          * @return {@code this}, for method call chaining.
+          */
+         public Builder addQualitative(final CharSequence name, final float sample) {
+             return addQualitative(name, NumberRange.create(sample, true, sample, true));
+         }
+ 
+         /**
+          * Adds a qualitative category for samples of the given double precision floating-point value.
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQualitative(CharSequence, NumberRange)}.</div>
+          *
+          * @param  name    the category name as a {@link String} or {@link InternationalString} object,
+          *                 or {@code null} for a default "no data" name.
+          * @param  sample  the sample value as a real number.
+          * @return {@code this}, for method call chaining.
+          */
+         public Builder addQualitative(final CharSequence name, final double sample) {
+             return addQualitative(name, NumberRange.create(sample, true, sample, true));
+         }
+ 
+         /**
+          * Adds a qualitative category for samples in the given range of values.
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQualitative(CharSequence, NumberRange)}.</div>
+          *
+          * @param  name     the category name as a {@link String} or {@link InternationalString} object,
+          *                  or {@code null} for a default "no data" name.
+          * @param  minimum  the minimum sample value, inclusive.
+          * @param  maximum  the maximum sample value, inclusive.
+          * @return {@code this}, for method call chaining.
+          * @throws IllegalArgumentException if the range is empty.
+          */
+         public Builder addQualitative(final CharSequence name, final Number minimum, final Number maximum) {
+             return addQualitative(name, range(Numbers.widestClass(minimum, maximum), minimum, maximum));
+         }
+ 
+         /**
+          * Adds a qualitative category for all samples in the specified range of values.
+          * This is the most generic method for adding a qualitative category.
+          * All other {@code addQualitative(name, …)} methods are convenience methods delegating their work to this method.
+          *
+          * @param  name     the category name as a {@link String} or {@link InternationalString} object,
+          *                  or {@code null} for a default "no data" name.
+          * @param  samples  the minimum and maximum sample values in the category.
+          * @return {@code this}, for method call chaining.
+          * @throws IllegalArgumentException if the given range is empty.
+          */
+         public Builder addQualitative(CharSequence name, final NumberRange<?> samples) {
+             if (name == null) {
+                 name = Vocabulary.formatInternational(Vocabulary.Keys.Nodata);
+             }
+             categories.add(new Category(name, samples, null, null, toNaN));
+             return this;
+         }
+ 
+         /**
+          * Constructs a quantitative category mapping samples to real values in the specified range.
+          * Sample values in the {@code samples} range will be mapped to real values in the {@code converted} range
+          * through a linear equation of the form:
+          *
+          * <blockquote><var>measure</var> = <var>sample</var> × <var>scale</var> + <var>offset</var></blockquote>
+          *
+          * where <var>scale</var> and <var>offset</var> coefficients are computed from the ranges supplied in arguments.
+          * The units of measurement will be taken from the {@code converted} range if it is an instance of {@link MeasurementRange}.
+          *
+          * <p><b>Warning:</b> this method is provided for convenience when the scale and offset factors are not explicitly specified.
+          * If those factor are available, then the other {@code addQuantitative(name, samples, …)} methods are more reliable.</p>
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.</div>
+          *
+          * @param  name        the category name as a {@link String} or {@link InternationalString} object.
+          * @param  samples     the minimum and maximum sample values in the category. Element class is usually
+          *                     {@link Integer}, but {@link Float} and {@link Double} values are accepted as well.
+          * @param  converted   the range of real values for this category, as an instance of {@link MeasurementRange}
+          *                     if those values are associated to an unit of measurement.
+          * @return {@code this}, for method call chaining.
+          * @throws ClassCastException if the range element class is not a {@link Number} subclass.
+          * @throws IllegalArgumentException if the range is invalid.
+          */
+         public Builder addQuantitative(final CharSequence name, final NumberRange<?> samples, final NumberRange<?> converted) {
+             ArgumentChecks.ensureNonNull("samples", samples);
+             ArgumentChecks.ensureNonNull("converted", converted);
+             /*
+              * We need to perform calculation using the same "included versus excluded" characteristics for sample and converted
+              * values. We pickup the characteristics of the range using floating point values because it is easier to adjust the
+              * bounds of the range using integer values (we just add or subtract 1 for integers, while the amount to add to real
+              * numbers is not so clear). If both ranges use floating point values, arbitrarily adjust the converted values.
+              */
+             final boolean isMinIncluded, isMaxIncluded;
+             if (Numbers.isInteger(samples.getElementType())) {
+                 isMinIncluded = converted.isMinIncluded();                         // This is the usual case.
+                 isMaxIncluded = converted.isMaxIncluded();
+             } else {
+                 isMinIncluded = samples.isMinIncluded();                            // Less common case.
+                 isMaxIncluded = samples.isMaxIncluded();
+             }
+             final double minValue  = converted.getMinDouble(isMinIncluded);
+             final double Δvalue    = converted.getMaxDouble(isMaxIncluded) - minValue;
+             final double minSample =   samples.getMinDouble(isMinIncluded);
+             final double Δsample   =   samples.getMaxDouble(isMaxIncluded) - minSample;
+             final double scale     = Δvalue / Δsample;
+             final TransferFunction transferFunction = new TransferFunction();
+             transferFunction.setScale(scale);
+             transferFunction.setOffset(minValue - scale * minSample);               // TODO: use Math.fma with JDK9.
+             return addQuantitative(name, samples, transferFunction.getTransform(),
+                     (converted instanceof MeasurementRange<?>) ? ((MeasurementRange<?>) converted).unit() : null);
+         }
+ 
+         /**
+          * Adds a quantitative category for values ranging from {@code minimum} to {@code maximum} inclusive
+          * in the given units of measurement. The transfer function is set to identity.
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.</div>
+          *
+          * @param  name     the category name as a {@link String} or {@link InternationalString} object.
+          * @param  minimum  the minimum value (inclusive) in the given units.
+          * @param  maximum  the maximum value (inclusive) in the given units.
+          * @param  units    the units of measurement.
+          * @return {@code this}, for method call chaining.
+          * @throws IllegalArgumentException if a value is NaN or if {@code minimum} is greater than {@code maximum}.
+          */
+         public Builder addQuantitative(CharSequence name, float minimum, float maximum, Unit<?> units) {
+             return addQuantitative(name, MeasurementRange.create(minimum, true, maximum, true, units), Category.identity(), units);
+         }
+ 
+         /**
+          * Adds a quantitative category for values ranging from {@code minimum} to {@code maximum} inclusive
+          * in the given units of measurement. The transfer function is set to identity.
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.</div>
+          *
+          * @param  name     the category name as a {@link String} or {@link InternationalString} object.
+          * @param  minimum  the minimum value (inclusive) in the given units.
+          * @param  maximum  the maximum value (inclusive) in the given units.
+          * @param  units    the units of measurement.
+          * @return {@code this}, for method call chaining.
+          * @throws IllegalArgumentException if a value is NaN or if {@code minimum} is greater than {@code maximum}.
+          */
+         public Builder addQuantitative(CharSequence name, double minimum, double maximum, Unit<?> units) {
+             return addQuantitative(name, MeasurementRange.create(minimum, true, maximum, true, units), Category.identity(), units);
+         }
+ 
+         /**
+          * Adds a quantitative category for sample values ranging from {@code lower} inclusive to {@code upper} exclusive.
+          * Sample values are converted into real values using the following linear equation:
+          *
+          * <blockquote><var>measure</var> = <var>sample</var> × <var>scale</var> + <var>offset</var></blockquote>
+          *
+          * Results of above conversion are measurements in the units specified by the {@code units} argument.
+          *
+          * <div class="note"><b>Implementation note:</b>
+          * this convenience method delegates to {@link #addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}.</div>
+          *
+          * @param  name    the category name as a {@link String} or {@link InternationalString} object.
+          * @param  lower   the lower sample value, inclusive.
+          * @param  upper   the upper sample value, exclusive.
+          * @param  scale   the scale value which is multiplied to sample values for the category. Must be different than zero.
+          * @param  offset  the offset value to add to sample values for this category.
+          * @param  units   the units of measurement of values after conversion by the scale factor and offset.
+          * @return {@code this}, for method call chaining.
+          * @throws IllegalArgumentException if {@code lower} is not smaller than {@code upper},
+          *         or if {@code scale} or {@code offset} are not real numbers, or if {@code scale} is zero.
+          */
+         public Builder addQuantitative(CharSequence name, int lower, int upper, double scale, double offset, Unit<?> units) {
+             final TransferFunction transferFunction = new TransferFunction();
+             transferFunction.setScale(scale);
+             transferFunction.setOffset(offset);
+             return addQuantitative(name, NumberRange.create(lower, true, upper, false), transferFunction.getTransform(), units);
+         }
+ 
+         /**
+          * Constructs a quantitative category for all samples in the specified range of values.
+          * Sample values (usually integers) will be converted into real values
+          * (usually floating-point numbers) through the {@code toUnits} transform.
+          * Results of that conversion are measurements in the units specified by the {@code units} argument.
+          *
+          * <p>This is the most generic method for adding a quantitative category.
+          * All other {@code addQuantitative(name, …)} methods are convenience methods delegating their work to this method.</p>
+          *
+          * @param  name     the category name as a {@link String} or {@link InternationalString} object.
+          * @param  samples  the minimum and maximum sample values in the category. Element class is usually
+          *                  {@link Integer}, but {@link Float} and {@link Double} types are accepted as well.
+          * @param  toUnits  the transfer function from sample values to real values in the specified units.
+          * @param  units    the units of measurement of values after conversion by the transfer function.
+          * @return {@code this}, for method call chaining.
+          * @throws ClassCastException if the range element class is not a {@link Number} subclass.
+          * @throws IllegalArgumentException if the range is invalid.
+          *
+          * @see TransferFunction
+          */
+         public Builder addQuantitative(CharSequence name, NumberRange<?> samples, MathTransform1D toUnits, Unit<?> units) {
+             ArgumentChecks.ensureNonNull("toUnits", toUnits);
+             categories.add(new Category(name, samples, toUnits, units, toNaN));
+             return this;
+         }
+ 
+         /**
+          * Returns {@code true} if the given range intersects the range of a previously added category.
+          * This method can be invoked before to add a new category for checking if it would cause a range collision.
+          *
+          * @param  minimum  minimal value of the range to test, inclusive.
+          * @param  maximum  maximal value of the range to test, inclusive.
+          * @return whether the given range intersects at least one previously added range.
+          */
+         public boolean rangeCollides(final double minimum, final double maximum) {
+             for (final Category category : categories) {
+                 if (maximum >= category.minimum && minimum <= category.maximum) {
+                     return true;
+                 }
+             }
+             return false;
+         }
+ 
+         /**
+          * Creates a new sample with the properties defined to this builder.
+          *
+          * @return the sample dimension.
+          */
+         public SampleDimension build() {
+             GenericName name = dimensionName;
+ defName:    if (name == null) {
+                 for (final Category category : categories) {
+                     if (category.isQuantitative()) {
+                         name = createLocalName(category.name);
+                         break defName;
+                     }
+                 }
+                 name = createLocalName(Vocabulary.formatInternational(Vocabulary.Keys.Untitled));
+             }
+             return new SampleDimension(name, background, categories);
+         }
+ 
+         /**
+          * Reset this builder to the same state than after construction.
+          * The sample dimension name, background values and all categories are discarded.
+          * This method can be invoked when the same builder is reused for creating more than one sample dimension.
+          */
+         public void clear() {
+             dimensionName = null;
+             background    = null;
+             categories.clear();
+             toNaN.clear();
+         }
+     }
+ }
diff --cc core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridCoverage.java
index 0000000,aebef90..fc2c60a
mode 000000,100644..100644
--- 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
@@@ -1,0 -1,189 +1,185 @@@
+ /*
+  * 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.List;
+ import java.util.Collection;
+ import java.util.Locale;
+ import java.awt.image.RenderedImage;
+ import org.opengis.geometry.DirectPosition;
 -import org.opengis.coverage.CannotEvaluateException;
 -import org.opengis.coverage.PointOutsideCoverageException;
+ import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ import org.apache.sis.internal.util.UnmodifiableArrayList;
+ import org.apache.sis.coverage.SampleDimension;
+ import org.apache.sis.util.collection.DefaultTreeTable;
+ import org.apache.sis.util.collection.TableColumn;
+ import org.apache.sis.util.collection.TreeTable;
+ import org.apache.sis.util.resources.Vocabulary;
+ import org.apache.sis.util.ArgumentChecks;
+ import org.apache.sis.util.Classes;
+ import org.apache.sis.util.Debug;
+ 
+ 
+ /**
+  * Base class of coverages with domains defined as a set of grid points.
+  * The essential property of coverage is to be able to generate a value for any point within its domain.
+  * Since a grid coverage is represented by a grid of values, the value returned by the coverage for a point
+  * is that of the grid value whose location is nearest the point.
+  *
+  * @author  Martin Desruisseaux (IRD, Geomatys)
+  * @version 1.0
+  * @since   1.0
+  * @module
+  */
+ public abstract class GridCoverage {
+     /**
+      * The grid extent, coordinate reference system (CRS) and conversion from cell indices to CRS.
+      */
+     private final GridGeometry gridGeometry;
+ 
+     /**
+      * List of sample dimension (band) information for the grid coverage. Information include such things
+      * as description, the no data values, minimum and maximum values, <i>etc</i>. A coverage must have
+      * at least one sample dimension. The content of this array shall never be modified.
+      */
+     private final SampleDimension[] sampleDimensions;
+ 
+     /**
+      * Constructs a grid coverage using the specified grid geometry and sample dimensions.
+      *
+      * @param grid   the grid extent, CRS and conversion from cell indices to CRS.
+      * @param bands  sample dimensions for each image band.
+      */
+     protected GridCoverage(final GridGeometry grid, final Collection<? extends SampleDimension> bands) {
+         ArgumentChecks.ensureNonNull("grid",  grid);
+         ArgumentChecks.ensureNonNull("bands", bands);
+         gridGeometry = grid;
+         sampleDimensions = bands.toArray(new SampleDimension[bands.size()]);
+         for (int i=0; i<sampleDimensions.length; i++) {
+             ArgumentChecks.ensureNonNullElement("bands", i, sampleDimensions[i]);
+         }
+     }
+ 
+     /**
+      * Returns the coordinate reference system to which the values in grid domain are referenced.
+      * This is the CRS used when accessing a coverage with the {@code evaluate(…)} methods.
+      * This coordinate reference system is usually different than the coordinate system of the grid.
+      * It is the target coordinate reference system of the {@link GridGeometry#getGridToCRS gridToCRS}
+      * math transform.
+      *
+      * <p>The default implementation delegates to {@link GridGeometry#getCoordinateReferenceSystem()}.</p>
+      *
+      * @return the CRS used when accessing a coverage with the {@code evaluate(…)} methods.
+      * @throws IncompleteGridGeometryException if the grid geometry has no CRS.
+      */
+     public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+         return gridGeometry.getCoordinateReferenceSystem();
+     }
+ 
+     /**
+      * Returns information about the <cite>domain</cite> of this grid coverage.
+      * Information includes the grid extent, CRS and conversion from cell indices to CRS.
+      * {@code GridGeometry} can also provide derived information like bounding box and resolution.
+      *
+      * @return grid extent, CRS and conversion from cell indices to CRS.
+      */
+     public GridGeometry getGridGeometry() {
+         return gridGeometry;
+     }
+ 
+     /**
+      * Returns information about the <cite>range</cite> of this grid coverage.
+      * Information include names, sample value ranges, fill values and transfer functions for all bands in this grid coverage.
+      *
+      * @return names, value ranges, fill values and transfer functions for all bands in this grid coverage.
+      */
+     public List<SampleDimension> getSampleDimensions() {
+         return UnmodifiableArrayList.wrap(sampleDimensions);
+     }
+ 
+     /**
+      * Returns a two-dimensional slice of grid data as a rendered image. The given {@code slicePoint} 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:
+      *
+      * <ul>
+      *   <li>The {@code slicePoint} has a CRS with two dimensions less than this grid coverage CRS.</li>
+      *   <li>The {@code slicePoint} has the same CRS than this grid coverage, but the two coordinates to
+      *       exclude are set to {@link Double#NaN}.</li>
+      * </ul>
+      *
+      * 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.
+      *
+      * <p>Implementations should return a view as much as possible, without copying sample values.</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.
+      * @return the grid slice as a rendered image.
 -     * @throws PointOutsideCoverageException if the given slice point is illegal.
 -     * @throws CannotEvaluateException if this method can not produce the render image for another reason.
+      */
 -    public abstract RenderedImage render(DirectPosition slicePoint) throws CannotEvaluateException;
++    public abstract RenderedImage render(DirectPosition slicePoint);
+ 
+     /**
+      * Returns a string representation of this grid coverage for debugging purpose.
+      * The returned string is implementation dependent and may change in any future version.
+      * Current implementation is equivalent to the following, where {@code EXTENT}, <i>etc.</i> are
+      * constants defined in {@link GridGeometry} class:
+      *
+      * {@preformat java
+      *   return toTree(Locale.getDefault(), EXTENT | ENVELOPE | CRS | GRID_TO_CRS | RESOLUTION).toString();
+      * }
+      *
+      * @return a string representation of this grid coverage for debugging purpose.
+      */
+     @Override
+     public String toString() {
+         return toTree(Locale.getDefault(), GridGeometry.EXTENT | GridGeometry.ENVELOPE
+                 | GridGeometry.CRS | GridGeometry.GRID_TO_CRS | GridGeometry.RESOLUTION).toString();
+     }
+ 
+     /**
+      * Returns a tree representation of some elements of this grid coverage.
+      * 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.
+      * @param  bitmask  combination of {@link GridGeometry} flags.
+      * @return a tree representation of the specified elements.
+      *
+      * @see GridGeometry#toTree(Locale, int)
+      */
+     @Debug
+     public TreeTable toTree(final Locale locale, final int bitmask) {
+         ArgumentChecks.ensureNonNull("locale", locale);
+         final Vocabulary vocabulary = Vocabulary.getResources(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));
+         TreeTable.Node branch = root.newChild();
+         branch.setValue(column, vocabulary.getString(Vocabulary.Keys.CoverageDomain));
+         gridGeometry.formatTo(locale, vocabulary, bitmask, branch);
+         branch = root.newChild();
+         branch.setValue(column, vocabulary.getString(Vocabulary.Keys.SampleDimensions));
+         branch.newChild().setValue(column, SampleDimension.toString(locale, sampleDimensions));
+         return tree;
+     }
+ }
diff --cc core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index bdb1491,0e17166..1a4e390
--- 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
@@@ -356,17 -478,35 +475,17 @@@ public class GridExtent implements Seri
       * @param  extent  the grid envelope to copy.
       * @throws IllegalArgumentException if a coordinate value in the low part is
       *         greater than the corresponding coordinate value in the high part.
 -     *
 -     * @see #castOrCopy(GridEnvelope)
       */
 -    protected GridExtent(final GridEnvelope extent) {
 +    protected GridExtent(final GridExtent extent) {
          ArgumentChecks.ensureNonNull("extent", extent);
          final int dimension = extent.getDimension();
-         ordinates = allocate(dimension);
+         coordinates = allocate(dimension);
          for (int i=0; i<dimension; i++) {
-             ordinates[i] = extent.getLow(i);
-             ordinates[i + dimension] = extent.getHigh(i);
+             coordinates[i] = extent.getLow(i);
+             coordinates[i + dimension] = extent.getHigh(i);
          }
-         checkCoherence(ordinates);
+         checkCoherence(coordinates);
 -        types = (extent instanceof GridExtent) ? ((GridExtent) extent).types : null;
 -    }
 -
 -    /**
 -     * Returns the given grid envelope as a {@code GridExtent} implementation.
 -     * If the given extent is already a {@code GridExtent} instance or is null, then it is returned as-is.
 -     * Otherwise a new extent is created using the {@linkplain #GridExtent(GridEnvelope) copy constructor}.
 -     *
 -     * @param  extent  the grid envelope to cast or copy, or {@code null}.
 -     * @return the grid envelope as a {@code GridExtent}, or {@code null} if the given extent was null.
 -     */
 -    public static GridExtent castOrCopy(final GridEnvelope extent) {
 -        if (extent == null || extent instanceof GridExtent) {
 -            return (GridExtent) extent;
 -        } else {
 -            return new GridExtent(extent);
 -        }
 +        types = extent.types;
      }
  
      /**
diff --cc core/sis-raster/src/test/java/org/apache/sis/image/DefaultIteratorTest.java
index 15f7c84,8c4832f..da83542
--- a/core/sis-raster/src/test/java/org/apache/sis/image/DefaultIteratorTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/image/DefaultIteratorTest.java
@@@ -25,6 -25,8 +25,7 @@@ import java.awt.image.Raster
  import java.awt.image.WritableRaster;
  import java.awt.image.WritableRenderedImage;
  import java.nio.FloatBuffer;
 -import org.opengis.coverage.grid.SequenceType;
+ import org.apache.sis.util.ArraysExt;
  import org.apache.sis.test.DependsOnMethod;
  import org.apache.sis.test.TestCase;
  import org.junit.After;
diff --cc core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
index 5c2754b,deb5dd9..8bf8273
--- a/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
+++ b/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
@@@ -350,14 -351,9 +350,14 @@@ public class ModifiableLocationType ext
  
      /**
       * Returns the name of organization or class of organization able to create and destroy location instances.
-      * If no organization has been explicitely set, then this method inherits the value from
+      * If no organization has been explicitly set, then this method inherits the value from
       * the parents providing that all parents specify the same organization.
       *
 +     * <div class="warning"><b>Upcoming API change — generalization</b><br>
 +     * in a future SIS version, the type of returned element may be generalized to the
 +     * {@code org.opengis.metadata.citation.Party} interface. This change is pending
 +     * GeoAPI revision for upgrade from ISO 19115:2003 to ISO 19115:2014.</div>
 +     *
       * @return organization or class of organization able to create and destroy location instances,
       *         or {@code null} if no value has been defined or can be inherited.
       *
diff --cc core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java
index 0000000,10144fc..0e4b1ae
mode 000000,100644..100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingFactoryContainer.java
@@@ -1,0 -1,213 +1,215 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.internal.referencing;
+ 
+ import org.opengis.util.FactoryException;
+ import org.opengis.referencing.cs.CSFactory;
+ import org.opengis.referencing.cs.CSAuthorityFactory;
+ import org.opengis.referencing.crs.CRSFactory;
+ import org.opengis.referencing.crs.CRSAuthorityFactory;
+ import org.opengis.referencing.datum.DatumFactory;
+ import org.opengis.referencing.datum.DatumAuthorityFactory;
+ import org.opengis.referencing.operation.MathTransformFactory;
 -import org.opengis.referencing.operation.CoordinateOperationFactory;
+ import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
+ import org.apache.sis.referencing.factory.NoSuchAuthorityFactoryException;
+ import org.apache.sis.internal.system.DefaultFactories;
+ import org.apache.sis.internal.util.Constants;
+ import org.apache.sis.referencing.CRS;
+ 
++// Branch-dependent imports
++import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
++
+ 
+ /**
+  * A container of factories frequently used together.
+  * This class may be temporary until we choose a dependency injection framework
+  * See <a href="https://issues.apache.org/jira/browse/SIS-102">SIS-102</a>.
+  *
+  * <p>This class is not thread safe. Synchronization, if needed, is caller's responsibility.</p>
+  *
+  * @author  Martin Desruisseaux (IRD, Geomatys)
+  * @version 1.0
+  *
+  * @see <a href="https://issues.apache.org/jira/browse/SIS-102">SIS-102</a>
+  *
+  * @since 1.0
+  * @module
+  */
+ public class ReferencingFactoryContainer {
+     /**
+      * The factory for creating coordinate reference systems from authority codes.
+      * If null, then a default factory will be created only when first needed.
+      */
+     private CRSAuthorityFactory crsAuthorityFactory;
+ 
+     /**
+      * The {@linkplain org.opengis.referencing.datum.Datum datum} factory.
+      * If null, then a default factory will be created only when first needed.
+      */
+     private DatumFactory datumFactory;
+ 
+     /**
+      * The {@linkplain org.opengis.referencing.cs.CoordinateSystem coordinate system} factory.
+      * If null, then a default factory will be created only when first needed.
+      */
+     private CSFactory csFactory;
+ 
+     /**
+      * The {@linkplain org.opengis.referencing.crs.CoordinateReferenceSystem coordinate reference system} factory.
+      * If null, then a default factory will be created only when first needed.
+      */
+     private CRSFactory crsFactory;
+ 
+     /**
+      * Factory for fetching operation methods and creating defining conversions.
+      * This is needed only for user-defined projected coordinate reference system.
+      */
 -    private CoordinateOperationFactory operationFactory;
++    private DefaultCoordinateOperationFactory operationFactory;
+ 
+     /**
+      * The {@linkplain org.opengis.referencing.operation.MathTransform math transform} factory.
+      * If null, then a default factory will be created only when first needed.
+      */
+     private MathTransformFactory mtFactory;
+ 
+     /**
+      * Creates a new instance for the default factories.
+      */
+     public ReferencingFactoryContainer() {
+     }
+ 
+     /**
+      * Returns the factory for creating coordinate reference systems from authority codes.
+      * Currently only EPSG codes are supported.
+      *
+      * @return the Coordinate Reference System authority factory (never {@code null}).
+      * @throws FactoryException if the authority factory can not be obtained.
+      */
+     public final CRSAuthorityFactory getCRSAuthorityFactory() throws FactoryException {
+         if (crsAuthorityFactory == null) {
+             crsAuthorityFactory = CRS.getAuthorityFactory(Constants.EPSG);
+         }
+         return crsAuthorityFactory;
+     }
+ 
+     /**
+      * Returns the factory for creating coordinate systems from authority codes.
+      * Currently only EPSG codes are supported.
+      *
+      * @return the Coordinate System authority factory (never {@code null}).
+      * @throws FactoryException if the authority factory can not be obtained.
+      */
+     public final CSAuthorityFactory getCSAuthorityFactory() throws FactoryException {
+         final CRSAuthorityFactory factory = getCRSAuthorityFactory();
+         if (factory instanceof CSAuthorityFactory) {                    // This is the case for SIS implementation.
+             return (CSAuthorityFactory) factory;
+         }
+         throw new NoSuchAuthorityFactoryException(null, Constants.EPSG);
+     }
+ 
+     /**
+      * Returns the factory for creating datum from authority codes.
+      * Currently only EPSG codes are supported.
+      *
+      * @return the Datum authority factory (never {@code null}).
+      * @throws FactoryException if the authority factory can not be obtained.
+      */
+     public final DatumAuthorityFactory getDatumAuthorityFactory() throws FactoryException {
+         final CRSAuthorityFactory factory = getCRSAuthorityFactory();
+         if (factory instanceof DatumAuthorityFactory) {                 // This is the case for SIS implementation.
+             return (DatumAuthorityFactory) factory;
+         }
+         throw new NoSuchAuthorityFactoryException(null, Constants.EPSG);
+     }
+ 
+     /**
+      * Returns the factory for creating coordinate operations from authority codes.
+      * Currently only EPSG codes are supported.
+      *
+      * @return the Coordinate Operation authority factory (never {@code null}).
+      * @throws FactoryException if the authority factory can not be obtained.
+      */
+     public final CoordinateOperationAuthorityFactory getCoordinateOperationAuthorityFactory() throws FactoryException {
+         final CRSAuthorityFactory factory = getCRSAuthorityFactory();
+         if (factory instanceof CoordinateOperationAuthorityFactory) {       // This is the case for SIS implementation.
+             return (CoordinateOperationAuthorityFactory) factory;
+         }
+         throw new NoSuchAuthorityFactoryException(null, Constants.EPSG);
+     }
+ 
+     /**
+      * Returns the factory for creating coordinate reference systems.
+      *
+      * @return the Coordinate Reference System factory (never {@code null}).
+      */
+     public final CRSFactory getCRSFactory() {
+         if (crsFactory == null) {
+             crsFactory = DefaultFactories.forBuildin(CRSFactory.class);
+         }
+         return crsFactory;
+     }
+ 
+     /**
+      * Returns the factory for creating coordinate systems and their axes.
+      *
+      * @return the Coordinate System factory (never {@code null}).
+      */
+     public final CSFactory getCSFactory() {
+         if (csFactory == null) {
+             csFactory = DefaultFactories.forBuildin(CSFactory.class);
+         }
+         return csFactory;
+     }
+ 
+     /**
+      * Returns the factory for creating datum, prime meridians and ellipsoids.
+      *
+      * @return the Datum factory (never {@code null}).
+      */
+     public final DatumFactory getDatumFactory() {
+         if (datumFactory == null) {
+             datumFactory = DefaultFactories.forBuildin(DatumFactory.class);
+         }
+         return datumFactory;
+     }
+ 
+     /**
+      * Returns the factory for fetching operation methods and creating defining conversions.
+      * This is needed only for user-defined projected coordinate reference system.
+      * The factory is fetched when first needed.
+      *
+      * @return the Coordinate Operation factory (never {@code null}).
+      */
 -    public final CoordinateOperationFactory getCoordinateOperationFactory() {
++    public final DefaultCoordinateOperationFactory getCoordinateOperationFactory() {
+         if (operationFactory == null) {
+             operationFactory = CoordinateOperations.factory();
+         }
+         return operationFactory;
+     }
+ 
+     /**
+      * Returns the factory for creating parameterized transforms.
+      *
+      * @return the Math Transform factory (never {@code null}).
+      */
+     public final MathTransformFactory getMathTransformFactory() {
+         if (mtFactory == null) {
+             mtFactory = DefaultFactories.forBuildin(MathTransformFactory.class);
+         }
+         return mtFactory;
+     }
+ }
diff --cc core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
index 0880923,3632436..a8c6208
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
@@@ -19,7 -19,8 +19,8 @@@ package org.apache.sis.referencing.oper
  import org.apache.sis.test.DependsOn;
  import org.junit.Test;
  
- import static org.junit.Assert.*;
+ import static java.lang.Double.NaN;
 -import static org.opengis.test.Assert.*;
++import static org.apache.sis.test.Assert.*;
  import static org.apache.sis.referencing.operation.matrix.Matrix4.SIZE;
  
  
diff --cc core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
index 9d8a33c,abebea9..9b2cb66
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
@@@ -114,10 -118,11 +114,10 @@@ public final strictfp class AbridgedMol
          verifyInDomain(CoordinateDomain.GEOGRAPHIC, -1941624852762631518L);
          /*
           * Calculation of "expected" transform derivative by finite difference
-          * seems too approximative for the default accuracy. (Actually, we are
-          * not completely sure that there is no bug in derivative formula...)
+          * does not seem accurate enough for the default accuracy. (Actually,
+          * we are not completely sure that there is no bug in derivative formula).
           */
          tolerance *= 10;
 -        isDerivativeSupported = true;
          verifyInDomain(CoordinateDomain.GEOGRAPHIC, 4350796528249596132L);
      }
  
diff --cc core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
index 98e17da,dc2be7b..c94414f
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
@@@ -18,16 -18,21 +18,18 @@@ package org.apache.sis.referencing.oper
  
  import java.util.Random;
  import java.io.IOException;
+ import java.io.UncheckedIOException;
 -import org.opengis.util.Factory;
  import org.opengis.referencing.operation.MathTransform;
  import org.opengis.referencing.operation.MathTransform1D;
  import org.opengis.referencing.operation.MathTransform2D;
  import org.opengis.referencing.operation.TransformException;
  import org.opengis.parameter.ParameterDescriptorGroup;
  import org.opengis.parameter.ParameterValueGroup;
 -import org.opengis.geometry.DirectPosition;
  import org.opengis.metadata.Identifier;
  import org.apache.sis.parameter.Parameterized;
 -import org.apache.sis.measure.Longitude;
  import org.apache.sis.util.Debug;
  import org.apache.sis.util.Classes;
+ import org.apache.sis.util.ArraysExt;
  import org.apache.sis.io.TableAppender;
  import org.apache.sis.io.wkt.Convention;
  import org.apache.sis.io.wkt.FormattableObject;
diff --cc core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
index 7a6b5ea,2b6398b..10c6725
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
@@@ -29,10 -32,11 +32,10 @@@ import org.apache.sis.util.ArraysExt
  import org.junit.Test;
  import org.apache.sis.test.TestUtilities;
  import org.apache.sis.test.DependsOn;
--import static org.junit.Assert.*;
++import static org.apache.sis.test.Assert.*;
  
  // Branch-dependent imports
 -import org.opengis.test.CalculationType;
 -import org.opengis.test.ToleranceModifier;
 +// (all imports removed)
  
  
  /**
@@@ -192,23 -221,84 +220,72 @@@ public final strictfp class PassThrough
           * Now process to the transform and compares the results with the expected ones.
           */
          tolerance         = 0;          // Results should be strictly identical because we used the same inputs.
-         final double[] transformedData = new double[expectedData.length];
 -        toleranceModifier = null;
+         final double[] transformedData = new double[StrictMath.max(sourceDim, targetDim) * numPts];
          transform.transform(passthroughData, 0, transformedData, 0, numPts);
-         assertCoordinatesEqual("Direct transform.", passthroughDim,
-                 expectedData, 0, transformedData, 0, numPts, false);
+         assertCoordinatesEqual("PassThroughTransform results do not match the results computed by this test.",
 -                targetDim, expectedData, 0, transformedData, 0, numPts, CalculationType.DIRECT_TRANSFORM);
++                targetDim, expectedData, 0, transformedData, 0, numPts, false);
          /*
           * Test inverse transform.
           */
-         tolerance = 1E-8;
-         Arrays.fill(transformedData, Double.NaN);
-         transform.inverse().transform(expectedData, 0, transformedData, 0, numPts);
-         assertCoordinatesEqual("Inverse transform.", passthroughDim,
-                 passthroughData, 0, transformedData, 0, numPts, false);
+         if (isInverseTransformSupported) {
+             tolerance         = 1E-8;
 -            toleranceModifier = ToleranceModifier.RELATIVE;
+             Arrays.fill(transformedData, Double.NaN);
+             transform.inverse().transform(expectedData, 0, transformedData, 0, numPts);
+             assertCoordinatesEqual("Inverse of PassThroughTransform do not give back the original data.",
 -                    sourceDim, passthroughData, 0, transformedData, 0, numPts, CalculationType.INVERSE_TRANSFORM);
++                    sourceDim, passthroughData, 0, transformedData, 0, numPts, false);
+         }
          /*
           * Verify the consistency between different 'transform(…)' methods.
           */
-         final float[] sourceAsFloat = Numerics.copyAsFloats(passthroughData);
+         final float[] sourceAsFloat = ArraysExt.copyAsFloats(passthroughData);
          final float[] targetAsFloat = verifyConsistency(sourceAsFloat);
          assertEquals("Unexpected length of transformed array.", expectedData.length, targetAsFloat.length);
 -        /*
 -         * We use a relatively high tolerance threshold because result are
 -         * computed using inputs stored as float values.
 -         */
 -        if (transform instanceof LinearTransform) {
 -            tolerance         = 1E-4;
 -            toleranceModifier = ToleranceModifier.RELATIVE;
 -            assertCoordinatesEqual("PassThroughTransform.transform(…) variants produce inconsistent results.",
 -                    sourceDim, expectedData, 0, targetAsFloat, 0, numPts, CalculationType.DIRECT_TRANSFORM);
 -        }
      }
+ 
+     /**
+      * Tests {@link PassThroughTransform#tryConcatenate(boolean, MathTransform, MathTransformFactory)}.
+      * This tests creates a non-linear transform of 6→7 dimensions, then applies a filter keeping only
+      * target dimensions 1, 4 and 6 (corresponding to source dimensions 1 and 5).
+      *
+      * @throws FactoryException if an error occurred while combining the transforms.
+      */
+     @Test
+     public void testTryConcatenate() throws FactoryException {
+         PassThroughTransform ps = PassThroughTransform.newInstance(2, new PseudoTransform(2, 3), 2);
+         MathTransform c = ps.tryConcatenate(false, MathTransforms.linear(Matrices.create(4, 8, new double[] {
+                 0, 1, 0, 0, 0, 0, 0, 0,
+                 0, 0, 0, 0, 1, 0, 0, 0,
+                 0, 0, 0, 0, 0, 0, 1, 0,
+                 0, 0, 0, 0, 0, 0, 0, 1})), null);
+ 
+         final List<MathTransform> steps = MathTransforms.getSteps(c);
+         assertEquals("Number of steps", 3, steps.size());
+         /*
+          * We need to remove source dimensions 0, 2, 3 and 4. We can not remove dimensions 2 and 3 before
+          * pass-through because they are used by the sub-transform. It leaves us dimensions 0 and 4 which
+          * can be removed here.
+          */
+         assertMatrixEquals("Expected removal of dimensions 0 and 4 before pass-through", Matrices.create(5, 7, new double[] {
+                 0, 1, 0, 0, 0, 0, 0,
+                 0, 0, 1, 0, 0, 0, 0,
+                 0, 0, 0, 1, 0, 0, 0,
+                 0, 0, 0, 0, 0, 1, 0,
 -                0, 0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(0)), null);
++                0, 0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(0)), 0);
+         /*
+          * The number of pass-through dimensions have decreased from 2 to 1 on both sides of the sub-transform.
+          */
+         final PassThroughTransform reduced = (PassThroughTransform) steps.get(1);
+         assertEquals("firstAffectedOrdinate", 1, reduced.firstAffectedOrdinate);
+         assertEquals("numTrailingOrdinates",  1, reduced.numTrailingOrdinates);
+         assertSame  ("subTransform", ps.subTransform, reduced.subTransform);
+         /*
+          * We still have to remove source dimensions 2 and 3. Since we removed dimension 0 in previous step,
+          * the indices of dimensions to removed have shifted to 1 and 2.
+          */
+         assertMatrixEquals("Expected removal of dimensions 1 and 2 after pass-through", Matrices.create(4, 6, new double[] {
+                 1, 0, 0, 0, 0, 0,
+                 0, 0, 0, 1, 0, 0,
+                 0, 0, 0, 0, 1, 0,
 -                0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(2)), null);
++                0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(2)), 0);
+     }
  }
diff --cc core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
index 9b821d6,743cfbe..ce546b6
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TransformSeparatorTest.java
@@@ -30,7 -37,8 +37,8 @@@ import org.apache.sis.test.DependsOn
  import org.apache.sis.test.TestCase;
  import org.junit.Test;
  
+ import static java.lang.Double.NaN;
 -import static org.opengis.test.Assert.*;
 +import static org.apache.sis.test.Assert.*;
  
  
  /**
diff --cc core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
index 0000000,ae4c4aa..00381b5
mode 000000,100644..100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/TranslationTransformTest.java
@@@ -1,0 -1,114 +1,113 @@@
+ /*
+  * 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.referencing.operation.transform;
+ 
+ import org.opengis.referencing.operation.TransformException;
+ import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+ import org.apache.sis.referencing.operation.matrix.Matrices;
+ import org.apache.sis.referencing.operation.matrix.Matrix4;
+ import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
+ import org.apache.sis.internal.util.DoubleDouble;
+ 
+ import org.apache.sis.test.DependsOnMethod;
+ import org.apache.sis.test.DependsOn;
 -import org.opengis.test.Assert;
+ import org.junit.Test;
+ 
 -import static org.opengis.test.Assert.*;
++import static org.apache.sis.test.Assert.*;
+ 
+ 
+ /**
+  * Tests the {@link TranslationTransform} class.
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.0
+  * @since   1.0
+  * @module
+  */
+ @DependsOn(AbstractMathTransformTest.class)
+ public final strictfp class TranslationTransformTest extends MathTransformTestCase {
+     /**
+      * Sets the {@link #transform} field to the {@link TranslationTransform} instance to test.
+      *
+      * @param  dimensions  expected number of source and target dimensions.
+      * @param  matrix      the data to use for creating the transform.
+      */
+     private void create(final int dimensions, final MatrixSIS matrix) {
+         final double[] elements = matrix.getElements();
+         final TranslationTransform tr = new TranslationTransform(matrix.getNumRow(), elements);
+         assertEquals("sourceDimensions", dimensions, tr.getSourceDimensions());
+         assertEquals("targetDimensions", dimensions, tr.getTargetDimensions());
 -        Assert.assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
++        assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
+         assertArrayEquals("elements", elements, tr.getExtendedElements(), 0.0);
+         transform = tr;
+         validate();
+     }
+ 
+     /**
+      * Tests a transform created from a square matrix with no error terms.
+      *
+      * @throws TransformException should never happen.
+      */
+     @Test
+     public void testConstantDimension() throws TransformException {
+         create(3, new Matrix4(
+                 1, 0, 0, 2,
+                 0, 1, 0, 3,
+                 0, 0, 1, 8,
+                 0, 0, 0, 1));
+ 
+         verifyTransform(new double[] {1,1,1,   6, 0,  2,   2, Double.NaN,  6},
+                         new double[] {3,4,9,   8, 3, 10,   4, Double.NaN, 14});
+     }
+ 
+     /**
+      * Verifies that {@link TranslationTransform} stores the error terms when they exist.
+      */
+     @Test
+     @DependsOnMethod("testConstantDimension")
+     public void testExtendedPrecision() {
+         final Number O = 0;
+         final Number l = 1;
+         final DoubleDouble r = DoubleDouble.createDegreesToRadians();
+         final MatrixSIS matrix = Matrices.create(4, 4, new Number[] {
+             l, O, O, r,
+             O, l, O, r,
+             O, O, l, O,
+             O, O, O, l
+         });
+         final double[] elements = ((ExtendedPrecisionMatrix) matrix).getExtendedElements();
+         assertTrue (r.value > r.error);
+         assertFalse(r.error == 0);          // Paranoiac checks for making sure that next assertion will test something.
+         assertArrayEquals(new double[] {    // Paranoiac check for making sure that getExtendedElements() is not broken.
+                 1, 0, 0, r.value,
+                 0, 1, 0, r.value,
+                 0, 0, 1, 0,
+                 0, 0, 0, 1,
+                 0, 0, 0, r.error,
+                 0, 0, 0, r.error,
+                 0, 0, 0, 0,
+                 0, 0, 0, 0}, elements, 0);
+ 
+         final TranslationTransform tr = new TranslationTransform(4, elements);
+         assertEquals("sourceDimensions", 3, tr.getSourceDimensions());
+         assertEquals("targetDimensions", 3, tr.getTargetDimensions());
 -        Assert.assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
++        assertMatrixEquals("matrix", matrix, tr.getMatrix(), 0.0);
+         assertArrayEquals("elements", elements, tr.getExtendedElements(), 0.0);
+         transform = tr;
+         validate();
+     }
+ }
diff --cc core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
index 595e1a9,59d03c5..eb798b6
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
@@@ -227,7 -235,17 +235,10 @@@ public class MeasurementRange<E extend
  
      /**
       * Returns the unit of measurement, or {@code null} if unknown.
+      * In principle the unit should never be null, otherwise a {@link NumberRange} should have been used
+      * instead than {@code MeasurementRange}. Nevertheless this method may return {@code null} if a unit
+      * <em>should</em> exist but for some reason is unavailable.
       *
 -     * <div class="note"><b>Example:</b>
 -     * ISO 19115-1 {@code SampleDimension} specifies that its
 -     * {@linkplain org.opengis.metadata.content.SampleDimension#getUnits() unit} property is mandatory if the
 -     * {@linkplain org.opengis.metadata.content.SampleDimension#getMinValue() minimum value} or
 -     * {@linkplain org.opengis.metadata.content.SampleDimension#getMaxValue() maximum value} are provided.
 -     * Nevertheless it happens sometime that this information is missing in metadata.</div>
 -     *
       * @return the unit of measurement, or {@code null}.
       */
      @Override
diff --cc core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
index a902a9f,b5cdd19..eaabd6c
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/IndexedResourceBundle.java
@@@ -30,7 -30,9 +30,8 @@@ import java.util.ResourceBundle
  import java.util.logging.Level;
  import java.util.logging.LogRecord;
  import java.lang.reflect.Modifier;
+ import javax.measure.Unit;
  import org.opengis.util.CodeList;
 -import org.opengis.util.ControlledVocabulary;
  import org.opengis.util.InternationalString;
  import org.apache.sis.util.Debug;
  import org.apache.sis.util.Classes;
diff --cc ide-project/NetBeans/nbproject/genfiles.properties
index 2505775,74bf923..f2edd02
--- 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=632785c4
 -nbproject/build-impl.xml.data.CRC32=aff02101
 -nbproject/build-impl.xml.script.CRC32=aa8f5386
++nbproject/build-impl.xml.data.CRC32=c09a569b
 +nbproject/build-impl.xml.script.CRC32=3a1dc6ad
  nbproject/build-impl.xml.stylesheet.CRC32=3a2fa800@1.89.1.48
diff --cc pom.xml
index 94546f8,a0af729..a5ccae8
--- a/pom.xml
+++ b/pom.xml
@@@ -524,8 -524,8 +524,8 @@@
      <maven.compiler.source>8</maven.compiler.source>
      <maven.compiler.target>8</maven.compiler.target>
      <sis.plugin.version>${project.version}</sis.plugin.version>
-     <sis.non-free.version>1.0-M1</sis.non-free.version>
+     <sis.non-free.version>1.0-M1</sis.non-free.version>                 <!-- Used only if "non-free" profile is enabled. -->
 -    <geoapi.version>3.1-SNAPSHOT</geoapi.version>
 +    <geoapi.version>3.0.1</geoapi.version>
    </properties>
  
    <profiles>
diff --cc storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
index 8d41055,830d663..79b11c6
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java
@@@ -104,24 -202,259 +202,259 @@@ public final class Axis extends NamedEl
      }
  
      /**
-      * Returns the axis direction for the given unit of measurement as a sign relative to the given direction.
+      * Returns the axis direction for the given unit of measurement, or {@code null} if unknown.
       * This method performs the second half of the work of parsing "degrees_east" or "degrees_west" units.
       *
-      * @param  unit      the string representation of the netCDF unit, or {@code null}.
-      * @param  positive  the direction to take as positive value: {@link AxisDirection#EAST} or {@link AxisDirection#NORTH}.
-      * @return the axis direction as a sign relative to the positive direction, or 0 if unrecognized.
+      * @param  unit  the string representation of the netCDF unit, or {@code null}.
+      * @return the axis direction, or {@code null} if unrecognized.
       */
-     public static int direction(final String unit, final AxisDirection positive) {
+     public static AxisDirection direction(final String unit) {
          if (unit != null) {
-             final int s = unit.indexOf('_');
+             int s = unit.indexOf('_');
+             if (s < 0) {
+                 s = unit.indexOf(' ');
+             }
              if (s > 0) {
-                 final AxisDirection dir = Types.forCodeName(AxisDirection.class, unit.substring(s+1), false);
-                 if (dir != null) {
-                     if (dir.equals(positive)) return +1;
-                     if (dir.equals(AxisDirections.opposite(positive))) return -1;
+                 return Types.forCodeName(AxisDirection.class, unit.substring(s+1), false);
+             }
+         }
+         return null;
+     }
+ 
+     /**
+      * Returns the name of this axis.
+      *
+      * @return the name of this element.
+      */
+     @Override
+     public final String getName() {
+         return coordinates.getName().trim();
+     }
+ 
+     /**
+      * Returns the unit of measurement of this axis, or {@code null} if unknown.
+      *
+      * @return the unit of measurement, or {@code null} if unknown.
+      */
+     public final Unit<?> getUnit() {
+         return coordinates.getUnit();
+     }
+ 
+     /**
+      * Returns {@code true} if the given axis specifies the same direction and unit of measurement than this axis.
+      * This is used for testing is a predefined axis can be used instead than invoking {@link #toISO(CSFactory)}.
+      */
+     final boolean isSameUnitAndDirection(final CoordinateSystemAxis axis) {
+         return axis.getDirection().equals(direction) && axis.getUnit().equals(getUnit());
+     }
+ 
+     /**
+      * Returns {@code true} if coordinates in this axis seem to map cell corner instead than cell center.
+      * A {@code false} value does not necessarily means that the axis maps cell center; it can be unknown.
+      * This method assumes a geographic CRS.
+      *
+      * <p>From CF-Convention: <cite>"If bounds are not provided, an application might reasonably assume the
+      * grid points to be at the centers of the cells, but we do not require that in this standard."</cite>
+      * We nevertheless tries to guess by checking if the "cell center" convention would result in coordinates
+      * outside the range of longitude or latitude values.</p>
+      */
+     final boolean isCellCorner() throws IOException, DataStoreException {
+         double min;
+         boolean wraparound;
+         switch (abbreviation) {
+             case 'λ': min = Longitude.MIN_VALUE; wraparound = true;  break;
+             case 'φ': min =  Latitude.MIN_VALUE; wraparound = false; break;
+             default: return false;
+         }
+         final Vector data = coordinates.read();
+         final int size = data.size();
+         if (size != 0) {
+             Unit<?> unit = getUnit();
+             if (unit == null) {
+                 unit = Units.DEGREE;
+             }
+             try {
+                 final UnitConverter uc = unit.getConverterToAny(Units.DEGREE);
+                 if (wraparound && uc.convert(data.doubleValue(size - 1)) > Longitude.MAX_VALUE) {
+                     min = 0;            // Replace [-180 … +180]° longitude range by [0 … 360]°.
+                 }
+                 return uc.convert(data.doubleValue(0)) == min;
+             } catch (IncommensurableException e) {
+                 coordinates.error(Grid.class, "getGridGeometry", e, Errors.Keys.InconsistentUnitsForCS_1, unit);
+             }
+         }
+         return false;
+     }
+ 
+     /**
+      * Compares this axis with the given axis for ordering based on the dimensions declared in a variable.
+      * This is used for sorting axes in the same order than the dimension order on the variable using axes.
+      * This order is not necessarily the same than the order in which variables are listed in the netCDF file.
+      *
+      * @param  other  the other axis to compare to this axis.
+      * @return -1 if this axis should be before {@code other}, +1 if it should be after.
+      */
+     @Override
+     public int compareTo(final Axis other) {
+         return sourceDimensions[0] - other.sourceDimensions[0];
+     }
+ 
+     /**
+      * Creates an ISO 19111 axis from the information stored in this netCDF axis.
+      *
+      * @param  factory  the factory to use for creating the coordinate system axis.
+      * @return the ISO axis.
+      */
+     final CoordinateSystemAxis toISO(final CSFactory factory) throws FactoryException {
+         /*
+          * The axis name is stored without namespace, because the variable name in a netCDF file can be anything;
+          * this is not controlled vocabulary. However the standard name, if any, is stored with "NetCDF" namespace
+          * because this is controlled vocabulary.
+          */
+         final String name = getName();
+         final Map<String,Object> properties = new HashMap<>(4);
+         properties.put(CoordinateSystemAxis.NAME_KEY, name);                        // Intentionally no namespace.
+         final List<GenericName> aliases = new ArrayList<>(2);
+         final String standardName = coordinates.getAttributeAsString(CF.STANDARD_NAME);
+         if (standardName != null) {
+             final NamedIdentifier std = new NamedIdentifier(Citations.NETCDF, standardName);
+             if (standardName.equals(name)) {
+                 properties.put(CoordinateSystemAxis.NAME_KEY, std);                 // Store as primary name.
+             } else {
+                 aliases.add(std);                                                   // Store as alias.
+             }
+         }
+         /*
+          * The long name is stored as an optional description of the primary name.
+          * It is also stored as an alias if not redundant with other names.
+          */
+         final String alt = coordinates.getAttributeAsString(CDM.LONG_NAME);
+         if (alt != null && !similar(alt, name)) {
 -            properties.put(org.opengis.metadata.Identifier.DESCRIPTION_KEY, alt);   // Description associated to primary name.
++            properties.put(org.apache.sis.metadata.iso.ImmutableIdentifier.DESCRIPTION_KEY, alt);   // Description associated to primary name.
+             if (!similar(alt, standardName)) {
+                 aliases.add(new NamedIdentifier(null, alt));                        // Additional alias.
+             }
+         }
+         if (!aliases.isEmpty()) {
+             properties.put(CoordinateSystemAxis.ALIAS_KEY, aliases.toArray(new GenericName[aliases.size()]));
+         }
+         /*
+          * Axis abbreviation, direction and unit of measurement are mandatory. If any of them is null,
+          * creation of CoordinateSystemAxis is likely to fail with an InvalidGeodeticParameterException.
+          * We provide default values for the most well-accepted values and leave other values to null.
+          * Those null values can be accepted if users specify their own factory.
+          */
+         Unit<?> unit = getUnit();
+         if (unit == null) {
+             switch (abbreviation) {
+                 /*
+                  * TODO: consider moving those default values in a separated class,
+                  * for example a netCDF-specific CSFactory, for allowing users to override.
+                  */
+                 case 'λ': case 'φ': unit = Units.DEGREE; break;
+             }
+         }
+         final String abbr;
+         if (abbreviation != 0) {
+             abbr = Character.toString(abbreviation).intern();
+         } else if (direction != null && unit != null) {
+             abbr = AxisDirections.suggestAbbreviation(name, direction, unit);
+         } else {
+             abbr = null;
+         }
+         return factory.createCoordinateSystemAxis(properties, abbr, direction, unit);
+     }
+ 
+     /**
+      * Sets the scale and offset coefficients in the given "grid to CRS" transform if possible.
+      * Source and target dimensions used by this method are in "natural" order (reverse of netCDF order).
+      * Setting the coefficient is possible only if values in this variable are regular,
+      * i.e. the difference between two consecutive values is constant.
+      *
+      * <p>If this method returns {@code true}, then the {@code nonLinears} list is left unchanged.
+      * If this method returns {@code false}, then a non-linear transform or {@code null} has been
+      * added to the {@code nonLinears} list.</p>
+      *
+      * @param  gridToCRS   the matrix in which to set scale and offset coefficient.
+      * @param  srcEnd      number of source dimensions (grid dimensions) - 1. Identifies the last column in the matrix.
+      * @param  tgtDim      the target dimension, which is a dimension of the CRS. Identifies the matrix row of scale factor.
+      * @param  nonLinears  where to add a non-linear transform if we can not compute a linear one. {@code null} may be added.
+      * @return whether this method successfully set the scale and offset coefficients.
+      * @throws IOException if an error occurred while reading the data.
+      * @throws DataStoreException if a logical error occurred.
+      */
+     final boolean trySetTransform(final Matrix gridToCRS, final int srcEnd, final int tgtDim,
+             final List<MathTransform> nonLinears) throws IOException, DataStoreException
+     {
+         switch (sourceDimensions.length) {
+             /*
+              * Defined as a matter of principle, but should never happen.
+              */
+             case 0: return true;
+             /*
+              * Normal case where the axis has only one dimension.
+              */
+             case 1: {
+                 final int srcDim = srcEnd - sourceDimensions[0];
+                 if (coordinates.trySetTransform(gridToCRS, srcDim, tgtDim, null)) {
+                     return true;
+                 } else {
+                     nonLinears.add(MathTransforms.interpolate(null, coordinates.read().doubleValues()));
+                     return false;
+                 }
+             }
+             /*
+              * In netCDF files, axes are sometime associated to two-dimensional localization grids.
+              * If this is the case, then the following block checks if we can reduce those grids to
+              * one-dimensional vector. For example the following localisation grids:
+              *
+              *    10 10 10 10                  10 12 15 20
+              *    12 12 12 12        or        10 12 15 20
+              *    15 15 15 15                  10 12 15 20
+              *    20 20 20 20                  10 12 15 20
+              *
+              * can be reduced to a one-dimensional {10 12 15 20} vector (orientation matter however).
+              *
+              * Note: following block is currently restricted to the two-dimensional case, but it could
+              * be generalized to n-dimensional case if we resolve the default case in the switch statement.
+              */
+             case 2: {
+                 Vector data = coordinates.read();
+                 final int[] repetitions = data.repetitions(sourceSizes);    // Detects repetitions as illustrated above.
+                 if (repetitions.length != 0) {
+                     for (int i=0; i<sourceDimensions.length; i++) {
+                         final int srcDim = srcEnd - sourceDimensions[i];    // "Natural" order is reverse of netCDF order.
+                         final int length = sourceSizes[i];
+                         int step = 1;
+                         for (int j=0; j<sourceDimensions.length; j++) {
+                             int previous = srcEnd - sourceDimensions[j];
+                             if (previous < srcDim) step *= sourceSizes[j];
+                         }
+                         final boolean condition;
+                         switch (srcDim) {
+                             case 0:  condition = repetitions.length > 1 && (repetitions[1] % length) == 0; break;
+                             case 1:  condition =                           (repetitions[0] % step)   == 0; break;
+                             default: throw new AssertionError();        // I don't know yet how to generalize to n dimensions.
+                         }
+                         if (condition) {                                // Repetition length shall be grid size (or a multiple).
+                             data = data.subSampling(0, step, length);
+                             if (coordinates.trySetTransform(gridToCRS, srcDim, tgtDim, data)) {
+                                 return true;
+                             } else {
+                                 nonLinears.add(MathTransforms.interpolate(null, data.doubleValues()));
+                                 return false;
+                             }
+                         }
+                     }
                  }
              }
+             /*
+              * Localization grid of 3 dimensions or more are theoretically possible, but uncommon.
+              * Such grids are not yet supported. Note that we can read three or more dimensional data;
+              * it is only the localization grid which is limited to one or two dimensions.
+              */
          }
-         return 0;
+         nonLinears.add(null);
+         return false;
      }
  }
diff --cc storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
index 60a5da1,90756d7..b0538a2
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
@@@ -28,10 -30,8 +30,11 @@@ import org.apache.sis.storage.DataStore
  import org.apache.sis.storage.DataStoreException;
  import org.apache.sis.util.logging.WarningListeners;
  import org.apache.sis.internal.system.DefaultFactories;
+ import org.apache.sis.internal.referencing.ReferencingFactoryContainer;
  
 +// Branch-dependent imports
 +import org.apache.sis.util.iso.DefaultNameFactory;
 +
  
  /**
   * The API used internally by Apache SIS for fetching variables and attribute values from a netCDF file.
@@@ -85,7 -104,8 +107,8 @@@ public abstract class Decoder extends R
          Objects.requireNonNull(listeners);
          this.geomlib     = geomlib;
          this.listeners   = listeners;
 -        this.nameFactory = DefaultFactories.forBuildin(NameFactory.class);
 +        this.nameFactory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
+         this.datumCache  = new Datum[CRSBuilder.DATUM_CACHE_SIZE];
      }
  
      /**
diff --cc storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
index c5fa53b,75402e8..4095467
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
@@@ -339,11 -343,11 +341,11 @@@ search: for (final VariableInfo counts 
           * @todo current reading process implies lot of seeks, which is inefficient.
           */
          @Override
 -        public boolean tryAdvance(final Consumer<? super Feature> action) {
 +        public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
-             final int   length = counts.intValue(index);
-             final int[] lower  = {position};
-             final int[] upper  = {position + length};
-             final int[] step   = {1};
+             final int length = counts.intValue(index);
+             final GridExtent extent = new GridExtent(null, new long[] {position},
+                             new long[] {Math.addExact(position, length)}, false);
+             final int[] step = {1};
              final Vector   id, t;
              final Vector[] coords = new Vector[coordinates.length];
              final Object[] props  = new Object[properties.length];
diff --cc storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
index 2615bbe,4072b5c..4efdb52
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
@@@ -56,12 -55,7 +55,7 @@@ final class FeaturesWrapper extends Dis
      }
  
      @Override
-     public GenericName getIdentifier() {
-         throw new UnsupportedOperationException();      // TODO
-     }
- 
-     @Override
 -    public FeatureType getType() {
 +    public DefaultFeatureType getType() {
          throw new UnsupportedOperationException();      // TODO
      }
  
diff --cc storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
index 75b665e,148233b..a9f7f47
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
@@@ -46,9 -47,9 +46,10 @@@ import org.opengis.metadata.maintenance
  import org.opengis.metadata.constraint.Restriction;
  import org.opengis.referencing.cs.AxisDirection;
  import org.opengis.referencing.crs.VerticalCRS;
+ import org.opengis.referencing.crs.CoordinateReferenceSystem;
  
  import org.apache.sis.util.iso.Types;
 +import org.apache.sis.util.iso.DefaultNameFactory;
  import org.apache.sis.util.logging.WarningListeners;
  import org.apache.sis.storage.DataStore;
  import org.apache.sis.storage.DataStoreException;
@@@ -921,14 -936,12 +929,12 @@@ split:  while ((start = CharSequences.s
          newSampleDimension();
          final String name = trim(variable.getName());
          if (name != null) {
 -            final NameFactory f = decoder.nameFactory;
 +            final DefaultNameFactory f = decoder.nameFactory;
              setBandIdentifier(f.createMemberName(null, name, f.createTypeName(null, variable.getDataTypeName())));
          }
-         Object[] v = variable.getAttributeValues(CF.STANDARD_NAME, false);
-         final String id = (v.length == 1) ? trim((String) v[0]) : null;
+         final String id = trim(variable.getAttributeAsString(CF.STANDARD_NAME));
          if (id != null && !id.equals(name)) {
-             v = variable.getAttributeValues(ACDD.standard_name_vocabulary, false);
-             addBandName(v.length == 1 ? (String) v[0] : null, id);
+             addBandName(variable.getAttributeAsString(ACDD.standard_name_vocabulary), id);
          }
          final String description = trim(variable.getDescription());
          if (description != null && !description.equals(name) && !description.equals(id)) {
diff --cc storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
index daebc6f,62b12ca..bce7673
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
@@@ -18,9 -18,9 +18,8 @@@ package org.apache.sis.internal.netcdf
  
  import java.io.IOException;
  import org.apache.sis.storage.DataStoreException;
- import org.apache.sis.storage.netcdf.AttributeNames;
  import org.apache.sis.test.DependsOn;
  import org.apache.sis.test.DependsOnMethod;
 -import org.opengis.test.dataset.TestData;
  import org.junit.Test;
  
  import static org.junit.Assert.*;
diff --cc storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java
index c6fd0d3,eb23e9b..ab80436
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/GridInfoTest.java
@@@ -24,11 -23,12 +23,14 @@@ import org.apache.sis.internal.netcdf.G
  import org.apache.sis.storage.DataStoreException;
  import org.apache.sis.util.ArraysExt;
  import org.apache.sis.test.DependsOn;
 -import org.opengis.test.dataset.TestData;
 +
++// Branch-specific imports
++import org.apache.sis.internal.netcdf.TestData;
+ 
  
  /**
-  * Tests the {@link GridGeometry} implementation. This test shall be executed only if the
-  * {@link GridGeometryTest} tests, which use the UCAR library has a reference implementation,
+  * Tests the {@link GridInfo} implementation. This test shall be executed only if the
+  * {@link GridTest} tests, which use the UCAR library has a reference implementation,
   * passed.
   *
   * @author  Martin Desruisseaux (Geomatys)
diff --cc storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
index 70fd76a,fe7ad8c..e548f0f
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractFeatureSet.java
@@@ -70,32 -72,44 +72,44 @@@ public abstract class AbstractFeatureSe
      }
  
      /**
-      * Returns a description of this set of features.
-      * Current implementation sets only the resource name; this may change in any future Apache SIS version.
+      * Returns the feature type name as the identifier for this resource.
       *
-      * <div class="note"><b>Note:</b>
-      * we currently do not set the geographic extent from the envelope because default {@link #getEnvelope()}
-      * implementation itself invokes {@code getMetadata()}. Consequently requesting the envelope from this
-      * method could create a never-ending loop.</div>
+      * @return the resource identifier inferred from metadata, or {@code null} if none or ambiguous.
+      * @throws DataStoreException if an error occurred while fetching the identifier.
+      *
+      * @see DataStore#getIdentifier()
       */
      @Override
-     public synchronized Metadata getMetadata() throws DataStoreException {
-         if (metadata == null) {
-             final DefaultMetadata metadata = new DefaultMetadata();
-             final DefaultFeatureType type = getType();
-             if (type != null) {
-                 final GenericName name = type.getName();
-                 if (name != null) {                         // Paranoiac check (should never be null).
-                     final DefaultCitation citation = new DefaultCitation(name.toInternationalString());
-                     final DefaultDataIdentification identification = new DefaultDataIdentification();
-                     identification.setCitation(citation);
-                 }
-             }
-             // No geographic extent - see above javadoc.
-             metadata.transition(DefaultMetadata.State.FINAL);
-             this.metadata = metadata;
+     public GenericName getIdentifier() throws DataStoreException {
 -        final FeatureType type = getType();
++        final DefaultFeatureType type = getType();
+         if (type != null) {
+             return type.getName();
          }
-         return metadata;
+         return null;
+     }
+ 
+     /**
+      * Returns an estimation of the number of features in this set, or {@code null} if unknown.
+      * The default implementation returns {@code null}.
+      *
+      * @return estimation of the number of features, or {@code null}.
+      */
+     protected Integer getFeatureCount() {
+         return null;
+     }
+ 
+     /**
+      * Invoked the first time that {@link #getMetadata()} is invoked. The default implementation populates metadata
+      * based on information provided by {@link #getType()}, {@link #getIdentifier()} and {@link #getEnvelope()}.
+      * Subclasses should override if they can provide more information.
+      *
+      * @param  metadata  the builder where to set metadata properties.
+      * @throws DataStoreException if an error occurred while reading metadata from the data store.
+      */
+     @Override
+     protected void createMetadata(final MetadataBuilder metadata) throws DataStoreException {
+         super.createMetadata(metadata);
+         metadata.addFeatureType(getType(), getFeatureCount());
      }
  
      /**
diff --cc storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
index b9b1c82,fb21125..fca1d07
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
@@@ -60,9 -58,8 +58,8 @@@ public class MemoryFeatureSet extends A
       * @param type       the type of all features in the given collection.
       * @param features   collection of stored features. This collection will not be copied.
       */
-     public MemoryFeatureSet(final WarningListeners<DataStore> listeners, Metadata metadata,
+     public MemoryFeatureSet(final WarningListeners<DataStore> listeners,
 -                            final FeatureType type, final Collection<Feature> features)
 +                            final DefaultFeatureType type, final Collection<AbstractFeature> features)
      {
          super(listeners);
          ArgumentChecks.ensureNonNull("type",     type);
@@@ -72,14 -69,13 +69,13 @@@
      }
  
      /**
-      * Returns the name of the feature type.
+      * Returns the type common to all feature instances in this set.
       *
-      * @return feature type name.
+      * @return a description of properties that are common to all features in this dataset.
       */
      @Override
-     public GenericName getIdentifier() {
-         return type.getName();
 -    public FeatureType getType() {
++    public DefaultFeatureType getType() {
+         return type;
      }
  
      /**
diff --cc storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
index 66a3edb,2557a06..9317a7f
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/StoreUtilities.java
@@@ -39,7 -44,7 +44,8 @@@ import org.apache.sis.util.CharSequence
  import org.apache.sis.util.Classes;
  
  // Branch-dependent imports
 -import org.opengis.feature.Feature;
 +import org.apache.sis.feature.AbstractFeature;
++import org.apache.sis.metadata.iso.identification.AbstractIdentification;
  
  
  /**
@@@ -141,6 -146,36 +147,39 @@@ public final class StoreUtilities exten
      }
  
      /**
+      * Returns the spatio-temporal envelope of the given metadata.
+      * This method computes the union of all {@link GeographicBoundingBox} in the metadata, assuming the
+      * {@linkplain org.apache.sis.referencing.CommonCRS#defaultGeographic() default geographic CRS}
+      * (usually WGS 84).
+      *
+      * @param  metadata  the metadata from which to compute the envelope, or {@code null}.
+      * @return the spatio-temporal extent, or {@code null} if none.
+      */
+     public static Envelope getEnvelope(final Metadata metadata) {
+         GeneralEnvelope bounds = null;
+         if (metadata != null) {
+             for (final Identification identification : metadata.getIdentificationInfo()) {
 -                for (final Extent extent : identification.getExtents()) {
++                if (!(identification instanceof AbstractIdentification)) {
++                    continue;       // Following cast is specific to GeoAPI 3.0 branch.
++                }
++                for (final Extent extent : ((AbstractIdentification) identification).getExtents()) {
+                     for (final GeographicExtent ge : extent.getGeographicElements()) {
+                         if (ge instanceof GeographicBoundingBox) {
+                             final GeneralEnvelope env = new GeneralEnvelope((GeographicBoundingBox) ge);
+                             if (bounds == null) {
+                                 bounds = env;
+                             } else {
+                                 bounds.add(env);
+                             }
+                         }
+                     }
+                 }
+             }
+         }
+         return bounds;
+     }
+ 
+     /**
       * Returns the most specific interface implemented by the given class.
       * For indicative purpose only, as this method has arbitrary behavior if more than one leaf is found.
       *
diff --cc storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
index fa6db66,9fe1f5f..c327d87
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/FeatureSubset.java
@@@ -16,9 -16,10 +16,8 @@@
   */
  package org.apache.sis.internal.storage.query;
  
 -import java.util.List;
  import java.util.stream.Stream;
  import org.opengis.util.GenericName;
- import org.opengis.geometry.Envelope;
 -import org.apache.sis.internal.feature.FeatureUtilities;
  import org.apache.sis.internal.storage.AbstractFeatureSet;
  import org.apache.sis.storage.DataStoreException;
  import org.apache.sis.storage.FeatureSet;
diff --cc storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
index 510ea56,0889df2..156f6d7
--- 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
@@@ -28,10 -31,10 +31,13 @@@ import org.apache.sis.util.Localized
  import org.apache.sis.util.ArgumentChecks;
  import org.apache.sis.util.logging.WarningListener;
  import org.apache.sis.util.logging.WarningListeners;
- import org.apache.sis.internal.storage.AbstractResource;
  import org.apache.sis.internal.storage.StoreUtilities;
  import org.apache.sis.internal.storage.Resources;
+ import org.apache.sis.referencing.NamedIdentifier;
+ 
++// Branch-specific imports
++import org.opengis.referencing.ReferenceIdentifier;
 +
  
  /**
   * Manages a series of features, coverages or sensor data.
@@@ -292,7 -295,31 +298,31 @@@ public abstract class DataStore impleme
       */
      @Override
      public GenericName getIdentifier() throws DataStoreException {
-         return AbstractResource.identifier(getMetadata());
+         final Metadata metadata = getMetadata();
+         if (metadata != null) {
+             Citation citation = null;
+             for (final Identification id : metadata.getIdentificationInfo()) {
+                 final Citation c = id.getCitation();
+                 if (c != null) {
+                     if (citation != null && citation != c) return null;                 // Ambiguity.
+                     citation = c;
+                 }
+             }
+             if (citation != null) {
 -                Identifier first = null;
++                ReferenceIdentifier first = null;
+                 for (final Identifier c : citation.getIdentifiers()) {
+                     if (c instanceof GenericName) {
+                         return (GenericName) c;
 -                    } else if (first == null) {
 -                        first = c;
++                    } else if (first == null && c instanceof ReferenceIdentifier) {
++                        first = (ReferenceIdentifier) c;
+                     }
+                 }
+                 if (first != null) {
+                     return new NamedIdentifier(first);
+                 }
+             }
+         }
+         return null;
      }
  
      /**