From: <svn...@os...> - 2012-03-08 14:34:12
|
Author: aaime Date: 2012-03-08 06:34:00 -0800 (Thu, 08 Mar 2012) New Revision: 38621 Modified: trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/GranuleDescriptor.java trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/RasterLayerResponse.java trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/ReadType.java trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/Utils.java trunk/modules/plugin/imagemosaic/src/test/java/org/geotools/gce/imagemosaic/ImageMosaicReaderTest.java Log: [GEOT-4066] ImageMosaic does not satisfy the specified background values when the request this a hole sitting on the side of the mosaic Modified: trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/GranuleDescriptor.java =================================================================== --- trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/GranuleDescriptor.java 2012-03-08 14:33:40 UTC (rev 38620) +++ trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/GranuleDescriptor.java 2012-03-08 14:34:00 UTC (rev 38621) @@ -343,9 +343,9 @@ reader = cachedReaderSPI.createReaderInstance(); if(reader == null) throw new IllegalArgumentException("Unable to get an ImageReader for the provided file "+granuleUrl.toString()); - + reader.setInput(inStream); //get selected level and base level dimensions - final Rectangle originalDimension = ImageUtilities.getDimension(0,inStream, reader); + final Rectangle originalDimension = Utils.getDimension(0, reader); // build the g2W for this tile, in principle we should get it // somehow from the tile itself or from the index, but at the moment @@ -625,6 +625,7 @@ inStream = cachedStreamSPI.createInputStreamInstance(granuleUrl, ImageIO.getUseCache(), ImageIO.getCacheDirectory()); if(inStream==null) return null; + // get a reader and try to cache the relevant SPI if(cachedReaderSPI==null){ @@ -663,7 +664,7 @@ } //get selected level and base level dimensions - final GranuleOverviewLevelDescriptor selectedlevel= getLevel(imageIndex,reader,inStream); + final GranuleOverviewLevelDescriptor selectedlevel= getLevel(imageIndex,reader); // now create the crop grid to world which can be used to decide @@ -925,13 +926,10 @@ } } - private GranuleOverviewLevelDescriptor getLevel(final int index, final ImageReader reader, final ImageInputStream inStream) { + private GranuleOverviewLevelDescriptor getLevel(final int index, final ImageReader reader) { if(reader==null) - throw new NullPointerException("Null reader passed to the internal GranuleOverviewLevelDescriptor method"); - if(inStream==null) - throw new NullPointerException("Null stream passed to the internal GranuleOverviewLevelDescriptor method"); - + throw new NullPointerException("Null reader passed to the internal GranuleOverviewLevelDescriptor method"); synchronized (granuleLevels) { if(granuleLevels.containsKey(Integer.valueOf(index))) return granuleLevels.get(Integer.valueOf(index)); @@ -945,7 +943,7 @@ // //get selected level and base level dimensions - final Rectangle levelDimension = ImageUtilities.getDimension(index,inStream, reader); + final Rectangle levelDimension = Utils.getDimension(index, reader); final GranuleOverviewLevelDescriptor baseLevel= granuleLevels.get(0); final double scaleX=baseLevel.width/(1.0*levelDimension.width); @@ -993,9 +991,10 @@ reader=cachedReaderSPI.createReaderInstance(); if(reader==null) throw new IllegalArgumentException("Unable to get an ImageReader for the provided file "+granuleUrl.toString()); + reader.setInput(inStream); // call internal method which will close everything - return getLevel(index, reader, inStream); + return getLevel(index, reader); } catch (IllegalStateException e) { Modified: trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/RasterLayerResponse.java =================================================================== --- trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/RasterLayerResponse.java 2012-03-08 14:33:40 UTC (rev 38620) +++ trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/RasterLayerResponse.java 2012-03-08 14:34:00 UTC (rev 38621) @@ -20,11 +20,8 @@ import java.awt.Dimension; import java.awt.Rectangle; import java.awt.RenderingHints; -import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; -import java.awt.geom.PathIterator; -import java.awt.geom.Rectangle2D; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.MultiPixelPackedSampleModel; @@ -60,6 +57,7 @@ import javax.media.jai.operator.ConstantDescriptor; import javax.media.jai.operator.FormatDescriptor; import javax.media.jai.operator.MosaicDescriptor; +import javax.media.jai.operator.TranslateDescriptor; import org.apache.commons.io.FilenameUtils; import org.geotools.coverage.Category; @@ -1018,11 +1016,14 @@ final Number[] values = ImageUtilities.getBackgroundValues(rasterManager.defaultSM, backgroundValues); // create a constant image with a proper layout - final RenderedImage finalImage = ConstantDescriptor.create( + RenderedImage finalImage = ConstantDescriptor.create( Float.valueOf(rasterBounds.width), Float.valueOf(rasterBounds.height), values, null); + if (rasterBounds.x != 0 || rasterBounds.y != 0) { + finalImage = TranslateDescriptor.create(finalImage, Float.valueOf(rasterBounds.x), Float.valueOf(rasterBounds.y), Interpolation.getInstance(Interpolation.INTERP_NEAREST), null); + } if(rasterManager.defaultCM!=null){ final ImageLayout2 il= new ImageLayout2(); il.setColorModel(rasterManager.defaultCM); @@ -1165,19 +1166,19 @@ // // SPECIAL CASE // 1 single tile, we try not do a mosaic. + final ROI[] sourceRoi = visitor.sourceRoi; if(visitor.granulesNumber==1 && Utils.OPTIMIZE_CROP){ // the roi is exactly equal to the final ROI roi = visitor.rois.get(0); - Rectangle bounds = toRectangle(roi.getAsShape()); + Rectangle bounds = Utils.toRectangle(roi.getAsShape()); if (bounds != null) { - final RenderedImage image= visitor.getSourcesAsArray()[0]; - final Rectangle imageBounds= PlanarImage.wrapRenderedImage(image).getBounds(); + RenderedImage image= visitor.getSourcesAsArray()[0]; + Rectangle imageBounds= PlanarImage.wrapRenderedImage(image).getBounds(); if(imageBounds.equals(bounds)){ - // do we need to crop - if(!imageBounds.equals(rasterBounds)){ + // do we need to crop? (image is bigger than requested?) + if(!rasterBounds.contains(imageBounds)){ // we have to crop - XRectangle2D.intersect(imageBounds, rasterBounds, imageBounds); if(imageBounds.isEmpty()){ @@ -1188,15 +1189,28 @@ ImageWorker iw = new ImageWorker(image); iw.setRenderingHints(localHints); iw.crop(imageBounds.x, imageBounds.y, imageBounds.width, imageBounds.height); - return iw.getRenderedImage(); + + image = iw.getRenderedImage(); + imageBounds = PlanarImage.wrapRenderedImage(image).getBounds(); } + + // and, do we need to add a border around the image? + if(!imageBounds.contains(rasterBounds)) { + image = MosaicDescriptor.create( + new RenderedImage[] {image}, + request.isBlend()? MosaicDescriptor.MOSAIC_TYPE_BLEND: MosaicDescriptor.MOSAIC_TYPE_OVERLAY, + (alphaIn || visitor.doInputTransparency) ? visitor.alphaChannels : null, sourceRoi, + visitor.sourceThreshold, + backgroundValues, + localHints); + } + return image; } } } - final ROI[] sourceRoi = visitor.sourceRoi; final RenderedImage mosaic = MosaicDescriptor.create( visitor.getSourcesAsArray(), request.isBlend()? MosaicDescriptor.MOSAIC_TYPE_BLEND: MosaicDescriptor.MOSAIC_TYPE_OVERLAY, @@ -1231,91 +1245,6 @@ } /** - * Checks if the Shape equates to a Rectangle, if it does it performs a conversion, otherwise - * returns null - * @param shape - * @return - */ - Rectangle toRectangle(Shape shape) { - if(shape instanceof Rectangle) { - return (Rectangle) shape; - } - - if(shape == null) { - return null; - } - - // check if it's equivalent to a rectangle - PathIterator iter = shape.getPathIterator(new AffineTransform()); - double[] coords = new double[2]; - - // not enough points? - if(iter.isDone()) { - return null; - } - - // get the first and init the data structures - iter.next(); - int action = iter.currentSegment(coords); - if(action != PathIterator.SEG_MOVETO && action != PathIterator.SEG_LINETO) { - return null; - } - double minx = coords[0]; - double miny = coords[1]; - double maxx = minx; - double maxy = miny; - double prevx = minx; - double prevy = miny; - int i = 0; - - // at most 4 steps, if more it's not a strict rectangle - for (; i < 4 && !iter.isDone(); i++) { - iter.next(); - action = iter.currentSegment(coords); - - if(action == PathIterator.SEG_CLOSE) { - break; - } - if(action != PathIterator.SEG_LINETO) { - return null; - } - - // check orthogonal step (x does not change and y does, or vice versa) - double x = coords[0]; - double y = coords[1]; - if(!(prevx == x && prevy != y) && - !(prevx != x && prevy == y)) { - return null; - } - - // update mins and maxes - if(x < minx) { - minx = x; - } else if(x > maxx) { - maxx = x; - } - if(y < miny) { - miny = y; - } else if(y > maxy) { - maxy = y; - } - - // keep track of prev step - prevx = x; - prevy = y; - } - - // if more than 4 other points it's not a standard rectangle - iter.next(); - if(!iter.isDone() || i != 3) { - return null; - } - - // turn it into a rectangle - return new Rectangle2D.Double(minx, miny, maxx - minx, maxy - miny).getBounds(); - } - - /** * This method is responsible for creating a coverage from the supplied {@link RenderedImage}. * * @param image @@ -1424,12 +1353,11 @@ null ).geophysics(true); } - return coverageFactory.create( rasterManager.getCoverageIdentifier(), image, new GridGeometry2D( - new GridEnvelope2D(PlanarImage.wrapRenderedImage(image).getBounds()), + new GridEnvelope2D(PlanarImage.wrapRenderedImage(image).getBounds()), PixelInCell.CELL_CORNER, finalGridToWorldCorner, this.mosaicBBox.getCoordinateReferenceSystem(), Modified: trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/ReadType.java =================================================================== --- trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/ReadType.java 2012-03-08 14:33:40 UTC (rev 38620) +++ trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/ReadType.java 2012-03-08 14:34:00 UTC (rev 38621) @@ -143,6 +143,7 @@ // check input stream final ImageInputStream inStream=(ImageInputStream) reader.getInput(); // read data + inStream.seek(0); final RenderedOp raster = ImageReadDescriptor.create( inStream, imageIndex, Modified: trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/Utils.java =================================================================== --- trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/Utils.java 2012-03-08 14:33:40 UTC (rev 38620) +++ trunk/modules/plugin/imagemosaic/src/main/java/org/geotools/gce/imagemosaic/Utils.java 2012-03-08 14:34:00 UTC (rev 38621) @@ -17,7 +17,11 @@ package org.geotools.gce.imagemosaic; import java.awt.Color; +import java.awt.Rectangle; +import java.awt.Shape; import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.RenderedImage; @@ -40,6 +44,7 @@ import java.net.URL; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Properties; @@ -48,6 +53,9 @@ import java.util.logging.Level; import java.util.logging.Logger; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; import javax.media.jai.Histogram; import javax.media.jai.RasterFactory; import javax.media.jai.remote.SerializableRenderedImage; @@ -71,6 +79,8 @@ import org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilderConfiguration; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.operation.matrix.XAffineTransform; +import org.geotools.resources.i18n.ErrorKeys; +import org.geotools.resources.i18n.Errors; import org.geotools.util.Converters; import org.geotools.util.Utilities; @@ -501,6 +511,64 @@ } /** + * Look for an {@link ImageReader} instance that is able to read the + * provided {@link ImageInputStream}, which must be non null. + * + * <p> + * In case no reader is found, <code>null</code> is returned. + * + * @param inStream + * an instance of {@link ImageInputStream} for which we need to + * find a suitable {@link ImageReader}. + * @return a suitable instance of {@link ImageReader} or <code>null</code> + * if one cannot be found. + */ + static ImageReader getReader(final ImageInputStream inStream) { + Utilities.ensureNonNull("inStream", inStream); + // get a reader + inStream.mark(); + final Iterator<ImageReader> readersIt = ImageIO + .getImageReaders(inStream); + if (!readersIt.hasNext()) { + return null; + } + return readersIt.next(); + } + + /** + * Retrieves the dimensions of the {@link RenderedImage} at index + * <code>imageIndex</code> for the provided {@link ImageReader} and + * {@link ImageInputStream}. + * + * <p> + * Notice that none of the input parameters can be <code>null</code> or a + * {@link NullPointerException} will be thrown. Morevoer the + * <code>imageIndex</code> cannot be negative or an + * {@link IllegalArgumentException} will be thrown. + * + * @param imageIndex + * the index of the image to get the dimensions for. + * @param inStream + * the {@link ImageInputStream} to use as an input + * @param reader + * the {@link ImageReader} to decode the image dimensions. + * @return a {@link Rectangle} that contains the dimensions for the image at + * index <code>imageIndex</code> + * @throws IOException + * in case the {@link ImageReader} or the + * {@link ImageInputStream} fail. + */ + static Rectangle getDimension(final int imageIndex,final ImageReader reader) + throws IOException { + Utilities.ensureNonNull("reader", reader); + if (imageIndex < 0) + throw new IllegalArgumentException(Errors.format( + ErrorKeys.INDEX_OUT_OF_BOUNDS_$1, imageIndex)); + return new Rectangle(0, 0, reader.getWidth(imageIndex), reader + .getHeight(imageIndex)); + } + + /** * Default priority for the underlying {@link Thread}. */ public static final int DEFAULT_PRIORITY = Thread.NORM_PRIORITY; @@ -1134,4 +1202,89 @@ return true; return false; } + + /** + * Checks if the Shape equates to a Rectangle, if it does it performs a conversion, otherwise + * returns null + * @param shape + * @return + */ + static Rectangle toRectangle(Shape shape) { + if(shape instanceof Rectangle) { + return (Rectangle) shape; + } + + if(shape == null) { + return null; + } + + // check if it's equivalent to a rectangle + PathIterator iter = shape.getPathIterator(new AffineTransform()); + double[] coords = new double[2]; + + // not enough points? + if(iter.isDone()) { + return null; + } + + // get the first and init the data structures + iter.next(); + int action = iter.currentSegment(coords); + if(action != PathIterator.SEG_MOVETO && action != PathIterator.SEG_LINETO) { + return null; + } + double minx = coords[0]; + double miny = coords[1]; + double maxx = minx; + double maxy = miny; + double prevx = minx; + double prevy = miny; + int i = 0; + + // at most 4 steps, if more it's not a strict rectangle + for (; i < 4 && !iter.isDone(); i++) { + iter.next(); + action = iter.currentSegment(coords); + + if(action == PathIterator.SEG_CLOSE) { + break; + } + if(action != PathIterator.SEG_LINETO) { + return null; + } + + // check orthogonal step (x does not change and y does, or vice versa) + double x = coords[0]; + double y = coords[1]; + if(!(prevx == x && prevy != y) && + !(prevx != x && prevy == y)) { + return null; + } + + // update mins and maxes + if(x < minx) { + minx = x; + } else if(x > maxx) { + maxx = x; + } + if(y < miny) { + miny = y; + } else if(y > maxy) { + maxy = y; + } + + // keep track of prev step + prevx = x; + prevy = y; + } + + // if more than 4 other points it's not a standard rectangle + iter.next(); + if(!iter.isDone() || i != 3) { + return null; + } + + // turn it into a rectangle + return new Rectangle2D.Double(minx, miny, maxx - minx, maxy - miny).getBounds(); + } } Modified: trunk/modules/plugin/imagemosaic/src/test/java/org/geotools/gce/imagemosaic/ImageMosaicReaderTest.java =================================================================== --- trunk/modules/plugin/imagemosaic/src/test/java/org/geotools/gce/imagemosaic/ImageMosaicReaderTest.java 2012-03-08 14:33:40 UTC (rev 38620) +++ trunk/modules/plugin/imagemosaic/src/test/java/org/geotools/gce/imagemosaic/ImageMosaicReaderTest.java 2012-03-08 14:34:00 UTC (rev 38621) @@ -19,6 +19,9 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Rectangle; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.io.File; import java.io.FileNotFoundException; @@ -34,7 +37,9 @@ import java.util.TimeZone; import java.util.logging.Logger; +import javax.imageio.ImageIO; import javax.media.jai.PlanarImage; +import javax.media.jai.iterator.RectIterFactory; import javax.media.jai.widget.ScrollingImagePanel; import javax.swing.JFrame; import javax.swing.SwingUtilities; @@ -52,6 +57,7 @@ import org.geotools.coverage.grid.io.OverviewPolicy; import org.geotools.coverage.grid.io.UnknownFormat; import org.geotools.factory.Hints; +import org.geotools.geometry.Envelope2D; import org.geotools.geometry.GeneralEnvelope; import org.geotools.parameter.Parameter; import org.geotools.referencing.CRS; @@ -61,8 +67,8 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; +import org.opengis.geometry.Envelope; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.parameter.GeneralParameterValue; import org.opengis.parameter.ParameterValue; @@ -869,7 +875,80 @@ checkCoverage(reader, new GeneralParameterValue[] { gg, outTransp },title); } + + @Test + public void testRequestInHole() throws Exception { + final AbstractGridFormat format = getFormat(rgbAURL); + final ImageMosaicReader reader = getReader(rgbAURL, format); + assertNotNull(reader); + + // ask to extract an area that is inside the coverage bbox, but in a hole (no data) + final ParameterValue<GridGeometry2D> ggp = AbstractGridFormat.READ_GRIDGEOMETRY2D.createValue(); + Envelope2D env = new Envelope2D(reader.getCrs(), 500000, 3200000, 1000, 1000); + GridGeometry2D gg = new GridGeometry2D(new GridEnvelope2D(0, 0, 100, 100), (Envelope) env); + ggp.setValue(gg); + + // red background + final ParameterValue<double[]> bgp = ImageMosaicFormat.BACKGROUND_VALUES.createValue(); + bgp.setValue(new double[] {255, 0, 0, 255}); + + // read and check we actually got a coverage in the requested area + GridCoverage2D coverage = reader.read(new GeneralParameterValue[] {ggp, bgp}); + assertNotNull(coverage); + assertTrue(coverage.getEnvelope2D().intersects((Rectangle2D) env)); + + // and that the color is the expected one given the background values provided + RenderedImage ri = coverage.getRenderedImage(); + int[] pixel = new int[4]; + Raster tile = ri.getTile(ri.getMinTileX() + 1, ri.getMinTileY() + 1); + tile.getPixel(tile.getMinX(), tile.getMinY(), pixel); + assertEquals(255, pixel[0]); + assertEquals(0, pixel[1]); + assertEquals(0, pixel[2]); + assertEquals(255, pixel[3]); + } + + @Test + public void testRequestInOut() throws Exception { + final AbstractGridFormat format = getFormat(rgbAURL); + final ImageMosaicReader reader = getReader(rgbAURL, format); + + assertNotNull(reader); + + // ask to extract an area that is inside the coverage bbox, so that the area is partly + // inside the raster, and partly outside + final ParameterValue<GridGeometry2D> ggp = AbstractGridFormat.READ_GRIDGEOMETRY2D.createValue(); + Envelope2D env = new Envelope2D(reader.getCrs(), 64887, 2499342, 646897 - 64887 , 3155705 - 2499342); + GridGeometry2D gg = new GridGeometry2D(new GridEnvelope2D(0, 0, 100, 100), (Envelope) env); + ggp.setValue(gg); + + // red background + final ParameterValue<double[]> bgp = ImageMosaicFormat.BACKGROUND_VALUES.createValue(); + bgp.setValue(new double[] {255, 0, 0, 255}); + + // read and check we actually got a coverage in the requested area + GridCoverage2D coverage = reader.read(new GeneralParameterValue[] {ggp, bgp}); + assertNotNull(coverage); + System.out.println(coverage.getEnvelope2D()); + System.out.println(env); + assertTrue(coverage.getEnvelope2D().contains((Rectangle2D) env)); + + // and that the color is the expected one given the background values provided + RenderedImage ri = coverage.getRenderedImage(); + ImageIO.write(ri, "PNG", new File("/tmp/mix.png")); + System.out.println(ri.getNumXTiles()); + System.out.println(ri.getNumYTiles()); + int[] pixel = new int[4]; + Raster tile = ri.getTile(ri.getMinTileX() + ri.getNumXTiles() - 1, + ri.getMinTileY() + ri.getNumYTiles() - 1); + tile.getPixel(tile.getWidth() / 2, tile.getHeight() / 2, pixel); + assertEquals(255, pixel[0]); + assertEquals(0, pixel[1]); + assertEquals(0, pixel[2]); + assertEquals(255, pixel[3]); + } + /** * @param args */ |