org.geotools.geometry.jts.LiteShape2 Maven / Gradle / Ivy
Show all versions of gt-main Show documentation
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-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.geometry.jts;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
/**
* A thin wrapper that adapts a JTS geometry to the Shape interface so that the geometry can be used
* by java2d without coordinate cloning.
*
* This implementation supports the use of MathTransform and can be constructed with a Decimation
* stratagy object (allowing you to fine tune the process by which a Geometry is simplified into a
* Shape).
*
*
This implementation is very careful about cloning; and has the ability to go faster when you
* are using a LiteCoordinateSequenceFactory behind your geometry classes.
*
* @author Jesse Eichar
* @version $Id$
*/
public final class LiteShape2 implements Shape, Cloneable {
/** The wrapped JTS geometry */
private Geometry geometry;
private boolean generalize = false;
private double maxDistance = 1;
private static GeometryFactory geomFac;
/** transform from dataspace to screenspace */
private MathTransform mathTransform;
/**
* Creates a new LiteShape object.
*
* @param geom - the wrapped geometry
* @param mathTransform - the transformation applied to the geometry in order to get to the
* shape points
* @param decimator -
* @param generalize - set to true if the geometry need to be generalized during rendering
* @param maxDistance - distance used in the generalization process
*/
public LiteShape2(
Geometry geom,
MathTransform mathTransform,
Decimator decimator,
boolean generalize,
double maxDistance)
throws TransformException, FactoryException {
this(geom, mathTransform, decimator, generalize);
this.maxDistance = maxDistance;
}
/**
* Creates a new LiteShape object.
*
* @param geom - the wrapped geometry
* @param mathTransform - the transformation applied to the geometry in order to get to the
* shape points
* @param decimator -
* @param generalize - set to true if the geometry need to be generalized during rendering
*/
public LiteShape2(
Geometry geom, MathTransform mathTransform, Decimator decimator, boolean generalize)
throws TransformException, FactoryException {
this(geom, mathTransform, decimator, generalize, true);
}
/**
* Creates a new LiteShape object.
*
* @param geom - the wrapped geometry
* @param mathTransform - the transformation applied to the geometry in order to get to the
* shape points
* @param decimator -
* @param generalize - set to true if the geometry need to be generalized during rendering
* @param clone - if clone is false the original geometry may be modified directly, if true it
* will be cloned to make sure the original remains untouched
*/
public LiteShape2(
Geometry geom,
MathTransform mathTransform,
Decimator decimator,
boolean generalize,
boolean clone)
throws TransformException, FactoryException {
if (geom != null) {
if (!clone
&& geom.getFactory().getCoordinateSequenceFactory()
instanceof LiteCoordinateSequenceFactory) this.geometry = geom;
else this.geometry = LiteCoordinateSequence.cloneGeometry(geom);
}
this.mathTransform = mathTransform;
if (decimator != null) {
this.geometry =
decimator.decimateTransformGeneralize(this.geometry, this.mathTransform);
this.geometry.geometryChanged();
} else {
// if we have a transform a decimation span can be detected, so try to decimate anyways
if (mathTransform != null
&& !mathTransform.isIdentity()
&& generalize
&& geometry != null) {
new Decimator(
mathTransform.inverse(),
getRectangle(this.geometry.getEnvelopeInternal()))
.decimate(this.geometry);
this.geometry.geometryChanged();
}
if (geometry != null) {
transformGeometry(geometry);
this.geometry.geometryChanged();
}
}
this.generalize = false;
}
private Rectangle getRectangle(Envelope envelope) {
int minX = (int) Math.floor(envelope.getMinX());
int minY = (int) Math.floor(envelope.getMinY());
int maxX = (int) Math.floor(envelope.getMaxX());
int maxY = (int) Math.floor(envelope.getMaxY());
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
private void transformGeometry(Geometry geometry) throws TransformException, FactoryException {
if (mathTransform == null || mathTransform.isIdentity()) return;
if (geometry instanceof GeometryCollection) {
GeometryCollection collection = (GeometryCollection) geometry;
for (int i = 0; i < collection.getNumGeometries(); i++) {
transformGeometry(collection.getGeometryN(i));
}
} else if (geometry instanceof Point) {
LiteCoordinateSequence seq =
(LiteCoordinateSequence) ((Point) geometry).getCoordinateSequence();
double[] coords = seq.getArray();
double[] newCoords = new double[coords.length];
mathTransform.transform(coords, 0, newCoords, 0, seq.size());
seq.setArray(newCoords);
} else if (geometry instanceof Polygon) {
Polygon polygon = (Polygon) geometry;
transformGeometry(polygon.getExteriorRing());
for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
transformGeometry(polygon.getInteriorRingN(i));
}
} else if (geometry instanceof LineString) {
LiteCoordinateSequence seq =
(LiteCoordinateSequence) ((LineString) geometry).getCoordinateSequence();
double[] coords = seq.getArray();
mathTransform.transform(coords, 0, coords, 0, seq.size());
seq.setArray(coords);
}
}
private GeometryFactory getGeometryFactory() {
if (geomFac == null) {
geomFac = new GeometryFactory(new LiteCoordinateSequenceFactory());
}
return geomFac;
}
/**
* Sets the geometry contained in this lite shape. Convenient to reuse this object instead of
* creating it again and again during rendering
*/
public void setGeometry(Geometry g) throws TransformException, FactoryException {
if (g != null) {
this.geometry = getGeometryFactory().createGeometry(g);
transformGeometry(geometry);
}
}
/**
* Tests if the interior of the Shape
entirely contains the specified
* Rectangle2D
. This method might conservatively return false
when:
*
*
* - the
intersect
method returns true
and
* - the calculations to determine whether or not the
Shape
entirely contains
* the Rectangle2D
are prohibitively expensive.
*
*
* This means that this method might return false
even though the Shape
*
contains the Rectangle2D
. The Area
class can be used to
* perform more accurate computations of geometric intersection for any Shape
* object if a more precise answer is required.
*
* @param r The specified Rectangle2D
* @return true
if the interior of the Shape
entirely contains the
* Rectangle2D
; false
otherwise or, if the Shape
* contains the Rectangle2D
and the intersects
method returns
* true
and the containment calculations would be too expensive to perform.
* @see #contains(double, double, double, double)
*/
public boolean contains(Rectangle2D r) {
Geometry rect = rectangleToGeometry(r);
return geometry.contains(rect);
}
/**
* Tests if a specified {@link Point2D}is inside the boundary of the Shape
.
*
* @param p a specified Point2D
* @return true
if the specified Point2D
is inside the boundary of the
* Shape
; false
otherwise.
*/
public boolean contains(Point2D p) {
Coordinate coord = new Coordinate(p.getX(), p.getY());
Geometry point = geometry.getFactory().createPoint(coord);
return geometry.contains(point);
}
/**
* Tests if the specified coordinates are inside the boundary of the Shape
.
*
* @param x the specified coordinates, x value
* @param y the specified coordinates, y value
* @return true
if the specified coordinates are inside the Shape
* boundary; false
otherwise.
*/
public boolean contains(double x, double y) {
Coordinate coord = new Coordinate(x, y);
Geometry point = geometry.getFactory().createPoint(coord);
return geometry.contains(point);
}
/**
* Tests if the interior of the Shape
entirely contains the specified rectangular
* area. All coordinates that lie inside the rectangular area must lie within the Shape
*
for the entire rectanglar area to be considered contained within the Shape
*
.
*
* This method might conservatively return false
when:
*
*
* - the
intersect
method returns true
and
* - the calculations to determine whether or not the
Shape
entirely contains
* the rectangular area are prohibitively expensive.
*
*
* This means that this method might return false
even though the Shape
*
contains the rectangular area. The Area
class can be used to perform
* more accurate computations of geometric intersection for any Shape
object if a
* more precise answer is required.
*
* @param x the coordinates of the specified rectangular area, x value
* @param y the coordinates of the specified rectangular area, y value
* @param w the width of the specified rectangular area
* @param h the height of the specified rectangular area
* @return true
if the interior of the Shape
entirely contains the
* specified rectangular area; false
otherwise or, if the Shape
* contains the rectangular area and the intersects
method returns true
*
and the containment calculations would be too expensive to perform.
* @see java.awt.geom.Area
* @see #intersects
*/
public boolean contains(double x, double y, double w, double h) {
Geometry rect = createRectangle(x, y, w, h);
return geometry.contains(rect);
}
/**
* Returns an integer {@link Rectangle}that completely encloses the Shape
. Note
* that there is no guarantee that the returned Rectangle
is the smallest bounding
* box that encloses the Shape
, only that the Shape
lies entirely
* within the indicated Rectangle
. The returned Rectangle
might also
* fail to completely enclose the Shape
if the Shape
overflows the
* limited range of the integer data type. The getBounds2D
method generally returns
* a tighter bounding box due to its greater flexibility in representation.
*
* @return an integer Rectangle
that completely encloses the Shape
.
* @see #getBounds2D
*/
public Rectangle getBounds() {
Rectangle2D env = getBounds2D();
return new Rectangle(
(int) Math.round(env.getMinX()),
(int) Math.round(env.getMinY()),
(int) Math.ceil(env.getWidth()),
(int) Math.ceil(env.getHeight()));
}
/**
* Returns a high precision and more accurate bounding box of the Shape
than the
* getBounds
method. Note that there is no guarantee that the returned {@link
* Rectangle2D}is the smallest bounding box that encloses the Shape
, only that the
* Shape
lies entirely within the indicated Rectangle2D
. The bounding
* box returned by this method is usually tighter than that returned by the getBounds
*
method and never fails due to overflow problems since the return value can be an
* instance of the Rectangle2D
that uses double precision values to store the
* dimensions.
*
* @return an instance of Rectangle2D
that is a high-precision bounding box of the
* Shape
.
* @see #getBounds
*/
public Rectangle2D getBounds2D() {
Envelope env = geometry.getEnvelopeInternal();
// note, we dont' use getWidth/getHeight since they are slower
return new Rectangle2D.Double(
env.getMinX(),
env.getMinY(),
env.getMaxX() - env.getMinX(),
env.getMaxY() - env.getMinY());
}
/**
* Returns an iterator object that iterates along the Shape
boundary and provides
* access to the geometry of the Shape
outline. If an optional {@link
* AffineTransform}is specified, the coordinates returned in the iteration are transformed
* accordingly.
*
* Each call to this method returns a fresh PathIterator
object that traverses
* the geometry of the Shape
object independently from any other PathIterator
*
objects in use at the same time.
*
*
It is recommended, but not guaranteed, that objects implementing the Shape
* interface isolate iterations that are in process from any changes that might occur to the
* original object's geometry during such iterations.
*
*
Before using a particular implementation of the Shape
interface in more than
* one thread simultaneously, refer to its documentation to verify that it guarantees that
* iterations are isolated from modifications.
*
* @param at an optional AffineTransform
to be applied to the coordinates as they
* are returned in the iteration, or null
if untransformed coordinates are
* desired
* @return a new PathIterator
object, which independently traverses the geometry of
* the Shape
.
*/
public PathIterator getPathIterator(AffineTransform at) {
PathIterator pi = null;
if (this.geometry == null || this.geometry.isEmpty()) return EmptyIterator.INSTANCE;
// return iterator according to the kind of geometry we include
if (this.geometry instanceof Point) {
pi = new PointIterator((Point) geometry, at);
}
if (this.geometry instanceof Polygon) {
pi = new PolygonIterator((Polygon) geometry, at, generalize, maxDistance);
} else if (this.geometry instanceof LineString) {
pi = new LineIterator((LineString) geometry, at, generalize, (float) maxDistance);
} else if (this.geometry instanceof GeometryCollection) {
pi =
new GeomCollectionIterator(
(GeometryCollection) geometry, at, generalize, maxDistance);
}
return pi;
}
/**
* Returns an iterator object that iterates along the Shape
boundary and provides
* access to a flattened view of the Shape
outline geometry.
*
*
Only SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point types are returned by the iterator.
*
*
If an optional AffineTransform
is specified, the coordinates returned in the
* iteration are transformed accordingly.
*
*
The amount of subdivision of the curved segments is controlled by the flatness
*
parameter, which specifies the maximum distance that any point on the unflattened
* transformed curve can deviate from the returned flattened path segments. Note that a limit on
* the accuracy of the flattened path might be silently imposed, causing very small flattening
* parameters to be treated as larger values. This limit, if there is one, is defined by the
* particular implementation that is used.
*
*
Each call to this method returns a fresh PathIterator
object that traverses
* the Shape
object geometry independently from any other PathIterator
* objects in use at the same time.
*
*
It is recommended, but not guaranteed, that objects implementing the Shape
* interface isolate iterations that are in process from any changes that might occur to the
* original object's geometry during such iterations.
*
*
Before using a particular implementation of this interface in more than one thread
* simultaneously, refer to its documentation to verify that it guarantees that iterations are
* isolated from modifications.
*
* @param at an optional AffineTransform
to be applied to the coordinates as they
* are returned in the iteration, or null
if untransformed coordinates are
* desired
* @param flatness the maximum distance that the line segments used to approximate the curved
* segments are allowed to deviate from any point on the original curve
* @return a new PathIterator
that independently traverses the Shape
* geometry.
*/
public PathIterator getPathIterator(AffineTransform at, double flatness) {
return getPathIterator(at);
}
/**
* Tests if the interior of the Shape
intersects the interior of a specified
* Rectangle2D
. This method might conservatively return true
when:
*
*
* - there is a high probability that the
Rectangle2D
and the Shape
*
intersect, but
* - the calculations to accurately determine this intersection are prohibitively expensive.
*
*
* This means that this method might return true
even though the Rectangle2D
*
does not intersect the Shape
.
*
* @param r the specified Rectangle2D
* @return true
if the interior of the Shape
and the interior of the
* specified Rectangle2D
intersect, or are both highly likely to intersect and
* intersection calculations would be too expensive to perform; false
* otherwise.
* @see #intersects(double, double, double, double)
*/
public boolean intersects(Rectangle2D r) {
Geometry rect = rectangleToGeometry(r);
return geometry.intersects(rect);
}
/**
* Tests if the interior of the Shape
intersects the interior of a specified
* rectangular area. The rectangular area is considered to intersect the Shape
if
* any point is contained in both the interior of the Shape
and the specified
* rectangular area.
*
* This method might conservatively return true
when:
*
*
* - there is a high probability that the rectangular area and the
Shape
* intersect, but
* - the calculations to accurately determine this intersection are prohibitively expensive.
*
*
* This means that this method might return true
even though the rectangular area
* does not intersect the Shape
. The {@link java.awt.geom.Area Area}class can be
* used to perform more accurate computations of geometric intersection for any Shape
*
object if a more precise answer is required.
*
* @param x the coordinates of the specified rectangular area, x value
* @param y the coordinates of the specified rectangular area, y value
* @param w the width of the specified rectangular area
* @param h the height of the specified rectangular area
* @return true
if the interior of the Shape
and the interior of the
* rectangular area intersect, or are both highly likely to intersect and intersection
* calculations would be too expensive to perform; false
otherwise.
* @see java.awt.geom.Area
*/
public boolean intersects(double x, double y, double w, double h) {
Geometry rect = createRectangle(x, y, w, h);
return geometry.intersects(rect);
}
/**
* Converts the Rectangle2D passed as parameter in a jts Geometry object
*
* @param r the rectangle to be converted
* @return a geometry with the same vertices as the rectangle
*/
private Geometry rectangleToGeometry(Rectangle2D r) {
return createRectangle(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight());
}
/**
* Creates a jts Geometry object representing a rectangle with the given parameters
*
* @param x left coordinate
* @param y bottom coordinate
* @param w width
* @param h height
* @return a rectangle with the specified position and size
*/
private Geometry createRectangle(double x, double y, double w, double h) {
Coordinate[] coords = {
new Coordinate(x, y),
new Coordinate(x, y + h),
new Coordinate(x + w, y + h),
new Coordinate(x + w, y),
new Coordinate(x, y)
};
LinearRing lr = geometry.getFactory().createLinearRing(coords);
return geometry.getFactory().createPolygon(lr, null);
}
public MathTransform getMathTransform() {
return mathTransform;
}
public Geometry getGeometry() {
return geometry;
}
}