This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 89c8fedc900d16ad9db58b314371276f9d435120 Author: jsorel <[email protected]> AuthorDate: Wed Oct 28 14:53:59 2026 +0100 feat(DGGS): add ReducedCodedResource, fix errors in cell indexing --- .../dggs/DiscreteGlobalGridCoverageProcessor.java | 2 +- .../rs/internal/shared/ArrayCodedCoverage.java | 7 +- .../storage/rs/internal/shared/CodeTransforms.java | 5 +- .../rs/internal/shared/ReducedCodedResource.java | 227 +++++++++++++++++++++ .../rs/internal/shared/ResampledCodedCoverage.java | 7 +- .../internal/shared/ReducedCodedResourceTest.java | 197 ++++++++++++++++++ 6 files changed, 440 insertions(+), 5 deletions(-) diff --git a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/dggs/DiscreteGlobalGridCoverageProcessor.java b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/dggs/DiscreteGlobalGridCoverageProcessor.java index b12f0eca86..53376a06af 100644 --- a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/dggs/DiscreteGlobalGridCoverageProcessor.java +++ b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/dggs/DiscreteGlobalGridCoverageProcessor.java @@ -643,7 +643,7 @@ public final class DiscreteGlobalGridCoverageProcessor { //remove any extra slice information coverage = ad.getSource(); } - + final DiscreteGlobalGridReferenceSystem dggrs = dggGrid.getReferenceSystem(); final DiscreteGlobalGridHierarchy dggh = dggrs.getGridSystem().getHierarchy(); final List<SampleDimension> sampleDimensions = coverage.getSampleDimensions(); diff --git a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ArrayCodedCoverage.java b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ArrayCodedCoverage.java index cbf3a38153..c95f76f674 100644 --- a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ArrayCodedCoverage.java +++ b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ArrayCodedCoverage.java @@ -134,10 +134,15 @@ public final class ArrayCodedCoverage extends AbstractCodedCoverage{ } } + /** + * Returns the linear position of given grid position, or -1 if outside the extent. + */ private long toLinearPosition(int[] pos) { long p = 0; for (int i = 0; i < dimension; i++) { - p += pos[i] * dimStep[i]; + final long k = pos[i] - dimOffsets[i]; + if (k < 0 || k >= dimSize[i]) return -1; + p += k * dimStep[i]; } return p; } diff --git a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/CodeTransforms.java b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/CodeTransforms.java index 8532c6b383..54cd738511 100644 --- a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/CodeTransforms.java +++ b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/CodeTransforms.java @@ -64,7 +64,7 @@ public final class CodeTransforms { if (trss.length == 1) return trss[0]; SubTransform rgt = new Compound((SubTransform)trss[0], (SubTransform)trss[1]); for (int i = 2; i < trss.length; i++) { - rgt = new Compound(rgt, (SubTransform) trss[2]); + rgt = new Compound(rgt, (SubTransform) trss[i]); } return rgt; } @@ -221,7 +221,8 @@ public final class CodeTransforms { final double[] gp = new double[dimension]; for(int i = 0; i < dimension; i++) gp[i] = (double) location[offset+i]; crsToGrid.transform(gp, 0, gp, 0, 1); - for(int i = 0; i < dimension; i++) gridPosition[offset+i] = (int) gp[i]; + //transform is cell center based, round to the nearest cell + for(int i = 0; i < dimension; i++) gridPosition[offset+i] = (int) Math.round(gp[i]); } @Override diff --git a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ReducedCodedResource.java b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ReducedCodedResource.java new file mode 100644 index 0000000000..6887418fbb --- /dev/null +++ b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ReducedCodedResource.java @@ -0,0 +1,227 @@ +/* + * 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.rs.internal.shared; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.opengis.referencing.ReferenceSystem; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; +import org.opengis.util.GenericName; +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.PixelInCell; +import org.apache.sis.referencing.dggs.DiscreteGlobalGridReferenceSystem; +import org.apache.sis.referencing.rs.ReferenceSystems; +import org.apache.sis.storage.AbstractResource; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.storage.rs.CodeIterator; +import org.apache.sis.storage.rs.CodeTransform; +import org.apache.sis.storage.rs.CodedCoverage; +import org.apache.sis.storage.rs.CodedGeometry; +import org.apache.sis.storage.rs.CodedResource; +import org.apache.sis.storage.rs.WritableCodeIterator; + + +/** + * View of a coded resource with one or more components of its reference system hidden. + * Each hidden component is fixed at a given grid index; the resulting resource + * only exposes the remaining components. + * + * <p>Hidden components must be one-dimensional (typically vertical or temporal components). + * The horizontal Discrete Global Grid component cannot be hidden.</p> + * + * @author Johann Sorel (Geomatys) + */ +public final class ReducedCodedResource extends AbstractResource implements CodedResource { + + private final GenericName name; + private final CodedResource resource; + /** + * Single components of the source resource reference system. + */ + private final List<ReferenceSystem> components; + /** + * For each source component, the source geometry slice if the component is kept, null otherwise. + */ + private final CodedGeometry[] keptSlices; + /** + * For each source component, a single cell geometry at the fixed index if the component is hidden, null otherwise. + */ + private final CodedGeometry[] hiddenSlices; + private final CodedGeometry geometry; + + /** + * Creates a new reduced view. + * + * @param name name of the reduced resource. + * @param resource the resource to reduce. + * @param hidden map of source component index to the grid index to use in this component. + * Component indices are those of {@code ReferenceSystems.getSingleComponents(rs, true)}. + * @throws DataStoreException if the resource geometry cannot be read. + * @throws IllegalArgumentException if a component index or grid index is invalid. + */ + public ReducedCodedResource(GenericName name, CodedResource resource, Map<Integer,Integer> hidden) throws DataStoreException { + super(null); + this.name = name; + this.resource = resource; + + final CodedGeometry resourceGeometry = resource.getGridGeometry(); + components = ReferenceSystems.getSingleComponents(resourceGeometry.getReferenceSystem(), true); + keptSlices = new CodedGeometry[components.size()]; + hiddenSlices = new CodedGeometry[components.size()]; + + for (Map.Entry<Integer,Integer> entry : hidden.entrySet()) { + final int idx = entry.getKey(); + final int index = entry.getValue(); + if (idx < 0 || idx >= components.size()) { + throw new IllegalArgumentException("Component " + idx + " does not exist, reference system has " + components.size() + " components"); + } + final ReferenceSystem component = components.get(idx); + if (component instanceof DiscreteGlobalGridReferenceSystem) { + throw new IllegalArgumentException("Component " + idx + " is a Discrete Global Grid, it can not be hidden"); + } + if (ReferenceSystems.getDimension(component) != 1) { + throw new IllegalArgumentException("Component " + idx + " is not one-dimensional, it can not be hidden"); + } + final CodedGeometry slice = resourceGeometry.slice(component).get(); + if (slice.isDefined(CodedGeometry.EXTENT) && !slice.getExtent().contains(index)) { + throw new IllegalArgumentException("Index " + index + " is outside of component " + idx + " extent " + slice.getExtent()); + } + final GridExtent cell = new GridExtent(null, new long[] {index}, new long[] {index}, true); + CodeTransform gridToRS = slice.getGridToRS(); + if (gridToRS instanceof CodeTransforms.Grid g) { + final GridGeometry grid = g.getGrid(); + gridToRS = CodeTransforms.toTransform(new GridGeometry(cell, PixelInCell.CELL_CENTER, + grid.getGridToCRS(PixelInCell.CELL_CENTER), grid.getCoordinateReferenceSystem())); + } + hiddenSlices[idx] = new CodedGeometry(component, cell, gridToRS, null); + } + + final List<CodedGeometry> kept = new ArrayList<>(); + for (int i = 0; i < keptSlices.length; i++) { + if (hiddenSlices[i] == null) { + keptSlices[i] = resourceGeometry.slice(components.get(i)).get(); + kept.add(keptSlices[i]); + } + } + if (kept.isEmpty()) { + throw new IllegalArgumentException("At least one component must be preserved"); + } + geometry = CodedGeometry.compound(kept.toArray(CodedGeometry[]::new)); + } + + /** + * Returns the resource being reduced. + * + * @return source resource + */ + public CodedResource getSource() { + return resource; + } + + @Override + public Optional<GenericName> getIdentifier() throws DataStoreException { + return Optional.ofNullable(name); + } + + @Override + public List<SampleDimension> getSampleDimensions() throws DataStoreException { + return resource.getSampleDimensions(); + } + + @Override + public CodedGeometry getGridGeometry() throws DataStoreException { + return geometry; + } + + @Override + public CodedCoverage read(CodedGeometry query, int... range) throws DataStoreException { + //rebuild a query on all source components, inserting the fixed slices + final CodedGeometry[] parts = new CodedGeometry[components.size()]; + for (int i = 0; i < parts.length; i++) { + parts[i] = (hiddenSlices[i] != null) ? hiddenSlices[i] : query.slice(components.get(i)).orElse(keptSlices[i]); + } + final CodedCoverage baseCoverage = resource.read(CodedGeometry.compound(parts), range); + + //map reduced grid dimensions to source grid dimensions + final CodedGeometry baseGeometry = baseCoverage.getGeometry(); + final int[] sourcePosition = new int[baseGeometry.getExtent().getDimension()]; + int[] targetToSource = new int[sourcePosition.length]; + final List<CodedGeometry> kept = new ArrayList<>(); + int sourceDim = 0; + int targetDim = 0; + try { + for (ReferenceSystem component : ReferenceSystems.getSingleComponents(baseGeometry.getReferenceSystem(), true)) { + final CodedGeometry slice = baseGeometry.slice(component).get(); + final int idx = components.indexOf(component); + if (idx >= 0 && hiddenSlices[idx] != null) { + sourcePosition[sourceDim++] = locate(slice, hiddenSlices[idx]); + } else { + kept.add(slice); + for (int i = 0, n = ReferenceSystems.getDimension(component); i < n; i++) { + targetToSource[targetDim++] = sourceDim++; + } + } + } + } catch (TransformException ex) { + throw new DataStoreException(ex.getMessage(), ex); + } + targetToSource = Arrays.copyOf(targetToSource, targetDim); + + final CodedCoverage subCoverage; + try { + subCoverage = new ArrayCodedCoverage(name, CodedGeometry.compound(kept.toArray(CodedGeometry[]::new)), + baseCoverage.getSampleDimensions().toArray(SampleDimension[]::new)); + } catch (FactoryException ex) { + throw new DataStoreException(ex.getMessage(), ex); + } + + final CodeIterator source = baseCoverage.createIterator(); + try (WritableCodeIterator iterator = subCoverage.createWritableIterator()) { + double[] cell = null; + while (iterator.next()) { + final int[] position = iterator.getPosition(); + for (int i = 0; i < targetToSource.length; i++) { + sourcePosition[targetToSource[i]] = position[i]; + } + source.moveTo(sourcePosition); + cell = source.getCell(cell); + iterator.setCell(cell); + } + } + return subCoverage; + } + + /** + * Finds the grid index in the given coverage slice matching the hidden slice. + */ + private static int locate(CodedGeometry slice, CodedGeometry hidden) throws TransformException { + final GridExtent extent = slice.getExtent(); + if (extent.getSize(0) == 1) { + //source returned the requested single slice + return Math.toIntExact(extent.getLow(0)); + } + //source returned more then the requested slice, search for it + final int index = Math.toIntExact(hidden.getExtent().getLow(0)); + return slice.getGridToRS().toGrid(hidden.getGridToRS().toCode(new int[] {index}))[0]; + } +} diff --git a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ResampledCodedCoverage.java b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ResampledCodedCoverage.java index e73c7ef2b1..06f31b78bb 100644 --- a/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ResampledCodedCoverage.java +++ b/incubator/src/org.apache.sis.referencing.dggs/main/org/apache/sis/storage/rs/internal/shared/ResampledCodedCoverage.java @@ -84,10 +84,15 @@ public final class ResampledCodedCoverage extends AbstractCodedCoverage { return base.getSampleDimensions().size(); } + /** + * Returns the linear position of given grid position, or -1 if outside the extent. + */ private long toLinearPosition(int[] pos) { long p = 0; for (int i = 0; i < dimension; i++) { - p += pos[i] * dimStep[i]; + final long k = pos[i] - dimOffsets[i]; + if (k < 0 || k >= dimSize[i]) return -1; + p += k * dimStep[i]; } return p; } diff --git a/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/storage/rs/internal/shared/ReducedCodedResourceTest.java b/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/storage/rs/internal/shared/ReducedCodedResourceTest.java new file mode 100644 index 0000000000..7d822f7d5a --- /dev/null +++ b/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/storage/rs/internal/shared/ReducedCodedResourceTest.java @@ -0,0 +1,197 @@ +/* + * 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.rs.internal.shared; + +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.opengis.referencing.ReferenceSystem; +import org.opengis.referencing.crs.VerticalCRS; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.GenericName; +import org.apache.sis.coverage.SampleDimension; +import org.apache.sis.coverage.grid.BufferedGridCoverage; +import org.apache.sis.coverage.grid.GridExtent; +import org.apache.sis.coverage.grid.GridGeometry; +import org.apache.sis.coverage.grid.GridOrientation; +import org.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.image.DataType; +import org.apache.sis.image.internal.shared.RasterFactory; +import org.apache.sis.referencing.CRS; +import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.referencing.dggs.DiscreteGlobalGridReferenceSystem; +import org.apache.sis.referencing.dggs.Zone; +import org.apache.sis.referencing.dggs.s2.S2Dggrs; +import org.apache.sis.referencing.rs.ReferenceSystems; +import org.apache.sis.storage.GridCoverageResource; +import org.apache.sis.storage.MemoryGridCoverageResource; +import org.apache.sis.storage.dggs.DiscreteGlobalGridGeometry; +import org.apache.sis.storage.rs.CodeIterator; +import org.apache.sis.storage.rs.CodedCoverage; +import org.apache.sis.storage.rs.CodedCoverages; +import org.apache.sis.storage.rs.CodedGeometry; +import org.apache.sis.storage.rs.CodedResource; +import org.apache.sis.util.iso.Names; + +// Test dependencies +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + + +/** + * Tests {@link ReducedCodedResource}. + * + * @author Johann Sorel (Geomatys) + */ +public class ReducedCodedResourceTest { + + private static final GenericName NAME = Names.createLocalName(null, null, "reduced"); + + private final DiscreteGlobalGridReferenceSystem dggrs = new S2Dggrs(); + + /** + * Hides the temporal component, the vertical component is preserved. + */ + @Test + public void testHideTemporal() throws Exception { + final CodedResource source = createSource(); + final ReducedCodedResource reduced = new ReducedCodedResource(NAME, source, Map.of(2, 1)); + + final CodedGeometry geometry = reduced.getGridGeometry(); + final List<ReferenceSystem> components = ReferenceSystems.getSingleComponents(geometry.getReferenceSystem(), true); + assertEquals(2, components.size()); + assertEquals(dggrs, components.get(0)); + assertInstanceOf(VerticalCRS.class, components.get(1)); + + final List<Object> zoneIds = rootZoneIds(); + final CodedGeometry query = CodedGeometry.compound( + DiscreteGlobalGridGeometry.unstructured(dggrs, zoneIds, null), + geometry.slice(components.get(1)).get()); + + final CodedCoverage coverage = reduced.read(query); + assertEquals(2, coverage.getGeometry().getExtent().getDimension()); + assertEquals(components, ReferenceSystems.getSingleComponents(coverage.getGeometry().getReferenceSystem(), true)); + + // values at time index 1 are 4, 5, 6 for vertical index 0, 1, 2 + final CodeIterator iterator = coverage.createIterator(); + int count = 0; + while (iterator.next()) { + final int[] position = iterator.getPosition(); + assertEquals(2, position.length); + assertEquals(4.0 + position[1], iterator.getSampleDouble(0), 0.0); + count++; + } + assertEquals(zoneIds.size() * 3, count); + } + + /** + * Hides both vertical and temporal components, only the DGGRS component is preserved. + */ + @Test + public void testHideVerticalAndTemporal() throws Exception { + final CodedResource source = createSource(); + final ReducedCodedResource reduced = new ReducedCodedResource(NAME, source, Map.of(1, 0, 2, 2)); + + final CodedGeometry geometry = reduced.getGridGeometry(); + assertEquals(dggrs, geometry.getReferenceSystem()); + + final List<Object> zoneIds = rootZoneIds(); + final CodedCoverage coverage = reduced.read(DiscreteGlobalGridGeometry.unstructured(dggrs, zoneIds, null)); + assertEquals(1, coverage.getGeometry().getExtent().getDimension()); + + // value at vertical index 0 and time index 2 is 7 + final CodeIterator iterator = coverage.createIterator(); + int count = 0; + while (iterator.next()) { + assertEquals(1, iterator.getPosition().length); + assertEquals(7.0, iterator.getSampleDouble(0), 0.0); + count++; + } + assertEquals(zoneIds.size(), count); + } + + /** + * Tests invalid constructor arguments. + */ + @Test + public void testInvalidArguments() throws Exception { + final CodedResource source = createSource(); + // component does not exist + assertThrows(IllegalArgumentException.class, () -> new ReducedCodedResource(NAME, source, Map.of(3, 0))); + // DGGRS component can not be hidden + assertThrows(IllegalArgumentException.class, () -> new ReducedCodedResource(NAME, source, Map.of(0, 0))); + // index outside vertical extent + assertThrows(IllegalArgumentException.class, () -> new ReducedCodedResource(NAME, source, Map.of(1, 3))); + } + + private List<Object> rootZoneIds() throws TransformException { + return dggrs.getGridSystem().getHierarchy().getGrids().get(1).getZones().map(Zone::getIdentifier).toList(); + } + + /** + * Creates a resource with DGGRS, vertical and temporal components. + * Cell values are {@code 1 + vertical index + 3 * temporal index}. + */ + private CodedResource createSource() throws Exception { + return CodedCoverages.viewAsDggrs(Names.createLocalName(null, null, "coverage4d"), create4D(), dggrs); + } + + private static GridCoverageResource create4D() throws Exception { + final GeneralEnvelope dataEnv = new GeneralEnvelope(CRS.compound( + CommonCRS.WGS84.normalizedGeographic(), + CommonCRS.Vertical.ELLIPSOIDAL.crs(), + CommonCRS.Temporal.JAVA.crs() + )); + dataEnv.setRange(0, -180, 180); + dataEnv.setRange(1, -90, 90); + dataEnv.setRange(2, 100, 300); + dataEnv.setRange(3, + Instant.parse("2000-06-10T10:00:00Z").toEpochMilli(), + Instant.parse("2000-06-10T12:00:00Z").toEpochMilli()); + final GridGeometry dataGrid = new GridGeometry( + new GridExtent(null, new long[4], new long[] { 2, 2, 3, 3 }, false), + dataEnv, + GridOrientation.DISPLAY + ); + + final ByteBuffer dataBuffer = ByteBuffer.wrap(new byte[] { + 1, 1, 1, 1, // v0 t0 + 2, 2, 2, 2, // v1 t0 + 3, 3, 3, 3, // v2 t0 + 4, 4, 4, 4, // v0 t1 + 5, 5, 5, 5, // v1 t1 + 6, 6, 6, 6, // v2 t1 + 7, 7, 7, 7, // v0 t2 + 8, 8, 8, 8, // v1 t2 + 9, 9, 9, 9 // v2 t2 + }); + + final BufferedGridCoverage coverage = new BufferedGridCoverage( + dataGrid, + (List) List.of(new SampleDimension.Builder().setName("test").build()), + RasterFactory.wrap(DataType.BYTE, dataBuffer) + ); + return new MemoryGridCoverageResource(null, null, coverage, null) { + @Override + public Optional<GenericName> getIdentifier() { + return Optional.of(Names.createLocalName(null, null, "test")); + } + }; + } +}
