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; + } + }