This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/sis.git
commit 01e0369fc47e13f250409017b02e7c8faa3aeb6d Merge: 8ef62b01f5 76ac26bca2 Author: Martin Desruisseaux <[email protected]> AuthorDate: Tue Mar 31 17:17:02 2026 +0200 Merge branch 'geoapi-3.1'. Contains (non-exhaustive list): - Implementation of `TileMatrixSet` by the GeoTIFF reader. - Redesign of the controls over coverage rendering in JavaFX. - Optimizations in filters and expressions. - Work in the geometry package (incubator). - Bug fixes. .../sis/coverage/grid/ClippedGridCoverage.java | 3 +- .../org/apache/sis/coverage/grid/GridCoverage.java | 12 +- .../apache/sis/coverage/grid/GridCoverage2D.java | 3 +- .../sis/coverage/grid/GridCoverageBuilder.java | 53 +- .../apache/sis/coverage/grid/GridDerivation.java | 2 +- .../org/apache/sis/coverage/grid/GridExtent.java | 61 ++- .../org/apache/sis/coverage/grid/GridGeometry.java | 24 +- .../main/org/apache/sis/feature/FeatureFormat.java | 8 +- .../feature/internal/shared/MovingFeatures.java | 28 +- .../org/apache/sis/filter/ComparisonFilter.java | 141 ++++- .../main/org/apache/sis/filter/LogicalFilter.java | 15 +- .../main/org/apache/sis/filter/Optimization.java | 2 +- .../sis/filter/base/BinaryFunctionWidening.java | 3 +- .../main/org/apache/sis/filter/math/Predicate.java | 27 +- .../main/org/apache/sis/image/BandedIterator.java | 23 + .../main/org/apache/sis/image/ComputedImage.java | 20 +- .../main/org/apache/sis/image/ComputedTiles.java | 26 +- .../main/org/apache/sis/image/ImageProcessor.java | 4 +- .../main/org/apache/sis/image/PlanarImage.java | 2 +- .../apache/sis/image/WritablePixelIterator.java | 50 +- .../sis/image/internal/shared/ReshapedImage.java | 128 +++-- .../main/org/apache/sis/image/package-info.java | 2 +- .../sis/coverage/grid/ClippedGridCoverageTest.java | 1 + .../apache/sis/coverage/grid/GridExtentTest.java | 10 +- .../apache/sis/filter/ComparisonFilterTest.java | 98 ++-- .../apache/sis/filter/DynamicOptimizationTest.java | 19 + .../image/internal/shared/ReshapedImageTest.java | 15 +- .../iso/extent/NotSpatioTemporalException.java | 4 +- .../metadata/sql/internal/shared/ScriptRunner.java | 2 +- .../org.apache.sis.portrayal/main/module-info.java | 2 +- .../coverage/MultiResolutionCoverageLoader.java | 46 +- .../org/apache/sis/map/coverage/RenderingData.java | 53 +- .../org/apache/sis/map/internal/Resources.java | 102 ++++ .../apache/sis/map/internal/Resources.properties | 23 + .../org/apache/sis/map/internal/Resources_en.java | 17 +- .../org/apache/sis/map/internal/Resources_fr.java | 17 +- .../sis/map/internal/Resources_fr.properties | 28 + .../org/apache/sis/map/internal}/package-info.java | 11 +- .../main/org/apache/sis/portrayal/Observable.java | 2 - .../org/apache/sis/portrayal/PlanarCanvas.java | 7 +- .../MultiResolutionCoverageLoaderTest.java | 18 +- .../main/org/apache/sis/geometry/Shapes2D.java | 2 +- .../main/org/apache/sis/referencing/CommonCRS.java | 8 +- .../referencing/internal/shared/WKTUtilities.java | 4 +- .../operation/CoordinateOperationContext.java | 31 +- .../operation/CoordinateOperationFinder.java | 30 +- .../operation/CoordinateOperationRegistry.java | 63 ++- .../DefaultCoordinateOperationFactory.java | 58 +- .../referencing/operation/SubOperationInfo.java | 8 +- .../sis/referencing/operation/matrix/Matrices.java | 4 +- .../sis/referencing/operation/package-info.java | 2 +- .../operation/transform/PassThroughTransform.java | 2 +- .../main/module-info.java | 2 +- .../sis/storage/geotiff/CompressedSubset.java | 18 +- .../org/apache/sis/storage/geotiff/DataCube.java | 65 +-- .../org/apache/sis/storage/geotiff/DataSubset.java | 36 +- .../apache/sis/storage/geotiff/GeoTiffStore.java | 2 +- .../sis/storage/geotiff/ImageFileDirectory.java | 218 +++++--- .../sis/storage/geotiff/MultiResolutionImage.java | 286 ---------- .../org/apache/sis/storage/geotiff/Reader.java | 22 +- .../apache/sis/storage/geotiff/package-info.java | 2 +- .../sis/storage/geotiff/GeoTiffStoreTest.java | 56 +- .../org/apache/sis/storage/geotiff/ReaderTest.java | 124 +++++ .../org/apache/sis/storage/geotiff/WriterTest.java | 5 +- .../org/apache/sis/storage/geotiff/untiled.tiff | Bin 0 -> 2284 bytes .../apache/sis/storage/netcdf/base/FeatureSet.java | 62 ++- .../sis/storage/netcdf/base/RasterResource.java | 2 + .../org/apache/sis/io/stream/ChannelDataInput.java | 2 +- .../sis/storage/AbstractGridCoverageResource.java | 82 ++- .../org/apache/sis/storage/CoverageSubset.java | 11 +- .../main/org/apache/sis/storage/DataStore.java | 18 +- .../apache/sis/storage/GridCoverageResource.java | 41 +- .../main/org/apache/sis/storage/Resource.java | 4 +- .../org/apache/sis/storage/StorageConnector.java | 25 +- .../sis/storage/UnsupportedQueryException.java | 1 + .../aggregate/BandAggregateGridResource.java | 4 +- .../aggregate/ConcatenatedGridResource.java | 12 +- .../apache/sis/storage/aggregate/GridSlice.java | 12 +- .../sis/storage/aggregate/GridSliceLocator.java | 5 +- .../sis/storage/base/GridResourceWrapper.java | 6 +- .../main/org/apache/sis/storage/csv/Store.java | 13 +- .../org/apache/sis/storage/event/StoreEvent.java | 2 +- .../apache/sis/storage/event/StoreListeners.java | 14 +- .../org/apache/sis/storage/event/package-info.java | 4 +- .../apache/sis/storage/image/SingleImageStore.java | 6 +- .../storage/image/WritableSingleImageStore.java | 4 +- .../org/apache/sis/storage/internal/Resources.java | 5 + .../sis/storage/internal/Resources.properties | 1 + .../sis/storage/internal/Resources_fr.properties | 1 + .../apache/sis/storage/tiling/ImagePyramid.java | 472 ++++++++++++++++ .../apache/sis/storage/tiling/ImageTileMatrix.java | 531 ++++++++++++++++++ .../apache/sis/storage/tiling/IterationDomain.java | 221 ++++++++ .../main/org/apache/sis/storage/tiling/Tile.java | 4 +- .../org/apache/sis/storage/tiling/TileMatrix.java | 11 +- .../apache/sis/storage/tiling/TileMatrixSet.java | 12 +- .../sis/storage/tiling/TileMatrixSetFormat.java | 524 ++++++++++++++++++ .../apache/sis/storage/tiling/TileReadEvent.java | 234 ++++++++ .../org/apache/sis/storage/tiling/TileStatus.java | 2 +- .../{base => tiling}/TiledDeferredImage.java | 21 +- .../{base => tiling}/TiledGridCoverage.java | 500 +++++++++++------ .../TiledGridCoverageResource.java} | 599 +++++++++++++++++++-- .../apache/sis/storage/tiling/TiledResource.java | 4 +- .../apache/sis/storage/tiling/package-info.java | 10 +- .../org/apache/sis/storage/CoverageQueryTest.java | 8 +- .../storage/MemoryGridCoverageResourceTest.java | 6 +- .../sis/storage/test/CoverageReadConsistency.java | 2 +- .../main/org/apache/sis/io/TableAppender.java | 16 +- .../main/org/apache/sis/io/TabularFormat.java | 4 +- .../main/org/apache/sis/io/package-info.java | 2 +- .../main/org/apache/sis/math/DecimalFunctions.java | 29 +- .../main/org/apache/sis/math/NumberType.java | 2 +- .../main/org/apache/sis/math/Vector.java | 2 +- .../main/org/apache/sis/util/ArgumentChecks.java | 26 +- .../main/org/apache/sis/util/collection/Cache.java | 4 +- .../org/apache/sis/util/collection/Containers.java | 30 +- .../apache/sis/util/collection/DerivedList.java | 31 +- .../sis/util/collection/WeakValueHashMap.java | 6 +- .../sis/util/collection/WritableDerivedList.java | 143 +++++ .../sis/util/internal/shared/AbstractMap.java | 2 +- .../apache/sis/util/internal/shared/Numerics.java | 118 +++- .../org/apache/sis/util/resources/Vocabulary.java | 20 + .../sis/util/resources/Vocabulary.properties | 4 + .../sis/util/resources/Vocabulary_fr.properties | 4 + .../org/apache/sis/math/DecimalFunctionsTest.java | 32 +- .../{DerivedSetTest.java => DerivedListTest.java} | 66 +-- .../apache/sis/util/collection/DerivedMapTest.java | 1 + .../apache/sis/util/collection/DerivedSetTest.java | 1 + .../apache/sis/storage/geoheif/FromImageIO.java | 9 +- .../main/org/apache/sis/storage/geoheif/Image.java | 3 +- .../apache/sis/storage/geoheif/ImageResource.java | 34 +- .../src/org.apache.sis.gui/main/module-info.java | 2 +- .../main/org/apache/sis/gui/Widget.java | 8 +- .../apache/sis/gui/controls/FormatApplicator.java | 6 +- .../apache/sis/gui/controls/SyncWindowList.java | 6 +- .../apache/sis/gui/controls/ValueColorMapper.java | 12 +- .../apache/sis/gui/coverage/BandRangeTable.java | 8 +- .../apache/sis/gui/coverage/CoverageCanvas.java | 335 ++++++++++-- .../apache/sis/gui/coverage/CoverageControls.java | 155 +++--- .../apache/sis/gui/coverage/CoverageExplorer.java | 36 +- .../apache/sis/gui/coverage/CoverageStyling.java | 16 +- .../apache/sis/gui/coverage/GridSliceSelector.java | 12 +- .../sis/gui/coverage/ImagePropertyExplorer.java | 6 +- ...IsolineRenderer.java => IsolineController.java} | 82 ++- .../apache/sis/gui/coverage/StyleController.java | 173 ++++++ .../sis/gui/coverage/StyledRenderingData.java | 6 +- .../apache/sis/gui/coverage/TileMatrixSetPane.java | 499 +++++++++++++++++ .../apache/sis/gui/coverage/ViewAndControls.java | 22 +- .../org/apache/sis/gui/coverage/package-info.java | 2 +- .../main/org/apache/sis/gui/dataset/LogViewer.java | 5 +- .../org/apache/sis/gui/dataset/ResourceCell.java | 138 +++-- .../apache/sis/gui/dataset/ResourceExplorer.java | 52 +- .../org/apache/sis/gui/dataset/ResourceItem.java | 178 +++--- .../org/apache/sis/gui/dataset/ResourceTree.java | 50 +- .../org/apache/sis/gui/dataset/RootResource.java | 37 +- .../org/apache/sis/gui/dataset/WindowHandler.java | 27 +- .../org/apache/sis/gui/dataset/package-info.java | 2 +- .../apache/sis/gui/internal/AlignedTableCell.java | 87 +++ .../apache/sis/gui/internal/BackgroundThreads.java | 8 +- .../apache/sis/gui/internal/DataStoreOpener.java | 57 +- .../apache/sis/gui/internal/ExceptionReporter.java | 36 +- .../org/apache/sis/gui/internal/GUIUtilities.java | 16 - .../org/apache/sis/gui/internal/LogHandler.java | 2 +- .../org/apache/sis/gui/internal/Resources.java | 20 + .../apache/sis/gui/internal/Resources.properties | 4 + .../sis/gui/internal/Resources_fr.properties | 4 + .../apache/sis/gui/internal/ShapeConverter.java | 153 ++++++ .../main/org/apache/sis/gui/internal/Styles.java | 6 + .../apache/sis/gui/internal/io/FileAccessView.java | 4 +- .../org/apache/sis/gui/map/GestureFollower.java | 18 +- .../main/org/apache/sis/gui/map/MapCanvas.java | 43 +- .../main/org/apache/sis/gui/map/MapCanvasAWT.java | 14 +- .../main/org/apache/sis/gui/map/MapMenu.java | 39 +- .../main/org/apache/sis/gui/map/StatusBar.java | 165 ++++-- .../main/org/apache/sis/gui/map/package-info.java | 2 +- .../apache/sis/gui/map/style/ItemController.java | 89 +++ .../apache/sis/gui/map/style/MapContextView.java | 143 +++++ .../main/org/apache/sis/gui/map/style/MapItem.java | 36 +- .../org/apache/sis/gui/map/style/MapLayer.java | 38 +- .../{referencing => map/style}/package-info.java | 10 +- .../sis/gui/metadata/IdentificationInfo.java | 2 +- .../apache/sis/gui/metadata/MetadataSummary.java | 8 +- .../org/apache/sis/gui/referencing/MenuSync.java | 57 +- .../gui/referencing/RecentReferenceSystems.java | 103 ++-- .../apache/sis/gui/referencing/package-info.java | 2 +- .../sis/gui/controls/ValueColorMapperApp.java | 7 +- .../main/org/apache/sis/storage/gdal/GDAL.java | 147 +---- .../apache/sis/storage/gdal/GDALStoreProvider.java | 126 ++++- .../org/apache/sis/storage/gdal/TiledCoverage.java | 12 +- .../org/apache/sis/storage/gdal/TiledResource.java | 33 +- .../org/apache/sis/storage/gdal/package-info.java | 2 +- .../apache/sis/storage/panama/LibraryLoader.java | 192 +++---- .../sis/storage/panama/LibraryReference.java | 207 +++++++ .../apache/sis/storage/panama/LibraryStatus.java | 51 +- .../apache/sis/storage/panama/NativeFunctions.java | 91 ++-- .../org/apache/sis/storage/gdal/GDALStoreTest.java | 3 +- 195 files changed, 8013 insertions(+), 2146 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java index 5189105c56,35cb92fbff..83f759bb86 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java @@@ -94,10 -92,10 +94,10 @@@ import org.apache.sis.coverage.PointOut * @author Martin Desruisseaux (IRD, Geomatys) * @author Alexis Manin (Geomatys) * @author Johann Sorel (Geomatys) - * @version 1.5 + * @version 1.7 * @since 1.0 */ -public class GridExtent implements GridEnvelope, LenientComparable, Serializable { +public class GridExtent implements Serializable, LenientComparable { /** * Serial number for inter-operability with different versions. */ diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java index 2ed70819fb,1586b58741..0f9c00b88e --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java @@@ -1880,6 -1881,26 +1880,26 @@@ public class GridGeometry implements Le } } + /** + * Returns a coordinate operation for transforming coordinates from the <abbr>CRS</abbr> of this grid geometry + * to the given <abbr>CRS</abbr>. The {@linkplain #getGeographicExtent() geographic bounding box} of this grid + * geometry is used as the desired domain of validity. + * + * @param target the target <abbr>CRS</abbr> of the desired operation. + * @return coordinate operation from the <abbr>CRS</abbr> of this grid geometry to the given <abbr>CRS</abbr>. + * @throws IncompleteGridGeometryException if this grid geometry has no <abbr>CRS</abbr>. + * @throws TransformException if the coordinate operation cannot be found. + * + * @since 1.7 + */ + public CoordinateOperation createChangeOfCRS(final CoordinateReferenceSystem target) throws TransformException { + try { + return findOperation(getCoordinateReferenceSystem(), target, geographicBBox()); + } catch (FactoryException e) { - throw new TransformException(e); ++ throw new TransformException(e.getLocalizedMessage(), e); + } + } + /** * Creates a transform from cell coordinates in this grid to cell coordinates in the given grid. * The returned transform handles change of Coordinate Reference System and wraparound axes diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/MovingFeatures.java index 30d6b6c1dc,a78a34c543..77e4ec4943 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/MovingFeatures.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/shared/MovingFeatures.java @@@ -124,4 -125,28 +124,28 @@@ public class MovingFeatures } dest.characteristics().values().add(ct); } + + /** + * Sets the "datetimes" characteristic on the given attribute to a single temporal value. + * If the {@code converter} is non-null, it will be used for converting the value to an {@link Instant} instance. + * Otherwise, the value is stored as-is as time elapsed in arbitrary units since an arbitrary epoch. + * + * @param dest the attribute on which to set time characteristic. + * @param value time in arbitrary units since an arbitrary epoch. + * @param converter the CRS to use for converting values to {@link Instant} instances, or {@code null}. + */ - public static void setTime(final Attribute<?> dest, final double value, final DefaultTemporalCRS converter) { - final Attribute<?> ct; ++ public static void setTime(final AbstractAttribute<?> dest, final double value, final DefaultTemporalCRS converter) { ++ final AbstractAttribute<?> ct; + if (converter != null) { + final Instant instant = converter.toInstant(value); - final Attribute<Instant> c = TIME_AS_INSTANTS.newInstance(); ++ final AbstractAttribute<Instant> c = TIME_AS_INSTANTS.newInstance(); + c.setValue(instant); + ct = c; + } else { - final Attribute<Number> c = TIME_AS_NUMBERS.newInstance(); ++ final AbstractAttribute<Number> c = TIME_AS_NUMBERS.newInstance(); + c.setValue(value); + ct = c; + } + dest.characteristics().values().add(ct); + } } diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java index 28bd310aa7,eeeb7a83d8..80a49dd1a3 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java @@@ -28,11 -28,15 +28,12 @@@ import org.apache.sis.filter.base.Node import org.apache.sis.filter.base.BinaryFunctionWidening; import org.apache.sis.temporal.TimeMethods; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.util.CodeList; -import org.opengis.filter.Filter; -import org.opengis.filter.Expression; -import org.opengis.filter.MatchAction; -import org.opengis.filter.ComparisonOperatorName; -import org.opengis.filter.BinaryComparisonOperator; -import org.opengis.filter.BetweenComparisonOperator; -import org.opengis.filter.Literal; +// Specific to the main branch: ++import org.apache.sis.pending.geoapi.filter.Literal; +import org.apache.sis.pending.geoapi.filter.MatchAction; +import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName; +import org.apache.sis.pending.geoapi.filter.BinaryComparisonOperator; +import org.apache.sis.pending.geoapi.filter.BetweenComparisonOperator; /** @@@ -242,8 -313,13 +310,13 @@@ abstract class ComparisonFilter<R> exte evaluator = (methods != null) ? methods.predicate(temporalTest(), otherType) : null; } + /** Creates a new filter which is reusing an already determined evaluator. */ + Time(final BiPredicate<T,S> evaluator) { + this.evaluator = evaluator; + } + /** Delegates to the enclosing class.*/ - @Override public CodeList<?> getOperatorType() {return ComparisonFilter.this.getOperatorType();} + @Override public Enum<?> getOperatorType() {return ComparisonFilter.this.getOperatorType();} @Override public Class<? super R> getResourceClass() {return ComparisonFilter.this.getResourceClass();} @Override public List<Expression<R,?>> getExpressions() {return ComparisonFilter.this.getExpressions();} @Override protected Collection<?> getChildren() {return ComparisonFilter.this.getChildren();} @@@ -261,6 -337,46 +334,46 @@@ } return false; } + + /** Creates a new filter of the same type but different parameters. */ + @Override public Filter<R> recreate(Expression<R,?>[] effective) { + return ComparisonFilter.this.recreate(effective).new Time<>(evaluator); + } + } + + /** + * An optimized versions of this filter for the case where the operands are comparable. + */ + private final class Comparables extends Node implements Optimization.OnFilter<R> { + /** For cross-version compatibility during (de)serialization. */ + private static final long serialVersionUID = -8311546273569455269L; + + /** Delegates to the enclosing class.*/ - @Override public CodeList<?> getOperatorType() {return ComparisonFilter.this.getOperatorType();} ++ @Override public Enum<?> getOperatorType() {return ComparisonFilter.this.getOperatorType();} + @Override public Class<? super R> getResourceClass() {return ComparisonFilter.this.getResourceClass();} + @Override public List<Expression<R,?>> getExpressions() {return ComparisonFilter.this.getExpressions();} + @Override protected Collection<?> getChildren() {return ComparisonFilter.this.getChildren();} + + /** Creates a new filter of the same type but different parameters. */ + @Override public Filter<R> recreate(Expression<R,?>[] effective) { + return ComparisonFilter.this.recreate(effective).new Comparables(); + } + + /** Determines if the test represented by this filter passes with the given operands. */ + @Override public boolean test(final R candidate) { + final Object left = expression1.apply(candidate); + if (left != null) { + final Object right = expression2.apply(candidate); + if (right != null) { + if (left.getClass() == right.getClass()) { + @SuppressWarnings("unchecked") + final int result = ((Comparable) left).compareTo(right); + return fromCompareTo(result); + } + } + } + return false; + } } /** diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Predicate.java index 3316e25dfd,2426fc031b..333770b4c4 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Predicate.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/Predicate.java @@@ -25,8 -26,9 +25,9 @@@ import org.apache.sis.filter.base.Unary import org.apache.sis.feature.internal.shared.FeatureExpression; import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.filter.Expression; -import org.opengis.filter.Filter; +// Specific to the main branch: ++import org.apache.sis.filter.Filter; +import org.apache.sis.filter.Expression; /** @@@ -78,6 -84,14 +83,14 @@@ final class Predicate<R> extends UnaryF return function.getFunctionName(); } + /** + * Returns the nature of the operator. + */ + @Override - public CodeList<?> getOperatorType() { - return FilteringFunction.valueOf(function.name()); ++ public Enum<?> getOperatorType() { ++ return null; // Supported in GeoAPI 3.1 branch. + } + /** * Returns the type of values computed by this expression. */ diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java index 7bca9dbf6a,71af21c212..87476c8069 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/BandedIterator.java @@@ -28,7 -28,11 +28,8 @@@ import java.awt.image.Raster import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.WritableRenderedImage; + import java.util.function.IntBinaryOperator; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.coverage.grid.SequenceType; - /** * A pixel iterator reading values directly from a {@link DataBuffer} instead of using {@link Raster} API. diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java index 1632d624a8,eccbade41e..8f7dc80cbe --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/WritablePixelIterator.java @@@ -23,8 -23,13 +23,10 @@@ import java.awt.image.Raster import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.WritableRenderedImage; + import java.util.function.IntBinaryOperator; import org.apache.sis.feature.internal.Resources; + import org.apache.sis.util.ArgumentChecks; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.coverage.grid.SequenceType; - /** * A pixel iterator capable to write sample values. This iterator can edit pixel values in place, diff --cc endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java index 914e9d44a0,5dd5449581..e09448da27 --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/ComparisonFilterTest.java @@@ -22,10 -22,16 +22,11 @@@ import static org.junit.jupiter.api.Ass import org.apache.sis.test.TestCase; import static org.apache.sis.test.Assertions.assertSerializedEquals; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.Feature; -import org.opengis.filter.Filter; -import org.opengis.filter.Literal; -import org.opengis.filter.FilterFactory; -import org.opengis.filter.ComparisonOperator; -import org.opengis.filter.ComparisonOperatorName; -import org.opengis.filter.BinaryComparisonOperator; -import org.opengis.filter.BetweenComparisonOperator; +// Specific to the main branch: +import org.apache.sis.feature.AbstractFeature; + import org.apache.sis.filter.visitor.FunctionNames; +import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName; +import org.apache.sis.pending.geoapi.filter.BetweenComparisonOperator; /** @@@ -44,18 -50,18 +45,18 @@@ public final class ComparisonFilterTes /** * Expressions used as constant for the tests. */ - private final Expression<AbstractFeature,Integer> c05, c10, c20; - private final Literal<Feature, Integer> c05, c10, c20; ++ private final Expression<AbstractFeature, Integer> c05, c10, c20; /** - * Expected name of the filter to be evaluated. The {@code evaluate(…)} methods + * Expected name of the filter to be evaluated. The {@code assertTestEqual(…)} methods * will compare {@link ComparisonFilter#getFunctionName()} against this value. */ private ComparisonOperatorName expectedName; /** - * The filter tested by last call to {@code evaluate(…)} methods. + * The filter tested by last call to {@code assertTestEqual(…)} methods. */ - private ComparisonOperator<Feature> filter; + private Filter<AbstractFeature> filter; /** * Creates a new test case. @@@ -68,10 -74,23 +69,23 @@@ } /** - * Evaluates the given filter. The {@link #expectedName} field must be set before this method is invoked. + * Performs various assertions on the given filter, including serialization. * This method assumes that the first expression of all filters is {@link #c10}. + * + * @param expected the expected result of evaluating the given filter. + * @param filter the filter to test. */ - private boolean evaluate(final Filter<AbstractFeature> filter) { - private void verify(final boolean expected, final BinaryComparisonOperator<Feature> filter) { ++ private void verify(final boolean expected, final Filter<AbstractFeature> filter) { + assertTestEquals(expected, filter); + assertSame(c10, filter.getExpressions().get(0)); + assertSerializedEquals(filter); + } + + /** + * Evaluates the given filter and asserts that the returned value is equal to the expected value. + * The {@link #expectedName} field must be set before this method is invoked. + */ - private void assertTestEquals(final boolean expected, final BinaryComparisonOperator<Feature> filter) { ++ private void assertTestEquals(final boolean expected, final Filter<AbstractFeature> filter) { this.filter = filter; assertInstanceOf(ComparisonFilter.class, filter); assertEquals(expectedName, filter.getOperatorType()); @@@ -80,9 -101,10 +96,10 @@@ } /** - * Evaluates the given "Property is between" filter. + * Evaluates the given "Property is between" filter and asserts that the returned value + * is equal to the expected value. */ - private boolean evaluate(final BetweenComparisonOperator<AbstractFeature> filter) { - private void assertTestEquals(final boolean expected, final BetweenComparisonOperator<Feature> filter) { ++ private void assertTestEquals(final boolean expected, final BetweenComparisonOperator<AbstractFeature> filter) { this.filter = filter; assertInstanceOf(ComparisonFilter.Between.class, filter); assertEquals(expectedName, filter.getOperatorType()); @@@ -166,10 -185,20 +180,20 @@@ */ @Test public void testBetween() { - expectedName = ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN); - assertTestEquals(true, factory.between(c10, c05, c20)); - assertTestEquals(false, factory.between(c20, c05, c10)); - assertTestEquals(false, factory.between(c05, c10, c20)); + expectedName = ComparisonOperatorName.PROPERTY_IS_BETWEEN; - assertTrue (evaluate((BetweenComparisonOperator<AbstractFeature>) factory.between(c10, c05, c20))); - assertFalse(evaluate((BetweenComparisonOperator<AbstractFeature>) factory.between(c20, c05, c10))); - assertFalse(evaluate((BetweenComparisonOperator<AbstractFeature>) factory.between(c05, c10, c20))); ++ assertTestEquals(true, (BetweenComparisonOperator<AbstractFeature>) factory.between(c10, c05, c20)); ++ assertTestEquals(false, (BetweenComparisonOperator<AbstractFeature>) factory.between(c20, c05, c10)); ++ assertTestEquals(false, (BetweenComparisonOperator<AbstractFeature>) factory.between(c05, c10, c20)); + assertSerializedEquals(filter); + } + + /** + * Tests "Equal" between Boolean values. + */ + @Test + public void testBooleans() { + expectedName = ComparisonOperatorName.PROPERTY_IS_EQUAL_TO; + assertTestEquals(true, factory.equal(factory.function("isNaN", c10), factory.literal(Boolean.FALSE))); assertSerializedEquals(filter); } } diff --cc endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoTiffStoreTest.java index 771258cf89,9c66b588e6..913659ab05 --- a/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoTiffStoreTest.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/test/org/apache/sis/storage/geotiff/GeoTiffStoreTest.java @@@ -41,9 -49,10 +49,10 @@@ import static org.apache.sis.test.Asser import static org.apache.sis.feature.Assertions.assertGridToCornerEquals; import org.apache.sis.test.TestCase; import org.apache.sis.referencing.crs.HardCodedCRS; + import org.apache.sis.referencing.operation.HardCodedConversions; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import static org.opengis.test.Assertions.assertAxisDirectionsEqual; +// Specific to the main branch: +import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual; /** diff --cc endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/FeatureSet.java index b36543956b,6d8ca82e91..2945b8eb37 --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/FeatureSet.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/FeatureSet.java @@@ -860,8 -892,12 +892,12 @@@ makeGeom: if (!isEmpty) * The time vector is the first vector after the geometry dimensions. */ if (hasTime) { - MovingFeatures.setTimes((AbstractAttribute<?>) feature.getProperty(TRAJECTORY), - coordinateValues[geometryDimension], timeCRS); - final var t = (Attribute<?>) feature.getProperty(TRAJECTORY); ++ final var t = (AbstractAttribute<?>) feature.getProperty(TRAJECTORY); + if (isTrajectory) { + MovingFeatures.setTimes(t, coordinateValues[geometryDimension], timeCRS); + } else { + MovingFeatures.setTime(t, coordinateValues[geometryDimension].doubleValue(offset), timeCRS); + } } action.accept(feature); dynamicPropertyPosition += length; // Check for ArithmeticException is already done by `extent(…)` call. diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java index 7b61878220,d1be9768ed..b4cbd50308 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java @@@ -140,11 -141,13 +141,13 @@@ final class CoverageSubset extends Abst * @throws DataStoreException if an error occurred while reading definitions from the underlying data store. */ @Override - public List<double[]> getResolutions() throws DataStoreException { - List<double[]> resolutions = source.getResolutions(); - if (reduction != null) { - JDK16.toList(resolutions.stream() + public List<double[]> getAvailableResolutions() throws DataStoreException { + List<double[]> resolutions = source.getAvailableResolutions(); + if (reduction != null) try { + resolutions = JDK16.toList(resolutions.stream() - .map((resolution) -> reduction.apply(new DirectPositionView.Double(resolution)).getCoordinates())); + .map((resolution) -> reduction.apply(new DirectPositionView.Double(resolution)).getCoordinate())); + } catch (BackingStoreException e) { + throw e.unwrapOrRethrow(DataStoreException.class); } return resolutions; } diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java index 826f758270,58d5d7760b..7765fdbcdc --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/csv/Store.java @@@ -226,10 -229,10 +227,10 @@@ final class Store extends URIDataStore geometries = Geometries.factory(connector.getOption(OptionKey.GEOMETRY_LIBRARY)); dissociate = connector.getOption(DataOptionKey.FOLIATION_REPRESENTATION) == FoliationRepresentation.FRAGMENTED; @SuppressWarnings("LocalVariableHidesMemberVariable") GeneralEnvelope envelope = null; - @SuppressWarnings("LocalVariableHidesMemberVariable") FeatureType featureType = null; + @SuppressWarnings("LocalVariableHidesMemberVariable") DefaultFeatureType featureType = null; @SuppressWarnings("LocalVariableHidesMemberVariable") Foliation foliation = null; try { - final List<String> elements = new ArrayList<>(); + final var elements = new ArrayList<String>(); source.mark(StorageConnector.READ_AHEAD_LIMIT); String line; while ((line = source.readLine()) != null) { @@@ -497,10 -500,10 +498,10 @@@ * @return the column metadata, or {@code null} if the given list does not contain enough elements. */ @SuppressWarnings("rawtypes") // "rawtypes" because of generic array creation. - private FeatureType parseFeatureType(final List<String> elements) throws DataStoreException { - AttributeType[] characteristics = null; + private DefaultFeatureType parseFeatureType(final List<String> elements) throws DataStoreException { + DefaultAttributeType[] characteristics = null; final int size = elements.size(); - final List<AbstractIdentifiedType> properties = new ArrayList<>(); - final var properties = new ArrayList<PropertyType>(); ++ final var properties = new ArrayList<AbstractIdentifiedType>(); for (int i=1; i<size; i++) { final String name = elements.get(i); Class<?> type = null; @@@ -566,10 -569,12 +567,12 @@@ } properties.add(createProperty(name, type, minOccurrence, maxOccurrence, characteristics)); } - // Do not use Map.of(…) because `name` may be null. Let constructor throw the exception. - final String name = IOUtilities.filenameWithoutExtension(super.getDisplayName()); + String name = IOUtilities.filenameWithoutExtension(super.getDisplayName()); + if (name == null) { + name = Vocabulary.forLocale(getLocale()).getString(Vocabulary.Keys.Unnamed); + } return new DefaultFeatureType(Collections.singletonMap(DefaultFeatureType.NAME_KEY, name), - false, null, properties.toArray(PropertyType[]::new)); + false, null, properties.toArray(AbstractIdentifiedType[]::new)); } /** diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java index 0000000000,0629472801..3207e8debe mode 000000,100644..100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java @@@ -1,0 -1,532 +1,531 @@@ + /* + * 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.tiling; + + import java.util.Arrays; + import java.util.Optional; + import java.util.stream.Stream; + import java.util.stream.StreamSupport; + import java.awt.Rectangle; + import java.awt.image.RenderedImage; + import java.nio.file.Path; + import java.util.logging.Logger; + import org.opengis.util.GenericName; + import org.opengis.referencing.operation.TransformException; + import org.apache.sis.referencing.operation.matrix.Matrices; + import org.apache.sis.referencing.operation.matrix.MatrixSIS; + import org.apache.sis.referencing.operation.transform.MathTransforms; + import org.apache.sis.storage.MemoryGridCoverageResource; + import org.apache.sis.storage.DataStoreException; + import org.apache.sis.storage.NoSuchDataException; + import org.apache.sis.storage.UnsupportedQueryException; + import org.apache.sis.storage.InternalDataStoreException; + import org.apache.sis.storage.Resource; + import org.apache.sis.coverage.grid.GridExtent; + import org.apache.sis.coverage.grid.GridGeometry; + import org.apache.sis.coverage.grid.GridCoverage2D; + import org.apache.sis.coverage.grid.GridCoverageProcessor; + import org.apache.sis.coverage.grid.IncompleteGridGeometryException; + import org.apache.sis.image.ComputedImage; + import org.apache.sis.image.internal.shared.ReshapedImage; + import org.apache.sis.pending.jdk.JDK18; + import org.apache.sis.util.ArgumentChecks; + import org.apache.sis.util.iso.Names; + import org.apache.sis.util.logging.Logging; + import org.apache.sis.util.resources.Errors; + import org.apache.sis.util.collection.BackingStoreException; + import org.apache.sis.math.DecimalFunctions; + import org.apache.sis.storage.internal.Resources; + + + /** + * Default implementation of {@code TileMatrix} as a wrapper for a {@code GridCoverage}. + * The tile size must be specified at construction time and must be equal to the size of + * the tiles of the rendered image. + * + * <p>This class is needed only when the application needs details about the tiling scheme, + * for example in order to implement a Web Map Tile Service (<abbr>WMTS</abbr>).</p> + * + * @author Martin Desruisseaux (Geomatys) + */ + final class ImageTileMatrix implements TileMatrix { + /** + * Logger for the tiling package. + */ + static final Logger LOGGER = Logger.getLogger("org.apache.sis.storage.tiling"); + + /** + * An alphanumeric identifier which is unique in the {@code TileMatrixSet} that contains this {@code TileMatrix}. + * The identifier contains the zoom level as a number encoded in <abbr>ASCII</abbr>. + */ + private final GenericName identifier; + + /** + * The resource for reading the tiles of this tile matrix. + */ + private final TiledGridCoverageResource resource; + + /** + * The coverage from which to get the rendered image which contains the tiles. + * This coverage uses deferred reading of tiles. Created when first requested. + * + * @see #coverage() + */ + private TiledGridCoverage coverage; + + /** + * Pattern for formatting tile indices using {@link java.util.Formatter}. + * The number of digits and the presence of a sign depend on the {@linkplain #tilingScheme tiling scheme}. + * + * @see #getTileIdentifier(long[]) + */ + private final String tileIndicesPattern; + + /** + * Extent of valid tile indices and their relationship with "real world" coordinates. + * The (0, 0) tile indices should map to the tile in the upper-left corner. The last + * row and last column of tiles may contain partial tiles if the coverage size is not + * a divisor of the tile size. + */ + private final GridGeometry tilingScheme; + + /** + * Size of tiles, in number of {@linkplain #coverage} cells. + * The length of this array may be shorter than the number of dimensions of the grid coverage, + * because some {@link TiledGridCoverage} implementations may use some metadata (typically the + * image date) as a third grid dimension, but still manage all tiles as two-dimensional. + * In such case, all extra dimensions are assumed to have a size of 1. + */ + private final int[] tileSize; + + /** + * Values to add to tile coordinates, after multiplication by {@link #tileSize}, for getting cell coordinates. + * + * @see #tileToCell(long, int) + */ + private final long[] tileToCell; + + /** + * Values to add to tile coordinates in {@link #image} for getting a tile coordinates in {@link #tilingScheme}. + * Those values are updated when a new image is rendered. + */ + private long imageToTileX, imageToTileY; + + /** + * The image containing the tiles of the tile matrix. Computed when first needed, and may be + * recomputed multiple times with different offsets if the tile indices are larger than the + * capacity of 32-bits integers. + */ + private RenderedImage image; + + /** + * The grid coverage processor to use when tiles use a subset of the bands. + * + * @see #createResourceView(long[], RenderedImage) + */ + private final GridCoverageProcessor processor; + + /** + * Creates a new tile matrix for the given coverage. + * + * @param identifier identifier unique in the {@code TileMatrixSet} that contains this {@code TileMatrix}. + * @param resource the resource for reading the tiles of this tile matrix. + * @param processor the grid coverage processor to use when tiles use a subset of the bands. + * @throws TransformException if the "tile indices to CRS" transform cannot be computed. + */ + ImageTileMatrix(final GenericName identifier, + final TiledGridCoverageResource resource, + final GridCoverageProcessor processor) + throws DataStoreException, TransformException + { + this.identifier = identifier; + this.processor = processor; + this.resource = resource; + this.tileSize = resource.getTileSize(); + final GridGeometry cellGrid = resource.getGridGeometry(); + final GridExtent extent = cellGrid.getExtent(); + final int dimension = extent.getDimension(); + final long[] tileCount = new long[dimension]; + final MatrixSIS toCells = Matrices.createIdentity(dimension + 1); + this.tileToCell = new long[dimension]; + final var pattern = new StringBuilder(6 * dimension); + for (int i=0; i<dimension; i++) { + long size = extent.getSize(i); + final long offset = extent.getLow(i); + if (i < tileSize.length) { + final int scale = tileSize[i]; + toCells.setNumber(i, i, scale); + size = JDK18.ceilDiv(size, scale); + } + toCells.setNumber(i, dimension, offset); + tileToCell[i] = offset; + tileCount [i] = size; + /* + * Prepare a pattern for formatting the tile indices. + * Indices are formatted with fixed number of digits, + * using the minimum number needed for the largest index. + */ + if (i != 0) pattern.append(','); + pattern.append("%0").append(DecimalFunctions.floorLog10(Math.max(tileCount[i] - 1, 1)) + 1).append('d'); + } + tilingScheme = new GridGeometry(cellGrid, extent.reshape(null, tileCount, false), MathTransforms.linear(toCells)); + tileIndicesPattern = pattern.toString(); + } + + /** + * Returns an identifier which is unique in the {@code TileMatrixSet} that contains this {@code TileMatrix}. + * The identifier contains the zoom level as a number encoded in <abbr>ASCII</abbr>. + */ + @Override + public GenericName getIdentifier() { + return identifier; + } + + /** + * Returns the resolution (in units of CRS axes) at which tiles in this matrix should be used. + * The array length is the number of <abbr>CRS</abbr> dimensions, and value at index <var>i</var> + * is the resolution along CRS dimension <var>i</var> in units of the CRS axis <var>i</var>. + * + * @throws IncompleteGridGeometryException if the tiling scheme has no resolution. + * Tile matrices with such tiling scheme should not have been constructed. + */ + @Override + public double[] getResolution() { + try { + return resource.getGridGeometry().getResolution(false); + } catch (DataStoreException e) { + throw new BackingStoreException(e); + } + } + + /** + * Returns the tile size of the given matrix if known, or {@code null} otherwise. + * This method returns a direct reference to the internal array, caller shall not modify. + */ + static int[] getTileSize(final TileMatrix matrix) { + return (matrix instanceof ImageTileMatrix) ? ((ImageTileMatrix) matrix).tileSize : null; + } + + /** + * Returns a description about how space is partitioned into individual tiled units. + * The description contains the extent of valid tile indices, the spatial reference system, + * and the conversion from tile indices to the spatial reference system coordinates. + * + * @return extent of valid tile indices and their relationship with "real world" coordinates. + */ + @Override + public GridGeometry getTilingScheme() { + return tilingScheme; + } + + /** + * Returns the coverage, which is read when first needed. + * This coverage uses deferred reading of tiles. + * + * @return the coverage from which to get the rendered image which contains the tiles. + * @throws DataStoreException if an error occurred during the construction of the coverage. + */ + private synchronized TiledGridCoverage coverage() throws DataStoreException { + if (coverage == null) { + coverage = resource.readAtGetTileTime(); + } + return coverage; + } + + /** + * Fetches information about whether a tile exists, is missing or failed to load. + * + * @param indices indices of the requested tile (may be outside the tile matrix extent). + * @return information about the availability of the specified tile. + * @throws DataStoreException if fetching the tile status failed. + */ + @Override + @SuppressWarnings("LocalVariableHidesMemberVariable") + public TileStatus getTileStatus(final long... indices) throws DataStoreException { + if (tilingScheme.getExtent().contains(indices)) try { + final TiledGridCoverage coverage; + final RenderedImage image; + final long imageToTileX; + final long imageToTileY; + synchronized (this) { + coverage = this.coverage; // Never null if `image` is non-null. + image = this.image; + imageToTileX = this.imageToTileX; + imageToTileY = this.imageToTileY; + } + if (image != null) { + final long tileX = Math.subtractExact(indices[coverage.xDimension], imageToTileX); + final long x0 = image.getMinTileX(); + if (tileX >= x0 && tileX < x0 + image.getNumXTiles()) { + final long tileY = Math.subtractExact(indices[coverage.yDimension], imageToTileY); + final long y0 = image.getMinTileY(); + if (tileY >= y0 && tileY < y0 + image.getNumYTiles()) { + return getTileStatus(image, Math.toIntExact(tileX), Math.toIntExact(tileY)); + } + } + } + return TileStatus.UNKNOWN; + } catch (ArithmeticException e) { + Logging.ignorableException(LOGGER, ImageTileMatrix.class, "getTileStatus", e); + } + return TileStatus.OUTSIDE_EXTENT; + } + + /** + * Returns the status of the tile at the given index. + * If the image is an instance of {@link ComputedImage}, + * then this method checks whether the tile is in error. + * + * This method does not check whether the tile indexes are outside the image domain + * ({@link TileStatus#OUTSIDE_EXTENT}). This verification must be done by the caller. + * + * @param image image from which to get a tile status. + * @param tileX row index of the tile for which to get the status. + * @param tileY column index of the tile for which to get the status. + * @return status of the tile at the specified indexes. + */ + private static TileStatus getTileStatus(final RenderedImage image, final int tileX, final int tileY) { + if (image instanceof ComputedImage) { + final var computed = (ComputedImage) image; + final var tiles = new Rectangle(tileX, tileY, 1, 1); + if (computed.hasErrorFlag(tiles)) { + return TileStatus.IN_ERROR; + } + } + return TileStatus.EXISTS; + } + + /** + * Gets a tile at the given indices if not missing. + * + * @param indices indices of the tile to fetch, as coordinates inside the matrix extent. + * @return the tile if it exists, or an empty value if the tile is missing. + * @throws NoSuchDataException if the given indices are outside the matrix extent. + * @throws DataStoreException if fetching the tile failed for another reason. + */ + @Override + public Optional<Tile> getTile(final long... indices) throws DataStoreException { + final GridExtent extent = tilingScheme.getExtent(); + if (extent.contains(indices)) try { + final Tile tile = iterator(extent.reshape(indices, indices, true)).createFirstTile(); + return (tile.getStatus() == TileStatus.MISSING) ? Optional.empty() : Optional.of(tile); + } catch (ArithmeticException e) { + throw new UnsupportedQueryException(e); + } + throw new NoSuchDataException(Resources.format(Resources.Keys.TileIndexesOutOfBounds)); + } + + /** + * Retrieves a stream of existing tiles in the specified region. + * The stream contains the existing tiles that are inside the given region and excludes missing tiles. + * If a tile is {@linkplain TileStatus#IN_ERROR in error}, then the stream nevertheless return a tile + * but its {@link Tile#getResource()} method should throw the exception. + * + * <h4>Limitations</h4> + * The current implementation limits the size of the given extent to {@link Integer#MAX_VALUE} + * in each dimension. Note that this is a maximum size in tile indices, not in pixel coordinates. + * + * @param indiceRanges ranges of tile indices in all dimensions, or {@code null} for all tiles. + * @param parallel {@code true} for a parallel stream (if supported), or {@code false} for a sequential stream. + * @return stream of tiles, excluding missing tiles. + * @throws DataStoreException if the tiles can not be fetched in the given ranges of tile indexes. + */ + @Override + public Stream<Tile> getTiles(GridExtent indiceRanges, final boolean parallel) throws DataStoreException { - ArgumentChecks.ensureDimensionMatches("indiceRanges", tilingScheme.getDimension(), indiceRanges); + if (indiceRanges == null) { + indiceRanges = tilingScheme.getExtent(); + } + try { + return StreamSupport.stream(iterator(indiceRanges).iterator(), parallel); + } catch (ArithmeticException e) { + throw new UnsupportedQueryException(e); + } + } + + /** + * Creates an object which can be used for retrieving a single tile or a stream tiles. + * + * @param indiceRanges ranges of tile indices in all dimensions, or {@code null} for all tiles. + * @return a request which can be used for getting a tile or a stream of tiles in the given region. + * @throws DataStoreException if the tiles can not be fetched in the given ranges of tile indexes. + * @throws ArithmeticException if coordinate computation exceeds the capacity of 64-bits integers. + */ + private synchronized IterationDomain<Tile> iterator(final GridExtent indiceRanges) throws DataStoreException { + @SuppressWarnings("LocalVariableHidesMemberVariable") + final TiledGridCoverage coverage = coverage(); + boolean retry = false; + do { // This loop will be executed only 1 or 2 times. + if (image != null) { + final long xmin, ymin, xmax, ymax; + xmin = Math.subtractExact(indiceRanges.getLow (coverage.xDimension), imageToTileX); + xmax = Math.subtractExact(indiceRanges.getHigh(coverage.xDimension), imageToTileX); + final long x0 = image.getMinTileX(); + if (xmin >= x0 && xmax < x0 + image.getNumXTiles()) { + ymin = Math.subtractExact(indiceRanges.getLow (coverage.yDimension), imageToTileY); + ymax = Math.subtractExact(indiceRanges.getHigh(coverage.yDimension), imageToTileY); + final long y0 = image.getMinTileY(); + if (ymin >= y0 && ymax < y0 + image.getNumYTiles()) { + return new Iterator(Math.toIntExact(xmin), + Math.toIntExact(ymin), + Math.toIntExact(xmax), + Math.toIntExact(ymax)); + } + } + } + /* + * Gets the bounds of the image to read. If deferred reading is supported, + * we can expand to the bounds of the whole coverage in order to perform a + * read operation (deferred) only once. + */ + final GridExtent extent = coverage.getGridGeometry().getExtent(); + final int dimension = extent.getDimension(); + final var low = new long[dimension]; + final var high = new long[dimension]; + for (int i=0; i<dimension; i++) { + final long limit = Math.incrementExact(extent.getHigh(i)); + high[i] = Math.min(limit, tileToCell(Math.incrementExact(indiceRanges.getHigh(i)), i)); + low [i] = Math.max(extent.getLow(i), tileToCell(indiceRanges.getLow(i), i)); + final long span = high[i] - low[i]; + if (span < 0 || span > Integer.MAX_VALUE) { + throw new ArithmeticException(resource.errors().getString(Errors.Keys.IntegerOverflow_1, Integer.SIZE)); + } + if (coverage.deferredTileReading) { + final long remain = Math.min(extent.getSize(i), Integer.MAX_VALUE) - span; + final long after = Math.min(remain >> 1, limit - high[i]); + final long before = Math.min(remain - after, low[i] - extent.getLow(i)); + low [i] -= before; + high[i] += after; + } + } + image = coverage.render(extent.reshape(low, high, false)); + imageToTileX = low[coverage.xDimension]; + imageToTileY = low[coverage.yDimension]; + } while ((retry = !retry) == true); + throw new InternalDataStoreException(); // Should never happen. + } + + /** + * Converts the give tile coordinate in the given dimension to cell coordinates. + * + * @param coordinate the tile coordinate to convert. + * @param dimension the dimension of the coordinate to convert. + * @return the cell coordinate. + * @throws ArithmeticException if the result overflows the capacity of 64-bits integers. + */ + private long tileToCell(long coordinate, final int dimension) { + if (dimension < tileSize.length) { + coordinate = Math.multiplyExact(coordinate, tileSize[dimension]); + } + return Math.addExact(tileToCell[dimension], coordinate); + } + + /** + * Factory for an iterator over tiles in ranges of user-specified tile indices. + */ + private final class Iterator extends IterationDomain<Tile> { + /** + * Snapshot of {@link ImageTileMatrix#image}. + */ + private final RenderedImage tiles; + + /** + * Snapshot of {@link ImageTileMatrix#imageToTileX} and {@link ImageTileMatrix#imageToTileY}. + */ + private final long offsetX, offsetY; + + /** + * Creates a new request for tile iterators. + * + * @param xmin first column index of tiles, inclusive. + * @param xmin first row index of tiles, inclusive. + * @param xmax last column index of tiles, inclusive. + * @param ymax last row index of tiles, inclusive. + */ + Iterator(final int xmin, final int ymin, final int xmax, final int ymax) { + super(xmin, ymin, xmax, ymax); + tiles = image; + offsetX = imageToTileX; + offsetY = imageToTileY; + } + + /** + * Creates the tile at the given indexes. + * The caller must ensure that the arguments are valid image tile indexes. + * This condition is not verified by this method. + */ + @Override + protected Tile createTile(final int tileX, final int tileY) { + return new Tile() { + /** This tile viewed as a resource, created when first requested. */ + private Resource resourceView; + + /** Returns the path to content of the tile if known. */ + @Override public Optional<Path> getContentPath() throws DataStoreException { + return Optional.ofNullable(coverage().getContentPath(getIndices())); + } + + /** Returns the indices of this tile in the {@code TileMatrix}. */ + @Override public long[] getIndices() { + return new long[] {offsetX + tileX, offsetY + tileY}; + } + + /** Returns information about whether the tile failed to load. */ + @Override public TileStatus getStatus() { + return getTileStatus(tiles, tileX, tileY); + } + + /** Returns the tile content as a resource. */ + @Override public synchronized Resource getResource() throws DataStoreException { + if (resourceView == null) { + resourceView = createResourceView(getIndices(), ReshapedImage.singleTile(tiles, tileX, tileY)); + } + return resourceView; + } + }; + } + } + + /** + * Creates a resource for the tile at the given indices. + * The resource wraps a grid coverage, which is itself wrapping the given image. + * The given image should contains only the desired tile. The caller currently sets + * the tile indexes and image coordinates to (0,0), but this is not mandatory. + * + * @param indices indices of the tile, as coordinates inside the matrix extent. + * @param tile a rendered image which contains only the tile. + * @return resource for the specified tile. + */ + private Resource createResourceView(final long[] indices, final RenderedImage tile) throws DataStoreException { + final Object[] args = new Object[indices.length]; + Arrays.setAll(args, (i) -> indices[i]); + final GenericName id = Names.createScopedName(identifier, null, String.format(tileIndicesPattern, args)); + final long[] low = new long[indices.length]; + final long[] high = new long[indices.length]; + for (int i=0; i<indices.length; i++) { + final long size = (i < tileSize.length) ? tileSize[i] : 1; + low [i] = Math.addExact(tileToCell[i], Math.multiplyExact(indices[i], size)); + high[i] = Math.addExact(low[i], size - 1); + } + @SuppressWarnings("LocalVariableHidesMemberVariable") + final TiledGridCoverage coverage = coverage(); + GridGeometry cellGrid = coverage.getGridGeometry(); + final GridExtent extent = cellGrid.getExtent().reshape(low, high, true); + cellGrid = cellGrid.derive().subgrid(extent, null).build(); + final var subset = new GridCoverage2D(cellGrid, coverage.getSampleDimensions(), tile); + return new MemoryGridCoverageResource(resource, id, subset, processor); + } + } diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java index 77aa7feb88,833361beb1..1958562d09 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java @@@ -45,15 -51,20 +51,20 @@@ import org.apache.sis.storage.Resource import org.apache.sis.storage.AbstractGridCoverageResource; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.RasterLoadingStrategy; + import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.measure.NumberRange; import org.apache.sis.util.ArraysExt; + import org.apache.sis.util.ArgumentChecks; + import org.apache.sis.util.resources.Errors; import org.apache.sis.util.internal.shared.Numerics; + import org.apache.sis.util.collection.BackingStoreException; + import org.apache.sis.util.collection.Containers; + import org.apache.sis.util.collection.ListOfUnknownSize; import org.apache.sis.util.collection.WeakValueHashMap; - import static org.apache.sis.storage.base.TiledGridCoverage.X_DIMENSION; - import static org.apache.sis.storage.base.TiledGridCoverage.Y_DIMENSION; + import org.apache.sis.util.iso.DefaultNameFactory; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.coverage.CannotEvaluateException; +// Specific to the main branch: +import org.apache.sis.coverage.CannotEvaluateException; /** diff --cc endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArgumentChecks.java index a828701bed,c393e45edb..0a6e312be7 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArgumentChecks.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ArgumentChecks.java @@@ -768,6 -769,30 +768,30 @@@ public final class ArgumentChecks */ public static void ensureDimensionMatches(final String name, final int expected, final int[] indices) throws MismatchedDimensionException + { + if (indices != null) { + final int dimension = indices.length; + if (dimension != expected) { - throw new org.opengis.geometry.MismatchedDimensionException(Errors.format( ++ throw new MismatchedDimensionException(Errors.format( + Errors.Keys.MismatchedDimension_3, name, expected, dimension)); + } + } + } + + /** + * Ensures that the given array of indices, if non-null, has the expected number of dimensions + * (taken as its length). This method does nothing if the given array is null. + * + * @param name the name of the argument to be checked. Used only if an exception is thrown. + * @param expected the expected number of dimensions. + * @param indices the array of indices to check for its number of dimensions, or {@code null}. + * @throws MismatchedDimensionException if the given array of indices is non-null and does not have + * the expected number of dimensions (taken as its length). + * + * @since 1.7 + */ + public static void ensureDimensionMatches(final String name, final int expected, final long[] indices) + throws MismatchedDimensionException { if (indices != null) { final int dimension = indices.length; diff --cc optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java index a6cc599843,3379cc7228..1893db341a --- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java +++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java @@@ -69,10 -72,10 +72,10 @@@ import org.apache.sis.gui.map.StatusBar * * <h2>Limitations</h2> * Current implementation is restricted to {@link GridCoverage} instances, but a future - * implementation may generalize to {@link org.opengis.coverage.Coverage} instances. + * implementation may generalize to {@code org.opengis.coverage.Coverage} instances. * * @author Martin Desruisseaux (Geomatys) - * @version 1.5 + * @version 1.7 * * @see CoverageCanvas * @see GridView
