/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2010-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2010-2012, 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.test.stress; import java.util.Random; import org.opengis.coverage.grid.GridEnvelope; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.datum.PixelInCell; import org.apache.sis.math.MathFunctions; import org.geotoolkit.resources.Errors; import org.geotoolkit.coverage.grid.GeneralGridEnvelope; import org.geotoolkit.coverage.grid.GeneralGridGeometry; import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.referencing.operation.transform.LinearTransform; /** * Generates random grid geometries in order to create random requests. * * @author Martin Desruisseaux (Geomatys) * @version 3.15 * * @since 3.14 */ public class RequestGenerator { /** * The random number generator. */ protected final Random random; /** * Contains the maximal extent of the random envelopes to be generated by * {@link #getRandomGrid()}. This grid geometry must have a valid CRS. */ protected final GeneralGridGeometry domain; /** * The minimal grid size to be requested. */ private final int[] minimalGridSize; /** * The maximal grid size to be requested. */ private final int[] maximalGridSize; /** * The maximal scale factor, relative to the domain <cite>grid to CRS</cite>. Shall always * be greater or equals than 1. The default value is computed in such a way that the image * at the largest resolution is 100 pixels width or height. * * @since 3.15 */ private double maximumScale; /** * The value of <code>sqrt({@linkplain #maximumScale} - 1)</code>. */ private double sqrtScale1; /** * The domain <cite>grid to CRS</cite> transform, represented as a matrix. */ private final Matrix gridToCRS; /** * The resolution of the domain. */ private final double[] domainResolution; /** * Creates a new request generator for the given domain. The domain shall use an * affine <cite>grid to CRS</cite> transform. * * @param domain Contains the maximal extent of the random envelopes to be generated. */ public RequestGenerator(final GeneralGridGeometry domain) { this(domain, 100, 2000); } /** * Creates a new request generator for the given domain, minimum and maximum sizes. * The domain shall use an affine <cite>grid to CRS</cite> transform. * * @param minSize The minimal grid size to be requested. * @param maxSize The maximal grid size to be requested. * @param domain Contains the maximal extent of the random envelopes to be generated. * * @since 3.15 */ public RequestGenerator(final GeneralGridGeometry domain, final int minSize, final int maxSize) { this.random = new Random(); this.domain = domain; this.gridToCRS = ((LinearTransform) domain.getGridToCRS(PixelInCell.CELL_CORNER)).getMatrix(); this.domainResolution = getResolution(domain); final GridEnvelope gridExtent = domain.getExtent(); final int dimension = gridExtent.getDimension(); minimalGridSize = new int[dimension]; maximalGridSize = new int[dimension]; for (int i=0; i<dimension; i++) { final int max = Math.min(maxSize, gridExtent.getSpan(i)); maximalGridSize[i] = max; minimalGridSize[i] = Math.min(minSize, max); } updateScale(); } /** * Returns the minimal grid size along each dimension. * * @return The minimal grid size along each dimension. * * @since 3.15 */ public int[] getMinimalGridSize() { return minimalGridSize.clone(); } /** * Sets the minimal grid size along each dimension. If a minimal size is greater than * a maximal size, the minimal size is set to the maximal size (the maximal size is * <strong>not</strong> modified by this method. This is done that way because the * maximal size is typically computed from the {@linkplain #domain}). * <p> * Invoking this method causes the {@linkplain #getMaximumScale() maximum scale} to * be recomputed. * * @param size The minimal grid size along each dimension. * * @since 3.15 */ public void setMinimalGridSize(final int... size) { if (size.length != minimalGridSize.length) { throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedArrayLength)); } for (int i=0; i<size.length; i++) { if (size[i] < 1) { throw new IllegalArgumentException(Errors.format( Errors.Keys.IllegalArgument_2, "size[" + i + ']', size[i])); } } System.arraycopy(size, 0, minimalGridSize, 0, size.length); for (int i=0; i<minimalGridSize.length; i++) { if (minimalGridSize[i] > maximalGridSize[i]) { minimalGridSize[i] = maximalGridSize[i]; } } updateScale(); } /** * Returns the maximal grid size along each dimension. * * @return The maximal grid size along each dimension. * * @since 3.15 */ public int[] getMaximalGridSize() { return maximalGridSize.clone(); } /** * Sets the maximal grid size along each dimension. * The maximal size can not be less than the minimal size. * <p> * Invoking this method causes the {@linkplain #getMaximumScale() maximum scale} to * be recomputed. * * @param size The maximal grid size along each dimension. * * @since 3.15 */ public void setMaximalGridSize(final int... size) { if (size.length != maximalGridSize.length) { throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedArrayLength)); } for (int i=0; i<size.length; i++) { if (size[i] < minimalGridSize[i]) { throw new IllegalArgumentException(Errors.format( Errors.Keys.IllegalArgument_2, "size[" + i + ']', size[i])); } } System.arraycopy(size, 0, maximalGridSize, 0, size.length); updateScale(); } /** * Computes automatically a new scale value after the minimal or maximal grid size has * been modified. */ private void updateScale() { double maxScale = Double.POSITIVE_INFINITY; final GridEnvelope gridExtent = domain.getExtent(); final int dimension = gridExtent.getDimension(); for (int i=0; i<dimension; i++) { final double scale = gridExtent.getSpan(i) / (double) minimalGridSize[i]; if (scale < maxScale) { maxScale = scale; } } setMaximumScale(maxScale); } /** * Returns the current maximal scale factor relative to the domain resolution. * * @return The maximal scale factor, as a value equals or greater than 1. * * @since 3.15 */ public double getMaximumScale() { return maximumScale; } /** * Sets the maximal scale factor, relative to the domain <cite>grid to CRS</cite>. The scale * shall always be greater or equals than 1. The default value is computed in such a way that * the image at the largest resolution has the {@linkplain #getMinimalGridSize() minimal grid * size}. * * @param scale * * @since 3.15 */ public void setMaximumScale(final double scale) { if (!(scale >= 1)) { throw new IllegalArgumentException("Illegal maximum scale: " + scale); } maximumScale = scale; sqrtScale1 = Math.sqrt(scale - 1); } /** * Returns a random grid geometry inside the {@linkplain #domain}. The grid envelope * size will range from {@link #minimalGridSize} to {@link #maximalGridSize} inclusive. * The <cite>grid to CRS</cite> transform is scaled to a maximal factor of * {@link #maximumScale} compare to the original grid. * * @return A new random grid geometry inside the domain. */ public GeneralGridGeometry getRandomGrid() { double scale = sqrtScale1 * random.nextDouble(); scale = (scale * scale) + 1; assert (scale >= 1 && scale <= maximumScale) : scale; final GridEnvelope gridExtent = domain.getExtent(); final int dimension = gridExtent.getDimension(); final int[] lower = new int[dimension]; final int[] upper = new int[dimension]; final Matrix mx = gridToCRS.clone(); for (int i=0; i<dimension; i++) { /* * Select a scale between 1 and maximumScale, * with highest probability for low values. */ mx.setElement(i, i, scale * mx.getElement(i, i)); /* * Compute the bounds. */ int span = minimalGridSize[i]; span += random.nextInt(maximalGridSize[i] - span + 1); int min = (int) Math.ceil ( gridExtent.getLow (i) / scale); int max = (int) Math.floor((gridExtent.getHigh(i) + 1) / scale); final int remainingSpace = (max - min) - span; if (remainingSpace > 0) { min += random.nextInt(remainingSpace + 1); max = min + span; } lower[i] = min; upper[i] = max; } return new GeneralGridGeometry(new GeneralGridEnvelope(lower, upper, false), PixelInCell.CELL_CORNER, MathTransforms.linear(mx), domain.getCoordinateReferenceSystem()); } /** * Returns the scale of the given grid geometry, relative to the domain. * * @param request The grid geometry from which to extract the scale. * @return The scale (unitless). * * @since 3.15 */ public double[] getScale(final GeneralGridGeometry request) { final double[] scale = getResolution(request); for (int i=0; i<scale.length; i++) { scale[i] /= domainResolution[i]; } return scale; } /** * Returns the resolution of the given grid geometry, in units of the envelope. * * @param request The grid geometry from which to extract the resolution. * @return The resolution, in units of the envelope. */ public static double[] getResolution(final GeneralGridGeometry request) { final Matrix gridToCRS = ((LinearTransform) request.getGridToCRS()).getMatrix(); final double[] row = new double[gridToCRS.getNumCol() - 1]; final double[] res = new double[gridToCRS.getNumRow() - 1]; for (int j=0; j<res.length; j++) { for (int i=0; i<row.length; i++) { row[i] = gridToCRS.getElement(j, i); } res[j] = MathFunctions.magnitude(row); } return res; } /** * Returns the mean value of the given vector. * This method is used mostly for information purpose. * * @param vector The vector for which to compute the main value. * @return The mean value. * * @since 3.15 */ public static double mean(final double[] vector) { double sum = 0; for (final double r : vector) { sum += r; } return sum / vector.length; } }