Package org.geotools.coverage.grid

Source Code of org.geotools.coverage.grid.GridCoverageBuilder$Variable

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2008, Open Source Geospatial Foundation (OSGeo)
*
*    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.coverage.grid;

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import javax.imageio.ImageIO;
import javax.measure.unit.Unit;

import org.opengis.geometry.Envelope;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;

import org.geotools.coverage.Category;
import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.factory.GeoTools;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.operation.transform.LinearTransform1D;
import org.geotools.referencing.operation.transform.LogarithmicTransform1D;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.util.NumberRange;


/**
* Helper class for the creation of {@link GridCoverage2D} instances. The only purpose of this
* builder is to make {@code GridCoverage2D} construction a little bit easier for some common
* cases. This class provides default values for each property which make it convenient for simple
* cases and testing purpose, but is not generic. Users wanting more control and flexibility should
* use {@link GridCoverageFactory} directly.
* <p>
* Usage example:
*
* <blockquote><pre>
* GridCoverageBuilder builder = new GridCoverageBuilder();
* builder.{@linkplain #setCoordinateReferenceSystem(String) setCoordinateReferenceSystem("EPSG:4326");
* builder.{@linkplain #setEnvelope(double...) setEnvelope}(-60, 40, -50, 50);
*
* // Will use sample value in the range 0 inclusive to 20000 exclusive.
* builder.{@linkplain #setSampleRange(int,int) setSampleRange}(0, 20000);
*
* // Defines elevation (m) = sample / 10
* Variable elevation = builder.{@linkplain #newVariable newVariable}("Elevation", SI.METRE);
* elevation.{@linkplain GridCoverageBuilder.Variable#setLinearTransform setLinearTransform}(0.1, 0);
* elevation.addNodataValue("No data", 32767);
*
* // Gets the image, draw anything we want in it.
* builder.{@linkplain #setImageSize(int,int) setImageSize}(500,500);
* BufferedImage image = builder.{@linkpalin #getBufferedImage getBufferedImage}();
* Graphics2D gr = image.createGraphics();
* gr.draw(...);
* gr.dispose();
*
* // Gets the coverage.
* GridCoverage2D coverage = builder.{@linkplain #getGridCoverage2D getGridCoverage2D}();
* </pre></blockquote>
*
* @since 2.5
* @author Martin Desruisseaux
*
*
* @source $URL$
* @version $Id$
*/
public class GridCoverageBuilder {
    /**
     * The envelope, including coordinate reference system.
     */
    private GeneralEnvelope envelope;

    /**
     * The range of sample values.
     */
    private NumberRange<? extends Number> range;

    /**
     * The default {@linkplain #range}.
     */
    private static final NumberRange<Integer> DEFAULT_RANGE = NumberRange.create(0, true, 256, false);

    /**
     * The list of variables created. Each variable will be mapped to a
     * {@linkplain GridSampleDimension sample dimension}.
     *
     * @see #newVariable
     */
    protected final List<Variable> variables;

    /**
     * The image size.
     */
    private int width, height;

    /**
     * The image. Will be created only when first needed.
     */
    private BufferedImage image;

    /**
     * The grid coverage. Will be created only when first needed.
     */
    private GridCoverage2D coverage;

    /**
     * The factory to use for creating grid coverages.
     */
    private final GridCoverageFactory factory;

    /**
     * Creates a builder initialized to default values and factory.
     */
    public GridCoverageBuilder() {
        this(CoverageFactoryFinder.getGridCoverageFactory(GeoTools.getDefaultHints()));
    }

    /**
     * Creates a builder initialized to default values.
     */
    public GridCoverageBuilder(final GridCoverageFactory factory) {
        this.factory = factory;
        variables = new ArrayList<Variable>();
        width  = 256;
        height = 256;
    }

    /**
     * Wraps an arbitrary envelope to an object that can be stored in {@link #envelope}.
     */
    private static GeneralEnvelope wrap(final Envelope envelope) {
        return (envelope==null || envelope instanceof GeneralEnvelope) ?
            (GeneralEnvelope) envelope : new GeneralEnvelope(envelope);
    }

    /**
     * Returns the current coordinate reference system. If no CRS has been
     * {@linkplain #setCoordinateReferenceSystem explicitly defined}, then
     * the default CRS is {@linkplain DefaultGeographicCRS#WGS84 WGS84}.
     */
    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
        return (envelope != null) ? envelope.getCoordinateReferenceSystem() : DefaultGeographicCRS.WGS84;
    }

    /**
     * Sets the coordinate reference system to the specified value. If an
     * {@linkplain #setEnvelope envelope was previously defined}, it will
     * be reprojected to the new CRS.
     *
     * @throws IllegalArgumentException if the CRS is illegal for the
     *         {@linkplain #getEnvelope current envelope}.
     */
    public void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs)
            throws IllegalArgumentException
    {
        if (envelope == null) {
            if (crs != null) {
                envelope = wrap(CRS.getEnvelope(crs));
                if (envelope == null) {
                    envelope = new GeneralEnvelope(crs);
                    envelope.setToNull();
                }
            }
        } else try {
            envelope = wrap(CRS.transform(envelope, crs));
        } catch (TransformException exception) {
            throw new IllegalArgumentException(Errors.format(
                    ErrorKeys.ILLEGAL_COORDINATE_REFERENCE_SYSTEM), exception);
        }
        coverage = null;
    }

    /**
     * Sets the coordinate reference system to the specified authority code. This convenience
     * method gives a preference to axis in (<var>longitude</var>, <var>latitude</var>) order.
     *
     * @throws IllegalArgumentException if the given CRS is illegal.
     */
    public void setCoordinateReferenceSystem(final String code) throws IllegalArgumentException {
        final CoordinateReferenceSystem crs;
        try {
            crs = CRS.decode(code, true);
        } catch (FactoryException exception) {
            throw new IllegalArgumentException(Errors.format(
                    ErrorKeys.ILLEGAL_COORDINATE_REFERENCE_SYSTEM), exception);
        }
        setCoordinateReferenceSystem(crs);
    }

    /**
     * Returns a copy of current envelope. If no envelope has been {@linkplain #setEnvelope
     * explicitly defined}, then the default is inferred from the CRS (by default a geographic
     * envelope from 180°W to 180°E and 90°S to 90°N).
     */
    public Envelope getEnvelope() {
        if (envelope != null) {
            return envelope.clone();
        } else {
            final CoordinateReferenceSystem crs = getCoordinateReferenceSystem();
            Envelope candidate = CRS.getEnvelope(crs);
            if (candidate == null) {
                final GeneralEnvelope copy = new GeneralEnvelope(crs);
                copy.setToNull();
                candidate = copy;
            }
            return candidate;
        }
    }

    /**
     * Sets the envelope to the specified value. If a {@linkplain #setCoordinateReferenceSystem
     * CRS was previously defined}, the envelope will be reprojected to that CRS. If no CRS was
     * previously defined, then the CRS will be set to the
     * {@linkplain Envelope#getCoordinateReferenceSystem envelope CRS}.
     *
     * @throws IllegalArgumentException if the envelope is illegal for the
     *         {@linkplain #getCoordinateReferenceSystem current CRS}.
     */
    public void setEnvelope(Envelope envelope) throws IllegalArgumentException {
        if (this.envelope != null) try {
            envelope = CRS.transform(envelope, this.envelope.getCoordinateReferenceSystem());
        } catch (TransformException exception) {
            throw new IllegalArgumentException(Errors.format(
                    ErrorKeys.ILLEGAL_COORDINATE_REFERENCE_SYSTEM), exception);
        }
        this.envelope = new GeneralEnvelope(envelope);
        coverage = null;
    }

    /**
     * Sets the envelope to the specified values, which must be the lower corner coordinates
     * followed by upper corner coordinates. The number of arguments provided shall be twice
     * the envelope dimension, and minimum shall not be greater than maximum.
     * <p>
     * <b>Example:</b>
     * (<var>x</var><sub>min</sub>, <var>y</var><sub>min</sub>, <var>z</var><sub>min</sub>,
     *  <var>x</var><sub>max</sub>, <var>y</var><sub>max</sub>, <var>z</var><sub>max</sub>)
     */
    public void setEnvelope(final double... ordinates) throws IllegalArgumentException {
        GeneralEnvelope envelope = this.envelope;
        if (envelope == null) {
            envelope = new GeneralEnvelope(ordinates.length / 2);
        }
        envelope.setEnvelope(ordinates);
        this.envelope = envelope; // Assigns only if successful.
    }

    /**
     * Returns the range of sample values. If no range has been {@linkplain #setSampleRange
     * explicitly defined}, then the default is a range from 0 inclusive to 256 exclusive.
     */
    public NumberRange<? extends Number> getSampleRange() {
        return (range != null) ? range : DEFAULT_RANGE;
    }

    /**
     * Sets the range of sample values.
     */
    public void setSampleRange(final NumberRange<? extends Number> range) {
        this.range = range;
        coverage = null;
    }

    /**
     * Sets the range of sample values.
     *
     * @param  lower The lower sample value (inclusive), typically 0.
     * @param  upper The upper sample value (exclusive), typically 256.
     */
    public void setSampleRange(final int lower, final int upper) {
        setSampleRange(NumberRange.create(lower, true, upper, false));
    }

    /**
     * Returns the image size. If no size has been {@linkplain #setImageSize explicitly defined},
     * then the default is 256&times;256 pixels.
     */
    public Dimension getImageSize() {
        return new Dimension(width, height);
    }

    /**
     * Sets the image size.
     */
    public void setImageSize(final Dimension size) {
        width    = size.width;
        height   = size.height;
        image    = null;
        coverage = null;
    }

    /**
     * Sets the image size.
     */
    public void setImageSize(final int width, final int height) {
        setImageSize(new Dimension(width, height));
    }

    /**
     * Creates a new variable, which will be mapped to a {@linkplain GridSampleDimension sample
     * dimension}. Additional information like scale, offset and nodata values can be provided
     * by invoking setters on the returned variable.
     *
     * @param  name  The variable name, or {@code null} for a default name.
     * @param  units The variable units, or {@code null} if unknown.
     * @return A new variable.
     */
    public Variable newVariable(final CharSequence name, final Unit<?> units) {
        final Variable variable = new Variable(name, units);
        variables.add(variable);
        return variable;
    }

    /**
     * Returns the buffered image to be wrapped by {@link GridCoverage2D}. If no image has been
     * {@linkplain #setBufferedImage explicitly defined}, a new one is created the first time
     * this method is invoked. Users can write in this image before to create the grid coverage.
     */
    public BufferedImage getBufferedImage() {
        if (image == null) {
            final int numBands = variables.size();
            if (numBands == 0) {
                image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
            } else {
                final GridSampleDimension sd = variables.get(0).getSampleDimension();
                final ColorModel cm;
                if (numBands == 1) {
                    cm = sd.getColorModel();
                } else {
                    cm = sd.getColorModel(0, numBands);
                }
                final WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
                image = new BufferedImage(cm, raster, false, null);
            }
        }
        return image;
    }

    /**
     * Sets the buffered image. Invoking this method overwrite the
     * {@linkplain #getImageSize image size} with the given image size.
     */
    public void setBufferedImage(final BufferedImage image) {
        setImageSize(image.getWidth(), image.getHeight());
        this.image = image; // Stores only if the above line succeed.
        coverage = null;
    }

    /**
     * Sets the buffered image by reading it from the given file. Invoking this method
     * overwrite the {@linkplain #getImageSize image size} with the given image size.
     *
     * @throws IOException if the image can't be read.
     */
    public void setBufferedImage(final File file) throws IOException {
        setBufferedImage(ImageIO.read(file));
    }

    /**
     * Sets the buffered image to a raster filled with random value using the specified random
     * number generator. This method can be used for testing purpose, or for adding noise to a
     * coverage.
     */
    public void setBufferedImage(final Random random) {
        image = null; // Will forces the creation of a new BufferedImage.
        final BufferedImage image = getBufferedImage();
        final WritableRaster raster = image.getRaster();
        final ColorModel model = image.getColorModel();
        final int size;
        if (model instanceof IndexColorModel) {
            size = ((IndexColorModel) model).getMapSize();
        } else {
            size = 1 << Short.SIZE;
        }
        for (int i=raster.getWidth(); --i>=0;) {
            for (int j=raster.getHeight(); --j>=0;) {
                raster.setSample(i,j,0, random.nextInt(size));
            }
        }
    }

    /**
     * Returns the grid coverage.
     */
    public GridCoverage2D getGridCoverage2D() {
        if (coverage == null) {
            final BufferedImage image = getBufferedImage();
            final Envelope envelope = getEnvelope();
            final GridSampleDimension[] bands;
            if (variables.isEmpty()) {
                bands = null;
            } else {
                bands = new GridSampleDimension[variables.size()];
                for (int i=0; i<bands.length; i++) {
                    bands[i] = variables.get(i).getSampleDimension();
                }
            }
            coverage = factory.create(null, image, envelope, bands, null, null);
        }
        return coverage;
    }


    /**
     * A variable to be mapped to a {@linkplain GridSampleDimension sample dimension}.
     * Variables are created by {@link GridCoverageBuilder#newVariable}.
     *
     * @since 2.5
     * @author Martin Desruisseaux
     * @source $URL$
     * @version $Id$
     */
    public class Variable {
        /**
         * The variable name, or {@code null} for a default name.
         */
        private final CharSequence name;

        /**
         * The variable units, or {@code null} for a default units.
         */
        private final Unit<?> units;

        /**
         * The "nodata" values.
         */
        private final Map<Integer,CharSequence> nodata;

        /**
         * The "<cite>sample to geophysics</cite>" transform.
         */
        private MathTransform1D transform;

        /**
         * The sample dimension. Will be created when first needed. May be reset to {@code null}
         * after creation if a new sample dimension need to be computed.
         */
        private GridSampleDimension sampleDimension;

        /**
         * Creates a new variable of the given name and units.
         *
         * @param name  The variable name, or {@code null} for a default name.
         * @param units The variable units, or {@code null} if unknown.
         *
         * @see GridCoverageBuilder#newVariable
         */
        protected Variable(final CharSequence name, final Unit<?> units) {
            this.name   = name;
            this.units  = units;
            this.nodata = new TreeMap<Integer,CharSequence>();
        }

        /**
         * Returns the "<cite>sample to geophysics</cite>" transform, or {@code null} if none.
         */
        public MathTransform1D getTransform() {
            return transform;
        }

        /**
         * Sets the "<cite>sample to geophysics</cite>" transform.
         */
        public void setTransform(final MathTransform1D transform) {
            this.transform = transform;
            sampleDimension = null;
        }

        /**
         * Sets the "<cite>sample to geophysics</cite>" transform from a scale and an offset.
         * The transformation formula will be:
         *
         * <blockquote>
         * <var>geophysics</var> = {@code scale} &times; <var>sample</var> + {@code offset}
         * </blockquote>
         *
         * @param scale  The {@code scale}  term in the linear equation.
         * @param offset The {@code offset} term in the linear equation.
         */
        public void setLinearTransform(final double scale, final double offset) {
            setTransform(LinearTransform1D.create(scale, offset));
        }

        /**
         * Sets the "<cite>sample to geophysics</cite>" logarithmic transform
         * from a scale and an offset. The transformation formula will be:
         *
         * <blockquote>
         * <var>geophysics</var> = log<sub>{@code base}</sub>(<var>sample</var>) + {@code offset}
         * </blockquote>
         *
         * @param base   The base of the logarithm (typically 10).
         * @param offset The offset to add to the logarithm.
         */
        public void setLogarithmicTransform(final double base, final double offset) {
            setTransform(LogarithmicTransform1D.create(base, offset));
        }

        /**
         * Adds a "nodata" value.
         *
         * @param  name  The name for the "nodata" value.
         * @param  value The pixel value to assign to "nodata".
         * @throws IllegalArgumentException if the given pixel value is already assigned.
         */
        public void addNodataValue(final CharSequence name, final int value)
                throws IllegalArgumentException
        {
            final Integer key = value;
            final CharSequence old = nodata.put(key, name);
            if (old != null) {
                nodata.put(key, old);
                throw new IllegalArgumentException(Errors.format(
                        ErrorKeys.ILLEGAL_ARGUMENT_$2, "value", key));
            }
            sampleDimension = null;
        }

        /**
         * Returns a sample dimension for the current
         * {@linkplain GridCoverageBuilder#getSampleRange range of sample values}.
         */
        public GridSampleDimension getSampleDimension() {
            if (sampleDimension == null) {
                NumberRange<? extends Number> range = getSampleRange();
                int lower = (int) Math.floor(range.getMinimum(true));
                int upper = (int) Math.ceil(range.getMaximum(false));
                final Category[] categories = new Category[nodata.size() + 1];
                int i = 0;
                for (final Map.Entry<Integer,CharSequence> entry : nodata.entrySet()) {
                    final int sample = entry.getKey();
                    if (sample >= lower && sample < upper) {
                        if (sample - lower <= upper - sample) {
                            lower = sample + 1;
                        } else {
                            upper = sample;
                        }
                    }
                    categories[i++] = new Category(entry.getValue(), null, sample);
                }
                range = NumberRange.create(lower, true, upper, false);
                categories[i] = new Category(name, null, range, (transform != null) ?
                                             transform : LinearTransform1D.IDENTITY);
                sampleDimension = new GridSampleDimension(name, categories, units);
            }
            return sampleDimension;
        }

        /**
         * Returns a string representation of this variable.
         */
        @Override
        public String toString() {
            final StringBuilder buffer = new StringBuilder(getClass().getSimpleName());
            buffer.append('[');
            if (name != null) {
                buffer.append('"').append(name).append('"');
                if (units != null) {
                    buffer.append(' ');
                }
            }
            if (units != null) {
                buffer.append('(').append(units).append(')');
            }
            return buffer.append(']').toString();
        }
    }
}
TOP

Related Classes of org.geotools.coverage.grid.GridCoverageBuilder$Variable

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.