/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2015, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.coverage.combineIterator; import java.util.Iterator; import java.util.List; import org.apache.sis.geometry.Envelopes; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.measure.NumberRange; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.NullArgumentException; import org.geotoolkit.coverage.grid.GeneralGridGeometry; import org.geotoolkit.internal.referencing.CRSUtilities; import org.geotoolkit.referencing.ReferencingUtilities; import org.opengis.coverage.grid.GridCoordinates; import org.opengis.coverage.grid.GridEnvelope; import org.opengis.geometry.Envelope; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.apache.sis.util.Utilities; /** * {@link Iterator} to iterate on each {@link Envelope} dimensions from {@link GeneralGridGeometry}.<br> * For each steps it return a slice part of the source envelope.<br> * The right way to use this iterator is into a while loop, like follow : <br><br> * {@code * while (iterator.hasNext()) { * Envelope myEnvelope = iterator.next(); * } } * * @author Remi Marechal (Geomatys). * @version 4.0 * @since 4.0 */ public final strictfp class GridCombineIterator implements Iterator<Envelope> { /** * Associate {@link MathTransform} with the grid. */ private final MathTransform gridToCrs; /** * Grid space dimension. */ private final int dim; /** * Current affected ordinate by iterator. */ private final int[] affectedOrdinateIndex; /** * The minimum grid values on all dimensions. * * @see GridEnvelope#getLow() * @see GridCoordinates#getCoordinateValues() */ private final int[] gridLow; /** * The maximum grid values on all dimensions. * * @see GridEnvelope#getHigh() * @see GridCoordinates#getCoordinateValues() */ private final int[] gridHigh; /** * Define if exist another iteration. * @see #hasNext() */ private boolean finish = false; /** * The destination base grid which will be use to build destination {@link Envelope}. * * @see #next() */ private final GeneralEnvelope currentGrid; /** * An internaly array which define on which ordinate dimension this iterator work. * * @see #nextCursorPos(int) */ private final int[] affectedOrdinate; /** * An internaly {@code Integer} use to refer the current traveled dimension array. */ private final int currentDimIndex; /** * <p> Create an {@link Iterator} on each {@link GridEnvelope} dimensions, * which are not within the 2D part of the {@link CoordinateReferenceSystem}.<br> * Objects returned by {@link #next() } are {@link Envelope} type, which are built from iteration on each * {@link GridEnvelope} dimensions and transformed into the outCrs by the {@linkplain MathTransform gridToCrs}.<br><br> * <strong> * Moreover : the specified {@link MathTransform} must be from CORNER of source grid point to CORNER of destination envelope point.<br> * The used MathTransform is consider with {@link PixelInCell#CELL_CORNER} configuration.</strong></p> * * @param extent the grid which will be travel by iterator. * @param outCRS the crs of the out envelope, may be {@code null}. * @param gridToCrs the specified MathTransform considered in {@link PixelInCell#CELL_CORNER} configuration. * * @throws NullArgumentException if any of extent or gridtocrs is {@code null}. * @throws MismatchedDimensionException if dimension between crs (if it is not null) dimension and {@linkplain MathTransform#getTargetDimensions() gridToCrs.getTargetDimensions()} mismatch, * or if dimension between extent and {@linkplain MathTransform#getSourceDimensions() gridToCrs.getSourceDimensions()} mismatch. */ public GridCombineIterator(final GridEnvelope extent, final CoordinateReferenceSystem outCRS, final MathTransform gridToCrs) { ArgumentChecks.ensureNonNull("extent", extent); ArgumentChecks.ensureNonNull("gridToCrs", gridToCrs); this.gridToCrs = gridToCrs; dim = extent.getDimension(); //-- check validity of non null parameters. if (gridToCrs.getSourceDimensions() != dim) throw new MismatchedDimensionException("GridCombineIterator : extent dimension and gridToCrs sourceDimension mismatch. " + "Extent dimension = "+dim+" GridToCrs source dimension = "+gridToCrs.getSourceDimensions()); if (outCRS != null && outCRS.getCoordinateSystem().getDimension() != dim) throw new MismatchedDimensionException("GridCombineIterator : crs dimension and expected targetDimension mismatch. " + "expected dimension = "+dim+" crs dimension = "+outCRS.getCoordinateSystem().getDimension()); //-- fill the min max array with min and max from interested dimension iteration gridLow = extent.getLow().getCoordinateValues(); gridHigh = extent.getHigh().getCoordinateValues(); //-- build the future generate grid. currentGrid = (outCRS != null) ? new GeneralEnvelope(outCRS) : new GeneralEnvelope(dim); //-- set grid coordinates into output grid from the 2D Crs part. final int minHorizonOrdinate = CRSUtilities.firstHorizontalAxis(outCRS); currentGrid.setRange(minHorizonOrdinate, gridLow[minHorizonOrdinate], gridHigh[minHorizonOrdinate]); currentGrid.setRange(minHorizonOrdinate + 1, gridLow[minHorizonOrdinate + 1], gridHigh[minHorizonOrdinate + 1]); //-- initialize the current dimension array index currentDimIndex = dim - 3; //-- in other words iterator travel first affectedOrdinate[currentDimIndex] see next() method. //-- iteration only if dimension > 2. if (dim >= 3) { if (dim != outCRS.getCoordinateSystem().getDimension()) throw new MismatchedDimensionException("GridCombineIterator : Extent number dimension mismatch with CRS " + "source Dimension. Extent dim = "+dim+" CRS dim = "+outCRS.getCoordinateSystem().getDimension()); affectedOrdinate = new int[dim - 2]; int aoi = 0; //-- fill affected ordinate which represente values at the first iteration. affectedOrdinateIndex = new int[dim - 2];//-- index dans l'iterateur. for (int i = 0; i < dim; i++) { if (i != minHorizonOrdinate && i != (minHorizonOrdinate + 1)) { affectedOrdinate[aoi] = i; affectedOrdinateIndex[aoi++] = extent.getLow(i);//-- prepare first iteration } } } else { affectedOrdinate = affectedOrdinateIndex = null; } } /** * <p> Create an {@link Iterator} which travel a specified {@link GridEnvelope} dimension, * which are not within the 2D part of the {@link CoordinateReferenceSystem}.<br> * Objects returned by {@link #next() } are {@link Envelope} type, which are built from iteration on the expected * {@link GridEnvelope} dimension and transformed into the outCrs by the {@linkplain MathTransform gridToCrs}.<br><br> * <strong> * Moreover : the specified {@link MathTransform} must be from CORNER of source grid point to CORNER of destination envelope point.<br> * The used MathTransform is consider with {@link PixelInCell#CELL_CORNER} configuration.</strong></p> * * @param extent the grid which will be travel by iterator. * @param outCRS the crs of the out envelope, may be {@code null}. * @param gridToCrs the specified MathTransform considered in {@link PixelInCell#CELL_CORNER} configuration. * @param interestedOrdinateIndex the specified dimension on which the iterator travel. * * @throws NullArgumentException if any of extent or gridtocrs is {@code null}. * @throws MismatchedDimensionException if dimension between crs (if it is not null) dimension and {@linkplain MathTransform#getTargetDimensions() gridToCrs.getTargetDimensions()} mismatch, * or if dimension between extent and {@linkplain MathTransform#getSourceDimensions() gridToCrs.getSourceDimensions()} mismatch. */ public GridCombineIterator(final GridEnvelope extent, final CoordinateReferenceSystem outCRS, final MathTransform gridToCrs, final int interestedOrdinateIndex) { ArgumentChecks.ensureNonNull("extent", extent); ArgumentChecks.ensureNonNull("gridToCrs", gridToCrs); ArgumentChecks.ensurePositive("interestedOrdinateIndex", interestedOrdinateIndex); this.gridToCrs = gridToCrs; dim = extent.getDimension(); //-- check validity of expected dimension if (interestedOrdinateIndex >= dim) throw new IndexOutOfBoundsException("GridCombineIterator : The interestedOrdinateIndex is upper or equals than maximum dimension number" + " from grid dimension. Expected lower than : "+dim+", found : "+interestedOrdinateIndex); //-- check validity of non null parameters. if (gridToCrs.getSourceDimensions() != dim) throw new MismatchedDimensionException("GridCombineIterator : extent dimension and gridToCrs sourceDimension mismatch. " + "Extent dimension = "+dim+" GridToCrs source dimension = "+gridToCrs.getSourceDimensions()); if (outCRS != null) { if (outCRS.getCoordinateSystem().getDimension() != dim) throw new MismatchedDimensionException("GridCombineIterator : crs dimension and expected targetDimension mismatch. " + "expected dimension = "+dim+" crs dimension = "+outCRS.getCoordinateSystem().getDimension()); final int minHorizonOrdinate = CRSUtilities.firstHorizontalAxis(outCRS); if (interestedOrdinateIndex == minHorizonOrdinate || interestedOrdinateIndex == minHorizonOrdinate + 1) throw new MismatchedDimensionException("GridCombineIterator : you cannot iterate on (geographic)2D CRS part." + "The interestedOrdinateIndex must be out of ["+minHorizonOrdinate+"; "+(minHorizonOrdinate + 1)+"] interval. Found : "+interestedOrdinateIndex); } //-- fill the min max array with min and max from interested dimension iteration gridLow = extent.getLow().getCoordinateValues(); gridHigh = extent.getHigh().getCoordinateValues(); //-- build the future generate grid. currentGrid = (outCRS != null) ? new GeneralEnvelope(outCRS) : new GeneralEnvelope(dim); for (int d = 0; d < dim; d++) { //-- set grid coordinates into output grid from other ordinates than interestedOrdinateIndex. if (d != interestedOrdinateIndex) currentGrid.setRange(d, gridLow[d], gridHigh[d]); } //-- initialize the current dimension array index currentDimIndex = 0; //-- in other words iterator travel affectedOrdinate[currentDimIndex] see next() method. //-- iteration only if dimension > 2. if (dim >= 3) { if (outCRS != null && dim != outCRS.getCoordinateSystem().getDimension()) throw new MismatchedDimensionException("GridCombineIterator : Extent number dimension mismatch with CRS " + "source Dimension. Extent dim = "+dim+" CRS dim = "+outCRS.getCoordinateSystem().getDimension()); //-- prepare iteration on only one axis affectedOrdinate = new int[]{interestedOrdinateIndex}; //-- fill affected ordinate which represente values at the first iteration. affectedOrdinateIndex = new int[]{extent.getLow(interestedOrdinateIndex)}; } else { affectedOrdinate = affectedOrdinateIndex = null; } } /** * Create an {@link Iterator} on each {@link Envelope} dimensions from {@link GeneralGridGeometry#getEnvelope() }, * which are not within the 2D part of the {@link CoordinateReferenceSystem} from {@link GeneralGridGeometry#getCoordinateReferenceSystem() }. * * @param gridGeom the grid Geometry which contain all needed informations to iterate on its envelope dimension. */ public GridCombineIterator(final GeneralGridGeometry gridGeom) { this(gridGeom.getExtent(), gridGeom.getCoordinateReferenceSystem(), gridGeom.getGridToCRS(PixelInCell.CELL_CORNER)); } /** * Pass to the next dimension cursor position. * * @param currentDim current traveled dimension. */ private void nextCursorPos(final int currentDim) { if (currentDim < 0) { finish = true; return; } assert currentDim >= 0 : "CombineIterator.nextCursorPos() : current dimension should be into N space. " + "But it is into Z space. currentDim = "+currentDim; affectedOrdinateIndex[currentDim]++; if (affectedOrdinateIndex[currentDim] > gridHigh[affectedOrdinate[currentDim]]) { //-- test if travel all current dimension values affectedOrdinateIndex[currentDim] = gridLow[affectedOrdinate[currentDim]]; //-- initialize current dimension value nextCursorPos(currentDim - 1);//-- pass to the next dimension } } /** * Returns {@code true} if an other slice {@link Envelope} may be computed else {@code false}. * * @return {@code true} if an other slice {@link Envelope} may be computed else {@code false}. */ @Override public boolean hasNext() { return !finish; } /** * Return if exist, the next multi-dimensional slice {@link Envelope}. * * @return the next multi-dimensional slice {@link Envelope}. * @throws IllegalStateException if it doesn't exist any next iteration. * @throws IllegalStateException if problem during gridToCrs transformation. */ @Override public Envelope next() { if (finish) throw new IllegalStateException("GridCombineIterator : Iteration on all dimensions is finished."); try { //-- if 2D if (dim < 3) { finish = true; final GeneralEnvelope outEnv = Envelopes.transform(gridToCrs, currentGrid); //-- set crs into out envelope if it is not null if (currentGrid.getCoordinateReferenceSystem() != null) outEnv.setCoordinateReferenceSystem(currentGrid.getCoordinateReferenceSystem()); return outEnv; } //-- build new envelope from new grid //-- 1 : fill gridLow and gridHigh arrays which represent grid ordinates of future returned envelope assert dim > 2 : "GridCombineIterator.next() : expected dimension > 2, found : dim = "+dim; assert (assertNext()); //-- Set appriopriate values into grid envelope. for (int i = 0; i < affectedOrdinate.length; i++) { currentGrid.setRange(affectedOrdinate[i], affectedOrdinateIndex[i], affectedOrdinateIndex[i]); } final GeneralEnvelope returnedEnvelope = Envelopes.transform(gridToCrs, currentGrid); //-- prepare next iteration nextCursorPos(currentDimIndex); //-- set crs into out envelope if it is not null if (currentGrid.getCoordinateReferenceSystem() != null) returnedEnvelope.setCoordinateReferenceSystem(currentGrid.getCoordinateReferenceSystem()); return returnedEnvelope; } catch (TransformException ex) { throw new IllegalStateException("GridCombineIterator.next() : ", ex); } } /** * Effectuate some verifications to expected {@link #next() } comportement. * * @return {@code true} if values are verify, else return {@code false}. */ private boolean assertNext() { if (currentDimIndex == 0) { assert (affectedOrdinate.length == 1) : "GridCombineIterator.next() : expected affectedOrdinate.length = 1, found : "+affectedOrdinate.length; assert (affectedOrdinate.length == affectedOrdinateIndex.length) : "GridCombineIterator.next() : expected affectedOrdinateIndex.length = "+(dim - 2)+", found : "+affectedOrdinateIndex.length; } else { assert (affectedOrdinate.length == dim - 2) : "GridCombineIterator.next() : expected affectedOrdinate.length = "+(dim - 2)+", found : "+affectedOrdinate.length; assert (affectedOrdinate.length == affectedOrdinateIndex.length) : "GridCombineIterator.next() : expected affectedOrdinateIndex.length = "+(dim - 2)+", found : "+affectedOrdinateIndex.length; } return true; } /** * {@inheritDoc } */ @Override public void remove() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } /** * Returns all ordinates ranges from grid transformation of {@linkplain GridEnvelope extent} * by {@linkplain MathTransform gridtocrs} on axis specified by expected one dimensional {@link CoordinateReferenceSystem}.<br><br> * <strong> * Moreover : the specified {@link MathTransform} must be from CORNER of source grid point to CORNER of destination envelope point.<br> * The used MathTransform is consider with {@link PixelInCell#CELL_CORNER} configuration.</strong> * * @param gridGeometry {@link GeneralGridGeometry} which contain {@linkplain GeneralGridGeometry#getExtent() extent} * and {@linkplain GeneralGridGeometry#getGridToCRS() gridToCrs} necessary to find all expected ordinate values. * @param crs 1 Dimensional {@link CoordinateReferenceSystem} which represent expected interested ordinate index. * @return an array which contain all MINIMUM destination ordinates values from interested dimension (axis). * * @throws NullArgumentException if any of egridGeometry or crs is {@code null}. * @throws IllegalArgumentException if crs in parameter is not include in the internal gridGeometry CoordinateReferenceSystem. * @throws MismatchedDimensionException if the parameter crs have a dimension upper than 1. * @see #extractAxisRanges(org.geotoolkit.coverage.grid.GeneralGridGeometry, int) */ public static NumberRange<Double>[] extractAxisRanges(final GeneralGridGeometry gridGeometry, final CoordinateReferenceSystem crs) { ArgumentChecks.ensureNonNull("gridGeometry", gridGeometry); ArgumentChecks.ensureNonNull("crs", crs); if (crs.getCoordinateSystem().getDimension() > 1) throw new MismatchedDimensionException("The parameter crs which define on each axis the ordinate values are compute have a " + "too great dimension number.Expected dimension number 1, found : "+crs.getCoordinateSystem().getDimension()); final List<CoordinateReferenceSystem> listCrs = ReferencingUtilities.decompose(gridGeometry.getCoordinateReferenceSystem()); int interestedOrdinateIndex = 0; for (final CoordinateReferenceSystem currentCrs : listCrs) { if (Utilities.equalsIgnoreMetadata(currentCrs, crs)) break; interestedOrdinateIndex += currentCrs.getCoordinateSystem().getDimension(); } /* * if interestedOrdinateIndex == gridgeometry dimension number -> means crs parameter * not include in gridGeometry compoundCrs */ if (interestedOrdinateIndex == gridGeometry.getDimension()) throw new IllegalArgumentException("The crs in parameter is not include in the internal " + "gridGeometry CoordinateReferenceSystem."); return GridCombineIterator.extractAxisRanges(gridGeometry, interestedOrdinateIndex); } /** * Returns all ordinates ranges from grid transformation of {@linkplain GridEnvelope extent} * by {@linkplain MathTransform gridtocrs} from {@link GeneralGridGeometry} on axis specified by expected dimension parameter.<br> * One range for each grid values on interested ordinate.<br><br> * <strong> * Moreover : the specified {@link MathTransform} must be from CORNER of source grid point to CORNER of destination envelope point.<br> * The used MathTransform is consider with {@link PixelInCell#CELL_CORNER} configuration.</strong> * * @param gridGeometry {@link GeneralGridGeometry} which contain {@linkplain GeneralGridGeometry#getExtent() extent} * and {@linkplain GeneralGridGeometry#getGridToCRS() gridToCrs} necessary to find all expected ordinate values. * @param interestedOrdinateIndex expected interested ordinate index. * @return an array which contain all destination ordinate values from interested dimension. * * @throws NullArgumentException if any of extent or gridtocrs is {@code null}. * @throws IllegalArgumentException if interestedOrdinateIndex is negative. * @throws MismatchedDimensionException if dimension between extent and * {@linkplain MathTransform#getSourceDimensions() gridToCrs.getSourceDimensions()} mismatch. * @throws IndexOutOfBoundsException if interestedOrdinateIndex is upper than extent or gridtocrs dimension. */ public static NumberRange<Double>[] extractAxisRanges(final GeneralGridGeometry gridGeometry, final int interestedOrdinateIndex) { return GridCombineIterator.extractAxisRanges(gridGeometry.getExtent(), gridGeometry.getGridToCRS(PixelInCell.CELL_CORNER), gridGeometry.getCoordinateReferenceSystem(), interestedOrdinateIndex); } /** * Returns all ordinate ranges from grid transformation of {@linkplain GridEnvelope extent} * by {@linkplain MathTransform gridtocrs} on axis specified by expected dimension parameter.<br> * One range for each grid values on interested ordinate.<br><br> * <strong> * Moreover : the specified {@link MathTransform} must be from CORNER of source grid point to CORNER of destination envelope point.<br> * The used MathTransform is consider with {@link PixelInCell#CELL_CORNER} configuration.</strong> * * @param extent used grid to define ordinate value. * @param gridToCrs used gridtocrs to define destination ordinate values. * @param interestedOrdinateIndex expected interested ordinate index. * @return an array which contain all destination ordinate values from interested dimension. * * @throws NullArgumentException if any of extent or gridtocrs is {@code null}. * @throws IllegalArgumentException if interestedOrdinateIndex is negative. * @throws MismatchedDimensionException if dimension between extent and * {@linkplain MathTransform#getSourceDimensions() gridToCrs.getSourceDimensions()} mismatch. * @throws IndexOutOfBoundsException if interestedOrdinateIndex is upper than extent or gridtocrs dimension. */ public static NumberRange<Double>[] extractAxisRanges(final GridEnvelope extent, final MathTransform gridToCrs, final int interestedOrdinateIndex) { return GridCombineIterator.extractAxisRanges(extent, gridToCrs, null, interestedOrdinateIndex); } /** * Returns all ordinate ranges from grid transformation of {@linkplain GridEnvelope extent} * by {@linkplain MathTransform gridtocrs} on axis specified by expected dimension parameter.<br> * One range for each grid values on interested ordinate.<br><br> * <strong> * Moreover : the specified {@link MathTransform} must be from CORNER of source grid point to CORNER of destination envelope point.<br> * The used MathTransform is consider with {@link PixelInCell#CELL_CORNER} configuration.</strong> * * @param extent used grid to define ordinate value. * @param gridToCrs used gridtocrs to define destination ordinate values. * @param crs destination {@link CoordinateReferenceSystem} of grid transformation by gridtocrs, may be {@code null}. * @param interestedOrdinateIndex expected interested ordinate index. * @return an array which contain all destination ordinate ranges from interested dimension. * * @throws NullArgumentException if any of extent or gridtocrs is {@code null}. * @throws IllegalArgumentException if interestedOrdinateIndex is negative. * @throws MismatchedDimensionException if dimension between crs (if it is not null) dimension and {@linkplain MathTransform#getTargetDimensions() gridToCrs.getTargetDimensions()} mismatch, * or if dimension between extent and {@linkplain MathTransform#getSourceDimensions() gridToCrs.getSourceDimensions()} mismatch. * @throws IndexOutOfBoundsException if interestedOrdinateIndex is upper than extent or gridtocrs dimension. */ private static NumberRange<Double>[] extractAxisRanges(final GridEnvelope extent, final MathTransform gridToCrs, final CoordinateReferenceSystem crs, final int interestedOrdinateIndex) { ArgumentChecks.ensureNonNull("extent", extent); ArgumentChecks.ensureNonNull("gridToCrs", gridToCrs); ArgumentChecks.ensurePositive("interstedOrdinateIndex", interestedOrdinateIndex); final int dim = extent.getDimension(); //-- check validity of parameters if (gridToCrs.getSourceDimensions() != dim) throw new MismatchedDimensionException("GridCombineIterator : extent dimension and gridToCrs sourceDimension mismatch. " + "Extent dimension = "+dim+" GridToCrs source dimension = "+gridToCrs.getSourceDimensions()); if (crs != null && crs.getCoordinateSystem().getDimension() != dim) throw new MismatchedDimensionException("GridCombineIterator : crs dimension and expected targetDimension mismatch. " + "expected dimension = "+dim+" crs dimension = "+crs.getCoordinateSystem().getDimension()); if (interestedOrdinateIndex >= dim) throw new IndexOutOfBoundsException("GridCombineIterator : The selected ordinate index exceed dimension number, it must be lesser."); //--------------------------------- //-- define out array from asked axis value type. final NumberRange[] resultArray = new NumberRange[extent.getHigh(interestedOrdinateIndex) - extent.getLow(interestedOrdinateIndex) + 1];//-- +1 because getHigh() always return inclusive values. int i = 0; final GridCombineIterator gcint = new GridCombineIterator(extent, crs, gridToCrs, interestedOrdinateIndex); while (gcint.hasNext()) { final Envelope env = gcint.next(); resultArray[i++] = new NumberRange(Double.class, env.getMinimum(interestedOrdinateIndex), true, env.getMaximum(interestedOrdinateIndex), true); } return resultArray; } }