All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.display.shape.ProjectedShape Maven / Gradle / Ivy

/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2011-2012, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2011-2012, 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.display.shape;

import java.awt.Shape;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.PathIterator;
import java.awt.geom.AffineTransform;
import java.awt.geom.FlatteningPathIterator;
import java.io.Serializable;
import net.jcip.annotations.NotThreadSafe;

import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.NoninvertibleTransformException;

import org.geotoolkit.geometry.Envelopes;
import org.geotoolkit.util.ArgumentChecks;
import org.geotoolkit.util.logging.Logging;
import org.geotoolkit.referencing.operation.MathTransforms;
import org.geotoolkit.referencing.operation.transform.AffineTransform2D;


/**
 * Applies an arbitrary {@link MathTransform2D} (typically a projection) on a {@link Shape}.
 * A {@code ProjectedShape} instance is a view over a shape, i.e. the shape coordinates
 * are transformed on the fly, never copied. Consequently this class consumes very few memory,
 * but may consume more CPU if the same shape is rendered many time.
 *
 * {@section Straight lines and curves}
 * Straight line segments may become curves after the transform. This class can create quadratic
 * or cubic curves approximating the original line segments.
 *
 * @author Rémi Maréchal (Geomatys)
 * @author Martin Desruisseaux (Geomatys)
 * @version 3.20
 *
 * @see TransformedShape
 *
 * @since 3.20
 * @module
 */
@NotThreadSafe
public class ProjectedShape implements Shape, Serializable {
    /**
     * For cross-version compatibility.
     */
    private static final long serialVersionUID = -583674918489345612L;

    /**
     * The original shape to transform.
     */
    protected final Shape shape;

    /**
     * The operation to apply on the original shape.
     */
    protected final MathTransform2D projection;

    /**
     * The inverse of {@link #projection}, computed when first needed.
     */
    private transient MathTransform2D inverse;

    /**
     * A temporary point.
     */
    private transient Point2D.Double point;

    /**
     * A temporary rectangle.
     */
    private transient Rectangle2D.Double rectangle;

    /**
     * Creates a new shape as a transform of the given shape. This constructor is for subclasses
     * only. Users should invoke the static {@link #wrap(Shape, MathTransform2D)} method instead.
     *
     * @param shape      The original shape to transform.
     * @param projection The operation to apply on the given shape.
     */
    protected ProjectedShape(final Shape shape, final MathTransform2D projection) {
        ArgumentChecks.ensureNonNull("shape", shape);
        ArgumentChecks.ensureNonNull("projection", projection);
        this.shape = shape;
        this.projection = projection;
    }

    /**
     * Returns a new shape wrapping the given shape and applying the given transform on the fly.
     * If the transform is {@code null} or an {@linkplain MathTransform2D#isIdentity() identity}
     * transform, then this method returns the given shape unchanged. Otherwise if the transform
     * is {@linkplain AffineTransform affine}, then this method wraps the given shape in an
     * {@link TransformedShape}. Otherwise this method wraps the given shape in a
     * {@code ProjectedShape}.
     *
     * @param  shape      The original shape to transform.
     * @param  projection The operation to apply on the given shape.
     * @return A view over the given shape, transformed by the given transform.
     */
    public static Shape wrap(final Shape shape, final MathTransform2D projection) {
        if (projection == null || projection.isIdentity()) {
            return shape;
        }
        if (projection instanceof AffineTransform) {
            return new TransformedShape(shape, (AffineTransform) projection);
        }
        return new ProjectedShape(shape, projection);
    }

    /**
     * Returns the inverse of {@link #projection}. This method initializes also the
     * {@link #point} and {@link #rectangle} fields as a side-effect, since they will
     * be needed together with the inverse transform.
     */
    private MathTransform2D inverse() throws NoninvertibleTransformException {
        if (inverse == null) {
            inverse = projection.inverse();
            if (point     == null) point     = new Point2D    .Double();
            if (rectangle == null) rectangle = new Rectangle2D.Double();
        }
        return inverse;
    }

    /**
     * Tests if the specified coordinate is inside the boundary of this shape.
     * This method might conservatively return {@code false} if the point can
     * not be transformed.
     * 

* The default implementation delegates to {@link #contains(Point2D)}. * * @param x The x ordinate of the point to be tested. * @param y The y ordinate of the point to be tested. * @return {@code true} if this shape contains the given point. */ @Override public boolean contains(final double x, final double y) { Point2D.Double p = point; if (p == null) { point = p = new Point2D.Double(); } p.x = x; p.y = y; return contains(p); } /** * Tests if a specified {@link Point2D} is inside the boundary of this shape. * This method might conservatively return {@code false} if the point can not * be transformed. * * @param p The point to be tested. * @return {@code true} if this shape contains the given point. */ @Override public boolean contains(final Point2D p) { try { return shape.contains(inverse().transform(p, point)); } catch (TransformException exception) { Logging.recoverableException(ProjectedShape.class, "contains", exception); return false; } } /** * Tests if the interior of this shape entirely contains the specified rectangular area. * This method might conservatively return {@code false} if some points can not be * transformed. *

* The default implementation delegates to {@link #contains(Rectangle2D)}. * * @param x The minimal x ordinate of the rectangle to be tested. * @param y The minimal y ordinate of the rectangle to be tested. * @param width The width of the rectangle to be tested. * @param height The height of the rectangle to be tested. * @return {@code true} if this shape contains the given rectangle. */ @Override public boolean contains(final double x, final double y, final double width, final double height) { Rectangle2D.Double r = rectangle; if (r == null) { rectangle = r = new Rectangle2D.Double(); } r.x = x; r.y = y; r.width = width; r.height = height; return contains(r); } /** * Tests if the interior of this shape entirely contains the specified rectangle. * This method might conservatively return {@code false} if some points can not * be transformed. * * @param r The rectangle to be tested. * @return {@code true} if this shape contains the given rectangle. */ @Override public boolean contains(final Rectangle2D r) { try { return shape.contains(Envelopes.transform(inverse(), r, rectangle)); } catch (TransformException exception) { Logging.recoverableException(ProjectedShape.class, "contains", exception); return false; // Consistent with the Shape interface contract. } } /** * Tests if the interior of this shape intersects the interior of a specified rectangular area. * This method might conservatively return {@code true} if some points can not be transformed. *

* The default implementation delegates to {@link #intersects(Rectangle2D)}. * * @param x The minimal x ordinate of the rectangle to be tested. * @param y The minimal y ordinate of the rectangle to be tested. * @param width The width of the rectangle to be tested. * @param height The height of the rectangle to be tested. * @return {@code true} if this shape intersects the given rectangle. */ @Override public boolean intersects(double x, double y, double width, double height) { Rectangle2D.Double r = rectangle; if (r == null) { rectangle = r = new Rectangle2D.Double(); } r.x = x; r.y = y; r.width = width; r.height = height; return intersects(r); } /** * Tests if the interior of this shape intersects the interior of a specified rectangle. * This method might conservatively return {@code true} if some points can not be transformed. * * @param r The rectangle to be tested. * @return {@code true} if this shape intersects the given rectangle. */ @Override public boolean intersects(final Rectangle2D r) { try { return shape.intersects(Envelopes.transform(inverse(), r, rectangle)); } catch (TransformException exception) { Logging.recoverableException(ProjectedShape.class, "intersects", exception); return true; // Consistent with the Shape interface contract. } } /** * Returns an integer rectangle that completely encloses this shape. *

* The default implementation delegates to {@link #getBounds2D()} and cast the resulting * rectangle. */ @Override public Rectangle getBounds() { final Rectangle bounds = new Rectangle(); bounds.setRect(bounds); return bounds; } /** * Returns a high precision and more accurate bounding box of the shape than the * {@link #getBounds} method. This method returns an infinite rectangle if some * points can not be transformed. */ @Override public Rectangle2D getBounds2D() { try { return Envelopes.transform(projection, shape.getBounds2D(), null); } catch (TransformException exception) { Logging.recoverableException(ProjectedShape.class, "getBounds2D", exception); return XRectangle2D.INFINITY; } } /** * Returns the concatenation of the {@linkplain #projection} with the given affine transform. * * @param at The affine transform to concatenate, or {@code null} if none. */ private MathTransform2D concatenate(final AffineTransform at) { MathTransform2D concatenated = projection; if (at != null && !at.isIdentity()) { concatenated = MathTransforms.concatenate(concatenated, new AffineTransform2D(at)); } return concatenated; } /** * Returns an iterator object that iterates along the shape boundary * and provides access to the geometry of the shape outline. */ @Override public PathIterator getPathIterator(final AffineTransform at) { final MathTransform2D concatenated = concatenate(at); if (concatenated.isIdentity()) { return shape.getPathIterator(at); } return new ProjectedPathIterator(shape.getPathIterator(null), concatenated); } /** * Returns an iterator object that iterates along the shape boundary and provides * access to a flattened view of the shape outline geometry. */ @Override public PathIterator getPathIterator(final AffineTransform at, final double flatness) { final MathTransform2D concatenated = concatenate(at); if (concatenated.isIdentity()) { return shape.getPathIterator(at, flatness); } return new FlatteningPathIterator(new ProjectedPathIterator(shape.getPathIterator(null), concatenated), flatness); } /** * Returns a hash code value for this shape. */ @Override public int hashCode() { return shape.hashCode() ^ projection.hashCode() ^ (int) serialVersionUID; } /** * Compares this shape with the given object for equality. * * @param object The object to compare with this shape. */ @Override public boolean equals(final Object object) { if (object instanceof ProjectedShape) { final ProjectedShape other = (ProjectedShape) object; return shape.equals(other.shape) && projection.equals(other.projection); } return false; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy