| /* |
| * 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.Map; |
| import java.util.List; |
| import java.util.Arrays; |
| import org.opengis.referencing.datum.PixelInCell; |
| import org.opengis.referencing.operation.Matrix; |
| import org.opengis.referencing.operation.MathTransform; |
| import org.opengis.referencing.operation.NoninvertibleTransformException; |
| import org.apache.sis.referencing.operation.matrix.MatrixSIS; |
| import org.apache.sis.storage.GridCoverageResource; |
| import org.apache.sis.storage.DataStoreException; |
| import org.apache.sis.coverage.grid.GridGeometry; |
| import org.apache.sis.coverage.grid.GridExtent; |
| import org.apache.sis.coverage.privy.CommonDomainFinder; |
| import org.apache.sis.util.privy.Strings; |
| |
| |
| /** |
| * A grid resource which is a slice in a larger coverage. |
| * A slice is not necessarily 1 cell tick; larger slices are accepted. |
| * |
| * <h2>Usage context</h2> |
| * Instances of {@code Gridslice} are grouped by CRS, then instances having the same CRS |
| * are grouped by "grid to CRS" transform in the {@link GroupByTransform#members} list. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| */ |
| final class GridSlice { |
| /** |
| * The "pixel in cell" value used for all "grid to CRS" computations. |
| */ |
| static final PixelInCell CELL_ANCHOR = PixelInCell.CELL_CORNER; |
| |
| /** |
| * The resource associated to this slice. |
| */ |
| final GridCoverageResource resource; |
| |
| /** |
| * Geometry of the grid coverage or resource. |
| */ |
| private final GridGeometry geometry; |
| |
| /** |
| * Translation from source coordinates of {@link GroupByTransform#gridToCRS} |
| * to grid coordinates of {@link #geometry}. Shall be considered read-only |
| * after initialization of {@link #setOffset(MatrixSIS)}. |
| */ |
| private final long[] offset; |
| |
| /** |
| * Creates a new slice for the specified resource. |
| * |
| * @param slice resource associated to this slice. |
| */ |
| GridSlice(final GridCoverageResource slice) throws DataStoreException { |
| resource = slice; |
| geometry = slice.getGridGeometry(); |
| offset = new long[geometry.getDimension()]; |
| } |
| |
| /** |
| * Returns the group of objects associated to the CRS and "grid to CRS" transform. |
| * The CRS comparisons ignore metadata and transform comparisons ignore integer translations. |
| * This method takes a synchronization lock on the given list. |
| * |
| * @param groups the list where to search for a group. |
| * @param strategy algorithm to apply when more than one grid coverage can be found at the same grid index. |
| * @return group of objects associated to the given transform (never null). |
| * @throws NoninvertibleTransformException if the transform is not invertible. |
| * @throws ArithmeticException if a translation term is NaN or overflows {@code long} integer capacity. |
| */ |
| final GroupByTransform getList(final List<GroupByCRS<GroupByTransform>> groups, final MergeStrategy strategy) |
| throws NoninvertibleTransformException |
| { |
| final MathTransform gridToCRS = geometry.getGridToCRS(CELL_ANCHOR); |
| final MathTransform crsToGrid = gridToCRS.inverse(); |
| final List<GroupByTransform> transforms = GroupByCRS.getOrAdd(groups, geometry).members; |
| synchronized (transforms) { |
| for (final GroupByTransform c : transforms) { |
| final Matrix groupToSlice = c.linearTransform(crsToGrid); |
| if (CommonDomainFinder.integerTranslation(groupToSlice, offset) != null) { |
| c.strategy = strategy; |
| return c; |
| } |
| } |
| final GroupByTransform c = new GroupByTransform(geometry, gridToCRS, strategy); |
| transforms.add(c); |
| return c; |
| } |
| } |
| |
| /** |
| * Returns the grid extent of this slice. The grid coordinate system is specific to this slice. |
| * For converting grid coordinates to the concatenated grid coordinate system, {@link #offset} |
| * must be subtracted. |
| */ |
| final GridExtent getGridExtent() { |
| return geometry.getExtent(); |
| } |
| |
| /** |
| * Writes information about grid extent into the given {@code DimensionSelector} objects. |
| * This is invoked by {@link GroupByTransform#findConcatenatedDimensions()} for choosing |
| * a dimension to concatenate. |
| */ |
| final void getGridExtent(final int i, final DimensionSelector[] writeTo) { |
| final GridExtent extent = getGridExtent(); |
| for (int dim = writeTo.length; --dim >= 0;) { |
| writeTo[dim].setSliceExtent(i, Math.subtractExact(extent.getMedian(dim), offset[dim]), extent.getSize(dim)); |
| } |
| } |
| |
| /** |
| * Returns the low grid index in the given dimension, relative to the grid of the group. |
| * This is invoked by {@link GroupByTransform#sortAndGetLows(int)} for sorting coverages. |
| * |
| * @param dim dimension of the desired grid coordinates. |
| * @return low index in the coordinate system of the group grid. |
| */ |
| final long getGridLow(final int dim) { |
| return Math.subtractExact(geometry.getExtent().getLow(dim), offset[dim]); |
| } |
| |
| /** |
| * Returns the translation from source coordinates of {@link GroupByTransform#gridToCRS} to |
| * grid coordinates of {@link #geometry}. This method returns a unique instance if possible. |
| * |
| * @param shared a pool of existing offset instances. |
| * @return translation from aggregated grid geometry to slice. Shall be considered read-only. |
| */ |
| final long[] getOffset(final Map<GridSlice,long[]> shared) { |
| final long[] old = shared.putIfAbsent(this, offset); |
| return (old != null) ? old : offset; |
| } |
| |
| /** |
| * Compares the offset of this grid slice with the offset of given slice. |
| * This method is defined only for the purpose of {@link #getOffset(Map)}. |
| * Equality should not be used in other context. |
| */ |
| @Override |
| public final boolean equals(final Object other) { |
| return (other instanceof GridSlice) && Arrays.equals(((GridSlice) other).offset, offset); |
| } |
| |
| /** |
| * Returns a hash code for the offset consistently with {@link #equals(Object)} purpose. |
| */ |
| @Override |
| public final int hashCode() { |
| return Arrays.hashCode(offset); |
| } |
| |
| /** |
| * Returns a string representation for debugging purposes. |
| */ |
| @Override |
| public String toString() { |
| Object id = null; |
| if (resource != null) try { |
| id = resource.getIdentifier().orElse(null); |
| } catch (DataStoreException e) { |
| id = e.toString(); |
| } |
| return Strings.toString(getClass(), null, id); |
| } |
| } |