/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2011, Open Source Geospatial Foundation (OSGeo) * (C) 2001-2007 TOPP - www.openplans.org. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.process.raster.gs; import java.awt.Rectangle; import java.awt.image.RenderedImage; import java.io.IOException; import java.util.Iterator; import java.util.NoSuchElementException; import javax.media.jai.PlanarImage; import javax.media.jai.iterator.RectIter; import javax.media.jai.iterator.RectIterFactory; import org.geotools.process.factory.DescribeParameter; import org.geotools.process.factory.DescribeProcess; import org.geotools.process.factory.DescribeResult; import org.geotools.process.gs.GSProcess; import org.geotools.process.gs.WrappingIterator; import org.geotools.process.raster.CoverageUtilities; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.factory.GeoTools; import org.geotools.feature.DefaultFeatureCollection; import org.geotools.feature.collection.AbstractFeatureCollection; import org.geotools.feature.collection.AdaptorFeatureCollection; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.geometry.jts.JTS; import org.geotools.geometry.jts.JTSFactoryFinder; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.process.ProcessException; import org.geotools.referencing.CRS; import org.geotools.resources.geometry.XRectangle2D; import org.geotools.util.Utilities; import org.opengis.feature.simple.SimpleFeature; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.metadata.spatial.PixelOrientation; import org.opengis.referencing.operation.MathTransform2D; import org.opengis.referencing.operation.TransformException; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; /** * A process that wraps a {@link GridCoverage2D} as a collection of point feature. * * @author Simone Giannecchini, GeoSolutions * * * @source $URL$ */ @DescribeProcess(title = "RasterAsPointCollection", description = "Convert a Raster into a collections of points") public class RasterAsPointCollectionProcess implements GSProcess { @DescribeResult(name = "result", description = "The point feature collection") public SimpleFeatureCollection execute( @DescribeParameter(name = "data", description = "The raster to be converted into a point feature collection") GridCoverage2D gc2d) throws ProcessException { if (gc2d ==null) { throw new ProcessException("Invalid input, source grid coverage should be not null"); } //return value try { return new RasterAsPointFeatureCollection(gc2d); } catch (IOException e) { throw new ProcessException("Unable to wrap provided grid coverage",e); } } /** * TODO @see {@link AdaptorFeatureCollection} * TODO @ee {@link DefaultFeatureCollection} * @author simboss * */ private final static class RasterAsPointFeatureCollection extends AbstractFeatureCollection implements SimpleFeatureCollection { /** * The {@link GeometryFactory} cached here for building points inside iterators */ static final GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory( GeoTools.getDefaultHints() ); /** * The {@link GridCoverage2D} that we want to expose as a point feature collection. */ final GridCoverage2D gc2d; /** * Number of points in this collections is as many as width*height. */ final int size; /** * Grid to world transformation at the upper left corner of the raster space. */ final MathTransform2D mt2D; /** * The bounding box for this feature collection */ private ReferencedEnvelope bounds; /** * Raster bounds for this coverage */ final Rectangle rasterBounds; /** * Number of bands */ final int numBands; public RasterAsPointFeatureCollection(final GridCoverage2D gc2d) throws IOException { super(CoverageUtilities.createFeatureType(gc2d, Point.class)); this.gc2d=gc2d; // // get various elements from this coverage // // SIZE final RenderedImage raster=gc2d.getRenderedImage(); size=raster.getWidth()*raster.getHeight(); // GRID TO WORLD mt2D= gc2d.getGridGeometry().getGridToCRS2D(PixelOrientation.UPPER_LEFT); // BOUNDS take into account that we want to map center coordinates rasterBounds = PlanarImage.wrapRenderedImage(raster).getBounds(); final XRectangle2D rasterBounds_=new XRectangle2D(raster.getMinX()+0.5, raster.getMinY()+0.5, raster.getWidth()-1, raster.getHeight()-1); try { bounds = new ReferencedEnvelope(CRS.transform(mt2D, rasterBounds_, null),gc2d.getCoordinateReferenceSystem2D()); } catch (MismatchedDimensionException e) { final IOException ioe= new IOException(); ioe.initCause(e); throw ioe; } catch (TransformException e) { final IOException ioe= new IOException(); ioe.initCause(e); throw ioe; } // BANDS numBands = gc2d.getNumSampleDimensions(); } @Override public SimpleFeatureIterator features() { return new RasterAsPointFeatureIterator(this); } @Override public int size() { return size; } @Override public ReferencedEnvelope getBounds() { return new ReferencedEnvelope(bounds); } @Override protected Iterator<SimpleFeature> openIterator() { return new WrappingIterator(features()); } @Override protected void closeIterator(Iterator<SimpleFeature> close) { if (close instanceof WrappingIterator) { ((WrappingIterator) close).close(); } } } private final static class RasterAsPointFeatureIterator implements SimpleFeatureIterator { private final double[] temp; private final SimpleFeatureBuilder fb; private final RasterAsPointFeatureCollection fc; private int index=0; private final int size; private final RectIter iterator; private final Coordinate coord= new Coordinate(); public RasterAsPointFeatureIterator(final RasterAsPointFeatureCollection fc) { //checks Utilities.ensureNonNull("fc", fc); //get elements this.fc= fc; this.fb = new SimpleFeatureBuilder(fc.getSchema()); this.size=fc.size; // create an iterator that only goes forward, it is the fastest one iterator= RectIterFactory.create(fc.gc2d.getRenderedImage(), null); // //start the iterator // iterator.startLines(); if(iterator.finishedLines()) throw new NoSuchElementException("Index beyond size:"+index+">"+size); iterator.startPixels(); if(iterator.finishedPixels()) throw new NoSuchElementException("Index beyond size:"+index+">"+size); // appo temp= new double[fc.numBands]; } /** * Closes this iterator */ public void close() { // NO OP } /** * Tells us whether or not we have more elements to iterate on. */ public boolean hasNext() { return index<size; } public SimpleFeature next() throws NoSuchElementException { if(!hasNext()) throw new NoSuchElementException("Index beyond size:"+index+">"+size); // iterate if(iterator.finishedPixels()) throw new NoSuchElementException("Index beyond size:"+index+">"+size); if(iterator.finishedLines()) throw new NoSuchElementException("Index beyond size:"+index+">"+size); // ID final int id=index; // POINT // can we reuse the coord? coord.x= 0.5+fc.rasterBounds.x+index%fc.rasterBounds.width; coord.y=0.5+ fc.rasterBounds.y+index/fc.rasterBounds.width ; final Point point = RasterAsPointFeatureCollection.geometryFactory.createPoint( coord ); try { fb.add(JTS.transform(point, fc.mt2D)); } catch (MismatchedDimensionException e) { final NoSuchElementException nse= new NoSuchElementException(); nse.initCause(e); throw nse; } catch (TransformException e) { final NoSuchElementException nse= new NoSuchElementException(); nse.initCause(e); throw nse; } // VALUES // loop on bands iterator.getPixel(temp); for(double d:temp){ // I exploit the internal converters to go from double to whatever the type is // TODO is this correct or we can do more. fb.add(d); } // do we need to wrap the line?? if(iterator.nextPixelDone()){ if(!iterator.nextLineDone()) iterator.startPixels(); } // return final SimpleFeature returnValue= fb.buildFeature(String.valueOf(id)); // increase index and iterator index++; return returnValue; } } }