Package org.vfny.geoserver.util

Source Code of org.vfny.geoserver.util.WCSUtils

/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.vfny.geoserver.util;

import java.awt.image.SampleModel;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.jai.Interpolation;

import org.geoserver.catalog.CoverageDimensionInfo;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.wcs.WCSInfo;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.DecimationPolicy;
import org.geotools.coverage.grid.io.OverviewPolicy;
import org.geotools.coverage.processing.CoverageProcessor;
import org.geotools.coverage.processing.operation.Interpolate;
import org.geotools.coverage.processing.operation.Resample;
import org.geotools.coverage.processing.operation.SelectSampleDimension;
import org.geotools.factory.Hints;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.util.NumberRange;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridGeometry;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.vfny.geoserver.wcs.WcsException;

/**
*
* @author Simone Giannecchini, GeoSolutions
* @author Alessio Fabiani, GeoSolutions
*
*/
public class WCSUtils {

    private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(WCSUtils.class);
   
    public static final String ELEVATION = "ELEVATION";
   
    public final static Hints LENIENT_HINT = new Hints(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);

    private final static SelectSampleDimension bandSelectFactory = new SelectSampleDimension();

    private final static Interpolate interpolateFactory = new Interpolate();

    private final static Resample resampleFactory = new Resample();

    private final static ParameterValueGroup bandSelectParams;

    private final static ParameterValueGroup interpolateParams;

    private final static ParameterValueGroup resampleParams;

    private final static Hints hints = new Hints();

    static {
        hints.add(LENIENT_HINT);
        // ///////////////////////////////////////////////////////////////////
        //
        // Static Processors
        //
        // ///////////////////////////////////////////////////////////////////
        final CoverageProcessor processor = CoverageProcessor.getInstance((LENIENT_HINT));
        bandSelectParams = processor.getOperation("SelectSampleDimension").getParameters();
        interpolateParams = processor.getOperation("Interpolate").getParameters();
        resampleParams = processor.getOperation("Resample").getParameters();       
    }

    /**
     * <strong>Reprojecting</strong><br>
     * The new grid geometry can have a different coordinate reference system than the underlying
     * grid geometry. For example, a grid coverage can be reprojected from a geodetic coordinate
     * reference system to Universal Transverse Mercator CRS.
     *
     *
     * @param coverage
     *            GridCoverage2D
     * @param sourceCRS
     *            CoordinateReferenceSystem
     * @param targetCRS
     *            CoordinateReferenceSystem
     * @return GridCoverage2D
     * @throws WcsException
     */
    public static GridCoverage2D resample(
        final GridCoverage2D coverage,
            final CoordinateReferenceSystem sourceCRS,
            final CoordinateReferenceSystem targetCRS,
            final GridGeometry2D gridGeometry,
            final Interpolation interpolation) throws WcsException {


        final ParameterValueGroup param = (ParameterValueGroup) resampleParams.clone();
        param.parameter("Source").setValue(coverage);
        param.parameter("CoordinateReferenceSystem").setValue(targetCRS);
        param.parameter("GridGeometry").setValue(gridGeometry);
        param.parameter("InterpolationType").setValue(interpolation);

        return (GridCoverage2D) resampleFactory.doOperation(param, hints);

    }

    /**
     * <strong>Interpolating</strong><br>
     * Specifies the interpolation type to be used to interpolate values for points which fall
     * between grid cells. The default value is nearest neighbor. The new interpolation type
     * operates on all sample dimensions. Possible values for type are: {@code "NearestNeighbor"},
     * {@code "Bilinear"} and {@code "Bicubic"} (the {@code "Optimal"} interpolation type is
     * currently not supported).
     *
     * @param coverage
     *            GridCoverage2D
     * @param interpolation
     *            Interpolation
     * @return GridCoverage2D
     * @throws WcsException
     */
    public static GridCoverage2D interpolate(
        final GridCoverage2D coverage,
            final Interpolation interpolation) throws WcsException {
        // ///////////////////////////////////////////////////////////////////
        //
        // INTERPOLATE
        //
        //
        // ///////////////////////////////////////////////////////////////////
        if (interpolation != null) {
            /* Operations.DEFAULT.interpolate(coverage, interpolation) */
            final ParameterValueGroup param = (ParameterValueGroup) interpolateParams.clone();
            param.parameter("Source").setValue(coverage);
            param.parameter("Type").setValue(interpolation);

            return (GridCoverage2D) interpolateFactory.doOperation(param, hints);
        }

        return coverage;
    }

    /**
     * <strong>Band Selecting</strong><br>
     * Chooses <var>N</var> {@linkplain org.geotools.coverage.GridSampleDimension sample dimensions}
     * from a grid coverage and copies their sample data to the destination grid coverage in the
     * order specified. The {@code "SampleDimensions"} parameter specifies the source
     * {@link org.geotools.coverage.GridSampleDimension} indices, and its size ({@code
     * SampleDimensions.length}) determines the number of sample dimensions of the destination grid
     * coverage. The destination coverage may have any number of sample dimensions, and a particular
     * sample dimension of the source coverage may be repeated in the destination coverage by
     * specifying it multiple times in the {@code "SampleDimensions"} parameter.
     *
     * @param params
     *            Set
     * @param coverage
     *            GridCoverage
     * @return Coverage
     * @throws WcsException
     */
    public static Coverage bandSelect(final Map params, final GridCoverage coverage)
            throws WcsException {
        // ///////////////////////////////////////////////////////////////////
        //
        // BAND SELECT
        //
        //
        // ///////////////////////////////////////////////////////////////////
        final int numDimensions = coverage.getNumSampleDimensions();
        final Map dims = new HashMap();
        final ArrayList selectedBands = new ArrayList();

        for (int d = 0; d < numDimensions; d++) {
            dims.put("band" + (d + 1), new Integer(d));
        }

        if ((params != null) && !params.isEmpty()) {
            for (Iterator p = params.keySet().iterator(); p.hasNext();) {
                final String param = (String) p.next();

                if (param.equalsIgnoreCase("BAND")) {
                    try {
                        final String values = (String) params.get(param);

                        if (values.indexOf("/") > 0) {
                            final String[] minMaxRes = values.split("/");
                            final int min = (int) Math.round(Double.parseDouble(minMaxRes[0]));
                            final int max = (int) Math.round(Double.parseDouble(minMaxRes[1]));
                            final double res = ((minMaxRes.length > 2) ? Double.parseDouble(minMaxRes[2]) : 0.0);

                            for (int v = min; v <= max; v++) {
                                final String key = param.toLowerCase() + v;

                                if (dims.containsKey(key)) {
                                    selectedBands.add(dims.get(key));
                                }
                            }
                        } else {
                            final String[] bands = values.split(",");

                            for (int v = 0; v < bands.length; v++) {
                                final String key = param.toLowerCase() + bands[v];

                                if (dims.containsKey(key)) {
                                    selectedBands.add(dims.get(key));
                                }
                            }

                            if (selectedBands.size() == 0) {
                                throw new Exception("WRONG PARAM VALUES.");
                            }
                        }
                    } catch (Exception e) {
                        throw new WcsException("Band parameters incorrectly specified: "
                                + e.getLocalizedMessage());
                    }
                }
            }
        }

        final int length = selectedBands.size();
        final int[] bands = new int[length];

        for (int b = 0; b < length; b++) {
            bands[b] = ((Integer) selectedBands.get(b)).intValue();
        }

        return bandSelect(coverage, bands);
    }

    public static Coverage bandSelect(final GridCoverage coverage, final int[] bands) {
        Coverage bandSelectedCoverage;

        if ((bands != null) && (bands.length > 0)) {
            /* Operations.DEFAULT.selectSampleDimension(coverage, bands) */
            final ParameterValueGroup param = (ParameterValueGroup) bandSelectParams.clone();
            param.parameter("Source").setValue(coverage);
            param.parameter("SampleDimensions").setValue(bands);
            // param.parameter("VisibleSampleDimension").setValue(bands);
            bandSelectedCoverage = bandSelectFactory.doOperation(param, hints);
        } else {
            bandSelectedCoverage = coverage;
        }

        return bandSelectedCoverage;
    }

    /**
     * Checks the coverage described by the specified geometry and sample model does not exceeds the output
     * WCS limits
     * @param info
     * @param gridRange2D
     * @param sampleModel
     */
  public static void checkOutputLimits(WCSInfo info, GridEnvelope2D gridRange2D, SampleModel sampleModel) {
        // do we have to check a limit at all?
      long limit = info.getMaxOutputMemory() * 1024;
        if(limit <= 0) {
            return;
        }
       
        // compute the coverage memory usage and compare with limit
        long actual = getCoverageSize(gridRange2D, sampleModel);
        if(actual > limit) {
            throw new WcsException("This request is trying to generate too much data, " +
                    "the limit is " + formatBytes(limit) + " but the actual amount of bytes to be " +
                            "written in the output is " + formatBytes(actual));
        }
    }

    /**
     * Checks the coverage read is below the input limits. Mind, at this point the reader might have
     * have subsampled the original image in some way so it is expected the coverage is actually
     * smaller than what computed but {@link #checkInputLimits(CoverageInfo, AbstractGridCoverage2DReader, GeneralEnvelope)},
     * however that method might have failed the computation due to lack of metadata (or wrong metadata)
     * so it's safe to double check the actual coverage wit this one.
     * Mind, this method might cause the coverage to be fully read in memory (if that is the case,
     * the actual WCS processing chain would result in the same behavior so this is not causing
     * any extra memory usage, just makes it happen sooner)
     * @param coverage
     */
    public static void checkInputLimits(WCSInfo info, GridCoverage2D coverage) {
        // do we have to check a limit at all?
        long limit = info.getMaxInputMemory() * 1024;
        if(limit <= 0) {
            return;
        }
       
        // compute the coverage memory usage and compare with limit
        long actual = getCoverageSize(coverage.getGridGeometry().getGridRange2D(),
                coverage.getRenderedImage().getSampleModel());
        if(actual > limit) {
            throw new WcsException("This request is trying to read too much data, " +
                    "the limit is " + formatBytes(limit) + " but the actual amount of " +
                        "bytes to be read is " + formatBytes(actual));
        }
    }
   
    /**
     * Computes the size of a grid coverage given its grid envelope and the target sample model
     * @param envelope
     * @param sm
     * @return
     */
    static long getCoverageSize(GridEnvelope2D envelope, SampleModel sm) {
        long pixels = 1;
        for(int i = 0; i < envelope.getDimension(); i++) {
            pixels *= envelope.getSpan(i);
        }
        long pixelSize = 0;
        for (int i = 0; i < sm.getNumBands(); i++) {
            pixelSize += sm.getSampleSize(i);
        }
        return pixels * pixelSize / 8;
    }

    /**
     * Utility method to called to check the amount of data to be read does not exceed the WCS read limits.
     * This method has to jump through a few hoops to estimate the size of the data to be read without
     * having to actually read the coverage (which might trigger the loading of the full coverage in
     * memory)
     * @param meta
     * @param reader
     * @param requestedEnvelope
     * @throws WcsException if the coverage size exceeds the configured limits
     */
    public static void checkInputLimits(WCSInfo info, CoverageInfo meta,
            AbstractGridCoverage2DReader reader, GridGeometry2D gridGeometry) throws WcsException {
        // do we have to check a limit at all?
        long limit = info.getMaxInputMemory() * 1024;
        if(limit <= 0) {
            return;
        }
       
        // compute the actual amount of data read
        long actual = 0;
        try {
            // if necessary reproject back to the original CRS
            GeneralEnvelope requestedEnvelope = new GeneralEnvelope(gridGeometry.getEnvelope());
            final CoordinateReferenceSystem requestCRS = requestedEnvelope.getCoordinateReferenceSystem();
            final CoordinateReferenceSystem nativeCRS = reader.getCrs();
            if(!CRS.equalsIgnoreMetadata(requestCRS, nativeCRS)) {
                requestedEnvelope = CRS.transform(CRS.findMathTransform(requestCRS, nativeCRS, true), requestedEnvelope);
            }
            // intersect with the native envelope, we cannot read outside of it
            requestedEnvelope.intersect(reader.getOriginalEnvelope());
           
            // check if we are still reading anything
            if(!requestedEnvelope.isEmpty()) {
                MathTransform crsToGrid = meta.getGrid().getGridToCRS().inverse();
                GeneralEnvelope requestedGrid = CRS.transform(crsToGrid, requestedEnvelope);
                double[] spans = new double[requestedGrid.getDimension()];
                double[] resolutions = new double[requestedGrid.getDimension()];
                for (int i = 0; i < spans.length; i++) {
                    spans[i] = requestedGrid.getSpan(i);
                    resolutions[i] = requestedEnvelope.getSpan(i) / spans[i];
                }
               
                // adjust the spans based on the overview policy
                OverviewPolicy policy = info.getOverviewPolicy();
                double[] readResoutions = reader.getReadingResolutions(policy, resolutions);
                double[] baseResolutions = reader.getReadingResolutions(OverviewPolicy.IGNORE, resolutions);
                for (int i = 0; i < spans.length; i++) {
                    spans[i] *= readResoutions[i] / baseResolutions[i];
                }
               
                // compute how many pixels we're going to read
                long pixels = 1;
                for (int i = 0; i < requestedGrid.getDimension(); i++) {
                    pixels *= Math.ceil(requestedGrid.getSpan(i));
                }
               
                // compute the size of a pixel using the coverage metadata (the reader won't give
                // us any information about the bands)
                long pixelSize = 0;
                if(meta.getDimensions() != null) {
                    for (CoverageDimensionInfo dimension : meta.getDimensions()) {
                        int size = guessSizeFromRange(dimension.getRange());
                        if(size == 0) {
                            LOGGER.log(Level.INFO, "Failed to guess the size of dimension "
                                    + dimension.getName() + ", skipping the pre-read check");
                            pixelSize = -1;
                            break;
                        }
                        pixelSize += size;
                    }
                }
               
                actual = pixels * pixelSize / 8;
            }
        } catch(Throwable t) {
            throw new WcsException("An error occurred while checking serving limits", t);
        }
       
        if(actual < 0) {
            // TODO: provide some more info about the request? It seems to be we'd have to
            // log again the full request... unless the request logger starts dumping the thread
            // id, in that case we can just refer to that and tell the admin to enable
            // the request logger to debug these issues?
            LOGGER.log(Level.INFO, "Warning, we could not estimate the amount of bytes to be " +
                    "read from the coverage source for the current request");
        }
       
        if(actual > limit) {
            throw new WcsException("This request is trying to read too much data, " +
                    "the limit is " + formatBytes(limit) + " but the actual amount of bytes " +
                        "to be read is " + formatBytes(actual));
        }
    }

    /**
     * Guesses the size of the sample able to contain the range fully
     * @param range
     * @return
     */
    static int guessSizeFromRange(NumberRange range) {
        double min = range.getMinimum();
        double max = range.getMaximum();
        double diff = max - min;
       
        if(diff <= ((int) Byte.MAX_VALUE - (int) Byte.MIN_VALUE)) {
            return 8;
        } else if(diff <= ((int) Short.MAX_VALUE - (int) Short.MIN_VALUE)) {
            return 16;
        } else if(diff <= ((double) Integer.MAX_VALUE - (double) Integer.MIN_VALUE)) {
            return 32;
        } else if(diff <= ((double) Float.MAX_VALUE - (double) Float.MIN_VALUE)) {
            return 32;
        } else {
            return 64;
        }
    }
   
    /**
     * Utility function to format a byte amount into a human readable string
     * @param bytes
     * @return
     */
    static String formatBytes(long bytes) {
        if(bytes < 1024) {
            return bytes + "B";
        } else if(bytes < 1024 * 1024) {
            return new DecimalFormat("#.##").format(bytes / 1024.0) + "KB";
        } else {
            return new DecimalFormat("#.##").format(bytes / 1024.0 / 1024.0) + "MB";
        }
    }

    /**
     * Returns the reader hints based on the current WCS configuration
     * @param wcs
     * @return
     */
    public static Hints getReaderHints(WCSInfo wcs) {
        Hints hints = new Hints();
        hints.add(new Hints(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE));
        if (wcs.getOverviewPolicy() == null) {
            hints.add(new Hints(Hints.OVERVIEW_POLICY, OverviewPolicy.IGNORE));
        } else {
            hints.add(new Hints(Hints.OVERVIEW_POLICY, wcs.getOverviewPolicy()));
        }
        hints.put(Hints.DECIMATION_POLICY, wcs.isSubsamplingEnabled() ? DecimationPolicy.ALLOW
                : DecimationPolicy.DISALLOW);
        return hints;
    }
}
TOP

Related Classes of org.vfny.geoserver.util.WCSUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.