/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2010-2016, 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.display2d.style.renderer; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.Area; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Level; import org.apache.sis.geometry.Envelopes; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.internal.feature.AttributeConvention; import org.apache.sis.internal.referencing.j2d.AffineTransform2D; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.NullArgumentException; import org.geotoolkit.coverage.Category; import org.geotoolkit.coverage.GridSampleDimension; import org.geotoolkit.coverage.grid.GeneralGridGeometry; import org.geotoolkit.storage.coverage.CoverageReference; import org.geotoolkit.coverage.grid.GridCoverage2D; import org.geotoolkit.coverage.grid.GridEnvelope2D; import org.geotoolkit.coverage.grid.GridGeometry2D; import org.geotoolkit.coverage.io.CoverageStoreException; import org.geotoolkit.coverage.io.DisjointCoverageDomainException; import org.geotoolkit.coverage.io.GridCoverageReadParam; import org.geotoolkit.coverage.io.GridCoverageReader; import org.geotoolkit.display.VisitFilter; import org.geotoolkit.display.PortrayalException; import org.geotoolkit.display2d.GO2Utilities; import org.geotoolkit.display2d.canvas.RenderingContext2D; import org.geotoolkit.display2d.container.stateless.StatelessContextParams; import org.geotoolkit.display2d.primitive.ProjectedCoverage; import org.geotoolkit.display2d.primitive.ProjectedFeature; import org.geotoolkit.display2d.primitive.ProjectedObject; import org.geotoolkit.display2d.primitive.SearchAreaJ2D; import org.geotoolkit.display2d.style.CachedSymbolizer; import static org.geotoolkit.display2d.style.renderer.DefaultRasterSymbolizerRenderer.extractQuery; import static org.geotoolkit.display2d.style.renderer.DefaultRasterSymbolizerRenderer.fixEnvelopeWithQuery; import org.geotoolkit.map.CoverageMapLayer; import org.geotoolkit.map.MapBuilder; import org.opengis.coverage.Coverage; import org.geotoolkit.image.interpolation.InterpolationCase; import org.geotoolkit.image.interpolation.ResampleBorderComportement; import org.geotoolkit.internal.referencing.CRSUtilities; import org.geotoolkit.utility.parameter.ParametersExt; import org.geotoolkit.process.ProcessDescriptor; import org.geotoolkit.process.ProcessException; import org.geotoolkit.processing.coverage.resample.ResampleDescriptor; import org.geotoolkit.processing.coverage.resample.ResampleProcess; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.operation.projection.ProjectionException; import org.geotoolkit.referencing.ReferencingUtilities; import org.opengis.geometry.Envelope; import org.opengis.metadata.spatial.PixelOrientation; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.opengis.style.Symbolizer; import org.opengis.util.FactoryException; import org.apache.sis.util.Utilities; import org.opengis.feature.PropertyNotFoundException; /** * Abstract renderer for symbolizer which only apply on coverages data. * This class will take care to implement the coverage hit method. * * @author Johann Sorel (Geomatys) * @module */ public abstract class AbstractCoverageSymbolizerRenderer<C extends CachedSymbolizer<? extends Symbolizer>> extends AbstractSymbolizerRenderer<C>{ public AbstractCoverageSymbolizerRenderer(final SymbolizerRendererService service, final C symbol, final RenderingContext2D context){ super(service, symbol,context); } @Override public void portray(final ProjectedObject graphic) throws PortrayalException { if(graphic instanceof ProjectedFeature){ final ProjectedFeature pf = (ProjectedFeature) graphic; final String geomName = symbol.getSource().getGeometryPropertyName(); Object obj; if(geomName == null || geomName.isEmpty()){ try{ obj = pf.getCandidate().getPropertyValue(AttributeConvention.GEOMETRY_PROPERTY.toString()); }catch(PropertyNotFoundException ex){ obj = null; } }else{ obj = GO2Utilities.evaluate(GO2Utilities.FILTER_FACTORY.property(geomName), pf.getCandidate(), null, null); } if(obj instanceof GridCoverage2D){ final CoverageMapLayer ml = MapBuilder.createCoverageLayer((GridCoverage2D)obj, GO2Utilities.STYLE_FACTORY.style(), ""); final StatelessContextParams params = new StatelessContextParams(renderingContext.getCanvas(),ml); params.update(renderingContext); final ProjectedCoverage pc = new ProjectedCoverage(params, ml); portray(pc); } } } @Override public boolean hit(final ProjectedObject graphic, final SearchAreaJ2D mask, final VisitFilter filter) { if(graphic instanceof ProjectedFeature){ final ProjectedFeature pf = (ProjectedFeature) graphic; final Object obj = GO2Utilities.evaluate(GO2Utilities.FILTER_FACTORY.property( symbol.getSource().getGeometryPropertyName()), pf.getCandidate(), null, null); if(obj instanceof GridCoverage2D){ final CoverageMapLayer ml = MapBuilder.createCoverageLayer((GridCoverage2D)obj, GO2Utilities.STYLE_FACTORY.style(), ""); final StatelessContextParams params = new StatelessContextParams(renderingContext.getCanvas(),ml); params.update(renderingContext); final ProjectedCoverage pc = new ProjectedCoverage(params, ml); return hit(pc,mask,filter); } } return false; } /** * {@inheritDoc } */ @Override public boolean hit(final ProjectedCoverage projectedCoverage, final SearchAreaJ2D search, final VisitFilter filter) { //TODO optimize test using JTS geometries, Java2D Area cost to much cpu final Shape mask = search.getDisplayShape(); final Shape[] shapes; try { shapes = projectedCoverage.getEnvelopeGeometry().getDisplayShape(); } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); return false; } for(Shape shape : shapes){ final Area area = new Area(mask); switch(filter){ case INTERSECTS : area.intersect(new Area(shape)); if(!area.isEmpty()) return true; break; case WITHIN : Area start = new Area(area); area.add(new Area(shape)); if(start.equals(area)) return true; break; } } return false; } /** * Effectuate some operations on source {@link GridCoverage2D} in relation with its internally symbolizer type. * * @param coverageSource source coverage which will be adapted to resampling. * @param symbolizer * @return coverage prepared to resampling. * @see DefaultRasterSymbolizerRenderer#prepareCoverageToResampling(org.geotoolkit.coverage.grid.GridCoverage2D, org.geotoolkit.display2d.style.CachedSymbolizer) */ protected abstract GridCoverage2D prepareCoverageToResampling(final GridCoverage2D coverageSource, C symbolizer); /** * Returns expected {@link GridCoverage2D} from given {@link ProjectedCoverage}, * adapted to asked {@linkplain #renderingContext internally rendering context} situation. * * @param projectedCoverage Convenient representation of a {@link Coverage} for rendering. * @return an expected slice 2D of given {@link ProjectedCoverage}. * @throws org.geotoolkit.coverage.io.CoverageStoreException if problem during coverage reading. * @throws org.opengis.referencing.operation.TransformException if problem during {@link Envelope} transformation. * @throws org.opengis.util.FactoryException if problem during {@link Envelope} study. * @throws org.geotoolkit.process.ProcessException if problem during resampling processing. * @see ResampleDescriptor * @see ResampleProcess * @see ProjectedCoverage#getCoverage(org.geotoolkit.coverage.io.GridCoverageReadParam) */ protected final GridCoverage2D getObjectiveCoverage(final ProjectedCoverage projectedCoverage/*, final CanvasType displayOrObjective*/) throws CoverageStoreException, TransformException, FactoryException, ProcessException { return getObjectiveCoverage(projectedCoverage, renderingContext.getCanvasObjectiveBounds(), renderingContext.getResolution(), renderingContext.getObjectiveToDisplay(), false); } /** * Returns expected {@linkplain GridCoverage2D elevation coverage} from given {@link ProjectedCoverage}, * adapted to asked {@linkplain #renderingContext internally rendering context} situation. * * @param projectedCoverage Convenient representation of a {@link Coverage} for rendering. * @return an expected slice 2D of given {@link ProjectedCoverage}. * @throws org.geotoolkit.coverage.io.CoverageStoreException if problem during coverage reading. * @throws org.opengis.referencing.operation.TransformException if problem during {@link Envelope} transformation. * @throws org.opengis.util.FactoryException if problem during {@link Envelope} study. * @throws org.geotoolkit.process.ProcessException if problem during resampling processing. * @see ResampleDescriptor * @see ResampleProcess * @see ProjectedCoverage#getElevationCoverage(org.geotoolkit.coverage.io.GridCoverageReadParam) */ protected final GridCoverage2D getObjectiveElevationCoverage(final ProjectedCoverage projectedCoverage/*, final CanvasType displayOrObjective*/) throws CoverageStoreException, TransformException, FactoryException, ProcessException { return getObjectiveCoverage(projectedCoverage, renderingContext.getCanvasObjectiveBounds(), renderingContext.getResolution(), renderingContext.getObjectiveToDisplay(), true); } /** * Returns expected {@linkplain GridCoverage2D elevation coverage} or {@linkplain GridCoverage2D coverage} * from given {@link ProjectedCoverage}. * * @param projectedCoverage Convenient representation of a {@link Coverage} for rendering. * @param renderingBound Rendering context enveloppe * @param resolution Rendering resolution in envelope crs * @param objToDisp Objective to displace affine transform * @param isElevation {@code true} if we want elevation coverage, else ({@code false}) for read coverage. * @return expected {@linkplain GridCoverage2D elevation coverage} or {@linkplain GridCoverage2D coverage} * @throws org.geotoolkit.coverage.io.CoverageStoreException if problem during coverage reading. * @throws org.opengis.referencing.operation.TransformException if problem during {@link Envelope} transformation. * @throws org.opengis.util.FactoryException if problem during {@link Envelope} study. * @throws org.geotoolkit.process.ProcessException if problem during resampling processing. * @see ProjectedCoverage#getElevationCoverage(org.geotoolkit.coverage.io.GridCoverageReadParam) * @see ProjectedCoverage#getCoverage(org.geotoolkit.coverage.io.GridCoverageReadParam) */ protected GridCoverage2D getObjectiveCoverage(final ProjectedCoverage projectedCoverage, Envelope renderingBound, double[] resolution, AffineTransform2D objToDisp, final boolean isElevation) throws CoverageStoreException, TransformException, FactoryException, ProcessException { ArgumentChecks.ensureNonNull("projectedCoverage", projectedCoverage); //-- ArgumentChecks.ensureNonNull("CanvasType", displayOrObjective); //////////////////////////////////////////////////////////////////// // 1 - Get data //////////////////////////////////////////////////////////////////// //-- resolution of horizontal Part of CRS assert resolution.length == 2 : "DefaultRasterSymboliser : resolution from renderingContext should only defined in 2D."; resolution = checkResolution(resolution, renderingBound); final CoverageMapLayer coverageLayer = projectedCoverage.getLayer(); final CoverageReference ref = coverageLayer.getCoverageReference(); final Envelope layerBounds = coverageLayer.getBounds(); final CoordinateReferenceSystem coverageMapLayerCRS = layerBounds.getCoordinateReferenceSystem(); final Map<String, Double> queryValues = extractQuery(projectedCoverage.getLayer()); if (queryValues != null && !queryValues.isEmpty()) { renderingBound = fixEnvelopeWithQuery(queryValues, renderingBound, coverageMapLayerCRS); } final GridCoverageReader reader = ref.acquireReader(); final GeneralGridGeometry gridGeometry = reader.getGridGeometry(ref.getImageIndex()); final List<GridSampleDimension> sampleDimensions = reader.getSampleDimensions(ref.getImageIndex()); final Envelope dataBBox = gridGeometry.getEnvelope(); ref.recycle(reader); /* * Study rendering context envelope and internal coverage envelope. * We try to define if the two geographic part from the two respectively * coverage and rendering envelope intersect. */ final CoordinateReferenceSystem renderingContextObjectiveCRS2D = CRSUtilities.getCRS2D(renderingBound.getCoordinateReferenceSystem()); final GeneralEnvelope renderingBound2D = GeneralEnvelope.castOrCopy(Envelopes.transform(renderingBound, renderingContextObjectiveCRS2D)); final GeneralEnvelope coverageIntoRender2DCRS = GeneralEnvelope.castOrCopy(Envelopes.transform(dataBBox, renderingContextObjectiveCRS2D)); if (!org.geotoolkit.geometry.Envelopes.containNAN(renderingBound2D) && !org.geotoolkit.geometry.Envelopes.containNAN(coverageIntoRender2DCRS) && !coverageIntoRender2DCRS.intersects(renderingBound2D, true)) { //-- in future jdk8 version return an Optional<Coverage> final StringBuilder strB = new StringBuilder(isElevation ? "getObjectiveElevationCoverage()" : "getObjectiveCoverage()"); strB.append(" : the 2D geographic part of rendering context does not intersect the 2D geographic part of coverage : "); strB.append("\n rendering context 2D CRS : "); strB.append(renderingContextObjectiveCRS2D); strB.append("\n rendering context boundary : "); strB.append(renderingBound2D); strB.append("\n 2D coverage geographic part into rendering context CRS : "); strB.append(coverageIntoRender2DCRS); LOGGER.log(Level.FINE, strB.toString()); return null; } //-- else //-- Note : in the case of NAN values we try later to clip requested envelope with coverage boundary. final GeneralEnvelope intersectionIntoRender2D = GeneralEnvelope.castOrCopy(coverageIntoRender2DCRS); intersectionIntoRender2D.intersect(renderingBound2D); /* * Study rendering context envelope and internal coverage envelope. * For example if we store data with a third dimension or more, with the 2 dimensional renderer * it is possible to miss some internal stored data. * To avoid this comportment we can "complete"(fill) render envelope with missing dimensions. */ GeneralEnvelope paramEnvelope; { try { paramEnvelope = org.geotoolkit.referencing.ReferencingUtilities.intersectEnvelopes(dataBBox, renderingBound); } catch(ProjectionException ex) { /* * Recently a new kind of exception is thrown when envelope is out of validity domain of target CRS. * To avoid no rendering, try to intersect 2D part and re-project into data CRS and add after others dimensions. */ //-- 1: get intersection between dataBBox and renderBBox into render space //-- 2: projection into dataBBox space paramEnvelope = GeneralEnvelope.castOrCopy(Envelopes.transform(intersectionIntoRender2D, CRSUtilities.getCRS2D(dataBBox.getCoordinateReferenceSystem()))); //-- 3: add others dataBBox dimensions paramEnvelope = org.geotoolkit.referencing.ReferencingUtilities.intersectEnvelopes(dataBBox, paramEnvelope); } } assert paramEnvelope.getCoordinateReferenceSystem() != null : "DefaultRasterSymbolizerRenderer : CRS from param envelope cannot be null."; //-- Check if projected coverage has NAN values on other dimension than geographic 2D part if (org.geotoolkit.geometry.Envelopes.containNAN(paramEnvelope) && !org.geotoolkit.geometry.Envelopes.containNANInto2DGeographicPart(paramEnvelope)) throw new DisjointCoverageDomainException("Rendering envelope extra dimensions does not intersect data envelope : " + "has some NAN values on other dimension than geographic part."+paramEnvelope); //-- We know we don't have NAN values on other dimension than geographic //-- We clip envelope with coverage boundary clipAndReplaceNANEnvelope(paramEnvelope, dataBBox, paramEnvelope); assert !org.geotoolkit.geometry.Envelopes.containNAN(paramEnvelope) : "paramEnvelope can't contain NAN values"; //-- convert resolution adapted to coverage CRS (resolution from rendering context --> coverage resolution) final double[] paramRes = ReferencingUtilities.convertResolution(org.geotoolkit.referencing.ReferencingUtilities.intersectEnvelopes(renderingBound, intersectionIntoRender2D), resolution, paramEnvelope.getCoordinateReferenceSystem()); final GridCoverageReadParam param = new GridCoverageReadParam(); param.setEnvelope(paramEnvelope); param.setResolution(paramRes); GridCoverage2D dataCoverage = (isElevation) ? projectedCoverage.getElevationCoverage(param) : projectedCoverage.getCoverage(param); if (dataCoverage == null) { //-- in future jdk8 version return an Optional<Coverage> final StringBuilder strB = new StringBuilder(isElevation ? "getObjectiveElevationCoverage()" : "getObjectiveCoverage()"); strB.append(" : \n impossible to read"); strB.append((isElevation) ? " Elevation ":" Image "); strB.append("Coverage with internally projected coverage boundary : "); strB.append(dataBBox); strB.append("\nwith the following renderer requested Envelope."); strB.append(paramEnvelope); LOGGER.log(Level.FINE, strB.toString()); return null; } //////////////////////////////////////////////////////////////////// // 3 - Reproject data // //////////////////////////////////////////////////////////////////// final CoordinateReferenceSystem coverageCRS = dataCoverage.getCoordinateReferenceSystem(); assert Utilities.equalsIgnoreMetadata(dataBBox.getCoordinateReferenceSystem(), coverageCRS); final CoordinateReferenceSystem coverageCRS2D = CRSUtilities.getCRS2D(coverageCRS); /* It appears EqualsIgnoreMetadata can return false sometimes, even if the two CRS are equivalent. * But mathematics don't lie, so if they really describe the same transformation, conversion from * one to another will give us an identity matrix. */ final MathTransform coverageToObjective = CRS.findOperation(coverageCRS2D, renderingContextObjectiveCRS2D, null).getMathTransform(); if (coverageToObjective.isIdentity()) return dataCoverage; //-- before try to read coverage in relation with rendering view boundary assert !renderingBound2D.isEmpty() : "2D rendering boundary should not be empty."; final GeneralEnvelope coverageEnv2D = new GeneralEnvelope(dataCoverage.getEnvelope2D()); assert !coverageEnv2D.isEmpty() : "2D coverage boundary should not be empty."; /* * In case where coverage2D envelope into rendering CRS is not empty, * try to reproject a coverage which have already been clipped with the objective rendering context boundary. */ GeneralEnvelope outputRenderingCoverageEnv2D = GeneralEnvelope.castOrCopy(Envelopes.transform(coverageToObjective, coverageEnv2D)); outputRenderingCoverageEnv2D.setCoordinateReferenceSystem(renderingContextObjectiveCRS2D); if (!outputRenderingCoverageEnv2D.isEmpty()) { outputRenderingCoverageEnv2D.intersect(renderingBound2D); } else { outputRenderingCoverageEnv2D = renderingBound2D; } //----------------------------- DISPLAY -------------------------------// //-- compute output grid Envelope into rendering context display //-- get destination image size final GeneralEnvelope dispEnv = Envelopes.transform(objToDisp, outputRenderingCoverageEnv2D); final int width = (int) Math.ceil(dispEnv.getSpan(0)); final int height = (int) Math.ceil(dispEnv.getSpan(1)); if (width <= 0 || height <= 0) { LOGGER.log(Level.FINE, "Coverage is out of rendering window."); return null; } //----------------------------------------------------------------------- //-- find most appropriate interpolation InterpolationCase interpolation; if(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(hints.get(RenderingHints.KEY_INTERPOLATION)) || (!(gridGeometry instanceof GridGeometry2D)) || width < 2 || height < 2) { //hints forced nearest neighbor interpolation interpolation = InterpolationCase.NEIGHBOR; } else { interpolation = findInterpolationCase(sampleDimensions); } //-- expand param envelope if we use an interpolation switch(interpolation){ case BILINEAR : expand(paramEnvelope, 1, gridGeometry); break; case BICUBIC : case BICUBIC2 : expand(paramEnvelope, 2, gridGeometry); break; case LANCZOS : expand(paramEnvelope, 4, gridGeometry); break; } //-- Use into DefaultRasterSymbolizerRenderer //-- force alpha if image do not get any "invalid data" rule (Ex : No-data in image or color map). dataCoverage = prepareCoverageToResampling(dataCoverage, symbol); /* * NODATA * * 1 : Normally all NODATA for all gridSampleDimension for a same coverage are equals. * 2 : Normally all NODATA for each coverage internally samples are equals. */ final double[] nodata = dataCoverage.getSampleDimension(0).getNoDataValues(); /* * If nodata is not know. * 1 : find a nodata value out of internal gridSampleDimension categories. * 2 : if category already contain all sample Datatype possible values, * transform image into a sample type with more bitspersample to define * an appropriate NODATA values out of categories borders. */ if (nodata == null) { //-- TODO } final GridGeometry2D gg = new GridGeometry2D(new GridEnvelope2D(0, 0, width, height), outputRenderingCoverageEnv2D); final ProcessDescriptor desc = ResampleDescriptor.INSTANCE; final ParameterValueGroup params = desc.getInputDescriptor().createValue(); ParametersExt.getOrCreateValue(params, ResampleDescriptor.IN_COVERAGE .getName().getCode()).setValue(dataCoverage); ParametersExt.getOrCreateValue(params, ResampleDescriptor.IN_BACKGROUND .getName().getCode()).setValue(nodata); ParametersExt.getOrCreateValue(params, ResampleDescriptor.IN_COORDINATE_REFERENCE_SYSTEM .getName().getCode()).setValue(renderingContextObjectiveCRS2D); ParametersExt.getOrCreateValue(params, ResampleDescriptor.IN_GRID_GEOMETRY .getName().getCode()).setValue(gg); ParametersExt.getOrCreateValue(params, ResampleDescriptor.IN_INTERPOLATION_TYPE .getName().getCode()).setValue(interpolation); ParametersExt.getOrCreateValue(params, ResampleDescriptor.IN_BORDER_COMPORTEMENT_TYPE .getName().getCode()).setValue(ResampleBorderComportement.FILL_VALUE); final org.geotoolkit.process.Process process = desc.createProcess(params); final ParameterValueGroup result = process.call(); dataCoverage = (GridCoverage2D) result.parameter("result").getValue(); return dataCoverage; } /** * Clip requested envelope with internally {@link ProjectedCoverage} boundary. * * <strong> * In some case when the rendering boundary is reprojected into coverage space * some {@linkplain Double#NaN NAN} values can be computed, which is an expected comportment. * To avoid normally exception during coverage reading this method replace NAN values by coverage boundary values. * </strong> * * @param requestedEnvelope envelope which will be clipped. * @param coverageEnvelope reference coverage envelope. * @param result set result of clipping into this {@link GeneralEnvelope}, * a new result envelope is built if it is {@code null}, you should pass the same Envelope as requestedEnvelope. * Moreover the result envelope is defined into same CRS than requestedEnvelope. * @return requested clipped envelope result. * @throws NullArgumentException if requestedEnvelope or coverageEnvelope are {@code null}. * @throws IllegalArgumentException if CRS from requestedEnvelope and coverageEnvelope are different. */ private GeneralEnvelope clipAndReplaceNANEnvelope(final Envelope requestedEnvelope, final Envelope coverageEnvelope, GeneralEnvelope result) { ArgumentChecks.ensureNonNull("requestedEnvelope", requestedEnvelope); ArgumentChecks.ensureNonNull("coverageEnvelope", coverageEnvelope); final CoordinateReferenceSystem requestCRS = requestedEnvelope.getCoordinateReferenceSystem(); if (!Utilities.equalsIgnoreMetadata(requestCRS, coverageEnvelope.getCoordinateReferenceSystem())) throw new IllegalArgumentException("requestedEnvelope and coverage envelope will be able to have same CRS : " + "\n Expected CRS : "+requestCRS + "\n Found : "+coverageEnvelope.getCoordinateReferenceSystem()); if (result == null) result = new GeneralEnvelope(requestCRS); for (int d = 0, dim = requestedEnvelope.getDimension(); d < dim; d++) { final double reqMin = requestedEnvelope.getMinimum(d); final double reqMax = requestedEnvelope.getMaximum(d); final double min = (Double.isNaN(reqMin) || Double.isInfinite(reqMin) ? coverageEnvelope.getMinimum(d) : StrictMath.max(reqMin, coverageEnvelope.getMinimum(d))); final double max = (Double.isNaN(reqMax) || Double.isInfinite(reqMax) ? coverageEnvelope.getMaximum(d) : StrictMath.min(reqMax, coverageEnvelope.getMaximum(d))); result.setRange(d, min, max); } return result; } /** * Detect the most appropriate interpolation type based on coverage sample dimensions. * Interpolation is possible only when data do not contain qualitative informations. */ private static InterpolationCase findInterpolationCase(List<GridSampleDimension> sampleDimensions) throws CoverageStoreException{ if (sampleDimensions != null) { for (GridSampleDimension sd : sampleDimensions) { final List<Category> categories = sd.getCategories(); if (categories != null) { for (Category cat : categories) { if (!cat.isQuantitative() && !cat.getName().toString(Locale.ENGLISH).equals("No data")) { return InterpolationCase.NEIGHBOR; } } } } } //no information on the data or datas are not qualitative, assume it can be interpolated //TODO : search geotk history for code made by Desruisseaux in old Resample operator, // it contained such verifications. return InterpolationCase.BILINEAR; } /** * Expand the given envelope by the given number of points. * * @param env envelope to expand * @param nbPoint in grid crs to expand the envelope * @param gridGeom coverage grid geometry */ private static void expand(final GeneralEnvelope env, final int nbPoint, final GeneralGridGeometry gridGeom) throws TransformException { if (!(gridGeom instanceof GridGeometry2D)) throw new IllegalArgumentException("Impossible to compute expand for interpolation with no 2D GridGeometry."); final GridGeometry2D grigrid = (GridGeometry2D) gridGeom; //-- we are only interested in the 2D part. final MathTransform gridToCrs2D = grigrid.getGridToCRS2D(PixelOrientation.UPPER_LEFT); final MathTransform crsToGrid2D = gridToCrs2D.inverse(); final Envelope env2D = Envelopes.transform(env, grigrid.getCoordinateReferenceSystem2D()); final GeneralEnvelope gridEnv2D = Envelopes.transform(crsToGrid2D,env2D); gridEnv2D.setRange(0, Math.floor(gridEnv2D.getMinimum(0)-nbPoint), Math.ceil(gridEnv2D.getMaximum(0)+nbPoint)); gridEnv2D.setRange(1, Math.floor(gridEnv2D.getMinimum(1)-nbPoint), Math.ceil(gridEnv2D.getMaximum(1)+nbPoint)); final Envelope geoEnv2D = Envelopes.transform(gridToCrs2D,gridEnv2D); final int horizontalAxis = CRSUtilities.firstHorizontalAxis(env.getCoordinateReferenceSystem()); env.setRange(horizontalAxis, geoEnv2D.getMinimum(0), geoEnv2D.getMaximum(0)); env.setRange(horizontalAxis + 1, geoEnv2D.getMinimum(1), geoEnv2D.getMaximum(1)); } }