blob: a2cd2721533a8919fc30d964073962b50755164f [file] [log] [blame]
/*
* (C) 2022, Geomatys
*/
package org.apache.sis.storage.tiling;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.LongStream;
import org.apache.sis.coverage.grid.GridClippingMode;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridRoundingMode;
import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.util.Static;
import org.apache.sis.util.Utilities;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.Matrix;
/**
* TileMatrix utilities.
*
* @author Johann Sorel (Geomatys)
* @version 1.3
* @since 1.3
*/
public final class TileMatrices extends Static {
private TileMatrices(){}
/**
* Compute tiling scheme of a list of tile geometries.
*
* @param grids tile grid geometries
* @return tiling scheme and map of each tile indices or empty if any tile do not fit in the scheme.
*/
public static Optional<Entry<GridGeometry, Map<GridGeometry,long[]>>> toTilingScheme(GridGeometry... grids) {
final GridGeometry first = grids[0];
GridGeometry tilingScheme;
{ // Create a first tiling scheme, pick the first grid as a tile reference
if (!first.isDefined(GridGeometry.EXTENT | GridGeometry.CRS | GridGeometry.GRID_TO_CRS)) {
//we don't have enough informations to compute the tiling scheme
return Optional.empty();
}
final MathTransform firstGridToCRS = first.getGridToCRS(PixelInCell.CELL_CENTER);
if (!(firstGridToCRS instanceof LinearTransform) || !((LinearTransform) firstGridToCRS).isAffine()) {
//detection works only for affine transforms
return Optional.empty();
}
//create a first tiling scheme
final GridGeometry forceLowerToZero = forceLowerToZero(first);
final int[] subsampling = LongStream.of(forceLowerToZero.getExtent().getHigh().getCoordinateValues())
.mapToInt(Math::toIntExact)
.map((int operand) -> operand+1) //high values are inclusive
.toArray();
tilingScheme = forceLowerToZero.derive().subgrid(null, subsampling).build();
}
//compute all tile indices
final Map<GridGeometry,long[]> tiles = new HashMap<>();
final int dimension = tilingScheme.getExtent().getDimension();
tiles.put(first, tilingScheme.getExtent().getLow().getCoordinateValues());
final long[] min = new long[dimension];
final long[] max = new long[dimension];
for (int i = 1; i < grids.length; i++) {
final Optional<long[]> indice = getTileIndices(first, tilingScheme, grids[i]);
if (!indice.isPresent()) return Optional.empty();
long[] r = indice.get();
tiles.put(grids[i], r);
//keep track of min/max range
for (int k = 0; k < dimension; k++) {
min[k] = Math.min(min[k], r[k]);
max[k] = Math.max(max[k], r[k]);
}
}
//rebuild the tiling scheme extent to contain all tiles
tilingScheme = new GridGeometry(
new GridExtent(null, min, max, true),
PixelInCell.CELL_CENTER,
tilingScheme.getGridToCRS(PixelInCell.CELL_CENTER),
tilingScheme.getCoordinateReferenceSystem());
tilingScheme = forceLowerToZero(tilingScheme);
//offset all indices
for (Entry<GridGeometry,long[]> entry : tiles.entrySet()) {
final long[] indices = entry.getValue();
for (int i = 0; i < dimension; i++) {
indices[i] -= min[i];
}
}
return Optional.of(new AbstractMap.SimpleImmutableEntry<>(tilingScheme, tiles));
}
/**
* Find tile indice in given tiling scheme.
*
* @param referenceTile a valid tile used a reference.
* @param tilingScheme the tiling scheme geometry.
* @param tileGrid searched tile grid geometry.
* @return tile index or empty if tile do not fit in the scheme.
*/
private static Optional<long[]> getTileIndices(GridGeometry referenceTile, GridGeometry tilingScheme, GridGeometry tileGrid) {
if (!tileGrid.isDefined(GridGeometry.EXTENT | GridGeometry.CRS | GridGeometry.GRID_TO_CRS)) {
//we don't have enough informations to compute the tile indices
return Optional.empty();
}
if (!Utilities.equalsIgnoreMetadata(referenceTile.getCoordinateReferenceSystem(), tileGrid.getCoordinateReferenceSystem())) {
//tile candidate has different CRS
return Optional.empty();
}
final MathTransform gridToCRS = tileGrid.getGridToCRS(PixelInCell.CELL_CENTER);
if (!(gridToCRS instanceof LinearTransform) || !((LinearTransform) gridToCRS).isAffine()) {
//indice computation works only for affine transforms
return Optional.empty();
}
//matrices must differ only by the last column (translation)
final LinearTransform firstLinear = (LinearTransform) referenceTile.getGridToCRS(PixelInCell.CELL_CENTER);
final Matrix matrix1 = firstLinear.getMatrix();
final LinearTransform linear2 = (LinearTransform) gridToCRS;
final Matrix matrix2 = linear2.getMatrix();
for (int x = 0, xn = matrix1.getNumCol() - 1, yn = matrix1.getNumRow(); x < xn; x++) {
for (int y = 0; y < yn; y++) {
if (matrix1.getElement(y, x) != matrix2.getElement(y, x)) {
return Optional.empty();
}
}
}
//tiles must have the same extent size
final GridExtent referenceExtent = referenceTile.getExtent();
final GridExtent candidateExtent = tileGrid.getExtent();
for (int i = 0, n = referenceExtent.getDimension(); i < n; i++) {
if (referenceExtent.getSize(i) != candidateExtent.getSize(i)) {
return Optional.empty();
}
}
//compute the tile indice
final GridExtent intersection = tilingScheme.derive().clipping(GridClippingMode.NONE).rounding(GridRoundingMode.ENCLOSING).subgrid(tileGrid).getIntersection();
final long[] low = intersection.getLow().getCoordinateValues();
final long[] high = intersection.getHigh().getCoordinateValues();
//if tile overlaps several indices then it's not part of the tiling scheme
if (!Arrays.equals(low, high)) {
return Optional.empty();
}
return Optional.of(low);
}
/**
* Shift lower extent to zero.
*/
private static GridGeometry forceLowerToZero(final GridGeometry gg) {
final GridExtent extent = gg.getExtent();
if (!extent.startsAtZero()) {
CoordinateReferenceSystem crs = null;
if (gg.isDefined(GridGeometry.CRS)) crs = gg.getCoordinateReferenceSystem();
final int dimension = extent.getDimension();
final double[] vector = new double[dimension];
final long[] high = new long[dimension];
for (int i = 0; i < dimension; i++) {
final long low = extent.getLow(i);
high[i] = extent.getHigh(i) - low;
vector[i] = low;
}
MathTransform gridToCRS = gg.getGridToCRS(PixelInCell.CELL_CENTER);
gridToCRS = MathTransforms.concatenate(MathTransforms.translation(vector), gridToCRS);
return new GridGeometry(new GridExtent(null, null, high, true), PixelInCell.CELL_CENTER, gridToCRS, crs);
}
return gg;
}
}