You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/09/21 10:03:55 UTC
[sis] 01/01: Merge branch 'geoapi-3.1'.
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 <ma...@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;
+ }
+ }