0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2004-2006, Geotools Project Managment Committee (PMC)
0005: *
0006: * This library is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation; either
0009: * version 2.1 of the License, or (at your option) any later version.
0010: *
0011: * This library is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: */
0016: package org.geotools.renderer.lite.gridcoverage2d;
0017:
0018: // J2SE dependencies
0019: import java.awt.AlphaComposite;
0020: import java.awt.Color;
0021: import java.awt.Composite;
0022: import java.awt.Graphics2D;
0023: import java.awt.Rectangle;
0024: import java.awt.RenderingHints;
0025: import java.awt.geom.AffineTransform;
0026: import java.awt.geom.NoninvertibleTransformException;
0027: import java.awt.image.BufferedImage;
0028: import java.awt.image.RenderedImage;
0029: import java.awt.image.renderable.RenderableImage;
0030: import java.io.File;
0031: import java.io.IOException;
0032: import java.util.HashMap;
0033: import java.util.logging.Level;
0034: import java.util.logging.Logger;
0035:
0036: import javax.imageio.ImageIO;
0037: import javax.media.jai.BorderExtender;
0038: import javax.media.jai.Interpolation;
0039: import javax.media.jai.InterpolationBilinear;
0040: import javax.media.jai.InterpolationNearest;
0041: import javax.media.jai.JAI;
0042:
0043: import org.geotools.coverage.grid.GeneralGridRange;
0044: import org.geotools.coverage.grid.GridCoverage2D;
0045: import org.geotools.coverage.grid.GridGeometry2D;
0046: import org.geotools.coverage.processing.DefaultProcessor;
0047: import org.geotools.coverage.processing.operation.Crop;
0048: import org.geotools.coverage.processing.operation.FilteredSubsample;
0049: import org.geotools.coverage.processing.operation.Resample;
0050: import org.geotools.coverage.processing.operation.Scale;
0051: import org.geotools.factory.Hints;
0052: import org.geotools.geometry.GeneralEnvelope;
0053: import org.geotools.geometry.jts.ReferencedEnvelope;
0054: import org.geotools.referencing.CRS;
0055: import org.geotools.referencing.operation.builder.GridToEnvelopeMapper;
0056: import org.geotools.referencing.operation.matrix.XAffineTransform;
0057: import org.geotools.renderer.lite.StreamingRenderer;
0058: import org.geotools.resources.CRSUtilities;
0059: import org.geotools.resources.image.CoverageUtilities;
0060: import org.geotools.resources.image.ImageUtilities;
0061: import org.geotools.styling.RasterSymbolizer;
0062: import org.geotools.styling.SLD;
0063: import org.opengis.coverage.grid.GridCoverage;
0064: import org.opengis.filter.expression.Literal;
0065: import org.opengis.parameter.ParameterValueGroup;
0066: import org.opengis.referencing.FactoryException;
0067: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0068: import org.opengis.referencing.datum.PixelInCell;
0069: import org.opengis.referencing.operation.MathTransform;
0070: import org.opengis.referencing.operation.TransformException;
0071:
0072: import com.vividsolutions.jts.geom.Envelope;
0073:
0074: /**
0075: * A helper class for rendering {@link GridCoverage} objects. Support for grid
0076: * coverage SLD stylers is still limited.
0077: *
0078: * @author Simone Giannecchini
0079: * @author Andrea Aime
0080: * @author Alessio Fabiani
0081: * @source $URL:
0082: * http://svn.geotools.org/geotools/trunk/gt/module/render/src/org/geotools/renderer/lite/GridCoverageRenderer.java $
0083: * @version $Id: GridCoverageRenderer.java 18352 2006-03-01 06:13:42Z
0084: * desruisseaux $
0085: *
0086: * @task Add support for SLD styles
0087: */
0088: public final class GridCoverageRenderer {
0089: /**
0090: * This variable is use for testing purposes in order to force this
0091: * {@link GridCoverageRenderer} to dump images at various steps on the disk.
0092: */
0093: private final static boolean DEBUG = Boolean
0094: .getBoolean("org.geotools.renderer.lite.gridcoverage2d.debug");
0095:
0096: protected static final double EPS = 1E-6;
0097:
0098: private static String debugDir;
0099: static {
0100: if (DEBUG) {
0101: final File tempDir = new File(System
0102: .getProperty("java.io.tmpdir"));
0103: if (!tempDir.exists() || !tempDir.canWrite()) {
0104: System.out
0105: .println("Unable to create debug dir, exiting application!!!");
0106: System.exit(1);
0107: debugDir = null;
0108: } else
0109: debugDir = tempDir.getAbsolutePath();
0110: }
0111:
0112: }
0113:
0114: /** Cached factory for the {@link Scale} operation. */
0115: private final static Scale scaleFactory = new Scale();
0116:
0117: /** Cached factory for the {@link FilteredSubsample} operation. */
0118: private final static FilteredSubsample filteredSubsampleFactory = new FilteredSubsample();
0119:
0120: /** Cached factory for the {@link Crop} operation. */
0121: private final static Crop coverageCropFactory = new Crop();
0122:
0123: /** Logger. */
0124: private static final Logger LOGGER = org.geotools.util.logging.Logging
0125: .getLogger("org.geotools.rendering");
0126:
0127: static {
0128:
0129: // ///////////////////////////////////////////////////////////////////
0130: //
0131: // Caching parameters for performing the various operations.
0132: //
0133: // ///////////////////////////////////////////////////////////////////
0134: final DefaultProcessor processor = new DefaultProcessor(
0135: CoverageUtilities.LENIENT_HINT);
0136: resampleParams = processor.getOperation("Resample")
0137: .getParameters();
0138: scaleParams = processor.getOperation("Scale").getParameters();
0139: cropParams = processor.getOperation("CoverageCrop")
0140: .getParameters();
0141: filteredSubsampleParams = processor.getOperation(
0142: "FilteredSubsample").getParameters();
0143: }
0144:
0145: /** The Display (User defined) CRS * */
0146: private final CoordinateReferenceSystem destinationCRS;
0147:
0148: /** Area we want to draw. */
0149: private final GeneralEnvelope destinationEnvelope;
0150:
0151: /** Size of the area we want to draw in pixels. */
0152: private final Rectangle destinationSize;
0153:
0154: private final GridToEnvelopeMapper gridToEnvelopeMapper;
0155:
0156: private final AffineTransform finalGridToWorld;
0157:
0158: private final AffineTransform finalWorldToGrid;
0159:
0160: private final Hints hints = new Hints(new HashMap(5));
0161:
0162: /** Parameters used to control the {@link Resample} operation. */
0163: private final static ParameterValueGroup resampleParams;
0164:
0165: /** Parameters used to control the {@link Scale} operation. */
0166: private final static ParameterValueGroup scaleParams;
0167:
0168: /** Parameters used to control the {@link Crop} operation. */
0169: private static ParameterValueGroup cropParams;
0170:
0171: /** Parameters used to control the {@link FilteredSubsample} operation. */
0172: private final static ParameterValueGroup filteredSubsampleParams;
0173:
0174: /** Parameters used to control the {@link Scale} operation. */
0175: private static final Resample resampleFactory = new Resample();
0176:
0177: /**
0178: * Tolerance for NOT drawing a coverage.
0179: *
0180: * If after a scaling a coverage has all dimensions smaller than
0181: * {@link GridCoverageRenderer#MIN_DIM_TOLERANCE} we just do not draw it.
0182: */
0183: private static final int MIN_DIM_TOLERANCE = 2;
0184:
0185: /**
0186: * Creates a new {@link GridCoverageRenderer} object.
0187: *
0188: * @param destinationCRS
0189: * the CRS of the {@link GridCoverage2D} to render.
0190: * @param envelope
0191: * delineating the area to be rendered.
0192: * @param screenSize
0193: * at which we want to rendere the source {@link GridCoverage2D}.
0194: * @throws TransformException
0195: * @throws NoninvertibleTransformException
0196: *
0197: */
0198: public GridCoverageRenderer(
0199: final CoordinateReferenceSystem destinationCRS,
0200: final Envelope envelope, Rectangle screenSize)
0201: throws TransformException, NoninvertibleTransformException {
0202:
0203: this (destinationCRS, envelope, screenSize, null);
0204:
0205: }
0206:
0207: /**
0208: * Creates a new {@link GridCoverageRenderer} object.
0209: *
0210: * @param destinationCRS
0211: * the CRS of the {@link GridCoverage2D} to render.
0212: * @param envelope
0213: * delineating the area to be rendered.
0214: * @param screenSize
0215: * at which we want to rendere the source {@link GridCoverage2D}.
0216: * @param java2dHints
0217: * to control this rendering process.
0218: *
0219: * @throws TransformException
0220: * @throws NoninvertibleTransformException
0221: */
0222: public GridCoverageRenderer(
0223: final CoordinateReferenceSystem destinationCRS,
0224: final Envelope envelope, Rectangle screenSize,
0225: RenderingHints java2dHints) throws TransformException,
0226: NoninvertibleTransformException {
0227:
0228: // ///////////////////////////////////////////////////////////////////
0229: //
0230: // Initialize this renderer
0231: //
0232: // ///////////////////////////////////////////////////////////////////
0233: this .destinationSize = screenSize;
0234: this .destinationCRS = CRSUtilities.getCRS2D(destinationCRS);
0235: gridToEnvelopeMapper = new GridToEnvelopeMapper();
0236: gridToEnvelopeMapper.setGridType(PixelInCell.CELL_CORNER);
0237: gridToEnvelopeMapper.setGridRange(new GeneralGridRange(
0238: destinationSize));
0239: destinationEnvelope = new GeneralEnvelope(
0240: new ReferencedEnvelope(envelope, destinationCRS));
0241: // ///////////////////////////////////////////////////////////////////
0242: //
0243: // FINAL DRAWING DIMENSIONS AND RESOLUTION
0244: // I am here getting the final drawing dimensions (on the device) and
0245: // the resolution for this rendererbut in the CRS of the source coverage
0246: // since I am going to compare this info with the same info for the
0247: // source coverage.
0248: //
0249: // ///////////////////////////////////////////////////////////////////
0250: gridToEnvelopeMapper.setEnvelope(destinationEnvelope);
0251: finalGridToWorld = new AffineTransform(gridToEnvelopeMapper
0252: .createAffineTransform());
0253: finalWorldToGrid = finalGridToWorld.createInverse();
0254:
0255: // ///////////////////////////////////////////////////////////////////
0256: //
0257: // HINTS
0258: //
0259: // ///////////////////////////////////////////////////////////////////
0260: if (java2dHints != null)
0261: this .hints.add(java2dHints);
0262: // this prevents users from overriding leninet hint
0263: this .hints.add(CoverageUtilities.LENIENT_HINT);
0264: this .hints.add(ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
0265:
0266: }
0267:
0268: /**
0269: * Paint this grid coverage. The caller must ensure that
0270: * <code>graphics</code> has an affine transform mapping "real world"
0271: * coordinates in the coordinate system given by {@link
0272: * #getCoordinateSystem}.
0273: *
0274: * @param graphics
0275: * the {@link Graphics2D} context in which to paint.
0276: * @param metaBufferedEnvelope
0277: * @throws FactoryException
0278: * @throws TransformException
0279: * @throws NoninvertibleTransformException
0280: * @throws Exception
0281: * @throws UnsupportedOperationException
0282: * if the transformation from grid to coordinate system in the
0283: * GridCoverage is not an AffineTransform
0284: */
0285: public void paint(final Graphics2D graphics,
0286: final GridCoverage2D gridCoverage,
0287: final RasterSymbolizer symbolizer) throws FactoryException,
0288: TransformException, NoninvertibleTransformException {
0289: // METABUFFER SUPPORT
0290: // public void paint(final Graphics2D graphics,
0291: // final GridCoverage2D gridCoverage, final RasterSymbolizer symbolizer,
0292: // GeneralEnvelope metaBufferedEnvelope)
0293: // throws FactoryException, TransformException,
0294: // NoninvertibleTransformException {
0295:
0296: // ///////////////////////////////////////////////////////////////////
0297: //
0298: // Getting information about the source coverage like the source CRS,
0299: // the source envelope and the source geometry.
0300: //
0301: // ///////////////////////////////////////////////////////////////////
0302: if (LOGGER.isLoggable(Level.FINE))
0303: LOGGER.fine(new StringBuffer("Drawing coverage ").append(
0304: gridCoverage.toString()).toString());
0305: final CoordinateReferenceSystem sourceCoverageCRS = gridCoverage
0306: .getCoordinateReferenceSystem2D();
0307: final GridGeometry2D sourceCoverageGG = (GridGeometry2D) gridCoverage
0308: .getGridGeometry();
0309: final GeneralGridRange sourceRange = (GeneralGridRange) sourceCoverageGG
0310: .getGridRange();
0311: final GeneralEnvelope sourceCoverageEnvelope = (GeneralEnvelope) gridCoverage
0312: .getEnvelope();
0313:
0314: // ///////////////////////////////////////////////////////////////////
0315: //
0316: // GET THE CRS MAPPING
0317: //
0318: // This step I instantiate the MathTransform for going from the source
0319: // crs to the desination crs.
0320: //
0321: // ///////////////////////////////////////////////////////////////////
0322: // math transform from source to target crs
0323: final MathTransform sourceCRSToDestinationCRSTransformation = StreamingRenderer
0324: .getMathTransform(sourceCoverageCRS, destinationCRS);
0325: final MathTransform destinationCRSToSourceCRSTransformation = sourceCRSToDestinationCRSTransformation
0326: .inverse();
0327: final boolean doReprojection = !sourceCRSToDestinationCRSTransformation
0328: .isIdentity();
0329: if (LOGGER.isLoggable(Level.FINE))
0330: LOGGER.fine(new StringBuffer(
0331: "Transforming coverage envelope with transform ")
0332: .append(
0333: destinationCRSToSourceCRSTransformation
0334: .toWKT()).toString());
0335:
0336: // //
0337: //
0338: // Do we need reprojection?
0339: //
0340: // //
0341: GeneralEnvelope croppedDestinationEnvelope = null;
0342: if (doReprojection) {
0343: // //
0344: //
0345: // Let's crop the destination envelope with respect to the source
0346: // coverage envelope in order to do resampling if needed.
0347: //
0348: // //
0349: // create a new envelope
0350: croppedDestinationEnvelope = new GeneralEnvelope(
0351: destinationEnvelope);
0352: croppedDestinationEnvelope
0353: .setCoordinateReferenceSystem(destinationCRS);
0354:
0355: //
0356: // transform the source coverage envelope to the destination
0357: // coordinate reference system for cropping the destination envelope
0358: final GeneralEnvelope transformedSourceCoverageEnvelope = CRS
0359: .transform(sourceCRSToDestinationCRSTransformation,
0360: sourceCoverageEnvelope);
0361: transformedSourceCoverageEnvelope
0362: .setCoordinateReferenceSystem(destinationCRS);
0363: croppedDestinationEnvelope
0364: .intersect(transformedSourceCoverageEnvelope);
0365:
0366: }
0367:
0368: final Interpolation interpolation = (Interpolation) hints
0369: .get(JAI.KEY_INTERPOLATION);
0370: if (LOGGER.isLoggable(Level.FINE))
0371: LOGGER.fine(new StringBuffer("Using interpolation ")
0372: .append(interpolation).toString());
0373:
0374: final GridCoverage2D preSymbolizer;
0375: if (!isScaleTranslate(gridCoverage.getGridGeometry()
0376: .getGridToCRS())) {
0377: /////////////////////////////////////////////////////////////////////
0378: //
0379: // REPROJECT to the requested crs.
0380: //
0381: //
0382: // ///////////////////////////////////////////////////////////////////
0383: if (doReprojection) {
0384: preSymbolizer = resample(
0385: gridCoverage,
0386: destinationCRS,
0387: interpolation == null ? new InterpolationBilinear()
0388: : interpolation,
0389: croppedDestinationEnvelope);
0390: if (LOGGER.isLoggable(Level.FINE))
0391: LOGGER
0392: .fine(new StringBuffer(
0393: "Reprojecting to crs ").append(
0394: destinationCRS.toWKT()).toString());
0395: if (DEBUG) {
0396: try {
0397: ImageIO.write(preSymbolizer.geophysics(false)
0398: .getRenderedImage(), "tiff", new File(
0399: debugDir, "reprojected.tiff"));
0400: } catch (IOException e) {
0401:
0402: e.printStackTrace();
0403: }
0404: }
0405: } else
0406: preSymbolizer = gridCoverage;
0407:
0408: } else {
0409: // /////////////////////////////////////////////////////////////////
0410: //
0411: // CROP
0412: // This step I aim to crop the area of the coverage we want to serve.
0413: // I know that drawRenderedImage takes already into account tiling but
0414: // there is reprojection in between and I do not want end up
0415: // reprojecting 2 Giga of image for using 1 Kb.
0416: //
0417: // First I need to convert the source envelope into the destination crs.
0418: //
0419: // /////////////////////////////////////////////////////////////////
0420: // //
0421: //
0422: // This is the destination envelope in the coverage crs which is going
0423: // to be used for getting the crop area to crop the source coverage
0424: //
0425: // //
0426: final GeneralEnvelope destinationEnvelopeInSourceGCCRS = doReprojection ? CRS
0427: .transform(destinationCRSToSourceCRSTransformation,
0428: destinationEnvelope)
0429: : new GeneralEnvelope(destinationEnvelope);
0430: destinationEnvelopeInSourceGCCRS
0431: .setCoordinateReferenceSystem(sourceCoverageCRS);
0432: final GridCoverage2D croppedGridCoverage = getCroppedCoverage(
0433: gridCoverage, destinationEnvelopeInSourceGCCRS,
0434: sourceCoverageCRS);
0435: if (croppedGridCoverage == null) {
0436: // nothing to render, the AOI does not overlap
0437: if (LOGGER.isLoggable(Level.FINE))
0438: LOGGER
0439: .fine(new StringBuffer(
0440: "Skipping current coverage because cropped to an empty area")
0441: .toString());
0442: return;
0443: }
0444: if (DEBUG) {
0445: try {
0446: ImageIO.write(croppedGridCoverage.geophysics(false)
0447: .getRenderedImage(), "tiff", new File(
0448: debugDir, "cropped.tiff"));
0449: } catch (IOException e1) {
0450: // TODO Auto-generated catch block
0451: e1.printStackTrace();
0452: }
0453: }
0454:
0455: // ///////////////////////////////////////////////////////////////////
0456: //
0457: // DRAWING DIMENSIONS AND RESOLUTION
0458: // I am here getting the final drawing dimensions (on the device) and
0459: // the resolution for this renderer but in the CRS of the source
0460: // coverage
0461: // since I am going to compare this info with the same info for the
0462: // source coverage. The objective is to come up with the needed scale
0463: // factors for the original coverage in order to decide how to proceed.
0464: // Options are first scale then reproject or the opposite.
0465: //
0466: // In case we need to upsample the coverage first we reproject and then
0467: // we upsample otherwise we do the opposite in order
0468: //
0469: // ///////////////////////////////////////////////////////////////////
0470: AffineTransform finalGridToWorldInGCCRS;
0471: if (!sourceCRSToDestinationCRSTransformation.isIdentity()) {
0472: assert new GeneralGridRange(destinationSize)
0473: .equals(gridToEnvelopeMapper.getGridRange());
0474: gridToEnvelopeMapper
0475: .setEnvelope(destinationEnvelopeInSourceGCCRS);
0476: finalGridToWorldInGCCRS = new AffineTransform(
0477: gridToEnvelopeMapper.createAffineTransform());
0478: } else {
0479: finalGridToWorldInGCCRS = new AffineTransform(
0480: finalGridToWorld);
0481: }
0482:
0483: // ///////////////////////////////////////////////////////////////////
0484: //
0485: // SCALE and REPROJECT in the best order.
0486: // Let me now scale down or up to the EXACT needed SPATIAL resolution.
0487: // This step does not prevent from having loaded an overview of the
0488: // original image based on the requested scale but it complements it.
0489: //
0490: // ///////////////////////////////////////////////////////////////////
0491: // //
0492: //
0493: // First step is computing the needed resolution levels for this
0494: // coverage in its original crs to see the scale factors.
0495: //
0496: // //
0497: final AffineTransform croppedCoverageGridToWorldTransformations = (AffineTransform) ((GridGeometry2D) croppedGridCoverage
0498: .getGridGeometry()).getGridToCRS2D();
0499: final boolean sourceGCHasLonFirst = (XAffineTransform
0500: .getSwapXY(croppedCoverageGridToWorldTransformations) != -1);
0501: final boolean destinationHasLonFirst = (XAffineTransform
0502: .getSwapXY(finalGridToWorldInGCCRS) != -1);
0503: final double actualScaleX = sourceGCHasLonFirst ? croppedCoverageGridToWorldTransformations
0504: .getScaleX()
0505: : croppedCoverageGridToWorldTransformations
0506: .getShearY();
0507: final double actualScaleY = sourceGCHasLonFirst ? croppedCoverageGridToWorldTransformations
0508: .getScaleY()
0509: : croppedCoverageGridToWorldTransformations
0510: .getShearX();
0511: final double scaleX = actualScaleX
0512: / (destinationHasLonFirst ? finalGridToWorldInGCCRS
0513: .getScaleX() : finalGridToWorldInGCCRS
0514: .getShearY());
0515: final double scaleY = actualScaleY
0516: / (destinationHasLonFirst ? finalGridToWorldInGCCRS
0517: .getScaleY() : finalGridToWorldInGCCRS
0518: .getShearX());
0519: if (LOGGER.isLoggable(Level.FINE))
0520: LOGGER.fine(new StringBuffer("Scale factors are ")
0521: .append(scaleX).append(" ").append(scaleY)
0522: .toString());
0523: final int actualW = sourceRange.getLength(0);
0524: final int actualH = sourceRange.getLength(1);
0525: if (Math.round(actualW * scaleX) < MIN_DIM_TOLERANCE
0526: && Math.round(actualH * scaleY) < MIN_DIM_TOLERANCE) {
0527: if (LOGGER.isLoggable(Level.FINE))
0528: LOGGER
0529: .fine(new StringBuffer(
0530: "Skipping the actual coverage because one of the final dimension is null")
0531: .toString());
0532: return;
0533: }
0534:
0535: // //
0536: //
0537: // Now if we are upsampling first reproject then scale else first scale
0538: // then reproject.
0539: //
0540: // //
0541: if (scaleX * scaleY <= 1.0) {
0542: int scaleXInt = (int) Math.floor(1 / scaleX);
0543: scaleXInt = scaleXInt == 0 ? 1 : scaleXInt;
0544: int scaleYInt = (int) Math.floor(1 / scaleY);
0545: scaleYInt = scaleYInt == 0 ? 1 : scaleYInt;
0546:
0547: // ///////////////////////////////////////////////////////////////////
0548: //
0549: // SCALE DOWN to the needed resolution
0550: //
0551: // ///////////////////////////////////////////////////////////////////
0552: // //
0553: //
0554: // first step for down sampling is filtered subsample which is fast.
0555: //
0556: // //
0557: if (LOGGER.isLoggable(Level.FINE))
0558: LOGGER.fine(new StringBuffer(
0559: "Filtered subsample with factors ").append(
0560: scaleXInt).append(scaleYInt).toString());
0561: final GridCoverage2D preScaledGridCoverage = filteredSubsample(
0562: croppedGridCoverage,
0563: scaleXInt,
0564: scaleYInt,
0565: new InterpolationNearest(),
0566: BorderExtender
0567: .createInstance(BorderExtender.BORDER_COPY));
0568: if (DEBUG) {
0569: try {
0570: ImageIO.write(preScaledGridCoverage.geophysics(
0571: false).getRenderedImage(), "tiff",
0572: new File(debugDir, "prescaled.tiff"));
0573: } catch (IOException e) {
0574: // TODO Auto-generated catch block
0575: e.printStackTrace();
0576: }
0577: }
0578: // //
0579: //
0580: // Second step is scale
0581: //
0582: // //
0583: if (LOGGER.isLoggable(Level.FINE))
0584: LOGGER.fine(new StringBuffer(
0585: "Scale down with factors ").append(
0586: scaleX * scaleXInt).append(
0587: scaleY * scaleYInt).toString());
0588: final GridCoverage2D scaledGridCoverage;
0589: if (scaleX * scaleXInt == 1.0
0590: && scaleY * scaleYInt == 1.0)
0591: scaledGridCoverage = preScaledGridCoverage;
0592: else
0593: scaledGridCoverage = scale(
0594: scaleX * scaleXInt,
0595: scaleY * scaleYInt,
0596: 0f,
0597: 0f,
0598: interpolation == null ? new InterpolationBilinear()
0599: : interpolation,
0600: BorderExtender
0601: .createInstance(BorderExtender.BORDER_COPY),
0602: preScaledGridCoverage);
0603:
0604: if (DEBUG) {
0605: try {
0606: ImageIO.write(scaledGridCoverage.geophysics(
0607: false).getRenderedImage(), "tiff",
0608: new File(debugDir, "scaled.tiff"));
0609: } catch (IOException e) {
0610:
0611: e.printStackTrace();
0612: }
0613: }
0614:
0615: // ///////////////////////////////////////////////////////////////////
0616: //
0617: // REPROJECT to the requested crs.
0618: //
0619: //
0620: // ///////////////////////////////////////////////////////////////////
0621: if (doReprojection) {
0622: preSymbolizer = resample(
0623: scaledGridCoverage,
0624: destinationCRS,
0625: interpolation == null ? new InterpolationBilinear()
0626: : interpolation,
0627: croppedDestinationEnvelope);
0628: if (LOGGER.isLoggable(Level.FINE))
0629: LOGGER.fine(new StringBuffer(
0630: "Reprojecting to crs ").append(
0631: destinationCRS.toWKT()).toString());
0632: if (DEBUG) {
0633: try {
0634: ImageIO.write(preSymbolizer.geophysics(
0635: false).getRenderedImage(), "tiff",
0636: new File(debugDir,
0637: "reprojected.tiff"));
0638: } catch (IOException e) {
0639:
0640: e.printStackTrace();
0641: }
0642: }
0643: } else
0644: preSymbolizer = scaledGridCoverage;
0645:
0646: } else {
0647:
0648: // ///////////////////////////////////////////////////////////////////
0649: //
0650: // REPROJECT to the requested crs
0651: //
0652: //
0653: // ///////////////////////////////////////////////////////////////////
0654: final GridCoverage2D reprojectedCoverage;
0655: if (doReprojection) {
0656: reprojectedCoverage = resample(
0657: croppedGridCoverage,
0658: destinationCRS,
0659: interpolation == null ? new InterpolationBilinear()
0660: : interpolation,
0661: croppedDestinationEnvelope);
0662: if (LOGGER.isLoggable(Level.FINE))
0663: LOGGER.fine(new StringBuffer(
0664: "Reprojecting to crs ").append(
0665: destinationCRS.toWKT()).toString());
0666: } else
0667: reprojectedCoverage = croppedGridCoverage;
0668:
0669: if (DEBUG) {
0670: try {
0671: ImageIO.write(reprojectedCoverage.geophysics(
0672: false).getRenderedImage(), "tiff",
0673: new File("c:/reprojected.tiff"));
0674: } catch (IOException e) {
0675:
0676: e.printStackTrace();
0677: }
0678: }
0679: // ///////////////////////////////////////////////////////////////////
0680: //
0681: // SCALE UP to the needed resolution
0682: //
0683: // ///////////////////////////////////////////////////////////////////
0684: if (LOGGER.isLoggable(Level.FINE))
0685: LOGGER.fine(new StringBuffer(
0686: "Scale up with factors ").append(scaleX)
0687: .append(scaleY).toString());
0688: preSymbolizer = (GridCoverage2D) scale(
0689: scaleX,
0690: scaleY,
0691: 0f,
0692: 0f,
0693: interpolation == null ? new InterpolationBilinear()
0694: : interpolation,
0695: BorderExtender
0696: .createInstance(BorderExtender.BORDER_COPY),
0697: reprojectedCoverage);
0698: if (DEBUG) {
0699: try {
0700: ImageIO.write(preSymbolizer.geophysics(false)
0701: .getRenderedImage(), "tiff", new File(
0702: debugDir, "scaleup.tiff"));
0703: } catch (IOException e) {
0704:
0705: e.printStackTrace();
0706: }
0707: }
0708:
0709: }
0710: }
0711:
0712: if (DEBUG) {
0713:
0714: try {
0715: ImageIO.write(preSymbolizer.geophysics(false)
0716: .getRenderedImage(), "tiff", new File(debugDir,
0717: "preSymbolizer.tiff"));
0718: } catch (IOException e) {
0719: // TODO Auto-generated catch block
0720: e.printStackTrace();
0721: }
0722: }
0723:
0724: // ///////////////////////////////////////////////////////////////////
0725: //
0726: // RECOLOR
0727: //
0728: //
0729: // ///////////////////////////////////////////////////////////////////
0730: if (LOGGER.isLoggable(Level.FINE))
0731: LOGGER.fine(new StringBuffer("Raster Symbolizer ")
0732: .toString());
0733: final RasterSymbolizerSupport rsp = new RasterSymbolizerSupport(
0734: symbolizer);
0735: final GridCoverage2D recoloredGridCoverage = (GridCoverage2D) rsp
0736: .recolorCoverage(preSymbolizer);
0737: final RenderedImage finalImage = recoloredGridCoverage
0738: .geophysics(false).getRenderedImage();
0739:
0740: // ///////////////////////////////////////////////////////////////////
0741: //
0742: // DRAW ME
0743: // I need the grid to world transform for drawing this grid coverage to
0744: // the display
0745: //
0746: // ///////////////////////////////////////////////////////////////////
0747: final AffineTransform finalGCgridToWorld = new AffineTransform(
0748: (AffineTransform) ((GridGeometry2D) recoloredGridCoverage
0749: .getGridGeometry()).getGridToCRS2D());
0750: if (!(finalGCgridToWorld instanceof AffineTransform)) {
0751: throw new UnsupportedOperationException(
0752: "Non-affine transformations not yet implemented"); // TODO
0753: }
0754:
0755: // //
0756: //
0757: // I need to translate half of a pixel since in wms 1.1.1 the envelope
0758: // map to the corners of the raster space not to the center of the
0759: // pixels.
0760: //
0761: // //
0762: finalGCgridToWorld.translate(-0.5, -0.5); // Map to upper-left corner.
0763:
0764: // //
0765: //
0766: // I am going to concatenate the final world to grid transform for the
0767: // screen area with the grid to world transform of the input coverage.
0768: //
0769: // This way i right away position the coverage at the right place in the
0770: // area of interest for the device.
0771: //
0772: // //
0773: final AffineTransform clonedFinalWorldToGrid = (AffineTransform) finalWorldToGrid
0774: .clone();
0775: clonedFinalWorldToGrid.concatenate(finalGCgridToWorld);
0776: if (LOGGER.isLoggable(Level.FINE))
0777: LOGGER.fine(new StringBuffer("clonedFinalWorldToGrid ")
0778: .append(clonedFinalWorldToGrid.toString())
0779: .toString());
0780:
0781: // it should be a simple translation TODO check
0782: final RenderingHints oldHints = graphics.getRenderingHints();
0783: graphics.setRenderingHints(this .hints);
0784:
0785: // //
0786: // Opacity
0787: // //
0788: final float alpha = rsp.getOpacity();
0789: final Composite oldAlphaComposite = graphics.getComposite();
0790: graphics.setComposite(AlphaComposite.getInstance(
0791: AlphaComposite.SRC_OVER, alpha));
0792: try {
0793: // //
0794: // Drawing the Image
0795: // //
0796: graphics.drawRenderedImage(finalImage,
0797: clonedFinalWorldToGrid);
0798: } catch (Throwable t) {
0799: try {
0800: if (DEBUG) {
0801: try {
0802: ImageIO.write(finalImage, "tiff", new File(
0803: debugDir, "final0.tiff"));
0804: } catch (IOException e) {
0805:
0806: e.printStackTrace();
0807: }
0808: }
0809: // /////////////////////////////////////////////////////////////
0810: // this is a workaround for a bug in Java2D
0811: // (see bug 4723021
0812: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4723021).
0813: //
0814: // AffineTransformOp.filter throws a
0815: // java.awt.image.ImagingOpException: Unable to tranform src
0816: // image when a PixelInterleavedSampleModel is used.
0817: //
0818: // CUSTOMER WORKAROUND :
0819: // draw the BufferedImage into a buffered image of type ARGB
0820: // then perform the affine transform. THIS OPERATION WASTES
0821: // RESOURCES BY PERFORMING AN ALLOCATION OF MEMORY AND A COPY ON
0822: // LARGE IMAGES.
0823: // /////////////////////////////////////////////////////////////
0824: final BufferedImage buf = new BufferedImage(
0825: (int) finalImage.getWidth(), (int) finalImage
0826: .getHeight(),
0827: BufferedImage.TYPE_4BYTE_ABGR);
0828: final Graphics2D g = (Graphics2D) buf.getGraphics();
0829: g.drawRenderedImage(finalImage, AffineTransform
0830: .getScaleInstance(1, 1));
0831: g.dispose();
0832: if (DEBUG) {
0833: try {
0834: ImageIO.write(buf, "tiff", new File(debugDir,
0835: "final1.tiff"));
0836: } catch (IOException e1) {
0837:
0838: }
0839: }
0840:
0841: graphics.drawImage(buf, clonedFinalWorldToGrid, null);
0842: buf.flush();
0843:
0844: } catch (Throwable t1) {
0845: // if fthe workaround fails again, there is really nothing to do
0846: // :-(
0847: LOGGER.log(Level.WARNING, t1.getLocalizedMessage(), t1);
0848: }
0849: }
0850:
0851: // ///////////////////////////////////////////////////////////////////
0852: //
0853: // Restore old elements
0854: //
0855: // ///////////////////////////////////////////////////////////////////
0856: graphics.setComposite(oldAlphaComposite);
0857: graphics.setRenderingHints(oldHints);
0858:
0859: }
0860:
0861: /**
0862: * Scaling the input coverage using the provided parameters.
0863: *
0864: * @param scaleX
0865: * @param scaleY
0866: * @param xTrans
0867: * @param yTrans
0868: * @param interpolation
0869: * @param be
0870: * @param gc
0871: * @return
0872: */
0873: private GridCoverage2D scale(final double scaleX,
0874: final double scaleY, float xTrans, float yTrans,
0875: final Interpolation interpolation, final BorderExtender be,
0876: final GridCoverage2D gc) {
0877:
0878: final ParameterValueGroup param = (ParameterValueGroup) scaleParams
0879: .clone();
0880: param.parameter("source").setValue(gc);
0881: param.parameter("xScale").setValue(new Float(scaleX));
0882: param.parameter("yScale").setValue(new Float(scaleY));
0883: param.parameter("xTrans").setValue(new Float(xTrans));
0884: param.parameter("yTrans").setValue(new Float(yTrans));
0885: param.parameter("Interpolation").setValue(interpolation);
0886: param.parameter("BorderExtender").setValue(be);
0887: return (GridCoverage2D) scaleFactory.doOperation(param, hints);
0888:
0889: }
0890:
0891: /**
0892: * Reprojecting the input coverage using the provided parameters.
0893: *
0894: * @param gc
0895: * @param crs
0896: * @param interpolation
0897: * @return
0898: */
0899: private GridCoverage2D resample(final GridCoverage2D gc,
0900: CoordinateReferenceSystem crs,
0901: final Interpolation interpolation,
0902: final GeneralEnvelope destinationEnvelope) {
0903: // paranoiac check
0904: assert CRS.equalsIgnoreMetadata(destinationEnvelope
0905: .getCoordinateReferenceSystem(), crs);
0906:
0907: final ParameterValueGroup param = (ParameterValueGroup) resampleParams
0908: .clone();
0909: param.parameter("source").setValue(gc);
0910: param.parameter("CoordinateReferenceSystem").setValue(crs);
0911: param.parameter("GridGeometry").setValue(
0912: new GridGeometry2D(gc.getGridGeometry().getGridRange(),
0913: destinationEnvelope));
0914: param.parameter("InterpolationType").setValue(interpolation);
0915: return (GridCoverage2D) resampleFactory.doOperation(param,
0916: hints);
0917:
0918: }
0919:
0920: /**
0921: * Subsampling the provided {@link GridCoverage2D} with the provided
0922: * parameters.
0923: *
0924: * @param gc
0925: * @param scaleXInt
0926: * @param scaleYInt
0927: * @param interpolation
0928: * @param be
0929: * @return
0930: */
0931: private GridCoverage2D filteredSubsample(final GridCoverage2D gc,
0932: int scaleXInt, int scaleYInt,
0933: final Interpolation interpolation, final BorderExtender be) {
0934: final GridCoverage2D preScaledGridCoverage;
0935: if (scaleXInt == 1 && scaleYInt == 1)
0936: preScaledGridCoverage = gc;
0937: else {
0938:
0939: final ParameterValueGroup param = (ParameterValueGroup) filteredSubsampleParams
0940: .clone();
0941: param.parameter("source").setValue(gc);
0942: param.parameter("scaleX").setValue(new Integer(scaleXInt));
0943: param.parameter("scaleY").setValue(new Integer(scaleYInt));
0944: if (hints.get(JAI.KEY_INTERPOLATION) != null
0945: && hints.get(JAI.KEY_INTERPOLATION).equals(
0946: new InterpolationNearest()))
0947: param.parameter("qsFilterArray").setValue(
0948: new float[] { 1.0F });
0949: else
0950: param.parameter("qsFilterArray").setValue(
0951: new float[] { 0.5F, 1.0F / 3.0F, 0.0F,
0952: -1.0F / 12.0F });
0953: param.parameter("Interpolation").setValue(interpolation);
0954: if (!(interpolation instanceof InterpolationNearest))
0955: param.parameter("BorderExtender").setValue(be);
0956: preScaledGridCoverage = (GridCoverage2D) filteredSubsampleFactory
0957: .doOperation(param, hints);
0958:
0959: }
0960: return preScaledGridCoverage;
0961: }
0962:
0963: /**
0964: * Cropping the provided coverage to the requested geographic area.
0965: *
0966: * @param gc
0967: * @param envelope
0968: * @param crs
0969: * @return
0970: */
0971: private GridCoverage2D getCroppedCoverage(GridCoverage2D gc,
0972: GeneralEnvelope envelope, CoordinateReferenceSystem crs) {
0973: final GeneralEnvelope oldEnvelope = (GeneralEnvelope) gc
0974: .getEnvelope();
0975: // intersect the envelopes in order to prepare for crooping the coverage
0976: // down to the neded resolution
0977: final GeneralEnvelope intersectionEnvelope = new GeneralEnvelope(
0978: envelope);
0979: intersectionEnvelope.setCoordinateReferenceSystem(crs);
0980: intersectionEnvelope.intersect((GeneralEnvelope) oldEnvelope);
0981:
0982: // Do we have something to show? After the crop I could get a null
0983: // coverage which would mean nothing to show.
0984: if (intersectionEnvelope.isEmpty())
0985: return null;
0986:
0987: // crop
0988: final ParameterValueGroup param = (ParameterValueGroup) cropParams
0989: .clone();
0990: param.parameter("source").setValue(gc);
0991: param.parameter("ConserveEnvelope").setValue(Boolean.TRUE);
0992: param.parameter("Envelope").setValue(intersectionEnvelope);
0993: return (GridCoverage2D) coverageCropFactory.doOperation(param,
0994: hints);
0995:
0996: }
0997:
0998: /**
0999: * Checks the transformation is a pure scale/translate instance (using a tolerance)
1000: * @param transform
1001: * @return
1002: */
1003: private boolean isScaleTranslate(MathTransform transform) {
1004: if (!(transform instanceof AffineTransform))
1005: return false;
1006: final AffineTransform at = new AffineTransform(
1007: (AffineTransform) transform);
1008: XAffineTransform.round(at, EPS);
1009: final double rotation = XAffineTransform.getRotation(at);
1010: final boolean retVal = (Math.abs(rotation) == 0);
1011: return retVal;
1012: }
1013: }
|