示例#1
0
  /**
   * Test Read exploiting JAI-ImageIO tools capabilities and GDAL warp.
   *
   * @throws FileNotFoundException
   * @throws IOException
   */
  @Test
  public void readWithWarp() throws FileNotFoundException, IOException {
    if (!isGDALAvailable) {
      return;
    }
    final ParameterBlockJAI pbjImageRead;
    String fileName = "utmByte.tif";
    final File file = TestData.file(this, fileName);

    SpatialReference destinationReference = new SpatialReference();
    destinationReference.SetProjCS("UTM 17 (WGS84) in northern hemisphere.");
    destinationReference.SetWellKnownGeogCS("WGS84");
    destinationReference.SetUTM(17, 1);

    GDALImageReadParam readParam = new GDALImageReadParam();
    readParam.setDestinationWkt(destinationReference.ExportToWkt());
    readParam.setResampleAlgorithm(ResampleAlgorithm.CUBIC);

    pbjImageRead = new ParameterBlockJAI("ImageRead");
    pbjImageRead.setParameter("Input", new FileImageInputStreamExtImpl(file));
    pbjImageRead.setParameter("Reader", new GeoTiffImageReaderSpi().createReaderInstance());
    pbjImageRead.setParameter("ReadParam", readParam);
    RenderedOp image = JAI.create("ImageRead", pbjImageRead);
    if (TestData.isInteractiveTest()) Viewer.visualizeAllInformation(image, "", true);
    else Assert.assertNotNull(image.getTiles());
  }
示例#2
0
 public static RenderedOp create(
     RenderedImage source0, ROIShape roi, Byte[] bandValues, RenderingHints hints) {
   ParameterBlockJAI pb =
       new ParameterBlockJAI("Shutter", RenderedRegistryMode.MODE_NAME); // $NON-NLS-1$
   pb.setSource("source0", source0); // $NON-NLS-1$
   pb.setParameter("roi", roi); // $NON-NLS-1$
   pb.setParameter("color", bandValues); // $NON-NLS-1$
   return JAI.create("Shutter", pb, hints); // $NON-NLS-1$
 }
示例#3
0
  /**
   * Reads a standard JFIF (PNG) file.
   *
   * <p>Creates a <code>ParameterBlockJAI</code> from all supplied arguments except <code>hints
   * </code> and invokes {@link JAI#create(String,ParameterBlock,RenderingHints)}.
   *
   * @see JAI
   * @see ParameterBlockJAI
   * @see RenderedOp
   * @param stream The SeekableStream to read from.
   * @param param The PNGDecodeParam to use. May be <code>null</code>.
   * @param hints The <code>RenderingHints</code> to use. May be <code>null</code>.
   * @return The <code>RenderedOp</code> destination.
   * @throws IllegalArgumentException if <code>stream</code> is <code>null</code>.
   */
  public static RenderedOp create(
      SeekableStream stream, PNGDecodeParam param, RenderingHints hints) {
    ParameterBlockJAI pb = new ParameterBlockJAI("PNG", RenderedRegistryMode.MODE_NAME);

    pb.setParameter("stream", stream);
    pb.setParameter("param", param);

    return JAI.create("PNG", pb, hints);
  }
  @Test
  public void rectanglePolyAcrossTiles() throws Exception {
    final int margin = 3;
    final int Ntiles = 3;

    int minx = margin;
    int miny = minx;
    int maxx = TILE_WIDTH * Ntiles - 2 * margin;
    int maxy = maxx;

    String wkt =
        String.format(
            "POLYGON((%d %d, %d %d, %d %d, %d %d, %d %d))",
            minx, miny, minx, maxy, maxx, maxy, maxx, miny, minx, miny);

    Polygon poly = (Polygon) reader.read(wkt);

    ParameterBlockJAI pb = new ParameterBlockJAI("VectorBinarize");
    pb.setParameter("width", Ntiles * TILE_WIDTH);
    pb.setParameter("height", Ntiles * TILE_WIDTH);
    pb.setParameter("geometry", poly);

    RenderedOp dest = JAI.create("VectorBinarize", pb);

    CoordinateSequence2D testPointCS = new CoordinateSequence2D(1);
    Point testPoint = gf.createPoint(testPointCS);

    for (int ytile = 0; ytile < Ntiles; ytile++) {
      for (int xtile = 0; xtile < Ntiles; xtile++) {
        Raster tile = dest.getTile(xtile, ytile);
        for (int y = tile.getMinY(), iy = 0; iy < tile.getHeight(); y++, iy++) {
          testPointCS.setY(0, y + 0.5);
          for (int x = tile.getMinX(), ix = 0; ix < tile.getWidth(); x++, ix++) {
            testPointCS.setX(0, x + 0.5);
            testPoint.geometryChanged();
            int expected = poly.intersects(testPoint) ? 1 : 0;
            assertEquals(
                "Failed test at position "
                    + x
                    + ", "
                    + y
                    + ", "
                    + "expected "
                    + expected
                    + " but got "
                    + tile.getSample(x, y, 0),
                expected,
                tile.getSample(x, y, 0));
          }
        }
      }
    }
  }
示例#5
0
  /**
   * Test Read exploiting JAI-ImageIO tools capabilities
   *
   * @throws FileNotFoundException
   * @throws IOException
   */
  @Test
  public void read() throws FileNotFoundException, IOException {
    if (!isGDALAvailable) {
      return;
    }
    final ParameterBlockJAI pbjImageRead;
    String fileName = "utmByte.tif";
    final File file = TestData.file(this, fileName);

    pbjImageRead = new ParameterBlockJAI("ImageRead");
    pbjImageRead.setParameter("Input", new FileImageInputStreamExtImpl(file));
    pbjImageRead.setParameter("Reader", new GeoTiffImageReaderSpi().createReaderInstance());
    RenderedOp image = JAI.create("ImageRead", pbjImageRead);
    if (TestData.isInteractiveTest()) Viewer.visualizeAllInformation(image, "", true);
    else Assert.assertNotNull(image.getTiles());
  }
示例#6
0
 protected void handleJAIEXTParams(ParameterBlockJAI parameters, ParameterValueGroup parameters2) {
   if (JAIExt.isJAIExtOperation("algebric")) {
     parameters.set(Operator.LOG, 0);
     Collection<GridCoverage2D> sources =
         (Collection<GridCoverage2D>) parameters2.parameter("sources").getValue();
     for (GridCoverage2D source : sources) {
       handleROINoDataInternal(parameters, source, "algebric", 1, 2);
     }
   }
 }
  @Test
  public void testFourColor() {
    // build a transparent image
    BufferedImage image = new BufferedImage(256, 256, BufferedImage.TYPE_4BYTE_ABGR);
    Graphics g = image.getGraphics();
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, 10, 10);
    g.setColor(Color.RED);
    g.fillRect(10, 0, 10, 10);
    g.setColor(Color.BLUE);
    g.fillRect(20, 0, 10, 10);
    g.setColor(Color.GREEN);
    g.fillRect(30, 0, 10, 10);
    g.dispose();

    //
    // create a palette out of it
    //
    CustomPaletteBuilder builder = new CustomPaletteBuilder(image, 255, 1, 1, 1);
    builder.buildPalette();
    RenderedImage indexed = builder.getIndexedImage();
    assertTrue(indexed.getColorModel() instanceof IndexColorModel);
    IndexColorModel icm = (IndexColorModel) indexed.getColorModel();

    // make sure we have 4 colors + transparent one
    assertEquals(5, icm.getMapSize());

    //
    // now use the JAI op
    //
    assertNotNull(
        (OperationDescriptor)
            JAI.getDefaultInstance()
                .getOperationRegistry()
                .getDescriptor(OperationDescriptor.class, "org.geotools.ColorReduction"));
    ParameterBlockJAI pbj = new ParameterBlockJAI("org.geotools.ColorReduction");
    // I will tile the image in 4 tiles and force parallelism here
    JAI.getDefaultInstance().getTileScheduler().setParallelism(4);
    pbj.addSource(
        new ImageWorker(image)
            .setRenderingHint(
                JAI.KEY_IMAGE_LAYOUT,
                new ImageLayout(image)
                    .setTileGridXOffset(0)
                    .setTileGridYOffset(0)
                    .setTileHeight(64)
                    .setTileWidth(64))
            .tile()
            .getRenderedImage());
    pbj.setParameter("numColors", 255);
    pbj.setParameter("alphaThreshold", 1);
    pbj.setParameter("subsampleX", 1);
    pbj.setParameter("subsampleY", 1);
    indexed = JAI.create("org.geotools.ColorReduction", pbj);
    PlanarImage.wrapRenderedImage(indexed).getTiles();
    assertTrue(indexed.getColorModel() instanceof IndexColorModel);

    // check that we get the same results
    assertEquals(indexed.getColorModel(), icm);
    icm = (IndexColorModel) indexed.getColorModel();

    // make sure we have 4 colors + transparent one
    assertEquals(5, icm.getMapSize());
    assertEquals(5, icm.getMapSize());

    //
    // now use the inversion of color
    //
    assertNotNull(
        (OperationDescriptor)
            JAI.getDefaultInstance()
                .getOperationRegistry()
                .getDescriptor(OperationDescriptor.class, "org.geotools.ColorInversion"));
    pbj = new ParameterBlockJAI("org.geotools.ColorInversion");
    pbj.addSource(
        new ImageWorker(image)
            .setRenderingHint(
                JAI.KEY_IMAGE_LAYOUT,
                new ImageLayout(image)
                    .setTileGridXOffset(0)
                    .setTileGridYOffset(0)
                    .setTileHeight(64)
                    .setTileWidth(64))
            .tile()
            .getRenderedImage());
    pbj.setParameter("quantizationColors", InverseColorMapRasterOp.DEFAULT_QUANTIZATION_COLORS);
    pbj.setParameter("alphaThreshold", 1);
    pbj.setParameter("IndexColorModel", icm);
    indexed = JAI.create("org.geotools.ColorInversion", pbj);
    PlanarImage.wrapRenderedImage(indexed).getTiles();
    assertTrue(indexed.getColorModel() instanceof IndexColorModel);

    // check that we get the same results
    assertEquals(indexed.getColorModel(), icm);
    icm = (IndexColorModel) indexed.getColorModel();

    // make sure we have 4 colors + transparent one
    assertEquals(5, icm.getMapSize());
    assertEquals(5, icm.getMapSize());
  }
示例#8
0
  /**
   * Test Read on a Paletted Image
   *
   * @throws FileNotFoundException
   * @throws IOException
   */
  @Test
  public void palette() throws FileNotFoundException, IOException {
    if (!isGDALAvailable) {
      return;
    }
    final File outputFile = TestData.temp(this, "writetest.tif", false);
    outputFile.deleteOnExit();
    final File inputFile = TestData.file(this, "paletted.tif");

    ImageReader reader = new GeoTiffImageReaderSpi().createReaderInstance();
    reader.setInput(inputFile);
    final IIOMetadata metadata = reader.getImageMetadata(0);

    final ParameterBlockJAI pbjImageRead = new ParameterBlockJAI("ImageRead");
    pbjImageRead.setParameter("Input", inputFile);
    pbjImageRead.setParameter("reader", reader);

    final ImageLayout l = new ImageLayout();
    l.setTileGridXOffset(0).setTileGridYOffset(0).setTileHeight(256).setTileWidth(256);

    RenderedOp image =
        JAI.create("ImageRead", pbjImageRead, new RenderingHints(JAI.KEY_IMAGE_LAYOUT, l));

    if (TestData.isInteractiveTest()) Viewer.visualizeAllInformation(image, "Paletted image read");

    // ////////////////////////////////////////////////////////////////
    // preparing to write
    // ////////////////////////////////////////////////////////////////
    final ParameterBlockJAI pbjImageWrite = new ParameterBlockJAI("ImageWrite");
    ImageWriter writer = new GeoTiffImageWriterSpi().createWriterInstance();
    pbjImageWrite.setParameter("Output", outputFile);
    pbjImageWrite.setParameter("writer", writer);
    pbjImageWrite.setParameter("ImageMetadata", metadata);
    pbjImageWrite.setParameter("Transcode", false);
    pbjImageWrite.addSource(image);
    final RenderedOp op = JAI.create("ImageWrite", pbjImageWrite);
    final ImageWriter writer2 =
        (ImageWriter) op.getProperty(ImageWriteDescriptor.PROPERTY_NAME_IMAGE_WRITER);
    writer2.dispose();

    // ////////////////////////////////////////////////////////////////
    // preparing to read again
    // ////////////////////////////////////////////////////////////////
    final ParameterBlockJAI pbjImageReRead = new ParameterBlockJAI("ImageRead");
    pbjImageReRead.setParameter("Input", outputFile);
    pbjImageReRead.setParameter("Reader", new GeoTiffImageReaderSpi().createReaderInstance());
    final RenderedOp image2 = JAI.create("ImageRead", pbjImageReRead);
    if (TestData.isInteractiveTest())
      Viewer.visualizeAllInformation(image2, "Paletted image read back after writing");
    else Assert.assertNotNull(image2.getTiles());
    ImageIOUtilities.disposeImage(image2);
    ImageIOUtilities.disposeImage(image);
  }
示例#9
0
  /**
   * Test Writing capabilities.
   *
   * @throws FileNotFoundException
   * @throws IOException
   */
  @Test
  public void write() throws IOException, FileNotFoundException {
    if (!isGDALAvailable) {
      return;
    }
    final File outputFile = TestData.temp(this, "writetest.tif", false);
    outputFile.deleteOnExit();
    final File inputFile = TestData.file(this, "utmByte.tif");

    ImageReadParam rparam = new ImageReadParam();
    rparam.setSourceRegion(new Rectangle(1, 1, 300, 500));
    rparam.setSourceSubsampling(1, 2, 0, 0);
    ImageReader reader = new GeoTiffImageReaderSpi().createReaderInstance();
    reader.setInput(inputFile);
    final IIOMetadata metadata = reader.getImageMetadata(0);

    final ParameterBlockJAI pbjImageRead = new ParameterBlockJAI("ImageRead");
    pbjImageRead.setParameter("Input", inputFile);
    pbjImageRead.setParameter("reader", reader);
    pbjImageRead.setParameter("readParam", rparam);

    final ImageLayout l = new ImageLayout();
    l.setTileGridXOffset(0).setTileGridYOffset(0).setTileHeight(256).setTileWidth(256);

    RenderedOp image =
        JAI.create("ImageRead", pbjImageRead, new RenderingHints(JAI.KEY_IMAGE_LAYOUT, l));

    if (TestData.isInteractiveTest()) Viewer.visualizeAllInformation(image, "geotiff");

    // ////////////////////////////////////////////////////////////////
    // preparing to write
    // ////////////////////////////////////////////////////////////////
    final ParameterBlockJAI pbjImageWrite = new ParameterBlockJAI("ImageWrite");
    ImageWriter writer = new GeoTiffImageWriterSpi().createWriterInstance();
    pbjImageWrite.setParameter("Output", outputFile);
    pbjImageWrite.setParameter("writer", writer);
    pbjImageWrite.setParameter("ImageMetadata", metadata);
    pbjImageWrite.setParameter("Transcode", false);
    ImageWriteParam param = new ImageWriteParam(Locale.getDefault());
    param.setSourceRegion(new Rectangle(10, 10, 100, 100));
    param.setSourceSubsampling(2, 1, 0, 0);
    pbjImageWrite.setParameter("writeParam", param);

    pbjImageWrite.addSource(image);
    final RenderedOp op = JAI.create("ImageWrite", pbjImageWrite);
    final ImageWriter writer2 =
        (ImageWriter) op.getProperty(ImageWriteDescriptor.PROPERTY_NAME_IMAGE_WRITER);
    writer2.dispose();

    // ////////////////////////////////////////////////////////////////
    // preparing to read again
    // ////////////////////////////////////////////////////////////////
    final ParameterBlockJAI pbjImageReRead = new ParameterBlockJAI("ImageRead");
    pbjImageReRead.setParameter("Input", outputFile);
    pbjImageReRead.setParameter("Reader", new GeoTiffImageReaderSpi().createReaderInstance());
    final RenderedOp image2 = JAI.create("ImageRead", pbjImageReRead);
    if (TestData.isInteractiveTest()) Viewer.visualizeAllInformation(image2, "geotif2");
    else Assert.assertNotNull(image2.getTiles());
  }
  /**
   * Executes the raster to vector process.
   *
   * @param coverage the input grid coverage
   * @param band the coverage band to process; defaults to 0 if {@code null}
   * @param insideEdges whether boundaries between raster regions with data values (ie. not NODATA)
   *     should be returned; defaults to {@code true} if {@code null}
   * @param roi optional polygonal {@code Geometry} to define a sub-area within which vectorizing
   *     will be done
   * @param noDataValues optional list of values to treat as NODATA; regions with these values will
   *     not be represented in the returned features; if {@code null}, 0 is used as the single
   *     NODATA value; ignored if {@code classificationRanges} is provided
   * @param classificationRanges optional list of {@code Range} objects to pre-classify the input
   *     coverage prior to vectorizing; values not included in the list will be treated as NODATA;
   *     values in the first {@code Range} are classified to 1, those in the second {@code Range} to
   *     2 etc.
   * @param progressListener an optional listener
   * @return a feature collection where each feature has a {@code Polygon} ("the_geom") and an
   *     attribute "value" with value of the corresponding region in either {@code coverage} or the
   *     classified coverage (when {@code classificationRanges} is used)
   * @throws ProcessException
   */
  @DescribeResult(name = "result", description = "The polygon feature collection")
  public SimpleFeatureCollection execute(
      @DescribeParameter(name = "data", description = "The raster to be used as the source")
          GridCoverage2D coverage,
      @DescribeParameter(
              name = "band",
              description = "(Integer, default=0) the source image band to process",
              min = 0)
          Integer band,
      @DescribeParameter(
              name = "insideEdges",
              description =
                  "(Boolean, default=true) whether to vectorize boundaries between adjacent regions with non-outside values",
              min = 0)
          Boolean insideEdges,
      @DescribeParameter(
              name = "roi",
              description = "The geometry used to delineate the area of interest in model space",
              min = 0)
          Geometry roi,
      @DescribeParameter(
              name = "nodata",
              description = "Collection<Number>, default={0}) values to treat as NODATA",
              collectionType = Number.class,
              min = 0)
          Collection<Number> noDataValues,
      @DescribeParameter(
              name = "ranges",
              description =
                  "The list of ranges to be applied. \n"
                      + "Each range is expressed as 'OPEN START ; END CLOSE'\n"
                      + "where 'OPEN:=(|[, CLOSE=)|]',\n "
                      + "START is the low value, or nothing to imply -INF,\n"
                      + "CLOSE is the biggest value, or nothing to imply +INF",
              collectionType = Range.class,
              min = 0)
          List<Range> classificationRanges,
      ProgressListener progressListener)
      throws ProcessException {

    //
    // initial checks
    //
    if (coverage == null) {
      throw new ProcessException("Invalid input, source grid coverage should be not null");
    }

    if (band == null) {
      band = 0;
    } else if (band < 0 || band >= coverage.getNumSampleDimensions()) {
      throw new ProcessException("Invalid input, invalid band number:" + band);
    }

    // do we have classification ranges?
    boolean hasClassificationRanges =
        classificationRanges != null && classificationRanges.size() > 0;

    // apply the classification by setting 0 as the default value and using 1, ..., numClasses for
    // the other classes.
    // we use 0 also as the noData for the resulting coverage.
    if (hasClassificationRanges) {

      final RangeLookupProcess lookup = new RangeLookupProcess();
      coverage = lookup.execute(coverage, band, classificationRanges, progressListener);
    }

    // Use noDataValues to set the "outsideValues" parameter of the Vectorize
    // operation unless classificationRanges are in use, in which case the
    // noDataValues arg is ignored.
    List<Number> outsideValues = new ArrayList<Number>();
    if (noDataValues != null && !hasClassificationRanges) {
      outsideValues.addAll(noDataValues);
    } else {
      outsideValues.add(0);
    }

    //
    // GRID TO WORLD preparation
    //
    final AffineTransform mt2D =
        (AffineTransform) coverage.getGridGeometry().getGridToCRS2D(PixelOrientation.UPPER_LEFT);

    // get the rendered image
    final RenderedImage raster = coverage.getRenderedImage();

    // perform jai operation
    ParameterBlockJAI pb = new ParameterBlockJAI("Vectorize");
    pb.setSource("source0", raster);

    if (roi != null) {
      pb.setParameter("roi", CoverageUtilities.prepareROI(roi, mt2D));
    }
    pb.setParameter("band", band);
    pb.setParameter("outsideValues", outsideValues);
    if (insideEdges != null) {
      pb.setParameter("insideEdges", insideEdges);
    }
    // pb.setParameter("removeCollinear", false);

    final RenderedOp dest = JAI.create("Vectorize", pb);
    @SuppressWarnings("unchecked")
    final Collection<Polygon> prop =
        (Collection<Polygon>) dest.getProperty(VectorizeDescriptor.VECTOR_PROPERTY_NAME);

    // wrap as a feature collection and return
    final SimpleFeatureType featureType =
        CoverageUtilities.createFeatureType(coverage, Polygon.class);
    final SimpleFeatureBuilder builder = new SimpleFeatureBuilder(featureType);
    int i = 0;
    final ListFeatureCollection featureCollection = new ListFeatureCollection(featureType);
    final AffineTransformation jtsTransformation =
        new AffineTransformation(
            mt2D.getScaleX(),
            mt2D.getShearX(),
            mt2D.getTranslateX(),
            mt2D.getShearY(),
            mt2D.getScaleY(),
            mt2D.getTranslateY());
    for (Polygon polygon : prop) {
      // get value
      Double value = (Double) polygon.getUserData();
      polygon.setUserData(null);
      // filter coordinates in place
      polygon.apply(jtsTransformation);

      // create feature and add to list
      builder.set("the_geom", polygon);
      builder.set("value", value);

      featureCollection.add(builder.buildFeature(String.valueOf(i++)));
    }

    // return value
    return featureCollection;
  }
  @DescribeResult(name = "result", description = "The contours feature collection")
  public SimpleFeatureCollection execute(
      @DescribeParameter(name = "data", description = "The raster to be used as the source")
          GridCoverage2D gc2d,
      @DescribeParameter(
              name = "band",
              description = "The source image band to process",
              min = 0,
              max = 1)
          Integer band,
      @DescribeParameter(name = "levels", description = "Values for which to generate contours")
          double[] levels,
      @DescribeParameter(
              name = "interval",
              description = "Interval between contour values (ignored if levels arg is supplied)",
              min = 0)
          Double interval,
      @DescribeParameter(
              name = "simplify",
              description = "Values for which to generate contours",
              min = 0)
          Boolean simplify,
      @DescribeParameter(
              name = "smooth",
              description = "Values for which to generate contours",
              min = 0)
          Boolean smooth,
      @DescribeParameter(
              name = "roi",
              description = "The geometry used to delineate the area of interest in model space",
              min = 0)
          Geometry roi,
      ProgressListener progressListener)
      throws ProcessException {

    //
    // initial checks
    //
    if (gc2d == null) {
      throw new ProcessException("Invalid input, source grid coverage should be not null");
    }
    if (band != null && (band < 0 || band >= gc2d.getNumSampleDimensions())) {
      throw new ProcessException("Invalid input, invalid band number:" + band);
    }
    boolean hasValues = !(levels == null || levels.length == 0);
    if (!hasValues && interval == null) {
      throw new ProcessException("One between interval and values must be valid");
    }

    // switch to geophisics if necessary
    gc2d = gc2d.view(ViewType.GEOPHYSICS);

    //
    // GRID TO WORLD preparation
    //
    final AffineTransform mt2D =
        (AffineTransform) gc2d.getGridGeometry().getGridToCRS2D(PixelOrientation.CENTER);

    // get the list of nodata, if any
    List<Object> noDataList = new ArrayList<Object>();
    for (GridSampleDimension sd : gc2d.getSampleDimensions()) {
      // grab all the explicit nodata
      final double[] sdNoData = sd.getNoDataValues();
      if (sdNoData != null) {
        for (double nodata : sdNoData) {
          noDataList.add(nodata);
        }
      }

      // handle also readers setting up nodata in a category with a specific name
      if (sd.getCategories() != null) {
        for (Category cat : sd.getCategories()) {
          if (cat.getName().equals(NO_DATA)) {
            final NumberRange<? extends Number> catRange = cat.getRange();
            if (catRange.getMinimum() == catRange.getMaximum()) {
              noDataList.add(catRange.getMinimum());
            } else {
              Range<Double> noData =
                  new Range<Double>(
                      catRange.getMinimum(),
                      catRange.isMinIncluded(),
                      catRange.getMaximum(),
                      catRange.isMaxIncluded());
              noDataList.add(noData);
            }
          }
        }
      }
    }

    // get the rendered image
    final RenderedImage raster = gc2d.getRenderedImage();

    // perform jai operation
    ParameterBlockJAI pb = new ParameterBlockJAI("Contour");
    pb.setSource("source0", raster);

    if (roi != null) {
      pb.setParameter("roi", CoverageUtilities.prepareROI(roi, mt2D));
    }
    if (band != null) {
      pb.setParameter("band", band);
    }
    if (interval != null) {
      pb.setParameter("interval", interval);
    } else {
      final ArrayList<Double> elements = new ArrayList<Double>(levels.length);
      for (double level : levels) elements.add(level);
      pb.setParameter("levels", elements);
    }
    if (simplify != null) {
      pb.setParameter("simplify", simplify);
    }
    if (smooth != null) {
      pb.setParameter("smooth", smooth);
    }
    if (noDataList != null) {
      pb.setParameter("nodata", noDataList);
    }

    final RenderedOp dest = JAI.create("Contour", pb);
    @SuppressWarnings("unchecked")
    final Collection<LineString> prop =
        (Collection<LineString>) dest.getProperty(ContourDescriptor.CONTOUR_PROPERTY_NAME);

    // wrap as a feature collection and return
    final SimpleFeatureType schema = CoverageUtilities.createFeatureType(gc2d, LineString.class);
    final SimpleFeatureBuilder builder = new SimpleFeatureBuilder(schema);
    int i = 0;
    final ListFeatureCollection featureCollection = new ListFeatureCollection(schema);
    final AffineTransformation jtsTransformation =
        new AffineTransformation(
            mt2D.getScaleX(),
            mt2D.getShearX(),
            mt2D.getTranslateX(),
            mt2D.getShearY(),
            mt2D.getScaleY(),
            mt2D.getTranslateY());
    for (LineString line : prop) {

      // get value
      Double value = (Double) line.getUserData();
      line.setUserData(null);
      // filter coordinates in place
      line.apply(jtsTransformation);

      // create feature and add to list
      builder.set("the_geom", line);
      builder.set("value", value);

      featureCollection.add(builder.buildFeature(String.valueOf(i++)));
    }

    // return value

    return featureCollection;
  }
  /**
   * For each raster: crop->scale->translate->add to mosaic
   *
   * @param queries
   * @param mosaicGeometry
   * @return
   * @throws IOException
   */
  private RenderedImage createMosaic(
      final List<RasterQueryInfo> queries,
      final GridEnvelope mosaicGeometry,
      final LoggingHelper log)
      throws IOException {

    List<RenderedImage> transformed = new ArrayList<RenderedImage>(queries.size());

    /*
     * Do we need to expand to RGB color space and then create a new colormapped image with the
     * whole mosaic?
     */
    boolean expandCM = queries.size() > 1 && rasterInfo.isColorMapped();
    if (expandCM) {
      LOGGER.fine(
          "Creating mosaic out of "
              + queries.size()
              + " colormapped rasters. The mosaic tiles will be expanded to "
              + "\nRGB space and the resulting mosaic reduced to a new IndexColorModel");
    }

    for (RasterQueryInfo query : queries) {
      RenderedImage image = query.getResultImage();
      log.log(image, query.getRasterId(), "01_original");
      if (expandCM) {
        if (LOGGER.isLoggable(Level.FINER)) {
          LOGGER.finer(
              "Creating color expanded version of tile for raster #" + query.getRasterId());
        }

        /*
         * reformat the image as a 4 band rgba backed by byte data
         */
        image = FormatDescriptor.create(image, Integer.valueOf(DataBuffer.TYPE_BYTE), null);

        log.log(image, query.getRasterId(), "04_1_colorExpanded");
      }

      image = cropToRequiredDimension(image, query.getResultGridRange());
      log.log(image, query.getRasterId(), "02_crop");

      // Raster data = image.getData();
      // image = new BufferedImage(image.getColorModel(), (WritableRaster) data, false, null);
      if (queries.size() == 1) {
        return image;
      }
      final GridEnvelope mosaicLocation = query.getMosaicLocation();
      // scale
      Float scaleX = Float.valueOf(((float) mosaicLocation.getSpan(0) / image.getWidth()));
      Float scaleY = Float.valueOf(((float) mosaicLocation.getSpan(1) / image.getHeight()));
      Float translateX = Float.valueOf(0);
      Float translateY = Float.valueOf(0);

      if (!(Float.valueOf(1.0F).equals(scaleX) && Float.valueOf(1.0F).equals(scaleY))) {
        ParameterBlock pb = new ParameterBlock();
        pb.addSource(image);
        pb.add(scaleX);
        pb.add(scaleY);
        pb.add(translateX);
        pb.add(translateY);
        pb.add(new InterpolationNearest());

        image = JAI.create("scale", pb);
        log.log(image, query.getRasterId(), "03_scale");

        int width = image.getWidth();
        int height = image.getHeight();

        assert mosaicLocation.getSpan(0) == width;
        assert mosaicLocation.getSpan(1) == height;
      }

      if (image.getMinX() != mosaicLocation.getLow(0)
          || image.getMinY() != mosaicLocation.getLow(1)) {
        // translate
        ParameterBlock pb = new ParameterBlock();
        pb.addSource(image);
        pb.add(Float.valueOf(mosaicLocation.getLow(0) - image.getMinX()));
        pb.add(Float.valueOf(mosaicLocation.getLow(1) - image.getMinY()));
        pb.add(null);

        image = JAI.create("translate", pb);
        log.log(image, query.getRasterId(), "04_translate");

        assert image.getMinX() == mosaicLocation.getLow(0)
            : image.getMinX() + " != " + mosaicLocation.getLow(0);
        assert image.getMinY() == mosaicLocation.getLow(1)
            : image.getMinY() + " != " + mosaicLocation.getLow(1);
        assert image.getWidth() == mosaicLocation.getSpan(0)
            : image.getWidth() + " != " + mosaicLocation.getSpan(0);
        assert image.getHeight() == mosaicLocation.getSpan(1)
            : image.getHeight() + " != " + mosaicLocation.getSpan(1);
      }

      transformed.add(image);
    }

    final RenderedImage mosaic;
    if (queries.size() == 1) {
      /*
       * This is besides a very slight perf improvement needed because the JAI mosaic
       * operation truncates floating point raster values to 0 and 1. REVISIT: If there's no
       * workaround for that we should prevent raster catalogs made of floating point rasters
       * and throw an exception as we could not really support that.
       */
      mosaic = transformed.get(0);
    } else {
      /*
       * adapted from RasterLayerResponse.java in the imagemosaic module
       */
      ParameterBlockJAI mosaicParams = new ParameterBlockJAI("Mosaic");
      mosaicParams.setParameter("mosaicType", MosaicDescriptor.MOSAIC_TYPE_OVERLAY);

      // set background values to raster's no-data
      double[] backgroundValues;
      if (expandCM) {
        backgroundValues = new double[] {0, 0, 0, 0};
      } else {
        final int numBands = rasterInfo.getNumBands();
        backgroundValues = new double[numBands];
        final int rasterIndex = 0;
        Number noDataValue;
        for (int bn = 0; bn < numBands; bn++) {
          noDataValue = rasterInfo.getNoDataValue(rasterIndex, bn);
          backgroundValues[bn] = noDataValue.doubleValue();
        }
      }
      mosaicParams.setParameter("backgroundValues", backgroundValues);

      final ImageLayout layout =
          new ImageLayout(
              mosaicGeometry.getLow(0),
              mosaicGeometry.getLow(1),
              mosaicGeometry.getSpan(0),
              mosaicGeometry.getSpan(1));
      final int tileWidth = rasterInfo.getTileDimension(0).width;
      final int tileHeight = rasterInfo.getTileDimension(0).height;
      layout.setTileWidth(tileWidth);
      layout.setTileHeight(tileHeight);

      final RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);

      for (RenderedImage img : transformed) {
        mosaicParams.addSource(img);
        log.appendLoggingGeometries(LoggingHelper.MOSAIC_RESULT, img);
      }
      log.log(LoggingHelper.MOSAIC_RESULT);

      LOGGER.fine("Creating mosaic out of " + queries.size() + " raster tiles");
      mosaic = JAI.create("Mosaic", mosaicParams, hints);

      log.log(mosaic, 0L, "05_mosaic_result");
    }
    return mosaic;
  }