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 6b507d7cbdd4abf57be410c19b0d7585e74577a0
Merge: a04bd4b298 e8bfb5ea7b
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Sep 21 12:03:27 2022 +0200

    Merge branch 'geoapi-3.1'.

 .../apache/sis/gui/coverage/CoverageExplorer.java  |   4 +-
 .../apache/sis/gui/coverage/GridSliceSelector.java |   1 +
 .../org/apache/sis/gui/coverage/ImageRequest.java  |   4 +-
 .../apache/sis/gui/coverage/ViewAndControls.java   |   2 +-
 .../org/apache/sis/gui/dataset/ResourceCell.java   | 208 ++++++
 .../org/apache/sis/gui/dataset/ResourceItem.java   | 418 ++++++++++++
 .../org/apache/sis/gui/dataset/ResourceTree.java   | 528 ++--------------
 .../org/apache/sis/gui/dataset/RootResource.java   | 135 ++++
 .../org/apache/sis/gui/dataset/TreeViewType.java   |  24 +-
 .../java/org/apache/sis/gui/map/MapCanvas.java     |  18 +-
 .../java/org/apache/sis/gui/map/StatusBar.java     |  68 +-
 .../org/apache/sis/gui/map/ValuesFormatter.java    | 522 +++++++++++++++
 .../org/apache/sis/gui/map/ValuesFromCoverage.java | 264 ++++++++
 .../org/apache/sis/gui/map/ValuesUnderCursor.java  | 699 +++++++--------------
 .../apache/sis/internal/gui/DataStoreOpener.java   |   1 +
 .../org/apache/sis/internal/gui/LogHandler.java    |  51 +-
 .../org/apache/sis/internal/gui/Resources.java     |  29 +-
 .../apache/sis/internal/gui/Resources.properties   |   2 +
 .../sis/internal/gui/Resources_fr.properties       |   2 +
 .../sis/coverage/grid/ConvertedGridCoverage.java   |   8 +
 .../sis/coverage/grid/DisjointExtentException.java |   6 +-
 .../org/apache/sis/coverage/grid/GridCoverage.java |  67 +-
 .../apache/sis/coverage/grid/GridCoverage2D.java   |  16 +-
 .../apache/sis/coverage/grid/GridDerivation.java   |   3 +-
 .../org/apache/sis/coverage/grid/GridExtent.java   | 130 +++-
 .../org/apache/sis/coverage/grid/GridGeometry.java | 105 +++-
 .../sis/coverage/grid/TranslatedGridCoverage.java  |  15 +-
 .../org/apache/sis/feature/AbstractAttribute.java  |   3 +-
 .../main/java/org/apache/sis/image/TileCache.java  |  31 +-
 .../apache/sis/coverage/grid/GridExtentTest.java   |  40 +-
 .../apache/sis/coverage/grid/GridGeometryTest.java |  32 +
 ...ataUtilities.java => ImplementationHelper.java} |  13 +-
 .../apache/sis/internal/metadata/package-info.java |   2 +-
 .../sis/internal/simple/CitationConstant.java      |   2 -
 .../org/apache/sis/metadata/MetadataStandard.java  |   1 +
 .../apache/sis/metadata/ModifiableMetadata.java    |   2 +-
 .../apache/sis/metadata/PropertyInformation.java   |   1 +
 .../iso/DefaultApplicationSchemaInformation.java   |   4 +
 .../iso/DefaultExtendedElementInformation.java     |   9 +-
 .../apache/sis/metadata/iso/DefaultIdentifier.java |   2 +
 .../apache/sis/metadata/iso/DefaultMetadata.java   |  39 +-
 .../iso/DefaultMetadataExtensionInformation.java   |   2 +
 .../sis/metadata/iso/DefaultMetadataScope.java     |   1 +
 .../iso/DefaultPortrayalCatalogueReference.java    |   1 +
 .../org/apache/sis/metadata/iso/ISOMetadata.java   |   9 +-
 .../sis/metadata/iso/MetadataScopeAdapter.java     |   6 +-
 .../acquisition/DefaultAcquisitionInformation.java |   7 +
 .../acquisition/DefaultEnvironmentalRecord.java    |   3 +-
 .../sis/metadata/iso/acquisition/DefaultEvent.java |   7 +-
 .../iso/acquisition/DefaultInstrument.java         |   4 +
 .../metadata/iso/acquisition/DefaultObjective.java |   7 +
 .../metadata/iso/acquisition/DefaultOperation.java |  10 +
 .../sis/metadata/iso/acquisition/DefaultPlan.java  |   3 +
 .../metadata/iso/acquisition/DefaultPlatform.java  |   4 +
 .../iso/acquisition/DefaultPlatformPass.java       |   2 +
 .../iso/acquisition/DefaultRequestedDate.java      |   4 +-
 .../iso/acquisition/DefaultRequirement.java        |   9 +-
 .../sis/metadata/iso/citation/AbstractParty.java   |   2 +
 .../sis/metadata/iso/citation/DefaultAddress.java  |   5 +
 .../sis/metadata/iso/citation/DefaultCitation.java |  15 +-
 .../metadata/iso/citation/DefaultCitationDate.java |   4 +-
 .../sis/metadata/iso/citation/DefaultContact.java  |  10 +-
 .../metadata/iso/citation/DefaultIndividual.java   |   1 +
 .../iso/citation/DefaultOnlineResource.java        |   2 +
 .../metadata/iso/citation/DefaultOrganisation.java |   2 +
 .../iso/citation/DefaultResponsibility.java        |   2 +
 .../iso/citation/DefaultResponsibleParty.java      |   2 +-
 .../sis/metadata/iso/citation/DefaultSeries.java   |   3 +
 .../metadata/iso/citation/DefaultTelephone.java    |   1 +
 .../iso/constraint/DefaultConstraints.java         |   6 +
 .../iso/constraint/DefaultLegalConstraints.java    |   3 +
 .../iso/constraint/DefaultReleasability.java       |   3 +
 .../iso/constraint/DefaultSecurityConstraints.java |   3 +
 .../iso/content/DefaultAttributeGroup.java         |   2 +
 .../sis/metadata/iso/content/DefaultBand.java      |   3 +-
 .../iso/content/DefaultCoverageDescription.java    |   6 +-
 .../DefaultFeatureCatalogueDescription.java        |   5 +-
 .../iso/content/DefaultFeatureTypeInfo.java        |   3 +-
 .../iso/content/DefaultImageDescription.java       |   7 +-
 .../iso/content/DefaultRangeDimension.java         |   3 +
 .../content/DefaultRangeElementDescription.java    |   3 +
 .../iso/content/DefaultSampleDimension.java        |   5 +-
 .../metadata/iso/distribution/DefaultDataFile.java |   3 +
 .../DefaultDigitalTransferOptions.java             |   7 +-
 .../iso/distribution/DefaultDistribution.java      |   4 +
 .../iso/distribution/DefaultDistributor.java       |   4 +
 .../metadata/iso/distribution/DefaultFormat.java   |   5 +
 .../metadata/iso/distribution/DefaultMedium.java   |   8 +-
 .../distribution/DefaultStandardOrderProcess.java  |   9 +-
 .../iso/extent/DefaultBoundingPolygon.java         |   1 +
 .../sis/metadata/iso/extent/DefaultExtent.java     |   4 +
 .../iso/extent/DefaultGeographicDescription.java   |   1 +
 .../iso/extent/DefaultSpatialTemporalExtent.java   |   4 +-
 .../metadata/iso/extent/DefaultTemporalExtent.java |   1 +
 .../metadata/iso/extent/DefaultVerticalExtent.java |   1 +
 .../apache/sis/metadata/iso/extent/Extents.java    |  55 +-
 .../sis/metadata/iso/extent/package-info.java      |   2 +-
 .../iso/identification/AbstractIdentification.java |  19 +
 .../identification/DefaultAssociatedResource.java  |   2 +
 .../iso/identification/DefaultBrowseGraphic.java   |   3 +
 .../iso/identification/DefaultCoupledResource.java |   4 +
 .../identification/DefaultDataIdentification.java  |   7 +-
 .../iso/identification/DefaultKeywordClass.java    |   3 +-
 .../iso/identification/DefaultKeywords.java        |   4 +-
 .../DefaultOperationChainMetadata.java             |   3 +
 .../identification/DefaultOperationMetadata.java   |   6 +
 .../DefaultRepresentativeFraction.java             |   9 +-
 .../iso/identification/DefaultResolution.java      |   3 +-
 .../DefaultServiceIdentification.java              |  10 +
 .../metadata/iso/identification/DefaultUsage.java  |  10 +-
 .../sis/metadata/iso/lineage/DefaultAlgorithm.java |   2 +
 .../sis/metadata/iso/lineage/DefaultLineage.java   |   7 +-
 .../iso/lineage/DefaultNominalResolution.java      |   2 +-
 .../metadata/iso/lineage/DefaultProcessStep.java   |  10 +
 .../iso/lineage/DefaultProcessStepReport.java      |   3 +
 .../metadata/iso/lineage/DefaultProcessing.java    |   5 +
 .../sis/metadata/iso/lineage/DefaultSource.java    |   9 +
 .../maintenance/DefaultMaintenanceInformation.java |   7 +-
 .../sis/metadata/iso/maintenance/DefaultScope.java |   2 +
 .../iso/maintenance/DefaultScopeDescription.java   |   1 +
 .../sis/metadata/iso/quality/AbstractElement.java  |   9 +-
 .../iso/quality/DefaultConformanceResult.java      |   2 +
 .../iso/quality/DefaultCoverageResult.java         |   4 +
 .../metadata/iso/quality/DefaultDataQuality.java   |   3 +
 .../iso/quality/DefaultQuantitativeResult.java     |   4 +
 .../spatial/AbstractGeolocationInformation.java    |   1 +
 .../sis/metadata/iso/spatial/DefaultDimension.java |   4 +-
 .../sis/metadata/iso/spatial/DefaultGCP.java       |   2 +
 .../metadata/iso/spatial/DefaultGCPCollection.java |   3 +
 .../iso/spatial/DefaultGeometricObjects.java       |   2 +-
 .../metadata/iso/spatial/DefaultGeorectified.java  |   6 +
 .../iso/spatial/DefaultGeoreferenceable.java       |   4 +
 .../spatial/DefaultGridSpatialRepresentation.java  |   3 +-
 .../DefaultVectorSpatialRepresentation.java        |   1 +
 .../java/org/apache/sis/util/iso/AbstractName.java |   1 +
 .../org/apache/sis/util/iso/DefaultLocalName.java  |   2 +
 .../org/apache/sis/util/iso/DefaultMemberName.java |   1 +
 .../org/apache/sis/util/iso/DefaultNameSpace.java  |   1 +
 .../org/apache/sis/util/iso/DefaultRecord.java     |   2 +
 .../org/apache/sis/util/iso/DefaultRecordType.java |   2 +
 .../org/apache/sis/util/iso/RecordDefinition.java  |   1 +
 ...tiesTest.java => ImplementationHelperTest.java} |  30 +-
 .../sis/metadata/iso/DefaultMetadataTest.java      |   3 +-
 .../apache/sis/test/suite/MetadataTestSuite.java   |   2 +-
 .../MultiResolutionCoverageLoaderTest.java         |   2 +-
 .../java/org/apache/sis/geometry/Envelope2D.java   |   2 +-
 .../jaxb/referencing/CC_OperationMethod.java       |   2 +-
 .../sis/internal/referencing/ExtentSelector.java   |  18 +-
 .../apache/sis/internal/referencing/Resources.java |   5 +-
 .../sis/internal/referencing/Resources.properties  |   2 +-
 .../internal/referencing/Resources_fr.properties   |   2 +-
 .../provider/CoordinateFrameRotation.java          |   8 +-
 .../provider/CoordinateFrameRotation2D.java        |   8 +-
 .../provider/CoordinateFrameRotation3D.java        |   8 +-
 .../provider/FranceGeocentricInterpolation.java    |   3 +-
 .../GeocentricAffineBetweenGeographic.java         |   2 +-
 .../provider/GeographicToGeocentric.java           |   2 +-
 .../internal/referencing/provider/Molodensky.java  |   2 +-
 .../referencing/provider/PositionVector7Param.java |   2 +-
 .../provider/PositionVector7Param2D.java           |   2 +-
 .../provider/PositionVector7Param3D.java           |   2 +-
 .../sis/parameter/AbstractParameterDescriptor.java |   2 +-
 .../sis/parameter/DefaultParameterDescriptor.java  |   6 +-
 .../parameter/DefaultParameterDescriptorGroup.java |   5 +-
 .../sis/parameter/DefaultParameterValue.java       |   7 +-
 .../sis/parameter/DefaultParameterValueGroup.java  |   8 +-
 .../apache/sis/parameter/FilteredParameters.java   | 114 ++++
 .../org/apache/sis/parameter/ParameterFormat.java  |   1 +
 .../apache/sis/parameter/ParameterValueList.java   |   2 +
 .../java/org/apache/sis/parameter/Parameters.java  |  43 ++
 .../org/apache/sis/parameter/TensorParameters.java |   1 +
 .../org/apache/sis/parameter/TensorValues.java     |   2 +
 .../sis/parameter/UninitializedParameter.java      |   1 +
 .../parameter/UnmodifiableParameterValueGroup.java |  23 +-
 .../sis/referencing/AbstractIdentifiedObject.java  |   6 +-
 .../sis/referencing/AbstractReferenceSystem.java   |   6 +-
 .../java/org/apache/sis/referencing/Builder.java   |   6 +-
 .../main/java/org/apache/sis/referencing/CRS.java  |  52 +-
 .../java/org/apache/sis/referencing/CommonCRS.java | 109 +++-
 .../apache/sis/referencing/crs/AbstractCRS.java    |   4 +-
 .../sis/referencing/crs/AbstractDerivedCRS.java    |   6 +-
 .../sis/referencing/crs/DefaultEngineeringCRS.java |   4 +-
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |   4 +-
 .../sis/referencing/crs/DefaultImageCRS.java       |   4 +-
 .../sis/referencing/crs/DefaultParametricCRS.java  |   4 +-
 .../sis/referencing/crs/DefaultTemporalCRS.java    |   4 +-
 .../sis/referencing/crs/DefaultVerticalCRS.java    |   4 +-
 .../cs/DefaultCoordinateSystemAxis.java            |  14 +-
 .../sis/referencing/datum/AbstractDatum.java       |  20 +-
 .../sis/referencing/datum/DefaultEllipsoid.java    |   6 +-
 .../referencing/datum/DefaultGeodeticDatum.java    |   6 +-
 .../sis/referencing/datum/DefaultImageDatum.java   |   4 +-
 .../referencing/datum/DefaultPrimeMeridian.java    |   4 +-
 .../referencing/datum/DefaultTemporalDatum.java    |   8 +-
 .../referencing/datum/DefaultVerticalDatum.java    |   4 +-
 .../referencing/factory/CommonAuthorityCode.java   | 159 +++++
 .../factory/CommonAuthorityFactory.java            | 220 +++----
 .../factory/GeodeticAuthorityFactory.java          |   9 +-
 .../factory/MultiAuthoritiesFactory.java           |   4 +-
 .../sis/referencing/factory/package-info.java      |   2 +-
 .../operation/AbstractCoordinateOperation.java     |  21 +-
 .../operation/AbstractSingleOperation.java         |  72 ++-
 .../operation/CoordinateOperationContext.java      |   1 +
 .../operation/CoordinateOperationFinder.java       |   7 +-
 .../operation/CoordinateOperationRegistry.java     |   7 +-
 .../operation/DefaultConcatenatedOperation.java    |   1 +
 .../referencing/operation/DefaultConversion.java   |  12 +-
 .../DefaultCoordinateOperationFactory.java         |   5 +-
 .../sis/referencing/operation/DefaultFormula.java  |   2 +
 .../operation/DefaultOperationMethod.java          |  14 +-
 .../operation/DefaultPassThroughOperation.java     |   5 +-
 .../operation/InverseOperationMethod.java          |   3 +-
 .../operation/builder/ProjectedTransformTry.java   |   4 +-
 .../sis/referencing/operation/package-info.java    |   2 +-
 .../transform/DefaultMathTransformFactory.java     | 214 +++++--
 .../org/apache/sis/geometry/TransformTestCase.java |   7 +-
 .../org/apache/sis/io/wkt/WKTDictionaryTest.java   |   2 +-
 .../parameter/DefaultParameterValueGroupTest.java  |   5 +-
 .../java/org/apache/sis/referencing/CRSTest.java   |  46 +-
 .../org/apache/sis/referencing/CommonCRSTest.java  |  37 +-
 .../factory/CommonAuthorityFactoryTest.java        |  32 +-
 .../operation/CoordinateOperationFinderTest.java   |   2 +-
 .../test/integration/CoordinateOperationTest.java  |  32 +-
 .../org/apache/sis/internal/util/Constants.java    |   9 +-
 .../apache/sis/internal/util/DefinitionURI.java    |  14 +-
 .../org/apache/sis/internal/util/Numerics.java     |  12 +-
 .../java/org/apache/sis/internal/util/URLs.java    |   7 +-
 .../java/org/apache/sis/math/MathFunctions.java    |   2 +-
 .../main/java/org/apache/sis/math/Statistics.java  |  17 +-
 .../java/org/apache/sis/util/CharSequences.java    |   2 +-
 .../java/org/apache/sis/util/collection/Cache.java | 163 +++--
 .../sis/util/collection/TreeTableFormat.java       |   2 +-
 .../apache/sis/util/collection/package-info.java   |   2 +-
 .../java/org/apache/sis/util/resources/Errors.java |  10 +
 .../apache/sis/util/resources/Errors.properties    |   2 +
 .../apache/sis/util/resources/Errors_fr.properties |   2 +
 .../sis/internal/util/CheckedArrayListTest.java    |   3 +-
 .../org/apache/sis/internal/util/NumericsTest.java |   5 +-
 .../test/java/org/apache/sis/test/TestRunner.java  |   3 +-
 .../util/collection/FrequencySortedSetTest.java    |   3 +-
 .../apache/sis/internal/earth/netcdf/GCOM_C.java   |  18 +-
 .../apache/sis/internal/earth/netcdf/GCOM_W.java   |  15 +-
 .../org/apache/sis/storage/landsat/BandGroup.java  |   4 +-
 .../org/apache/sis/storage/geotiff/DataCube.java   |   8 +-
 .../sis/storage/geotiff/MultiResolutionImage.java  |   8 +-
 .../org/apache/sis/internal/netcdf/Convention.java |  19 +-
 .../apache/sis/internal/netcdf/RasterResource.java |  56 +-
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |  37 +-
 .../sis/internal/netcdf/impl/VariableInfo.java     |   2 +-
 .../sis/internal/netcdf/ucar/DecoderWrapper.java   |  22 +-
 .../internal/storage/DocumentedStoreProvider.java  |   2 +-
 .../sis/internal/storage/GridResourceWrapper.java  |   8 +-
 .../sis/internal/storage/MemoryGridResource.java   |  18 +-
 .../sis/internal/storage/MetadataBuilder.java      |  61 +-
 .../apache/sis/internal/storage/RangeArgument.java |  42 +-
 .../sis/internal/storage/ResourceLineage.java      | 151 +++++
 .../org/apache/sis/internal/storage/Resources.java |  36 ++
 .../sis/internal/storage/Resources.properties      |   9 +-
 .../sis/internal/storage/Resources_fr.properties   |  11 +-
 .../org/apache/sis/internal/storage/csv/Store.java |   2 +-
 .../sis/internal/storage/esri/AsciiGridStore.java  |  13 +-
 .../sis/internal/storage/esri/RawRasterStore.java  |   6 +-
 .../apache/sis/internal/storage/folder/Store.java  |  46 +-
 .../UnstructuredAggregate.java}                    |  27 +-
 .../sis/internal/storage/image/FormatFinder.java   |  34 +-
 .../internal/storage/image/SingleImageStore.java   |   8 +-
 .../internal/storage/image/WorldFileResource.java  |  26 +-
 .../sis/internal/storage/io/InternalOptionKey.java |   3 +
 .../org/apache/sis/storage/CoverageSubset.java     |  37 +-
 .../java/org/apache/sis/storage/DataOptionKey.java |  13 +-
 .../java/org/apache/sis/storage/DataStore.java     |   4 +-
 .../java/org/apache/sis/storage/FeatureSubset.java |  17 +
 .../apache/sis/storage/GridCoverageResource.java   |  13 +-
 .../org/apache/sis/storage/StorageConnector.java   |  36 +-
 .../storage/aggregate/AggregatedFeatureSet.java    |   2 +-
 .../sis/storage/aggregate/AggregatedResource.java  |  57 ++
 .../storage/aggregate/ConcatenatedFeatureSet.java  |  36 +-
 .../aggregate/ConcatenatedGridCoverage.java        | 352 +++++++++++
 .../aggregate/ConcatenatedGridResource.java        | 437 +++++++++++++
 .../sis/storage/aggregate/CoverageAggregator.java  | 290 +++++++++
 .../sis/storage/aggregate/DimensionSelector.java   | 150 +++++
 .../apache/sis/storage/aggregate/GridSlice.java    | 233 +++++++
 .../sis/storage/aggregate/GridSliceLocator.java    | 217 +++++++
 .../org/apache/sis/storage/aggregate/Group.java    | 105 ++++
 .../sis/storage/aggregate/GroupAggregate.java      | 296 +++++++++
 .../apache/sis/storage/aggregate/GroupByCRS.java   | 103 +++
 .../sis/storage/aggregate/GroupBySample.java       |  99 +++
 .../sis/storage/aggregate/GroupByTransform.java    | 163 +++++
 .../storage/aggregate/JoinFeatureSet.java          |   2 +-
 .../sis/storage/aggregate/MergeStrategy.java       | 184 ++++++
 .../storage/aggregate/package-info.java            |  11 +-
 .../apache/sis/storage/event/StoreListeners.java   |  27 +-
 .../org/apache/sis/storage/FeatureQueryTest.java   |   2 +-
 .../org/apache/sis/storage/GridResourceMock.java   |   6 +-
 .../aggregate/ConcatenatedFeatureSetTest.java      |   2 +-
 .../storage/aggregate/JoinFeatureSetTest.java      |   2 +-
 .../apache/sis/test/suite/StorageTestSuite.java    |   4 +-
 297 files changed, 7458 insertions(+), 1909 deletions(-)

diff --cc 
application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesFormatter.java
index 0000000000,8df7202973..dd0d50c4d4
mode 000000,100644..100644
--- 
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesFormatter.java
+++ 
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/ValuesFormatter.java
@@@ -1,0 -1,522 +1,522 @@@
+ /*
+  * 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.gui.map;
+ 
+ import java.util.List;
+ import java.util.Map;
+ import java.util.HashMap;
+ import java.util.HashSet;
+ import java.util.BitSet;
+ import java.util.Locale;
+ import java.util.Optional;
+ import java.text.FieldPosition;
+ import java.text.NumberFormat;
+ import java.text.DecimalFormat;
+ import javax.measure.Unit;
+ import org.opengis.geometry.DirectPosition;
+ import org.opengis.metadata.content.TransferFunctionType;
+ import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ import org.apache.sis.geometry.AbstractDirectPosition;
+ import org.apache.sis.referencing.operation.transform.TransferFunction;
+ import org.apache.sis.coverage.grid.GridExtent;
+ import org.apache.sis.coverage.grid.GridCoverage;
+ import org.apache.sis.coverage.SampleDimension;
+ import org.apache.sis.coverage.Category;
+ import org.apache.sis.internal.system.Modules;
+ import org.apache.sis.math.DecimalFunctions;
+ import org.apache.sis.math.MathFunctions;
+ import org.apache.sis.measure.NumberRange;
+ import org.apache.sis.measure.UnitFormat;
+ import org.apache.sis.util.Characters;
+ import org.apache.sis.util.logging.Logging;
+ 
+ import static java.util.logging.Logger.getLogger;
+ 
+ // Branch-dependent imports
 -import org.opengis.coverage.CannotEvaluateException;
++import org.apache.sis.coverage.CannotEvaluateException;
+ 
+ 
+ /**
+  * Fetches values from the coverage and formats them. This task is executed 
in a background thread
+  * because calls to {@link GridCoverage#render(GridExtent)} can take an 
arbitrary amount of time.
+  * The same {@code Formatter} instance can be reused as long as the 
configuration does not change.
+  *
+  * <p>As a rule of thumbs, all fields in {@link ValuesFromCoverage} class 
shall be read and written
+  * from the JavaFX thread, while all fields in this {@code Formatter} class 
can be read and written
+  * from a background thread.</p>
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.3
+  * @since   1.1
+  * @module
+  */
+ final class ValuesFormatter extends ValuesUnderCursor.Formatter {
+     /**
+      * The separator to insert between sample values. We use EM space.
+      */
+     private static final char SEPARATOR = '\u2003';
+ 
+     /**
+      * Pseudo amount of fraction digits for default format.
+      * Used when we don't know how many fraction digits to use.
+      */
+     private static final int DEFAULT_FORMAT = -1;
+ 
+     /**
+      * Pseudo amount of fraction digits for scientific notation.
+      */
+     private static final int SCIENTIFIC_NOTATION = -2;
+ 
+     /**
+      * The object computing or interpolation sample values in the coverage.
+      */
+     private final GridCoverage.Evaluator evaluator;
+ 
+     /**
+      * Formatter for the values computed or interpolated by {@link 
#evaluator}.
+      * The number of fraction digits is computed from transfer function 
resolution.
+      * The same {@link NumberFormat} instance may appear at more than one 
index.
+      * Array shall not be modified after construction.
+      */
+     private final NumberFormat[] sampleFormats;
+ 
+     /**
+      * Buffer where to format the textual content.
+      * We use this buffer as a synchronization lock because this class is 
already synchronized,
+      * so synchronizing on {@cod buffer} allows us to use only one lock.
+      */
+     private final StringBuffer buffer;
+ 
+     /**
+      * Ignored but required by {@link NumberFormat}.
+      */
+     private final FieldPosition field;
+ 
+     /**
+      * Unit symbol to write after each value.
+      * Array shall not be modified after construction.
+      */
+     private final String[] units;
+ 
+     /**
+      * The text to show when value under cursor is a NaN value.
+      * Values are packed with band number in low bits and float ordinal value 
in high bits.
+      * Map content shall not be modified after construction.
+      *
+      * @see #toNodataKey(int, float)
+      * @see MathFunctions#toNanOrdinal(float)
+      */
+     private final Map<Long,String> nodata;
+ 
+     /**
+      * The text to show when cursor is outside coverage area. It should 
contain dimension names, for example "(SST)".
+      * May be {@code null} if {@link #setSelectedBands(BitSet, String[], 
HashSet)} needs to be invoked.
+      */
+     private String outsideText;
+ 
+     /**
+      * The selection status of each band at the time of {@link 
#setSelectedBands(BitSet, String[], HashSet)} invocation.
+      * We need a copy of {@link ValuesFromCoverage} field because the two 
sets are read and updated in different threads.
+      * This set should not be modified; instead, copy should be made.
+      *
+      * @see ValuesFromCoverage#selectedBands
+      */
+     private BitSet selectedBands;
+ 
+     /**
+      * Non-null when a new slice needs to be passed to {@link #evaluator}.
+      * A new value is set when {@link CoverageCanvas#sliceExtentProperty} 
changed.
+      * This value is reset to {@code null} after the slice has been taken in 
account.
+      *
+      * @see #setSlice(GridExtent)
+      */
+     private GridExtent newSlice;
+ 
+     /**
+      * Creates a new formatter for the specified coverage.
+      * This constructor should be invoked in a background thread.
+      *
+      * @param owner     the instance which will evaluate values under cursor 
position.
+      * @param inherit   formatter from which to inherit band configuration, 
or {@code null} if none.
+      * @param coverage  new coverage. Shall not be null.
+      * @param slice     initial value of {@link #newSlice}.
+      * @param bands     sample dimensions of the new coverage.
+      * @param locale    locale of number formats to create.
+      */
+     ValuesFormatter(final ValuesUnderCursor owner, final ValuesFormatter 
inherit, final GridCoverage coverage,
+                     final GridExtent slice, final List<SampleDimension> 
bands, final Locale locale)
+     {
+         super(owner);
+         buffer    = new StringBuffer();
+         field     = new FieldPosition(0);
+         newSlice  = slice;
+         evaluator = coverage.forConvertedValues(true).evaluator();
+         evaluator.setNullIfOutside(true);
+         evaluator.setWraparoundEnabled(true);
+         selectedBands = new BitSet();
+         if (inherit != null) {
+             // Same configuration than previous coverage.
+             synchronized (inherit.buffer) {
+                 units         = inherit.units;
+                 nodata        = inherit.nodata;
+                 outsideText   = inherit.outsideText;
+                 sampleFormats = inherit.sampleFormats.clone();
+                 for (int i=0; i < sampleFormats.length; i++) {
+                     sampleFormats[i] = (NumberFormat) 
sampleFormats[i].clone();
+                 }
+             }
+             return;
+         }
+         final int numBands = bands.size();
+         sampleFormats = new NumberFormat[numBands];
+         units         = new String[numBands];
+         nodata        = new HashMap<>();
+         /*
+          * Loop below initializes number formats and unit symbols for all 
bands, regardless
+          * if selected or not. We do that on the assumption that the same 
format and symbol
+          * are typically shared by all bands.
+          */
+         final Map<Integer,NumberFormat> sharedFormats = new HashMap<>();
+         final Map<Unit<?>,String>       sharedSymbols = new HashMap<>();
+         final UnitFormat                unitFormat    = new 
UnitFormat(locale);
+         for (int b=0; b<numBands; b++) {
+             /*
+              * Build the list of texts to show for missing values. A coverage 
can have
+              * different NaN values representing different kind of missing 
values.
+              */
+             final SampleDimension sd = bands.get(b);
+             for (final Category c : 
sd.forConvertedValues(true).getCategories()) {
+                 final float value = ((Number) 
c.getSampleRange().getMinValue()).floatValue();
+                 if (Float.isNaN(value)) try {
+                     nodata.putIfAbsent(toNodataKey(b, value), 
c.getName().toString(locale));
+                 } catch (IllegalArgumentException e) {
+                     recoverableException("changed", e);
+                 }
+             }
+             /*
+              * Format in advance the units of measurement. If none, an empty 
string is used.
+              * Note: it is quite common that all bands use the same unit of 
measurement.
+              */
+             units[b] = sd.getUnits().map((unit) -> 
sharedSymbols.computeIfAbsent(unit,
+                                           (key) -> format(unitFormat, 
key))).orElse("");
+             /*
+              * Infer a number of fraction digits to use for the resolution of 
sample values in each band.
+              */
+             final SampleDimension isd = sd.forConvertedValues(false);
+             final Integer nf = isd.getTransferFunctionFormula().map(
+                     (formula) -> suggestFractionDigits(formula, 
isd)).orElse(DEFAULT_FORMAT);
+             /*
+              * Create number formats with a number of fraction digits 
inferred from sample value resolution.
+              * The same format instances are shared when possible. Keys are 
the number of fraction digits.
+              * Special values:
+              *
+              *   - Key  0 is for integer values.
+              *   - Key -1 is for default format with unspecified number of 
fraction digits.
+              *   - Key -2 is for scientific notation.
+              */
+             sampleFormats[b] = sharedFormats.computeIfAbsent(nf, (precision) 
-> {
+                 switch (precision) {
+                     case 0:              return 
NumberFormat.getIntegerInstance(locale);
+                     case DEFAULT_FORMAT: return 
NumberFormat.getNumberInstance(locale);
+                     case SCIENTIFIC_NOTATION: {
+                         final NumberFormat format = 
NumberFormat.getNumberInstance(locale);
+                         if (precision == SCIENTIFIC_NOTATION && format 
instanceof DecimalFormat) {
+                             ((DecimalFormat) format).applyPattern("0.000E00");
+                         }
+                         return format;
+                     }
+                     default: {
+                         final NumberFormat format = 
NumberFormat.getNumberInstance(locale);
+                         format.setMinimumFractionDigits(precision);
+                         format.setMaximumFractionDigits(precision);
+                         return format;
+                     }
+                 }
+             });
+         }
+     }
+ 
+     /**
+      * Formats the unit symbol to append after a sample value. The unit 
symbols are created in advance
+      * and reused for all sample value formatting as long as the sample 
dimensions do not change.
+      */
+     private String format(final UnitFormat format, final Unit<?> unit) {
+         synchronized (buffer) {         // Take lock once instead of at each 
StringBuffer method call.
+             buffer.setLength(0);
+             format.format(unit, buffer, field);
+             if (buffer.length() != 0 && 
Character.isLetterOrDigit(buffer.codePointAt(0))) {
+                 buffer.insert(0, Characters.NO_BREAK_SPACE);
+             }
+             return buffer.toString();
+         }
+     }
+ 
+     /**
+      * Formats the widest text that we expect. This text is used for 
computing the label width.
+      * Also computes the text to show when cursor is outside coverage area. 
This method is invoked
+      * when the bands selection changed, either because of selection in 
contextual menu or because
+      * {@link ValuesUnderCursor} is providing data for a new coverage.
+      *
+      * <p>We use {@link ValuesFromCoverage#needsBandRefresh} as a flag 
meaning that this method needs to be invoked.
+      * This method invocation sometime needs to be delayed because 
calculation of text width may be wrong
+      * (produce 0 values) if invoked before {@link StatusBar#sampleValues} 
label is added in the scene graph.</p>
+      *
+      * <p>This method uses the same synchronization lock than {@link 
#evaluate(DirectPosition)}.
+      * Consequently this method may block if data loading are in progress in 
another thread.</p>
+      *
+      * @param  selection  copy of {@link ValuesFromCoverage#selectedBands} 
made by the caller in JavaFX thread.
+      * @param  labels     labels of {@link ValuesFromCoverage#valueChoices} 
menu items computed by caller in JavaFX thread.
+      * @param  others     an initially empty set where to put textual 
representation of "no data" values.
+      * @return the text to use as a prototype for sample values.
+      */
+     final String setSelectedBands(final BitSet selection, final String[] 
labels, final HashSet<String> others) {
+         synchronized (buffer) {
+             final List<SampleDimension> bands = 
evaluator.getCoverage().getSampleDimensions();
+             final StringBuilder names = new StringBuilder().append('(');
+             buffer.setLength(0);
+             for (int i = -1; (i = selection.nextSetBit(i+1)) >= 0;) {
+                 if (buffer.length() != 0) {
+                     buffer.append(SEPARATOR);
+                     names.append(", ");
+                 }
+                 names.append(labels[i]);
+                 final int start = buffer.length();
+                 final Comparable<?>[] sampleValues = 
bands.get(i).forConvertedValues(true)
+                         .getSampleRange().map((r) -> new Comparable<?>[] 
{r.getMinValue(), r.getMaxValue()})
+                         .orElseGet(() -> new Comparable<?>[] {0xFFFF});       
          // Arbitrary value.
+                 for (final Comparable<?> value : sampleValues) {
+                     final int end = buffer.length();
+                     sampleFormats[i].format(value, buffer, field);
+                     final int length = buffer.length();
+                     if (length - end >= end - start) {
+                         buffer.delete(start, end);      // Delete first 
number if it was shorter.
+                     } else {
+                         buffer.setLength(end);          // Delete second 
number if it is shorter.
+                     }
+                 }
+                 buffer.append(units[i]);
+             }
+             final String text = buffer.toString();
+             /*
+              * At this point, `text` is the longest string of numerical 
values that we expect.
+              * We also need to take in account the width required for 
displaying "no data" labels.
+              * If a "no data" label is shown, it will be shown alone (we do 
not need to compute a
+              * sum of "no data" label widths).
+              */
+             for (final Map.Entry<Long,String> other : nodata.entrySet()) {
+                 if (selection.get(other.getKey().intValue())) {
+                     others.add(other.getValue());
+                 }
+             }
+             outsideText = text.isEmpty() ? "" : names.append(')').toString();
+             selectedBands = selection;              // Set only on success.
+             return text;
+         }
+     }
+ 
+     /**
+      * Sets the slice in grid coverages where sample values should be 
evaluated for next positions.
+      * The given slice will apply to all positions formatted after this 
method call,
+      * until this method is invoked again for a new slice.
+      *
+      * <p>This method shall be synchronized on the same lock than {@link 
#copy(DirectPosition)},
+      * which is the lock used by {@link #evaluateLater(DirectPosition)}.</p>
+      *
+      * @param  slice  grid coverage slice where to evaluate the sample values.
+      */
+     final synchronized void setSlice(final GridExtent slice) {
+         newSlice = slice;
+     }
+ 
+     /**
+      * Position of next point to evaluate, together with the grid slice where 
sample values should be evaluated.
+      * Those two information are kept together because they are closely 
related: the slice depends on position
+      * in dimensions not necessarily expressed in the given {@link 
DirectPosition}, and we want to take those
+      * two information in the same synchronized block.
+      */
+     private static final class Position extends AbstractDirectPosition {
+         /** Coordinates of this position. */
+         private final double[] coordinates;
+ 
+         /** Coordinate reference system of this position. */
+         private final CoordinateReferenceSystem crs;
+ 
+         /**
+          * Non-null when a new slice needs to be passed to {@link #evaluator}.
+          * Should be null if the slice did not changed since last invocation.
+          * This is a copy of {@link ValuesFormatter#newSlice}.
+          */
+         final GridExtent newSlice;
+ 
+         /**
+          * Creates a copy of the given position. If {@link #evaluator} needs 
to be set
+          * to a new default slice position in order to evaluate the given 
coordinates,
+          * that position should be given as a non-null {@code slice} argument.
+          */
+         Position(final DirectPosition position, final GridExtent slice) {
+             coordinates = position.getCoordinate();
+             crs         = position.getCoordinateReferenceSystem();
+             newSlice    = slice;
+         }
+ 
+         /** Returns the number of dimensions of this position. */
+         @Override public int getDimension() {
+             return coordinates.length;
+         }
+ 
+         /** Returns the coordinate value in given dimension. */
+         @Override public double getOrdinate(final int dimension) {
+             return coordinates[dimension];
+         }
+ 
+         /** Returns the CRS of this position, or {@code null} if unspecified. 
*/
+         @Override public CoordinateReferenceSystem 
getCoordinateReferenceSystem() {
+             return crs;
+         }
+     }
+ 
+     /**
+      * Invoked in JavaFX thread for creating a copy of the given position 
together with related information.
+      * The related information is the grid coverage slice where the given 
position can be evaluated.
+      * The instance returned by this method will be given to {@link 
#evaluate(DirectPosition)}.
+      *
+      * @param  point  coordinates of the point for which to evaluate the grid 
coverage value.
+      * @return a copy of the given point, augmented with the slice where the 
point can be evaluated.
+      */
+     @Override
+     DirectPosition copy(final DirectPosition point) {
+         assert Thread.holdsLock(this);
+         final Position position = new Position(point, newSlice);
+         newSlice = null;
+         return position;
+     }
+ 
+     /**
+      * Computes a string representation of data under the given position.
+      * The position may be in any CRS; this method will convert coordinates 
as needed.
+      * This method should be invoked in a background thread.
+      *
+      * @param  point  the cursor location in arbitrary CRS, or {@code null} 
if outside canvas region.
+      * @return string representation of data under given position.
+      *
+      * @see GridCoverage.Evaluator#apply(DirectPosition)
+      */
+     @Override
+     public String evaluate(final DirectPosition point) {
+         synchronized (buffer) {
+             buffer.setLength(0);
+             if (point != null) try {
+                 final GridExtent slice = ((Position) point).newSlice;       
// This cast should never fail.
+                 if (slice != null) {
+                     evaluator.setDefaultSlice(slice.getSliceCoordinates());
+                 }
+                 final double[] results = evaluator.apply(point);
+                 if (results != null) {
+                     final BitSet selection = selectedBands;
+                     for (int i = -1; (i = selection.nextSetBit(i+1)) >= 0;) {
+                         if (buffer.length() != 0) {
+                             buffer.append(SEPARATOR);
+                         }
+                         final double value = results[i];
+                         if (Double.isNaN(value)) try {
+                             /*
+                              * If a value is NaN, returns its label as the 
whole content. Numerical values
+                              * in other bands are lost. We do that because 
"no data" strings are often too
+                              * long for being shown together with numerical 
values, and are often the same
+                              * for all bands. Users can see numerical values 
by hiding the band containing
+                              * "no data" values with contextual menu on the 
status bar.
+                              */
+                             final String label = nodata.get(toNodataKey(i, 
(float) value));
+                             if (label != null) {
+                                 return label;
+                             }
+                         } catch (IllegalArgumentException e) {
+                             recoverableException("evaluate", e);
+                         }
+                         sampleFormats[i].format(value, buffer, 
field).append(units[i]);
+                     }
+                     return buffer.toString();
+                 }
+             } catch (CannotEvaluateException e) {
+                 recoverableException("evaluate", e);
+             }
+             /*
+              * Point is considered outside coverage area.
+              * We will write the sample dimension names.
+              */
+             return outsideText;
+         }
+     }
+ 
+     /**
+      * Returns the key to use in {@link #nodata} map for the given "no data" 
value.
+      * The band number can be obtained by {@link Long#intValue()}.
+      *
+      * @param  band   band index.
+      * @param  value  the NaN value used for "no data".
+      * @return key to use in {@link #nodata} map.
+      * @throws IllegalArgumentException if the given value is not a NaN value
+      *         or does not use a supported bits pattern.
+      */
+     private static Long toNodataKey(final int band, final float value) {
+         return (((long) MathFunctions.toNanOrdinal(value)) << Integer.SIZE) | 
band;
+     }
+ 
+     /**
+      * Suggests a number of fraction digits for numbers formatted after 
conversion by the given formula.
+      * This is either a positive number (including 0 for integers), or the 
{@value #SCIENTIFIC_NOTATION}
+      * or {@value #DEFAULT_FORMAT} sentinel values.
+      */
+     private static Integer suggestFractionDigits(final TransferFunction 
formula, final SampleDimension isd) {
+         int nf;
+         if (formula.getType() != TransferFunctionType.LINEAR) {
+             nf = SCIENTIFIC_NOTATION;
+         } else {
+             double resolution = formula.getScale();
+             if (resolution > 0 && resolution <= Double.MAX_VALUE) {     // 
Non-zero, non-NaN and finite.
+                 final Optional<NumberRange<?>> range = isd.getSampleRange();
+                 if (range.isPresent()) {
+                     // See StatusBar.inflatePrecisions for rationale.
+                     resolution *= (0.5 / range.get().getSpan()) + 1;
+                 }
+                 nf = DecimalFunctions.fractionDigitsForDelta(resolution, 
false);
+                 if (nf < -9 || nf > 6) nf = SCIENTIFIC_NOTATION;        // 
Arbitrary thresholds.
+             } else {
+                 nf = DEFAULT_FORMAT;
+             }
+         }
+         return nf;
+     }
+ 
+     /**
+      * Message of the last exception, used for avoiding flooding the logger 
with repetitive errors.
+      *
+      * @see #recoverableException(String, Exception)
+      */
+     private String lastErrorMessage;
+ 
+     /**
+      * Invoked when an exception occurred while computing values.
+      */
+     private void recoverableException(final String method, final Exception e) 
{
+         final String message = e.getMessage();
+         if (!message.equals(lastErrorMessage)) {
+             lastErrorMessage = message;
+             Logging.recoverableException(getLogger(Modules.APPLICATION), 
ValuesUnderCursor.class, method, e);
+         }
+     }
+ }
diff --cc 
core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index aa78630922,1e6f66f825..fe8bf53828
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@@ -85,12 -90,9 +86,13 @@@ import static java.util.logging.Logger.
   * <p>{@code GridExtent} instances are immutable and thread-safe.
   * The same instance can be shared by different {@link GridGeometry} 
instances.</p>
   *
 + * <div class="note"><b>Upcoming API generalization:</b>
 + * this class may implement the {@code GridEnvelope} interface in a future 
Apache SIS version.
 + * This is pending GeoAPI update.</div>
 + *
   * @author  Martin Desruisseaux (IRD, Geomatys)
   * @author  Alexis Manin (Geomatys)
+  * @author  Johann Sorel (Geomatys)
   * @version 1.3
   * @since   1.0
   * @module
diff --cc 
core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
index 86edfcc255,e897bb4829..352f9f5737
--- 
a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
@@@ -169,10 -174,10 +168,10 @@@ public abstract class AbstractAttribute
      private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
          in.defaultReadObject();
          try {
 -            final Attribute<?>[] characterizedBy = (Attribute<?>[]) 
in.readObject();
 +            final AbstractAttribute<?>[] characterizedBy = 
(AbstractAttribute<?>[]) in.readObject();
              if (characterizedBy != null) {
                  characteristics = newCharacteristicsMap();
-                 
characteristics.values().addAll(Arrays.asList(characterizedBy));
+                 Collections.addAll(characteristics.values(), characterizedBy);
              }
          } catch (RuntimeException e) {
              // At least ClassCastException, NullPointerException, 
IllegalArgumentException and IllegalStateException.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
index 01d4ed2d7b,a2de59106e..5349ba1af9
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/simple/CitationConstant.java
@@@ -22,11 -22,11 +22,9 @@@ import java.io.ObjectStreamException
  import org.opengis.metadata.Identifier;
  import org.opengis.metadata.citation.Citation;
  import org.opengis.metadata.citation.CitationDate;
--import org.opengis.metadata.citation.OnlineResource;
  import org.opengis.metadata.citation.PresentationForm;
  import org.opengis.metadata.citation.ResponsibleParty;
  import org.opengis.metadata.citation.Series;
--import org.opengis.metadata.identification.BrowseGraphic;
  import org.opengis.util.InternationalString;
  import org.apache.sis.xml.IdentifierSpace;
  import org.apache.sis.metadata.sql.MetadataSource;
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
index aabcbdfcc8,398d9281c4..1da8b18936
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultExtendedElementInformation.java
@@@ -37,13 -39,8 +37,13 @@@ import org.apache.sis.internal.metadata
  import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
  import org.apache.sis.internal.util.CollectionsExt;
  
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.ensurePositive;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * New metadata element, not found in ISO 19115, which is required to 
describe geographic data.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
index edfea27e0d,7304d37232..51bb08dafa
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
@@@ -226,7 -219,8 +227,8 @@@ public class DefaultMetadata extends IS
      /**
       * Scope to which the metadata applies.
       */
+     @SuppressWarnings("serial")
 -    private Collection<MetadataScope> metadataScopes;
 +    private Collection<DefaultMetadataScope> metadataScopes;
  
      /**
       * Parties responsible for the metadata information.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
index 38f99ed949,a3decc1e6c..bd6a165888
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
@@@ -19,8 -19,9 +19,8 @@@ package org.apache.sis.metadata.iso
  import java.util.List;
  import java.util.Iterator;
  import java.util.Collection;
+ import java.util.Collections;
  import java.util.ConcurrentModificationException;
 -import org.opengis.metadata.MetadataScope;
  import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
  
  
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
index 5d80f31e2f,262cf5c0a5..39d3b02466
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
@@@ -104,7 -108,8 +108,8 @@@ public class DefaultObjective extends I
      /**
       * Event or events associated with objective completion.
       */
+     @SuppressWarnings("serial")
 -    private Collection<Event> objectiveOccurrences;
 +    private Collection<Event> objectiveOccurences;
  
      /**
       * Pass of the platform over the objective.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
index 166ad72143,3568dcb4b5..5b9b492448
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
@@@ -39,13 -39,9 +39,13 @@@ import org.apache.sis.metadata.iso.ISOM
  import org.apache.sis.xml.IdentifierSpace;
  import org.apache.sis.xml.IdentifierMap;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
  import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
- import static org.apache.sis.internal.metadata.MetadataUtilities.toDate;
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.toMilliseconds;
+ import static org.apache.sis.internal.metadata.ImplementationHelper.toDate;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.toMilliseconds;
  
  
  /**
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
index 479685a357,293f4c9f36..65ffcbe43f
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOrganisation.java
@@@ -76,7 -64,8 +77,8 @@@ public class DefaultOrganisation extend
      /**
       * Individuals in the named organization.
       */
+     @SuppressWarnings("serial")
 -    private Collection<Individual> individual;
 +    private Collection<DefaultIndividual> individual;
  
      /**
       * Constructs an initially empty organization.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
index df1e9b5636,d8a1b51ca7..b5d2019b6f
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultResponsibility.java
@@@ -100,7 -89,8 +101,8 @@@ public class DefaultResponsibility exte
      /**
       * Information about the parties.
       */
+     @SuppressWarnings("serial")
 -    private Collection<Party> parties;
 +    private Collection<AbstractParty> parties;
  
      /**
       * Constructs an initially empty responsible party.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
index a90b82555c,c6103f1897..58f3cfd29b
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultConstraints.java
@@@ -106,12 -106,14 +110,14 @@@ public class DefaultConstraints extend
      /**
       * Information concerning the parties to whom the resource can or cannot 
be released.
       */
+     @SuppressWarnings("serial")
 -    private Releasability releasability;
 +    private DefaultReleasability releasability;
  
      /**
       * Party responsible for the resource constraints.
       */
+     @SuppressWarnings("serial")
 -    private Collection<Responsibility> responsibleParties;
 +    private Collection<DefaultResponsibility> responsibleParties;
  
      /**
       * Constructs an initially empty constraints.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
index 410cbb21e1,a4ad2499dd..eece6ce4a0
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/constraint/DefaultReleasability.java
@@@ -72,7 -59,8 +72,8 @@@ public class DefaultReleasability exten
      /**
       * Party to which the release statement applies.
       */
+     @SuppressWarnings("serial")
 -    private Collection<Responsibility> addressees;
 +    private Collection<DefaultResponsibility> addressees;
  
      /**
       * Release statement.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java
index 9046ca2d63,74e61a8799..89499ad1f1
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultBand.java
@@@ -31,13 -31,8 +31,13 @@@ import org.apache.sis.measure.ValueRang
  import org.apache.sis.internal.jaxb.gco.GO_Real;
  import org.apache.sis.internal.jaxb.gco.UnitAdapter;
  
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.ensurePositive;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Range of wavelengths in the electromagnetic spectrum.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
index 11d3d9b95b,3cd043289e..2b4b2ff4a7
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultCoverageDescription.java
@@@ -37,11 -38,7 +37,11 @@@ import org.apache.sis.internal.metadata
  import org.apache.sis.internal.jaxb.FilterByVersion;
  import org.apache.sis.internal.jaxb.metadata.MD_Identifier;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.valueIfDefined;
  
  
  /**
@@@ -100,7 -99,8 +102,8 @@@ public class DefaultCoverageDescriptio
      /**
       * Information on attribute groups of the resource.
       */
+     @SuppressWarnings("serial")
 -    private Collection<AttributeGroup> attributeGroups;
 +    private Collection<DefaultAttributeGroup> attributeGroups;
  
      /**
       * Provides the description of the specific range elements of a coverage.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
index 5c54394bc4,0d6328df8c..8c08446d42
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureCatalogueDescription.java
@@@ -33,12 -34,7 +33,12 @@@ import org.apache.sis.internal.metadata
  import org.apache.sis.internal.metadata.legacy.LegacyPropertyAdapter;
  import org.apache.sis.internal.jaxb.lan.LocaleAndCharset;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Obligation.CONDITIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.valueIfDefined;
  
  
  /**
@@@ -108,7 -105,8 +109,8 @@@ public class DefaultFeatureCatalogueDes
      /**
       * Subset of feature types from cited feature catalogue occurring in 
resource.
       */
+     @SuppressWarnings("serial")
 -    private Collection<FeatureTypeInfo> featureTypes;
 +    private Collection<DefaultFeatureTypeInfo> featureTypes;
  
      /**
       * Complete bibliographic reference to one or more external feature 
catalogues.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
index 6ba9464370,d0cdbb0171..eef2eea971
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultFeatureTypeInfo.java
@@@ -24,14 -25,8 +24,14 @@@ import org.apache.sis.measure.ValueRang
  import org.apache.sis.metadata.TitleProperty;
  import org.apache.sis.metadata.iso.ISOMetadata;
  
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.ensurePositive;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Obligation.MANDATORY;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Information about the occurring feature type.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
index 3797ccc5f6,9123887af7..83f8cf6d67
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/content/DefaultSampleDimension.java
@@@ -33,14 -34,8 +33,14 @@@ import org.apache.sis.internal.jaxb.gco
  import org.apache.sis.internal.jaxb.gco.GO_Record;
  import org.apache.sis.internal.jaxb.gco.GO_RecordType;
  
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.ensurePositive;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Obligation.CONDITIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * The characteristic of each dimension (layer) included in the resource.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
index b72959d770,312ad36da6..0c4e655c70
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultDigitalTransferOptions.java
@@@ -35,13 -35,8 +35,13 @@@ import org.apache.sis.internal.jaxb.gts
  import org.apache.sis.internal.jaxb.FilterByVersion;
  import org.apache.sis.internal.util.CollectionsExt;
  
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.ensurePositive;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Technical means and media by which a resource is obtained from the 
distributor.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
index 7062ba20ed,12a0bd6b4a..a300fdac34
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultMedium.java
@@@ -41,13 -43,7 +41,13 @@@ import org.apache.sis.internal.xml.Lega
  import org.apache.sis.internal.util.CollectionsExt;
  import org.apache.sis.internal.util.CodeLists;
  
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.ensurePositive;
++import static 
org.apache.sis.internal.metadata.ImplementationHelper.valueIfDefined;
 +
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
  
  
  /**
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
index 35a3c662de,2a5df18360..570a5f454a
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java
@@@ -30,12 -30,8 +30,12 @@@ import org.apache.sis.internal.jaxb.gco
  import org.apache.sis.internal.jaxb.gco.GO_Record;
  import org.apache.sis.metadata.iso.ISOMetadata;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
- import static org.apache.sis.internal.metadata.MetadataUtilities.toDate;
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.toMilliseconds;
+ import static org.apache.sis.internal.metadata.ImplementationHelper.toDate;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.toMilliseconds;
  
  
  /**
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
index c291138f20,bc777cc120..5733cbb7e8
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultSpatialTemporalExtent.java
@@@ -29,11 -29,7 +29,11 @@@ import org.opengis.metadata.extent.Spat
  import org.opengis.referencing.operation.TransformException;
  import org.apache.sis.internal.metadata.ReferencingServices;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.valueIfDefined;
  
  
  /**
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
index 7a7932bfde,9ed63cf613..6c8e219268
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
@@@ -119,6 -123,52 +124,52 @@@ public final class Extents extends Stat
          WORLD = world;
      }
  
+     /**
+      * Returns the extents found in all {@code Identification} elements of 
the given metadata.
+      * If there is only one {@link Identification} element (which is the 
usual case), then its
+      * collection of extents is returned <em>as-is</em>; the collection is 
not copied.
+      *
+      * <p>In the less usual case where there is many {@link Identification} 
elements providing
+      * non-empty collection of extents, then this method returns the union of 
all collections
+      * without duplicated elements (duplication determined by {@link 
Object#equals(Object)}).
+      * In the special case where the first non-empty collection of extents 
contains all other
+      * collections, then that collection is returned <em>as-is</em>.</p>
+      *
+      * <div class="note"><b>Rational:</b>
+      * above policy makes a best effort for avoiding to create new 
collections.
+      * The reason is that collection implementations may perform lazy 
calculations of {@link Extent} elements.
+      * This method tries to preserve the lazy behavior (if any).</div>
+      *
+      * @param  metadata  the metadata, or {@code null} if none.
+      * @return extents found in all {@link Identification} elements, or an 
empty collection if none.
+      *
+      * @since 1.3
+      */
+     public static Collection<? extends Extent> fromIdentificationInfo(final 
Metadata metadata) {
+         Collection<? extends Extent> result = null;
+         if (metadata != null) {
+             Set<Extent> union = null;
+             for (final Identification id : 
nonNull(metadata.getIdentificationInfo())) {
 -                if (id != null) {       // Should not be allowed, but we are 
paranoiac.
 -                    final Collection<? extends Extent> extents = 
id.getExtents();
++                if (id instanceof DataIdentification) {
++                    final Collection<? extends Extent> extents = 
((DataIdentification) id).getExtents();
+                     if (extents != result && !isNullOrEmpty(extents)) {
+                         if (result == null) {
+                             result = extents;
+                         } else {
+                             if (union == null) {
+                                 union = new LinkedHashSet<>(result);
+                             }
+                             if (union.addAll(extents)) {
+                                 result = union;
+                             }
+                         }
+                     }
+                 }
+             }
+         }
+         return (result != null) ? result : Collections.emptyList();
+     }
+ 
      /**
       * Returns a single geographic bounding box from the specified metadata. 
If the given metadata
       * contains many {@link Identification} or many {@link Extent} instances, 
then this method returns
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
index bad2442f8f,14d6af33e6..cfbad98d14
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/AbstractIdentification.java
@@@ -170,8 -172,15 +177,9 @@@ public class AbstractIdentification ext
      /**
       * Factor which provides a general understanding of the density of 
spatial data in the resource(s).
       */
+     @SuppressWarnings("serial")
      private Collection<Resolution> spatialResolutions;
  
 -    /**
 -     * Smallest resolvable temporal period in a resource.
 -     */
 -    @SuppressWarnings("serial")
 -    private Collection<Duration> temporalResolutions;
 -
      /**
       * Main theme(s) of the resource.
       */
@@@ -226,7 -245,8 +244,8 @@@
      /**
       * Provides aggregate dataset information.
       */
+     @SuppressWarnings("serial")
 -    private Collection<AssociatedResource> associatedResources;
 +    private Collection<DefaultAssociatedResource> associatedResources;
  
      /**
       * Constructs an initially empty identification.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
index 7e646ea003,309dc69d2e..0f52fd7330
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultCoupledResource.java
@@@ -102,7 -93,8 +105,8 @@@ public class DefaultCoupledResource ext
      /**
       * The service operation.
       */
+     @SuppressWarnings("serial")
 -    private OperationMetadata operation;
 +    private DefaultOperationMetadata operation;
  
      /**
       * Constructs an initially empty coupled resource.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
index c086746039,a71c14cced..e623f2042e
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultKeywords.java
@@@ -91,7 -89,8 +93,8 @@@ public class DefaultKeywords extends IS
       * User-defined categorization of groups of keywords that extend or are 
orthogonal
       * to the standardized {@linkplain #getType() keyword type} codes.
       */
+     @SuppressWarnings("serial")
 -    private KeywordClass keywordClass;
 +    private DefaultKeywordClass keywordClass;
  
      /**
       * Constructs an initially empty keywords.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
index d6130a9ded,f5571256c5..9cf68b68d0
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationChainMetadata.java
@@@ -95,7 -84,8 +97,8 @@@ public class DefaultOperationChainMetad
      /**
       * Information about the operations applied by the chain.
       */
+     @SuppressWarnings("serial")
 -    private List<OperationMetadata> operations;
 +    private List<DefaultOperationMetadata> operations;
  
      /**
       * Constructs an initially empty operation chain metadata.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
index e099dbf9ff,4aebb7f7fe..39b99eefd3
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultOperationMetadata.java
@@@ -98,7 -82,8 +98,8 @@@ public class DefaultOperationMetadata e
      /**
       * Distributed computing platforms on which the operation has been 
implemented.
       */
+     @SuppressWarnings("serial")
 -    private Collection<DistributedComputingPlatform> 
distributedComputingPlatforms;
 +    private Collection<CodeList<?>> distributedComputingPlatforms;
  
      /**
       * Free text description of the intent of the operation and the results 
of the operation.
@@@ -123,7 -112,8 +128,8 @@@
      /**
       * List of operation that must be completed immediately.
       */
+     @SuppressWarnings("serial")
 -    private List<OperationMetadata> dependsOn;
 +    private List<DefaultOperationMetadata> dependsOn;
  
      /**
       * Constructs an initially empty operation metadata.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java
index f1285826d3,1f8415c212..a17b0b3577
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultResolution.java
@@@ -31,13 -31,8 +31,13 @@@ import org.apache.sis.metadata.iso.ISOM
  import org.apache.sis.measure.ValueRange;
  import org.apache.sis.util.resources.Messages;
  
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.ensurePositive;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.CONDITIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Level of detail expressed as a scale factor or a ground distance.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
index ee128ae2aa,e3554607a0..2cc8f1ac1a
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultServiceIdentification.java
@@@ -122,7 -119,8 +125,8 @@@ public class DefaultServiceIdentificati
      /**
       * Further description of the data coupling in the case of tightly 
coupled services.
       */
+     @SuppressWarnings("serial")
 -    private Collection<CoupledResource> coupledResources;
 +    private Collection<DefaultCoupledResource> coupledResources;
  
      /**
       * References to the resource on which the service operates.
@@@ -142,7 -143,8 +149,8 @@@
      /**
       * Information about the operations that comprise the service.
       */
+     @SuppressWarnings("serial")
 -    private Collection<OperationMetadata> containsOperations;
 +    private Collection<DefaultOperationMetadata> containsOperations;
  
      /**
       * Information on the resources that the service operates on.
@@@ -152,7 -155,8 +161,8 @@@
      /**
       * Information about the chain applied by the service.
       */
+     @SuppressWarnings("serial")
 -    private Collection<OperationChainMetadata> containsChain;
 +    private Collection<DefaultOperationChainMetadata> containsChain;
  
      /**
       * Constructs an initially empty service identification.
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java
index 513d1bf2dc,b1a92cfc3b..7af1c67b4f
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultUsage.java
@@@ -30,12 -30,8 +30,12 @@@ import org.apache.sis.metadata.iso.ISOM
  import org.apache.sis.metadata.TitleProperty;
  import org.apache.sis.util.iso.Types;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
- import static org.apache.sis.internal.metadata.MetadataUtilities.toDate;
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.toMilliseconds;
+ import static org.apache.sis.internal.metadata.ImplementationHelper.toDate;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.toMilliseconds;
  
  
  /**
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
index 2e3f0ba852,cc62bd9b5c..2a668201fc
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultMaintenanceInformation.java
@@@ -41,11 -41,7 +41,11 @@@ import org.apache.sis.internal.jaxb.Fil
  import org.apache.sis.internal.xml.LegacyNamespaces;
  import org.apache.sis.internal.util.CollectionsExt;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.valueIfDefined;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.valueIfDefined;
  
  
  /**
diff --cc 
core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
index e546b184df,36a3a4f610..2988b1e42a
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/spatial/DefaultDimension.java
@@@ -30,13 -30,8 +30,13 @@@ import org.apache.sis.metadata.TitlePro
  import org.apache.sis.measure.ValueRange;
  import org.apache.sis.util.ArgumentChecks;
  
- import static 
org.apache.sis.internal.metadata.MetadataUtilities.ensurePositive;
+ import static 
org.apache.sis.internal.metadata.ImplementationHelper.ensurePositive;
  
 +// Branch-specific imports
 +import org.opengis.annotation.UML;
 +import static org.opengis.annotation.Obligation.OPTIONAL;
 +import static org.opengis.annotation.Specification.ISO_19115;
 +
  
  /**
   * Axis properties.
diff --cc 
core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
index 3c40cea517,23ebde8350..9e8348c8c8
--- 
a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
+++ 
b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/DefaultMetadataTest.java
@@@ -21,8 -21,10 +21,9 @@@ import java.util.Locale
  import java.util.Arrays;
  import java.util.Iterator;
  import java.util.Collection;
+ import java.util.Collections;
  import java.net.URISyntaxException;
  import javax.xml.bind.JAXBException;
 -import org.opengis.metadata.MetadataScope;
  import org.opengis.metadata.citation.Citation;
  import org.opengis.metadata.citation.DateType;
  import org.opengis.metadata.maintenance.ScopeCode;
diff --cc 
storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ResourceLineage.java
index 0000000000,a5e3aba17e..4fd058a950
mode 000000,100644..100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ResourceLineage.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ResourceLineage.java
@@@ -1,0 -1,152 +1,151 @@@
+ /*
+  * 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.storage;
+ 
+ import java.util.Collection;
+ import org.opengis.metadata.Metadata;
 -import org.opengis.metadata.MetadataScope;
+ import org.opengis.metadata.extent.Extent;
+ import org.opengis.metadata.citation.Citation;
+ import org.opengis.metadata.maintenance.ScopeCode;
+ import org.opengis.metadata.identification.Resolution;
+ import org.opengis.metadata.identification.Identification;
++import org.opengis.metadata.identification.DataIdentification;
+ import org.opengis.referencing.ReferenceSystem;
+ import org.opengis.util.InternationalString;
+ import org.apache.sis.internal.util.CollectionsExt;
+ import org.apache.sis.metadata.iso.extent.Extents;
+ import org.apache.sis.metadata.iso.lineage.DefaultSource;
+ import org.apache.sis.metadata.iso.maintenance.DefaultScope;
+ 
+ import static org.apache.sis.internal.util.CollectionsExt.nonNull;
+ 
+ 
+ /**
+  * Metadata about a resource which is a single source of another resource.
+  * This is an experimental class which may be revisited in any future version.
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.3
+  * @since   1.3
+  * @module
+  */
+ final class ResourceLineage {
+     /**
+      * Description of the level of the source data, or {@code null} if none.
+      * Current implementation uses the first non-null title of a citation.
+      */
+     private InternationalString description;
+ 
+     /**
+      * Recommended reference to be used for the source data, or {@code null}.
+      * Current implementation uses the first citation provided by {@link 
Metadata#getIdentificationInfo()}.
+      */
+     private Citation sourceCitation;
+ 
+     /**
+      * The type and extent of the source, or {@code null} if none.
+      * Current implementation uses the resource scope declared in source 
metadata,
+      * together with the source extents.
+      */
+     private DefaultScope scope;
+ 
+     /**
+      * She spatial reference system used by the source data, or {@code null} 
if none.
+      * Current implementation uses the first reference system declared by 
metadata.
+      */
+     private ReferenceSystem referenceSystem;
+ 
+     /**
+      * Spatial resolution expressed as a scale factor, an angle or a level of 
detail.
+      * Current implementation uses the first resolution found in 
identification information.
+      */
+     private Resolution resolution;
+ 
+     /**
+      * Returns {@code false} if this object has at least one non-null value.
+      */
+     final boolean isEmpty() {
+         return description == null && sourceCitation == null && scope == null
+                 && referenceSystem == null && resolution == null;
+     }
+ 
+     /**
+      * Collects information about a source of the derived resource for which 
to provide lineage.
+      *
+      * @param  source  metadata of a source of the derived resource for which 
to provide lineage.
+      */
+     ResourceLineage(final Metadata source) {
+         referenceSystem = 
CollectionsExt.first(source.getReferenceSystemInfo());
+         for (final Identification info : 
nonNull(source.getIdentificationInfo())) {
+             final Citation citation = info.getCitation();
+             if (citation != null) {
+                 if (sourceCitation == null) {
+                     sourceCitation = citation;
+                 }
+                 if (description == null) {
+                     description = citation.getTitle();
+                 }
+             }
 -            if (resolution == null) {
 -                for (final Resolution candidate : 
nonNull(info.getSpatialResolutions())) {
++            if (resolution == null && info instanceof DataIdentification) {
++                for (final Resolution candidate : 
nonNull(((DataIdentification) info).getSpatialResolutions())) {
+                     if (candidate != null) {
+                         resolution = candidate;
+                     }
+                 }
+             }
+         }
+         final ScopeCode level = getScopeLevel(source);
+         final Collection<? extends Extent> extents = 
Extents.fromIdentificationInfo(source);
+         if (level != null || !extents.isEmpty()) {
+             scope = new DefaultScope(level);
+             scope.setExtents(extents);
+         }
+     }
+ 
+     /**
+      * Returns the type (coverage, feature, …) of the source to be stored in 
the "level" attribute of the scope.
+      *
+      * @return scope level (coverage, feature, …), or {@code null} if none.
+      */
+     private static ScopeCode getScopeLevel(final Metadata source) {
+         ScopeCode level = null;
 -        for (final MetadataScope ms : nonNull(source.getMetadataScopes())) {
 -            final ScopeCode c = ms.getResourceScope();
++        for (final ScopeCode c : nonNull(source.getHierarchyLevels())) {
+             if (c != null) {
+                 if (level == null) {
+                     level = c;
+                 } else if (!level.equals(c)) {
+                     level = null;
+                     break;
+                 }
+             }
+         }
+         return level;
+     }
+ 
+     /**
+      * Creates an ISO 19115 metadata object from the information collected in 
this class.
+      */
+     final DefaultSource build() {
+         final DefaultSource source = new DefaultSource();
+         source.setDescription(description);
+         source.setSourceCitation(sourceCitation);
+         source.setScope(scope);
+         source.setSourceReferenceSystem(referenceSystem);
+         source.setSourceSpatialResolution(resolution);
+         return source;
+     }
+ }
diff --cc 
storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
index 0000000000,b3672cffa7..1a30706929
mode 000000,100644..100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridCoverage.java
@@@ -1,0 -1,352 +1,352 @@@
+ /*
+  * 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.storage.aggregate;
+ 
+ import java.util.List;
+ import java.util.ArrayList;
+ import java.util.logging.Logger;
+ import java.awt.image.RenderedImage;
++import org.opengis.referencing.operation.TransformException;
+ import org.apache.sis.coverage.SampleDimension;
+ import org.apache.sis.coverage.grid.GridExtent;
+ import org.apache.sis.coverage.grid.GridGeometry;
+ import org.apache.sis.coverage.grid.GridCoverage;
+ import org.apache.sis.coverage.SubspaceNotSpecifiedException;
+ import org.apache.sis.coverage.grid.DisjointExtentException;
+ import org.apache.sis.storage.GridCoverageResource;
+ import org.apache.sis.storage.DataStoreException;
+ import org.apache.sis.internal.storage.Resources;
+ import org.apache.sis.internal.system.Modules;
+ import org.apache.sis.internal.util.Numerics;
+ import org.apache.sis.util.collection.Cache;
+ import org.apache.sis.util.logging.Logging;
+ 
+ // Branch-dependent imports
 -import org.opengis.coverage.CannotEvaluateException;
 -import org.opengis.referencing.operation.TransformException;
++import org.apache.sis.coverage.CannotEvaluateException;
+ 
+ 
+ /**
+  * A grid coverage where a single dimension is the concatenation of many grid 
coverages.
+  * All components must have the same "grid to CRS" transform, except for a 
translation term.
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.3
+  * @since   1.3
+  * @module
+  */
+ final class ConcatenatedGridCoverage extends GridCoverage {
+     /**
+      * The object for identifying indices in the {@link #slices} array.
+      */
+     private final GridSliceLocator locator;
+ 
+     /**
+      * Index of the first slice in {@link #locator}.
+      */
+     private final int startAt;
+ 
+     /**
+      * The class in charge of loading and caching grid coverages.
+      * The same loader may be shared by many {@link ConcatenatedGridCoverage} 
instances.
+      * Loaders are immutable (except for the cache) and thread-safe.
+      */
+     private static final class Loader {
+         /**
+          * Whether loading of grid coverages should be deferred to rendering 
time.
+          * This is a bit set packed as {@code long} values. A bit value of 1 
means
+          * that the coverages at the corresponding index should be loaded 
from the
+          * {@linkplain #slices slices} at same index only when first needed.
+          *
+          * @see ConcatenatedGridResource#deferredLoading
+          * @see #isDeferred(int)
+          */
+         final int[] deferred;
+ 
+         /**
+          * The domain to request when reading a coverage from the resource.
+          */
+         private final GridGeometry domain;
+ 
+         /**
+          * The sample dimensions to request when loading slices from the 
{@linkplain #resources}.
+          */
+         private final int[] ranges;
+ 
+         /**
+          * Cache of {@link GridCoverage} instances. Keys are index in the 
{@link #slices} array.
+          */
+         private final Cache<Integer,GridCoverage> coverages;
+ 
+         /**
+          * Creates a new loader.
+          *
+          * @param deferred  whether loading of grid coverages should be 
deferred to rendering time.
+          * @param domain    grid geometry to request when loading data.
+          * @param ranges    bands to request when loading coverages.
+          */
+         Loader(final int[] deferred, final GridGeometry domain, final int[] 
ranges) {
+             this.deferred = deferred;
+             this.domain   = domain;
+             this.ranges   = ranges;
+             coverages     = new Cache<>(15, 2, true);   // Keep 2 slices by 
strong reference (for interpolations).
+         }
+ 
+         /**
+          * Returns the coverage if available in the cache, or load it 
immediately otherwise.
+          * This method shall be invoked only when {@code isDeferred(key) == 
true}.
+          * This method can be invoked from any thread.
+          *
+          * @param  key     index of the {@link GridCoverageResource} in the 
{@link #slices} array.
+          * @param  source  value of {@code slices[key]}, used only if data 
need to be loaded.
+          * @return the coverage at the given index.
+          * @throws NullPointerException if no {@link GridCoverageResource} 
are expected to exist.
+          * @throws DataStoreException if an error occurred while loading data 
from the resource.
+          */
+         final GridCoverage getOrLoad(final Integer key, final 
GridCoverageResource source) throws DataStoreException {
+             GridCoverage coverage = coverages.peek(key);
+             if (coverage == null) {
+                 final Cache.Handler<GridCoverage> handler = 
coverages.lock(key);
+                 try {
+                     coverage = handler.peek();
+                     if (coverage == null) {
+                         coverage = source.read(domain, ranges);
+                     }
+                 } finally {
+                     handler.putAndUnlock(coverage);
+                 }
+             }
+             return coverage;
+         }
+     }
+ 
+     /**
+      * The object in charge of loading and caching grid coverages, or {@code 
null} if none.
+      * The same loader may be shared by many {@link ConcatenatedGridCoverage} 
instances.
+      */
+     private final Loader loader;
+ 
+     /**
+      * The slices of this coverage, in the same order than {@link 
GridSliceLocator#sliceLows}.
+      * Array elements shall be instances of {@link GridCoverage} or {@link 
GridCoverageResource}.
+      * Each slice is not necessarily 1 cell tick; larger slices are accepted.
+      * The length of this array shall be at least 2. Shall be read-only.
+      */
+     private final Object[] slices;
+ 
+     /**
+      * Whether this grid coverage should be considered as converted.
+      * This is used only if the {@linkplain #slices} are lazily loaded.
+      */
+     private final boolean isConverted;
+ 
+     /**
+      * Algorithm to apply when more than one grid coverage can be found at 
the same grid index.
+      * This is {@code null} if no merge should be attempted.
+      */
+     private final MergeStrategy strategy;
+ 
+     /**
+      * Creates a new aggregated coverage.
+      *
+      * @param source    the concatenated resource which is creating this 
coverage.
+      * @param domain    domain of the coverage to create.
+      * @param slices    each slice as instances of {@link GridCoverage} or 
{@link GridCoverageResource}.
+      * @param startAt   index of the first slice in {@link #locator}.
+      * @param deferred  whether loading of grid coverages should be deferred 
to rendering time, or {@code null} if none.
+      * @param request   grid geometry to request when loading data. Used only 
if {@code slices} are lazily loaded.
+      * @param ranges    bands to request when loading coverages. Used only if 
{@code slices} are lazily loaded.
+      */
+     ConcatenatedGridCoverage(final ConcatenatedGridResource source, final 
GridGeometry domain, final Object[] slices,
+                              final int startAt, final int[] deferred, final 
GridGeometry request, final int[] ranges)
+     {
+         super(domain, source.getSampleDimensions());
+         loader = (deferred != null) ? new Loader(deferred, request, ranges) : 
null;
+         this.slices      = slices;
+         this.startAt     = startAt;
+         this.isConverted = source.isConverted;
+         this.locator     = source.locator;
+         this.strategy    = source.strategy;
+     }
+ 
+     /**
+      * Creates a new aggregated coverage for the result of a conversion 
from/to packed values.
+      * This constructor assumes that all slices use the same sample 
dimensions.
+      */
+     private ConcatenatedGridCoverage(final ConcatenatedGridCoverage source, 
final Object[] slices,
+                                      final List<SampleDimension> 
sampleDimensions, final boolean converted)
+     {
+         super(source.getGridGeometry(), sampleDimensions);
+         this.slices      = slices;
+         this.loader      = source.loader;
+         this.startAt     = source.startAt;
+         this.locator     = source.locator;
+         this.strategy    = source.strategy;
+         this.isConverted = converted;
+     }
+ 
+     /**
+      * Returns {@code true} if the loading of the coverage at the given index 
is deferred.
+      * If {@code true},  then {@code slices[i]} shall be an instance of 
{@link GridCoverageResource}.
+      * If {@code false}, then {@code slices[i]} shall be an instance of 
{@link GridCoverage}.
+      */
+     private boolean isDeferred(final int i) {
+         return (loader == null) || (loader.deferred[i >>> Numerics.INT_SHIFT] 
& (1 << i)) != 0;
+     }
+ 
+     /**
+      * Returns a grid coverage that contains real values or sample values,
+      * depending if {@code converted} is {@code true} or {@code false} 
respectively.
+      * This method delegates to all slices in this concatenated coverage.
+      *
+      * @param  converted  {@code true} for a coverage containing converted 
values,
+      *                    or {@code false} for a coverage containing packed 
values.
+      * @return a coverage containing requested values. May be {@code this} 
but never {@code null}.
+      */
+     @Override
+     protected GridCoverage createConvertedValues(final boolean converted) {
+         boolean changed = false;
+         GridCoverage template = null;           // Arbitrary instance to use 
as a template for sample dimensions.
+         final Object[] c = slices.clone();
+         for (int i=0; i<c.length; i++) {
+             if (!isDeferred(i)) {
+                 final GridCoverage source = (GridCoverage) c[i];        // 
Should never fail.
+                 changed |= (c[i] = source.forConvertedValues(converted)) != 
source;
+                 template = source;
+             } else {
+                 changed |= (converted != isConverted);
+             }
+         }
+         if (!changed) {
+             return this;
+         }
+         final List<SampleDimension> sampleDimensions;
+         if (template !=null) {
+             sampleDimensions = template.getSampleDimensions();
+         } else {
+             sampleDimensions = new ArrayList<>(getSampleDimensions());
+             sampleDimensions.replaceAll((b) -> 
b.forConvertedValues(converted));
+         }
+         return new ConcatenatedGridCoverage(this, c, sampleDimensions, 
converted);
+     }
+ 
+     /**
+      * Returns a two-dimensional slice of grid data as a rendered image.
+      * Invoking this method may cause the loading of data from {@link 
ConcatenatedGridResource}.
+      * Most recently used slices are cached for future invocations of this 
method.
+      *
+      * @param  extent  a subspace of this grid coverage extent where all 
dimensions except two have a size of 1 cell.
+      * @return the grid slice as a rendered image. Image location is relative 
to {@code sliceExtent}.
+      */
+     @Override
+     public RenderedImage render(GridExtent extent) {
+         int lower = startAt, upper = lower + slices.length;
+         if (extent != null) {
+             upper = locator.getUpper(extent, lower, upper);
+             lower = locator.getLower(extent, lower, upper);
+         } else {
+             extent = gridGeometry.getExtent();
+         }
+         final GridGeometry   request;           // The geographic area and 
temporal extent requested by user.
+         final GridGeometry[] candidates;        // Grid geometry of all 
slices that intersect the request.
+         final int count = upper - lower;
+         if (count > 1) {
+             if (strategy == null) {
+                 /*
+                  * Can not infer a slice. If the user specified a single 
slice but that slice
+                  * maps to more than one coverage, the error message tells 
that this problem
+                  * can be avoided by specifying a merge strategy.
+                  */
+                 final short message;
+                 final Object[] arguments;
+                 if (locator.isSlice(extent)) {
+                     message   = Resources.Keys.NoSliceMapped_3;
+                     arguments = new Object[] 
{locator.getDimensionName(extent), lower, count};
+                 } else {
+                     message   = Resources.Keys.NoSliceSpecified_2;
+                     arguments = new Object[] 
{locator.getDimensionName(extent), count};
+                 }
+                 throw new 
SubspaceNotSpecifiedException(Resources.format(message, arguments));
+             }
+             /*
+              * Prepare a list of slice candidates. Later in this method, a 
single slice will be selected
+              * among those candidates using the user-specified merge 
strategy. Elements in `candidates`
+              * array will become null if that candidate did not worked and we 
want to look again among
+              * remaining candidates.
+              */
+             try {
+                 request    = new GridGeometry(getGridGeometry(), extent, 
null);
+                 candidates = new GridGeometry[count];
+                 for (int i=0; i<count; i++) {
+                     final int j = lower + i;
+                     final Object slice = slices[j];
+                     candidates[i] = isDeferred(j) ? ((GridCoverageResource) 
slice).getGridGeometry()
+                                                   : ((GridCoverage)         
slice).getGridGeometry();
+                 }
+             } catch (DataStoreException | TransformException e) {
+                 throw new 
CannotEvaluateException(Resources.format(Resources.Keys.CanNotSelectSlice), e);
+             }
+         } else {
+             request    = null;
+             candidates = null;
+         }
+         /*
+          * The following loop should be executed exactly once. However it may 
happen that the "best" slice
+          * actually does not intersect the requested extent, for example 
because the merge strategy looked
+          * only for temporal intersection and did not saw that the geographic 
extents do not intersect.
+          */
+         DisjointExtentException failure = null;
+         if (count > 0) do {
+             int index = lower;
+             if (candidates != null) {
+                 final Integer n = strategy.apply(request, candidates);
+                 if (n == null) break;
+                 candidates[n] = null;
+                 index += n;
+             }
+             final Object slice = slices[index];
+             final GridCoverage coverage;
+             if (!isDeferred(index)) {
+                 coverage = (GridCoverage) slice;        // This cast should 
never fail.
+             } else try {
+                 coverage = loader.getOrLoad(index, (GridCoverageResource) 
slice).forConvertedValues(isConverted);
+             } catch (DataStoreException e) {
+                 throw new 
CannotEvaluateException(Resources.format(Resources.Keys.CanNotReadSlice_1, 
index + startAt), e);
+             }
+             /*
+              * At this point, coverage of the "best" slice has been fetched 
from the cache or read from resource.
+              * Delegate the rendering to that coverage, after converting the 
extent from this grid coverage space
+              * to the slice coordinate space. If the coverage said that the 
converted extent does not intersect,
+              * try the "next best" slice until we succeed or until we 
exhausted the candidate list.
+              */
+             try {
+                 final RenderedImage image = 
coverage.render(locator.toSliceExtent(extent, index));
+                 if (failure != null) {
+                     
Logging.ignorableException(Logger.getLogger(Modules.STORAGE),
+                             ConcatenatedGridCoverage.class, "render", 
failure);
+                 }
+                 return image;
+             } catch (DisjointExtentException e) {
+                 if (failure == null) failure = e;
+                 else failure.addSuppressed(e);
+             }
+         } while (candidates != null);
+         if (failure == null) {
+             failure = new DisjointExtentException(gridGeometry.getExtent(), 
extent, locator.searchDimension);
+         }
+         throw failure;
+     }
+ }

Reply via email to