/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2011, Open Source Geospatial Foundation (OSGeo) * (C) 2008-2011 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.geom.AffineTransform; import java.awt.image.RenderedImage; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.media.jai.JAI; import javax.media.jai.ParameterBlockJAI; import javax.media.jai.RenderedOp; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.util.AffineTransformation; import org.jaitools.media.jai.contour.ContourDescriptor; import org.jaitools.media.jai.contour.ContourRIF; import org.jaitools.numeric.Range; import org.geotools.coverage.Category; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.ViewType; import org.geotools.data.collection.ListFeatureCollection; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.image.jai.Registry; import org.geotools.process.ProcessException; 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.raster.CoverageUtilities; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; import org.geotools.util.NumberRange; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.metadata.spatial.PixelOrientation; import org.opengis.util.InternationalString; import org.opengis.util.ProgressListener; /** * A process to extract contours based on values in a specified band of the * input {@linkplain GridCoverage2D}. This is a geo-spatial wrapper around the JAITools * "Contour" operation (see {@linkplain ContourDescriptor} for details of the underlying * algorithm). * <p> * You can specify the specific values for which contours will be generated, or alternatively * the interval between contour values. * <p> * Contours are returned as a feature collection, where each feature has, as its default * geometry, a {@linkplain LineString} for the contour ("the_geom"), and the contour * value as the {@code Double} attribute "value". * * @author Simone Giannecchini, GeoSolutions * @since 8.0 * * @source $URL$ * @version $Id$ */ @DescribeProcess(title = "Contour", description = "Perform the contouring on a provided raster") public class ContourProcess implements GSProcess { private static final InternationalString NO_DATA = Vocabulary .formatInternational(VocabularyKeys.NODATA); static { Registry.registerRIF(JAI.getDefaultInstance(), new ContourDescriptor(), new ContourRIF(), Registry.JAI_TOOLS_PRODUCT); } /** * Perform the contouring on the input {@linkplain GridCoverage2D} and returns * the results as a feature collection. You can control which contours are generated * either by providing a list of values via the {@code levels} argument, or by specifying * the interval between contour values via the {@code interval} argument. In the interval * case, the resulting contour values will be integer multiples of the specified interval. * If both {@code levels} and {@code interval} are supplied the {@code interval} argument * is ignored. * * @param data the input grid coverage * * @param band the coverage band to process; defaults to 0 if {@code null} * * @param levels the values for which contours should be generated * * @param interval the interval between contour values (if {@code levels} is not provided) * * @param simplify whether to simplify contour lines by removing co-linear vertices; * default is to simplify * * @param smooth whether to apply Bezier smooth to the contours; default is no smoothing * * @param roi an optional polygonal {@code Geometry} to define the region of interest * within which contours will be generated * * @return the contours a feature collection where each feature contains a contour * as a {@linkplain LineString} and the contour value as a {@code Double} * * @throws ProcessException */ public static SimpleFeatureCollection process(GridCoverage2D gc2d, Integer band, double[] levels, Double interval, Boolean simplify, Boolean smooth, Geometry roi, ProgressListener progressListener) throws ProcessException { ContourProcess process = new ContourProcess(); return process.execute(gc2d, band, levels, interval, simplify, smooth, roi, progressListener); } @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 (!Double.isNaN(catRange.getMinimum())) { 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 (levels != null && levels.length > 0) { final ArrayList<Double> elements = new ArrayList<Double>(levels.length); for (double level : levels) elements.add(level); pb.setParameter("levels", elements); } else { pb.setParameter("interval", interval); } if (simplify != null) { pb.setParameter("simplify", simplify); } if (smooth != null) { pb.setParameter("smooth", smooth); } if (!noDataList.isEmpty()) { 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; } }