Package org.geotools.coverage.io.netcdf

Source Code of org.geotools.coverage.io.netcdf.NetCDFResponse

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2007-2014, Open Source Geospatial Foundation (OSGeo)
*
*    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.geotools.coverage.io.netcdf;

import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.media.jai.BorderExtender;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.TileCache;
import javax.media.jai.TileScheduler;

import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.io.DimensionDescriptor;
import org.geotools.coverage.io.CoverageReadRequest;
import org.geotools.coverage.io.CoverageResponse;
import org.geotools.coverage.io.SpatialRequestHelper.CoverageProperties;
import org.geotools.coverage.io.impl.DefaultGridCoverageResponse;
import org.geotools.coverage.io.range.FieldType;
import org.geotools.coverage.io.range.RangeType;
import org.geotools.data.DataSourceException;
import org.geotools.data.Query;
import org.geotools.factory.Hints;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.image.ImageWorker;
import org.geotools.imageio.netcdf.utilities.NetCDFUtilities;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.matrix.XAffineTransform;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.resources.coverage.CoverageUtilities;
import org.geotools.resources.coverage.FeatureUtilities;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.util.DateRange;
import org.geotools.util.NumberRange;
import org.geotools.util.Range;
import org.geotools.util.Utilities;
import org.opengis.coverage.SampleDimension;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.geometry.BoundingBox;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
/**
* A RasterLayerResponse. An instance of this class is produced everytime a
* requestCoverage is called to a reader.
*
* @author Simone Giannecchini, GeoSolutions
* @author Daniele Romagnoli, GeoSolutions
* @author Stefan Alfons Krueger (alfonx), Wikisquare.de : Support for jar:file:foo.jar/bar.properties URLs
*/
@SuppressWarnings("rawtypes")
class NetCDFResponse extends CoverageResponse{
   
    private final static double EPS = 1E-6;

    private final static double[] DEFAULT_BACKGROUND_VALUES = new double[]{0d};

    /** Logger. */
    private final static Logger LOGGER = org.geotools.util.logging.Logging
            .getLogger(NetCDFResponse.class);

    /** The {@link NetCDFRequest} originating this response */
    private NetCDFRequest request;

    /** The coverage factory producing a {@link GridCoverage} from an image */
    final private static GridCoverageFactory COVERAGE_FACTORY = new GridCoverageFactory();

    /** The base envelope related to the input coverage */
    private GeneralEnvelope coverageEnvelope;

    private ReferencedEnvelope targetBBox;

    private Rectangle rasterBounds;

    private MathTransform2D finalGridToWorldCorner;

    private MathTransform2D finalWorldToGridCorner;

    private URL datasetURL;

    private ImageReadParam baseReadParameters = new ImageReadParam();

    private boolean oversampledRequest;

    private AffineTransform baseGridToWorld;

    private Hints hints;

    private AffineTransform targetWorldToGrid;

    /**
     * Construct a {@code RasterLayerResponse} given a specific {@link RasterLayerRequest}, a {@code GridCoverageFactory} to produce
     * {@code GridCoverage}s and an {@code ImageReaderSpi} to be used for instantiating an Image Reader for a read operation,
     *
     * @param request a {@link RasterLayerRequest} originating this response.
     * @param COVERAGE_FACTORY a {@code GridCoverageFactory} to produce a {@code GridCoverage} when calling the {@link #createResponse()} method.
     * @param readerSpi the Image Reader Service provider interface.
     */
    public NetCDFResponse(final NetCDFRequest request) {
        this.request = request;
        CoverageReadRequest readRequest = request.originalRequest;
        setRequest(readRequest);
        datasetURL = (URL) request.source.reader.getInput();
    }

    public CoverageResponse createResponse() throws IOException {
        processRequest();
        return this;

    }

    /**
     * This method creates the GridCoverage2D from the underlying file given a specified envelope, and a requested dimension.
     *
     * @param iUseJAI specify if the underlying read process should leverage on a JAI ImageRead operation or a simple direct call to the {@code read}
     *        method of a proper {@code ImageReader}.
     * @param overviewPolicy the overview policy which need to be adopted
     * @return a {@code GridCoverage}
     *
     * @throws java.io.IOException
     */
    private void processRequest() throws IOException {

        // is this query empty?
        if (request.spatialRequestHelper.isEmpty()) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Request is empty: " + request.toString());
            }
            return;
        }

        // assemble granules
        prepareParams();
        String timeFilterAttribute = null;
        String elevationFilterAttribute = null;
        CoverageReadRequest readRequest = (CoverageReadRequest) getRequest();
        RangeType rangeType = request.source.getRangeType(null);
        List<DimensionDescriptor> dimensionDescriptors = request.source.getDimensionDescriptors();
        for (DimensionDescriptor dimensionDescriptor : dimensionDescriptors) {
            if (dimensionDescriptor.getName().equalsIgnoreCase(NetCDFUtilities.ELEVATION_DIM)) {
                // TODO Update this with ranged attributes
                elevationFilterAttribute = dimensionDescriptor.getStartAttribute();
            } else if (dimensionDescriptor.getName().equalsIgnoreCase(NetCDFUtilities.TIME_DIM)) {
                // TODO Update this with ranged attributes
                timeFilterAttribute = dimensionDescriptor.getStartAttribute();
            }
        }

        Set<DateRange> temporalSubset = readRequest.getTemporalSubset();
        Set<NumberRange<Double>> verticalSubset = readRequest.getVerticalSubset();
        RangeType requestedRange = readRequest.getRangeSubset();
        Set<FieldType> fieldTypes = requestedRange.getFieldTypes();

        //
        // adding GridCoverages to the results list
        //
        // //
        Set<SampleDimension> sampleDims = null;
        for (FieldType fieldType : fieldTypes) {
            final Name name = fieldType.getName();
            sampleDims = fieldType.getSampleDimensions();
            if (rangeType != null) {
                final FieldType ft = rangeType.getFieldType(name.getLocalPart());
                if (ft != null)
                    sampleDims = ft.getSampleDimensions();
            }
        }
        final GridSampleDimension[] sampleDimensions = sampleDims
                .toArray(new GridSampleDimension[sampleDims.size()]);

        // Forcing creation of subsets (even with a single null element)
        Set<DateRange> tempSubset = null;
        if (!temporalSubset.isEmpty()) {
            tempSubset = temporalSubset;
        } else {
            tempSubset = new HashSet<DateRange>();
            tempSubset.add(null);
        }

        Set<NumberRange<Double>> vertSubset = null;
        if (!verticalSubset.isEmpty()) {
            vertSubset = verticalSubset;
        } else {
            vertSubset = new HashSet<NumberRange<Double>>();
            vertSubset.add(null);
        }

        Map<String, Set<?>> domainsSubset = readRequest.getAdditionalDomainsSubset();
        Filter requestFilter = request.originalRequest.getFilter();

        // handling date and time
        for (DateRange timeRange : tempSubset) {
            for (NumberRange<Double> elevation : vertSubset) {

                Query query = new Query();
                // handle time and elevation
                createTimeElevationQuery(timeRange, elevation, query, requestFilter,
                        timeFilterAttribute, elevationFilterAttribute);

                // handle additional params
                additionalParamsManagement(query, domainsSubset, dimensionDescriptors);

                // bbox
                query.setFilter(FeatureUtilities.DEFAULT_FILTER_FACTORY.and(query.getFilter(),
                        FeatureUtilities.DEFAULT_FILTER_FACTORY.bbox(
                                FeatureUtilities.DEFAULT_FILTER_FACTORY.property("the_geom"),
                                targetBBox)));

                query.setTypeName(request.name);
                List<Integer> indexes = request.source.reader.getImageIndex(query);
                if (indexes == null || indexes.isEmpty()) {
                    if (LOGGER.isLoggable(Level.FINE)) {
                        LOGGER.fine(" No indexes found for this query: " + query.toString());
                    }
                    continue;
                }
                int imageIndex = indexes.get(0);
                final RenderedImage image = loadRaster(baseReadParameters, imageIndex, targetBBox,
                        finalWorldToGridCorner, hints);

                // postproc
                RenderedImage finalRaster = postProcessRaster(image);
                // create the coverage
                GridCoverage2D gridCoverage = prepareCoverage(finalRaster, sampleDimensions);

                // Adding coverage domain
                if (gridCoverage != null) {
                    GridCoverage gcResponse = new DefaultGridCoverageResponse(gridCoverage,
                            timeRange, elevation);
                    addResult(gcResponse);
                }
            }
        }

        // success
        setStatus(Status.SUCCESS);

    }

    /**
     * @param query
     * @param domainsSubset
     */
    private void additionalParamsManagement(Query query, Map<String, Set<?>> domainsSubset,  List<DimensionDescriptor> dimensionDescriptors) {
        if (domainsSubset.isEmpty()){
            return;
        }
        Filter filter = query.getFilter();
        for(Entry<String, Set<?>> entry:domainsSubset.entrySet()){
            Set<?> values = entry.getValue();
            String attribute = null;
            for (DimensionDescriptor dim: dimensionDescriptors) {
                if (dim.getName().toUpperCase().equalsIgnoreCase(entry.getKey())) {
                     attribute = dim.getStartAttribute();
                     break;
                }
            }
            for(Object value:values){
                if(value instanceof Range){
                    throw new UnsupportedOperationException();
                } else {
                    filter=FeatureUtilities.DEFAULT_FILTER_FACTORY.and(filter,
                            FeatureUtilities.DEFAULT_FILTER_FACTORY.equals(FeatureUtilities.DEFAULT_FILTER_FACTORY.property(attribute),
                                    FeatureUtilities.DEFAULT_FILTER_FACTORY.literal(value)));
                }
            }

        }
        query.setFilter(filter);
       
    }

    /** Create the query to retrive the imageIndex related to the specified time (if any)
     * and the specified elevation (if any)
     * @param time
     * @param elevation
     * @param query
     * @param requestFilter 
     * @param elevationFilterAttribute
     * @param timeFilterAttribute
     * @return
     */
    private void createTimeElevationQuery(
            DateRange time,
            NumberRange<Double> elevation,
            Query query,
            Filter requestFilter, String timeFilterAttribute, String elevationFilterAttribute) {
        final List<Filter> filters = new ArrayList<Filter>();
       
        // //
        // Setting up time filter
        // //
        if (time != null) {
            final Range range = (Range) time;
            // schema with only one time attribute. Consider adding code for schema with begin,end attributes
            filters.add(FeatureUtilities.DEFAULT_FILTER_FACTORY.and(
                    FeatureUtilities.DEFAULT_FILTER_FACTORY.lessOrEqual(FeatureUtilities.DEFAULT_FILTER_FACTORY.property(timeFilterAttribute),
                            FeatureUtilities.DEFAULT_FILTER_FACTORY.literal(range.getMaxValue())),
                    FeatureUtilities.DEFAULT_FILTER_FACTORY.greaterOrEqual(FeatureUtilities.DEFAULT_FILTER_FACTORY.property(timeFilterAttribute),
                            FeatureUtilities.DEFAULT_FILTER_FACTORY.literal(range.getMinValue()))));
        }
       
        // //
        // Setting up elevation filter
        // //
        if (elevation != null) {
            final Range range = (Range) elevation;
            // schema with only one elevation attribute. Consider adding code for schema with begin,end attributes
            filters.add(FeatureUtilities.DEFAULT_FILTER_FACTORY.and(
                    FeatureUtilities.DEFAULT_FILTER_FACTORY.lessOrEqual(FeatureUtilities.DEFAULT_FILTER_FACTORY.property(elevationFilterAttribute),
                            FeatureUtilities.DEFAULT_FILTER_FACTORY.literal(range.getMaxValue())),
                    FeatureUtilities.DEFAULT_FILTER_FACTORY.greaterOrEqual(FeatureUtilities.DEFAULT_FILTER_FACTORY.property(elevationFilterAttribute),
                            FeatureUtilities.DEFAULT_FILTER_FACTORY.literal(range.getMinValue()))));
        }

        if (requestFilter != null) {
            filters.add(requestFilter);
        }

        Filter filter = FeatureUtilities.DEFAULT_FILTER_FACTORY.and(filters);
        query.setFilter(filter);
    }

    private RenderedImage postProcessRaster(RenderedImage image) {
        // alpha on the final mosaic

        if (!request.spatialRequestHelper.isNeedsReprojection()) {

            //
            // Check and see if the affine transform is doing a copy.
            // If so call the copy operation.
            //
            // we are in raster space here, so 1E-3 is safe
            if (XAffineTransform.isIdentity(targetWorldToGrid, EPS))
                return image;

            // create final image
            //
            // In case we are asked to use certain tile dimensions we tile
            // also at this stage in case the read type is Direct since
            // buffered images comes up untiled and this can affect the
            // performances of the subsequent affine operation.
            //
            final Hints localHints = new Hints(hints);
            if (hints != null && !hints.containsKey(JAI.KEY_BORDER_EXTENDER)) {
                final Object extender = hints.get(JAI.KEY_BORDER_EXTENDER);
                if (!(extender != null && extender instanceof BorderExtender)) {
                    localHints.add(ImageUtilities.EXTEND_BORDER_BY_COPYING);
                }
            }

            ImageWorker iw = new ImageWorker(image);
            iw.setRenderingHints(localHints);
            iw.affine(targetWorldToGrid, request.getInterpolation(), DEFAULT_BACKGROUND_VALUES);
            image = iw.getRenderedImage();
        }
        return image;
    }

    private void prepareParams() throws DataSourceException {

        try {
            baseReadParameters = new ImageReadParam();
            performDecimation(baseReadParameters);

            // === extract bbox
            initBBOX();

            // === init transformations
            initTransformations();

            // === init raster bounds
            initRasterBounds();
           
            // === init targetGrid2World
            initTargetTransformation();

        } catch (Exception e) {
            throw new DataSourceException("Unable to create this mosaic", e);
        }
    }

    private void initTargetTransformation() throws NoninvertibleTransformException {
        // creating source grid to world corrected to the pixel corner
        final AffineTransform sourceGridToWorld = new AffineTransform(
                (AffineTransform) finalGridToWorldCorner);

        // AffineTransform finalGridToWorldCorner = new AffineTransform((AffineTransform) finalGridToWorldCorner);

        // target world to grid at the corner
        final AffineTransform targetGridToWorld = new AffineTransform(
                request.spatialRequestHelper.getRequestedGridToWorld());
        targetGridToWorld.concatenate(CoverageUtilities.CENTER_TO_CORNER);

        // target world to grid at the corner
        targetWorldToGrid = targetGridToWorld.createInverse();
        // final complete transformation
        targetWorldToGrid.concatenate(sourceGridToWorld);

        // update final grid to world
        finalGridToWorldCorner = new AffineTransform2D(targetGridToWorld);
       
    }

    /**
     * This method is responsible for computing the raster bounds of the final mosaic.
     *
     * @throws TransformException In case transformation fails during the process.
     */
    private void initRasterBounds() throws TransformException {
        final GeneralEnvelope tempRasterBounds = CRS.transform(finalWorldToGridCorner, targetBBox);
        rasterBounds = tempRasterBounds.toRectangle2D().getBounds();

        // SG using the above may lead to problems since the reason is that may be a little (1 px) bigger
        // than what we need. The code below is a bit better since it uses a proper logic (see GridEnvelope
        // Javadoc)
        // rasterBounds = new GridEnvelope2D(new Envelope2D(tempRasterBounds), PixelInCell.CELL_CORNER);
        if (rasterBounds.width == 0)
            rasterBounds.width++;
        if (rasterBounds.height == 0)
            rasterBounds.height++;
        if (oversampledRequest)
            rasterBounds.grow(2, 2);
    }

    /**
     * This method is responsible for initializing transformations g2w and back
     *
     * @throws Exception in case we don't manage to instantiate some of them.
     *
     */
    private void initTransformations() throws Exception {
        //compute final world to grid
        // base grid to world for the center of pixels
        final AffineTransform g2w;
        CoverageProperties properties = request.spatialRequestHelper.getCoverageProperties();
        baseGridToWorld = (AffineTransform) properties.getGridToWorld2D();
        double[] coverageFullResolution = properties.getFullResolution();
        final double resX = coverageFullResolution[0];
        final double resY = coverageFullResolution[1];
        final double[] requestRes = request.spatialRequestHelper.getRequestedResolution();

        g2w = new AffineTransform((AffineTransform) baseGridToWorld);
        g2w.concatenate(CoverageUtilities.CENTER_TO_CORNER);

        if ((requestRes[0] < resX || requestRes[1] < resY)) {
            // Using the best available resolution
            oversampledRequest = true;
        } else {
               
            // SG going back to working on a per level basis to do the composition
            // g2w = new AffineTransform(request.getRequestedGridToWorld());
            g2w.concatenate(AffineTransform.getScaleInstance(baseReadParameters.getSourceXSubsampling(), baseReadParameters.getSourceYSubsampling()));
        }  
        // move it to the corner
        finalGridToWorldCorner = new AffineTransform2D(g2w);
        finalWorldToGridCorner = finalGridToWorldCorner.inverse();// compute raster bounds
       
    }

    /**
     * This method is responsible for initializing the bbox for the
     * mosaic produced by this response.
     *
     */
    private void initBBOX() {
        // ok we got something to return, let's load records from the index
        final BoundingBox cropBBOX = request.spatialRequestHelper.getCropBBox();
        if (cropBBOX != null) {
            targetBBox = ReferencedEnvelope.reference(cropBBOX);
        } else {
            targetBBox = new ReferencedEnvelope(coverageEnvelope);
        }
    }

    /**
     * This method is responsible for creating a coverage from the supplied {@link RenderedImage}.
     *
     * @param image
     * @param sampleDimensions
     * @return
     * @throws IOException
     */
    private GridCoverage2D prepareCoverage(RenderedImage image, GridSampleDimension[] sampleDimensions) throws IOException {

        // creating the final coverage by keeping into account the fact that we
        Map<String, String> properties = null;
//        if (granulesPaths != null) {
//            properties = new HashMap<String, String>();
//            properties.put(AbstractGridCoverage2DReader.FILE_SOURCE_PROPERTY, granulesPaths);
//        }
//        image = TransposeDescriptor.create(image, TransposeDescriptor.FLIP_VERTICAL, hints);
        return COVERAGE_FACTORY.create(request.name, image, new GridGeometry2D(new GridEnvelope2D(PlanarImage.wrapRenderedImage(image)
                        .getBounds()), PixelInCell.CELL_CORNER, finalGridToWorldCorner,
                        this.targetBBox.getCoordinateReferenceSystem(), hints), sampleDimensions, null,
                properties);
    }
   

    /**
     * Load a specified a raster as a portion of the granule describe by this {@link DefaultGranuleDescriptor}.
     *
     * @param imageReadParameters the {@link ImageReadParam} to use for reading.
     * @param index the index to use for the {@link ImageReader}.
     * @param cropBBox the bbox to use for cropping.
     * @param mosaicWorldToGrid the cropping grid to world transform.
     * @param request the incoming request to satisfy.
     * @param hints {@link Hints} to be used for creating this raster.
     * @return a specified a raster as a portion of the granule describe by this {@link DefaultGranuleDescriptor}.
     * @throws IOException in case an error occurs.
     */
    private RenderedImage loadRaster(final ImageReadParam imageReadParameters, final int index,
            final ReferencedEnvelope cropBBox, final MathTransform2D mosaicWorldToGrid,
            final Hints hints) throws IOException {

        if (LOGGER.isLoggable(java.util.logging.Level.FINER)) {
            final String name = Thread.currentThread().getName();
            LOGGER.finer("Thread:" + name + " Loading raster data for granuleDescriptor "
                    + this.toString());
        }
        ImageReadParam readParameters = null;
        int imageIndex;
        final ReferencedEnvelope bbox = request.spatialRequestHelper.getCoverageProperties().getBbox();

        // intersection of this tile bound with the current crop bbox
        final ReferencedEnvelope intersection = new ReferencedEnvelope(bbox.intersection(cropBBox),
                cropBBox.getCoordinateReferenceSystem());
        if (intersection.isEmpty()) {
            if (LOGGER.isLoggable(java.util.logging.Level.FINE)) {
                LOGGER.fine(new StringBuilder("Got empty intersection for granule ")
                        .append(this.toString()).append(" with request ")
                        .append(request.toString())
                        .append(" Resulting in no granule loaded: Empty result").toString());
            }
            return null;
        }
        try {

            // What about thread safety?
           
            imageIndex = index;
            readParameters = imageReadParameters;

            // now create the crop grid to world which can be used to decide
            // which source area we need to crop in the selected level taking
            // into account the scale factors imposed by the selection of this
            // level together with the base level grid to world transformation

            final AffineTransform gridToWorldTransform_ = new AffineTransform();
            gridToWorldTransform_.preConcatenate(CoverageUtilities.CENTER_TO_CORNER);
            gridToWorldTransform_.preConcatenate(baseGridToWorld);
            AffineTransform2D cropWorldToGrid = new AffineTransform2D(gridToWorldTransform_);
            cropWorldToGrid = (AffineTransform2D) cropWorldToGrid.inverse();
            // computing the crop source area which lives into the
            // selected level raster space, NOTICE that at the end we need to
            // take into account the fact that we might also decimate therefore
            // we cannot just use the crop grid to world but we need to correct
            // it.
            Rectangle sourceArea = CRS.transform(cropWorldToGrid, intersection)
                    .toRectangle2D().getBounds();
            // Selection of the source original area for cropping the computed source area
            // (may have negative values for the approximation)
            final Rectangle initialArea = request.source.getSpatialDomain()
                    .getRasterElements(true, null).iterator().next().toRectangle();
            sourceArea = sourceArea.intersection(initialArea);

            if (sourceArea.isEmpty()) {
                if (LOGGER.isLoggable(java.util.logging.Level.FINE)) {
                    LOGGER.fine("Got empty area for granuleDescriptor " + this.toString()
                            + " with request " + request.toString()
                            + " Resulting in no granule loaded: Empty result");

                }
                return null;

            } else if (LOGGER.isLoggable(java.util.logging.Level.FINER)) {
                LOGGER.finer("Loading level " + imageIndex + " with source region: " + sourceArea
                        + " subsampling: " + readParameters.getSourceXSubsampling() + ","
                        + readParameters.getSourceYSubsampling() + " for granule:" + datasetURL);
            }

            // set the source region
            readParameters.setSourceRegion(sourceArea);
            final RenderedImage raster;
            try {
                // read
                raster = request.readType.read(readParameters, imageIndex, datasetURL,
                        request.spatialRequestHelper.getCoverageProperties().getRasterArea(),
                        request.source.reader, hints, false);

            } catch (Throwable e) {
                if (LOGGER.isLoggable(java.util.logging.Level.FINE)) {
                    LOGGER.log(java.util.logging.Level.FINE,
                            "Unable to load raster for granuleDescriptor " + this.toString()
                                    + " with request " + request.toString()
                                    + " Resulting in no granule loaded: Empty result", e);
                }
                return null;
            }

            // use fixed source area
            sourceArea.setRect(readParameters.getSourceRegion());

            //
            // setting new coefficients to define a new affineTransformation
            // to be applied to the grid to world transformation
            // -----------------------------------------------------------------------------------
            //
            // With respect to the original envelope, the obtained planarImage
            // needs to be rescaled. The scaling factors are computed as the
            // ratio between the cropped source region sizes and the read
            // image sizes.
            //
            // place it in the mosaic using the coords created above;
            double decimationScaleX = ((1.0 * sourceArea.width) / raster.getWidth());
            double decimationScaleY = ((1.0 * sourceArea.height) / raster.getHeight());
            final AffineTransform decimationScaleTranform = XAffineTransform.getScaleInstance(
                    decimationScaleX, decimationScaleY);

            // keep into account translation to work into the selected level raster space
            final AffineTransform afterDecimationTranslateTranform = XAffineTransform
                    .getTranslateInstance(sourceArea.x, sourceArea.y);

            // // now we need to go back to the base level raster space
            // final AffineTransform backToBaseLevelScaleTransform =selectedlevel.baseToLevelTransform;
            //
            // now create the overall transform
            final AffineTransform finalRaster2Model = new AffineTransform(baseGridToWorld);
            finalRaster2Model.concatenate(CoverageUtilities.CENTER_TO_CORNER);

            if (!XAffineTransform.isIdentity(afterDecimationTranslateTranform, EPS))
                finalRaster2Model.concatenate(afterDecimationTranslateTranform);
            if (!XAffineTransform.isIdentity(decimationScaleTranform, EPS))
                finalRaster2Model.concatenate(decimationScaleTranform);

            // keep into account translation factors to place this tile
            finalRaster2Model.preConcatenate((AffineTransform) mosaicWorldToGrid);

            final Interpolation interpolation = request.getInterpolation();
            // paranoiac check to avoid that JAI freaks out when computing its internal layouT on images that are too small
            Rectangle2D finalLayout = ImageUtilities.layoutHelper(raster,
                    (float) finalRaster2Model.getScaleX(), (float) finalRaster2Model.getScaleY(),
                    (float) finalRaster2Model.getTranslateX(),
                    (float) finalRaster2Model.getTranslateY(), interpolation);
            if (finalLayout.isEmpty()) {
                if (LOGGER.isLoggable(java.util.logging.Level.INFO))
                    LOGGER.info("Unable to create a granuleDescriptor " + this.toString()
                            + " due to jai scale bug creating a null source area");
                return null;
            }
            // apply the affine transform conserving indexed color model
            final RenderingHints localHints = new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL,
                    interpolation instanceof InterpolationNearest ? Boolean.FALSE : Boolean.TRUE);
            //
            // In case we are asked to use certain tile dimensions we tile
            // also at this stage in case the read type is Direct since
            // buffered images comes up untiled and this can affect the
            // performances of the subsequent affine operation.
            //
            // final Dimension tileDimensions=request.getTileDimensions();
            // if(tileDimensions!=null&&request.getReadType().equals(ReadType.DIRECT_READ)) {
            // final ImageLayout layout = new ImageLayout();
            // layout.setTileHeight(tileDimensions.width).setTileWidth(tileDimensions.height);
            // localHints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT,layout));
            // } else {
            // if (hints != null && hints.containsKey(JAI.KEY_IMAGE_LAYOUT)) {
            // final Object layout = hints.get(JAI.KEY_IMAGE_LAYOUT);
            // if (layout != null && layout instanceof ImageLayout) {
            // localHints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT, ((ImageLayout) layout).clone()));
            // }
            // }
            // }
            if (hints != null && hints.containsKey(JAI.KEY_TILE_CACHE)) {
                final Object cache = hints.get(JAI.KEY_TILE_CACHE);
                if (cache != null && cache instanceof TileCache)
                    localHints.add(new RenderingHints(JAI.KEY_TILE_CACHE, (TileCache) cache));
            }
            if (hints != null && hints.containsKey(JAI.KEY_TILE_SCHEDULER)) {
                final Object scheduler = hints.get(JAI.KEY_TILE_SCHEDULER);
                if (scheduler != null && scheduler instanceof TileScheduler)
                    localHints.add(new RenderingHints(JAI.KEY_TILE_SCHEDULER,
                            (TileScheduler) scheduler));
            }
            boolean addBorderExtender = true;
            if (hints != null && hints.containsKey(JAI.KEY_BORDER_EXTENDER)) {
                final Object extender = hints.get(JAI.KEY_BORDER_EXTENDER);
                if (extender != null && extender instanceof BorderExtender) {
                    localHints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER,
                            (BorderExtender) extender));
                    addBorderExtender = false;
                }
            }
            // border extender
            if (addBorderExtender) {
                localHints.add(ImageUtilities.BORDER_EXTENDER_HINTS);
            }

            ImageWorker iw = new ImageWorker(raster);
            iw.setRenderingHints(localHints);
            iw.affine(finalRaster2Model, interpolation, DEFAULT_BACKGROUND_VALUES);
            return iw.getRenderedImage();

        } catch (IllegalStateException e) {
            if (LOGGER.isLoggable(java.util.logging.Level.WARNING)) {
                LOGGER.log(java.util.logging.Level.WARNING,
                        new StringBuilder("Unable to load raster for granuleDescriptor ")
                                .append(this.toString()).append(" with request ")
                                .append(request.toString())
                                .append(" Resulting in no granule loaded: Empty result").toString(),
                        e);
            }
            return null;
        } catch (org.opengis.referencing.operation.NoninvertibleTransformException e) {
            if (LOGGER.isLoggable(java.util.logging.Level.WARNING)) {
                LOGGER.log(java.util.logging.Level.WARNING,
                        new StringBuilder("Unable to load raster for granuleDescriptor ")
                                .append(this.toString()).append(" with request ")
                                .append(request.toString())
                                .append(" Resulting in no granule loaded: Empty result").toString(),
                        e);
            }
            return null;
        } catch (TransformException e) {
            if (LOGGER.isLoggable(java.util.logging.Level.WARNING)) {
                LOGGER.log(java.util.logging.Level.WARNING,
                        new StringBuilder("Unable to load raster for granuleDescriptor ")
                                .append(this.toString()).append(" with request ")
                                .append(request.toString())
                                .append(" Resulting in no granule loaded: Empty result").toString(),
                        e);
            }
            return null;

        } finally {
//            try {
//                if (request.getReadType() != ReadType.JAI_IMAGEREAD && inStream != null) {
//                    inStream.close();
//                }
//            } finally {
//                if (request.getReadType() != ReadType.JAI_IMAGEREAD && reader != null) {
//                    reader.dispose();
//                }
//            }
        }
    }

    /**
     * This method is responsible for evaluating possible subsampling factors once the best resolution level has been found, in case we have support
     * for overviews, or starting from the original coverage in case there are no overviews available.
     *
     * Anyhow this method should not be called directly but subclasses should make use of the setReadParams method instead in order to transparently
     * look for overviews.
     *
     * @param levelIndex
     * @param readParameters
     * @param requestedRes
     */
    private void performDecimation(ImageReadParam readParameters) {

        final double[] requestedResolution = request.spatialRequestHelper.getRequestedResolution();
        final Rectangle coverageRasterArea = request.spatialRequestHelper.getCoverageProperties().getRasterArea();
        final double[] fullResolution = request.spatialRequestHelper.getCoverageProperties().getFullResolution();
        // the read parameters cannot be null
        Utilities.ensureNonNull("readParameters", readParameters);

        // get the requested resolution
        if (requestedResolution == null) {
            // if there is no requested resolution we don't do any
            // subsampling
            readParameters.setSourceSubsampling(1, 1, 0, 0);
            return;
        }

        final int rasterWidth, rasterHeight;
        // highest resolution
        rasterWidth = coverageRasterArea.width;
        rasterHeight = coverageRasterArea.height;

        // /////////////////////////////////////////////////////////////////////
        // DECIMATION ON READING
        // Setting subsampling factors with some checks
        // 1) the subsampling factors cannot be zero
        // 2) the subsampling factors cannot be such that the w or h are
        // zero
        // /////////////////////////////////////////////////////////////////////
        int subSamplingFactorX = (int) Math.floor(requestedResolution[0] / fullResolution[0]);
        subSamplingFactorX = subSamplingFactorX == 0 ? 1 : subSamplingFactorX;

        while (rasterWidth / subSamplingFactorX <= 0 && subSamplingFactorX >= 0)
            subSamplingFactorX--;
        subSamplingFactorX = subSamplingFactorX <= 0 ? 1 : subSamplingFactorX;

        int subSamplingFactorY = (int) Math.floor(requestedResolution[1] / fullResolution[1]);
        subSamplingFactorY = subSamplingFactorY == 0 ? 1 : subSamplingFactorY;

        while (rasterHeight / subSamplingFactorY <= 0 && subSamplingFactorY >= 0)
            subSamplingFactorY--;
        subSamplingFactorY = subSamplingFactorY <= 0 ? 1 : subSamplingFactorY;

        readParameters.setSourceSubsampling(subSamplingFactorX, subSamplingFactorY, 0, 0);
    }
}
TOP

Related Classes of org.geotools.coverage.io.netcdf.NetCDFResponse

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.