/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.processing.coverage.coveragetovector; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Polygon; import java.awt.Point; import java.awt.image.RenderedImage; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.media.jai.iterator.RectIter; import javax.media.jai.iterator.RectIterFactory; import org.geotoolkit.coverage.grid.GridCoverage2D; import org.geotoolkit.coverage.grid.ViewType; import org.geotoolkit.geometry.jts.JTS; import org.geotoolkit.processing.AbstractProcess; import org.geotoolkit.process.ProcessException; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.measure.NumberRange; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.operation.MathTransform2D; import org.opengis.referencing.operation.TransformException; import static org.geotoolkit.parameter.Parameters.*; import org.geotoolkit.utility.parameter.ParametersExt; /** * Process to extract Polygon from a coverage. * NumberRange is stored in geometry userData. * * @author Johann Sorel (Geomatys) */ public class CoverageToVectorProcess extends AbstractProcess { private static final GeometryFactory GF = new GeometryFactory(); private static final int LAST_LINE = 0; private static final int CURRENT_LINE = 1; //last line cache boundary private final Map<NumberRange, List<Polygon>> polygons = new HashMap<NumberRange, List<Polygon>>(); //buffer[0] holds last line buffer //buffer[1] holds current line buffer private Boundary[][] buffers; //current pixel block private final Block block = new Block(); CoverageToVectorProcess(final ParameterValueGroup input) { super(CoverageToVectorDescriptor.INSTANCE,input); } /** * * @param coverage coverage to process * @param ranges data value ranges * @param band coverage band to process */ public CoverageToVectorProcess(GridCoverage2D coverage, NumberRange[] ranges, int band){ super(CoverageToVectorDescriptor.INSTANCE, asParameters(coverage,ranges,band)); } private static ParameterValueGroup asParameters(GridCoverage2D coverage, NumberRange[] ranges, int band){ final ParameterValueGroup params = CoverageToVectorDescriptor.INPUT_DESC.createValue(); ParametersExt.getOrCreateValue(params, CoverageToVectorDescriptor.COVERAGE.getName().getCode()).setValue(coverage); ParametersExt.getOrCreateValue(params, CoverageToVectorDescriptor.RANGES.getName().getCode()).setValue(coverage); ParametersExt.getOrCreateValue(params, CoverageToVectorDescriptor.BAND.getName().getCode()).setValue(coverage); return params; } /** * Execute process now. * * @return geometries * @throws ProcessException */ public Geometry[] executeNow() throws ProcessException { execute(); return (Geometry[]) outputParameters.parameter(CoverageToVectorDescriptor.GEOMETRIES.getName().getCode()).getValue(); } public Geometry[] toPolygon(GridCoverage2D coverage, final NumberRange[] ranges, final int band) throws IOException, TransformException { coverage = coverage.view(ViewType.GEOPHYSICS); //add a range for Nan values. NumberRange NaNRange = new NaNRange(); polygons.put(NaNRange, new ArrayList<Polygon>()); for (final NumberRange range : ranges) { polygons.put(range, new ArrayList<Polygon>()); } final RenderedImage image = coverage.getRenderedImage(); final RectIter iter = RectIterFactory.create(image, null); final MathTransform2D gridToCRS = coverage.getGridGeometry().getGridToCRS2D(); final Point gridPosition = new Point(0, 0); buffers = new Boundary[2][image.getWidth()]; int bandNum = -1; iter.startBands(); if (!iter.finishedBands()) { //iteration over bands do { bandNum++; //System.err.println("bande " + bandNum); if (bandNum == band) { //iteration over lines iter.startLines(); if (!iter.finishedLines()) { do { //System.err.println("ligne " + gridPosition.y); //iteration over pixels iter.startPixels(); if (!iter.finishedPixels()) { do { //---------------------------------------------- final double value = iter.getSampleDouble(); append(gridPosition, value); //---------------------------------------------- gridPosition.x += 1; } while (!iter.nextPixelDone()); } //insert last geometry constructBlock(); //flip buffers, reuse old buffer line. Boundary[] oldLine = buffers[LAST_LINE]; buffers[LAST_LINE] = buffers[CURRENT_LINE]; buffers[CURRENT_LINE] = oldLine; // final Set<Boundary> boundaries = new HashSet<Boundary>(); // //System.err.println("--------------------------------------------------------------------------------------------"); // for(int i=0; i< buffers[LAST_LINE].length; i++) { // //System.err.println("> " + i + " " + block.y +" " + buffers[LAST_LINE][i].toString()); // boundaries.add(buffers[LAST_LINE][i]); // } block.reset(); gridPosition.x = 0; gridPosition.y += 1; } while (!iter.nextLineDone()); } //we have finish the requested band, close all geometries for(int i=0;i<buffers[LAST_LINE].length;i++) { Polygon poly = buffers[LAST_LINE][i].link( new Coordinate(i, gridPosition.y), new Coordinate(i+1, gridPosition.y) ); if(poly != null) { polygons.get(buffers[LAST_LINE][i].range).add(poly); } } } gridPosition.x = 0; gridPosition.y = 0; } while (!iter.nextBandDone()); } final List<Geometry> polygones = new ArrayList<Geometry>(); for (int i=0; i<ranges.length; i++) { final NumberRange range = ranges[i]; for(Polygon poly : polygons.get(range)) { polygones.add(JTS.transform(poly, gridToCRS)); } //we dont merge them in a single polygon to avoid to complexe geometries } return polygones.toArray(new Polygon[polygones.size()]); } private void append(final Point point, Number value) { //System.err.println("POINT["+point+"] value = " + value); //special case for NaN or null //todo for (final NumberRange range : polygons.keySet()) { if (range.containsAny(value)) { if (block.range == range) { //last pixel was in the same range block.endX = point.x; return; } else if (block.range != null) { //last pixel was in a different range, save it's geometry constructBlock(); } //start a pixel serie block.range = range; block.startX = point.x; block.endX = point.x; block.y = point.y; return; } } throw new IllegalArgumentException("Value not in any range :" + value); } private void constructBlock() { //System.err.println("BLOCK ["+block.startX+","+block.endX+"]"); if(block.y == 0) { //first line, the buffer is empty, must fill it final Boundary boundary = new Boundary(block.range); boundary.start(block.startX, block.endX+1, block.y); for(int i=block.startX; i<=block.endX; i++) { buffers[CURRENT_LINE][i] = boundary; } }else{ Boundary currentBoundary = null; //first pass to close unfriendly blocks ---------------------------- for(int i=block.startX; i<=block.endX;) { final Boundary candidate = buffers[LAST_LINE][i]; final int[] candidateExtent = findExtent(i); //do not treat same blockes here if(candidate.range != block.range) { //System.err.println("A different block extent : "+ candidateExtent[0] + " " + candidateExtent[1]); //System.err.println("before :" + candidate.toString()); if(candidateExtent[0] >= block.startX && candidateExtent[1] <= block.endX) { //block overlaps completly candidate final Polygon poly = candidate.link( new Coordinate(candidateExtent[0], block.y), new Coordinate(candidateExtent[1]+1, block.y) ); if(poly != null) polygons.get(candidate.range).add(poly); }else{ final Polygon poly = candidate.link( new Coordinate( (block.startX<candidateExtent[0]) ? candidateExtent[0]: block.startX, block.y), new Coordinate( (block.endX>candidateExtent[1]) ? candidateExtent[1]+1: block.endX+1, block.y) ); if(poly != null) polygons.get(candidate.range).add(poly); } //System.err.println("after :" + candidate.toString()); } i = candidateExtent[1]+1; } //second pass to fuse with friendly blocks ------------------------- //we first merge the last line boundary if needed int firstAnchor = Integer.MAX_VALUE; int lastAnchor = Integer.MIN_VALUE; for(int i=block.startX; i<=block.endX; ) { final Boundary candidate = buffers[LAST_LINE][i]; final int[] candidateExtent = findExtent(i); //do not treat different blocks here if(candidate.range == block.range) { //System.err.println("A firnet block extent : "+ candidateExtent[0] + " " + candidateExtent[1]); // //System.err.println("before :" + candidate.toString()); if(currentBoundary == null) { //set the current boundary, will expend this one currentBoundary = candidate; }else if(currentBoundary != null) { if(currentBoundary != candidate) { //those two blocks doesnt belong to the same boundaries, we must merge them currentBoundary.merge(candidate); } currentBoundary.link( new Coordinate(lastAnchor, block.y), new Coordinate(candidateExtent[0], block.y) ); replaceInLastLigne(candidate, currentBoundary); //System.out.println("Merging : " + currentBoundary.toString()); } if(candidateExtent[0] < firstAnchor) { firstAnchor = candidateExtent[0]; } lastAnchor = candidateExtent[1]+1; } i = candidateExtent[1]+1; } if(currentBoundary != null) { //System.err.println("before :" + currentBoundary.toString()); } if(currentBoundary == null) { //no previous friendly boundary to link with //make a new one currentBoundary = new Boundary(block.range); currentBoundary.start(block.startX, block.endX+1, block.y); }else{ if(firstAnchor < block.startX) { //the previous block has created a floating sequence to this end firstAnchor = block.startX; } //add the coordinates //System.err.println("> first anchor : " +firstAnchor + " lastAnchor : " +lastAnchor); if(firstAnchor == block.startX) { currentBoundary.add( new Coordinate(firstAnchor, block.y), new Coordinate(block.startX, block.y+1) ); }else{ currentBoundary.add( new Coordinate(firstAnchor, block.y), new Coordinate(block.startX, block.y) ); currentBoundary.add( new Coordinate(block.startX, block.y), new Coordinate(block.startX, block.y+1) ); } if(block.endX+1 >= lastAnchor) { if(lastAnchor == block.endX+1) { currentBoundary.add( new Coordinate(lastAnchor, block.y), new Coordinate(block.endX+1, block.y+1) ); }else{ //System.err.println("0 add :" + currentBoundary.toString()); currentBoundary.add( new Coordinate(lastAnchor, block.y), new Coordinate(block.endX+1, block.y) ); //System.err.println("1 add:" + currentBoundary.toString()); currentBoundary.add( new Coordinate(block.endX+1, block.y), new Coordinate(block.endX+1, block.y+1) ); //System.err.println("after add:" + currentBoundary.toString()); } }else{ currentBoundary.addFloating( new Coordinate(block.endX+1, block.y), new Coordinate(block.endX+1, block.y+1) ); } //System.err.println(currentBoundary.toString()); } //System.err.println("after :" + currentBoundary.toString()); //fill in the current line ----------------------------------------- for(int i=block.startX; i<=block.endX; i++) { if(currentBoundary.isEmpty()) { throw new IllegalArgumentException("An empty boundary inserted ? not possible."); } buffers[CURRENT_LINE][i] = currentBoundary; } } } private void replaceInLastLigne(final Boundary old, final Boundary newone) { for(int i=0,n=buffers[LAST_LINE].length; i<n; i++) { if(buffers[LAST_LINE][i] == old) { buffers[LAST_LINE][i] = newone; } if(buffers[CURRENT_LINE][i] == old) { buffers[CURRENT_LINE][i] = newone; } } } private int[] findExtent(final int index) { final int[] extent = new int[]{index,index}; final Boundary bnd = buffers[LAST_LINE][index]; while(extent[0] > 0 && buffers[LAST_LINE][ extent[0]-1 ] == bnd) { extent[0]--; } while(extent[1] < buffers[LAST_LINE].length-1 && buffers[LAST_LINE][ extent[1]+1 ] == bnd) { extent[1]++; } return extent; } @Override protected void execute() throws ProcessException{ ArgumentChecks.ensureNonNull("inputParameters", inputParameters); final GridCoverage2D coverage = value(CoverageToVectorDescriptor.COVERAGE, inputParameters); final NumberRange[] ranges = value(CoverageToVectorDescriptor.RANGES, inputParameters); Integer band = value(CoverageToVectorDescriptor.BAND, inputParameters); if(band == null) { band = 0; } Geometry[] result = null; try { result = toPolygon(coverage, ranges, 0); } catch (IOException ex) { throw new ProcessException(ex.getMessage(), this, ex); } catch (TransformException ex) { throw new ProcessException(ex.getMessage(), this, ex); } //avoid memory use buffers = null; polygons.clear(); getOrCreate(CoverageToVectorDescriptor.GEOMETRIES, outputParameters).setValue(result); } private static class NaNRange extends NumberRange{ public NaNRange() { super(Double.class, 0d, true, 0d, true); } @Override public boolean contains(final Comparable number) throws IllegalArgumentException { return Double.isNaN(((Number) number).doubleValue()); } } }