/*
* The JTS Topology Suite is a collection of Java classes that
* implement the fundamental operations required to validate a given
* geo-spatial data set to a known topological specification.
*
* Copyright (C) 2001 Vivid Solutions
*
* 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; either
* version 2.1 of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.revolsys.geometry.model;
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 java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import javax.measure.quantity.Area;
import javax.measure.quantity.Length;
import javax.measure.unit.Unit;
import com.revolsys.datatype.DataType;
import com.revolsys.datatype.DataTypeProxy;
import com.revolsys.datatype.DataTypes;
import com.revolsys.geometry.algorithm.Centroid;
import com.revolsys.geometry.algorithm.ConvexHull;
import com.revolsys.geometry.algorithm.InteriorPointArea;
import com.revolsys.geometry.algorithm.InteriorPointLine;
import com.revolsys.geometry.algorithm.PointLocator;
import com.revolsys.geometry.cs.CoordinateSystem;
import com.revolsys.geometry.graph.linemerge.LineMerger;
import com.revolsys.geometry.model.editor.GeometryEditor;
import com.revolsys.geometry.model.segment.Segment;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.geometry.operation.buffer.Buffer;
import com.revolsys.geometry.operation.buffer.BufferParameters;
import com.revolsys.geometry.operation.distance.DistanceOp;
import com.revolsys.geometry.operation.overlay.OverlayOp;
import com.revolsys.geometry.operation.overlay.snap.SnapIfNeededOverlayOp;
import com.revolsys.geometry.operation.predicate.RectangleIntersects;
import com.revolsys.geometry.operation.relate.RelateOp;
import com.revolsys.geometry.operation.union.UnaryUnionOp;
import com.revolsys.geometry.operation.valid.GeometryValidationError;
import com.revolsys.geometry.operation.valid.IsValidOp;
import com.revolsys.record.io.format.wkt.EWktWriter;
import com.revolsys.util.Emptyable;
import com.revolsys.util.Pair;
import com.revolsys.util.Property;
import com.revolsys.util.number.Doubles;
/**
* A representation of a planar, linear vector geometry.
* <P>
*
* <H3>Binary Predicates</H3>
* Because it is not clear at this time
* what semantics for spatial
* analysis methods involving <code>GeometryCollection</code>s would be useful,
* <code>GeometryCollection</code>s are not supported as arguments to binary
* predicates or the <code>relate</code>
* method.
*
* <H3>Overlay Methods</H3>
*
* The overlay methods
* return the most specific class possible to represent the result. If the
* result is homogeneous, a <code>Point</code>, <code>LineString</code>, or
* <code>Polygon</code> will be returned if the result contains a single
* element; otherwise, a <code>MultiPoint</code>, <code>MultiLineString</code>,
* or <code>MultiPolygon</code> will be returned. If the result is
* heterogeneous a <code>GeometryCollection</code> will be returned. <P>
*
* Because it is not clear at this time what semantics for set-theoretic
* methods involving <code>GeometryCollection</code>s would be useful,
* <code>GeometryCollections</code>
* are not supported as arguments to the set-theoretic methods.
*
* <H4>Representation of Computed Geometries </H4>
*
* The SFS states that the result
* of a set-theoretic method is the "point-set" result of the usual
* set-theoretic definition of the operation (SFS 3.2.21.1). However, there are
* sometimes many ways of representing a point set as a <code>Geometry</code>.
* <P>
*
* The SFS does not specify an unambiguous representation of a given point set
* returned from a spatial analysis method. One goal of JTS is to make this
* specification precise and unambiguous. JTS uses a canonical form for
* <code>Geometry</code>s returned from overlay methods. The canonical
* form is a <code>Geometry</code> which is simple and noded:
* <UL>
* <LI> Simple means that the Geometry returned will be simple according to
* the JTS definition of <code>isSimple</code>.
* <LI> Noded applies only to overlays involving <code>LineString</code>s. It
* means that all intersection points on <code>LineString</code>s will be
* present as endpoints of <code>LineString</code>s in the result.
* </UL>
* This definition implies that non-simple geometries which are arguments to
* spatial analysis methods must be subjected to a line-dissolve process to
* ensure that the results are simple.
*
* <H4> Constructed Point And The Precision Model </H4>
*
* The results computed by the set-theoretic methods may
* contain constructed points which are not present in the input <code>Geometry</code>
* s. These new points arise from intersections between line segments in the
* edges of the input <code>Geometry</code>s. In the general case it is not
* possible to represent constructed points exactly. This is due to the geometryFactory
* that the coordinates of an intersection point may contain twice as many bits
* of precision as the coordinates of the input line segments. In order to
* represent these constructed points explicitly, JTS must truncate them to fit
* the <code>PrecisionModel</code>. <P>
*
* Unfortunately, truncating coordinates moves them slightly. Line segments
* which would not be coincident in the exact result may become coincident in
* the truncated representation. This in turn leads to "topology collapses" --
* situations where a computed element has a lower dimension than it would in
* the exact result. <P>
*
* When JTS detects topology collapses during the computation of spatial
* analysis methods, it will throw an exception. If possible the exception will
* report the location of the collapse. <P>
*
* <h3>Geometry Equality</h3>
*
* There are two ways of comparing geometries for equality:
* <b>structural equality</b> and <b>topological equality</b>.
*
* <h4>Structural Equality</h4>
*
* Structural Equality is provided by the
* {@link #equals(2,Geometry)} method.
* This implements a comparison based on exact, structural pointwise
* equality.
* The {@link #equals(Object)} is a synonym for this method,
* to provide structural equality semantics for
* use in Java collections.
* It is important to note that structural pointwise equality
* is easily affected by things like
* ring order and component order. In many situations
* it will be desirable to normalize geometries before
* comparing them (using the {@link #norm()}
* or {@link #normalize()} methods).
* {@link #equalsNorm(Geometry)} is provided
* as a convenience method to compute equality over
* normalized geometries, but it is expensive to use.
* Finally, {@link #equalsExact(Geometry, double)}
* allows using a tolerance value for point comparison.
*
*
* <h4>Topological Equality</h4>
*
* Topological Equality is provided by the
* {@link #equalsTopo(Geometry)} method.
* It implements the SFS definition of point-set equality
* defined in terms of the DE-9IM matrix.
* To support the SFS naming convention, the method
* {@link #equals(Geometry)} is also provided as a synonym.
* However, due to the potential for confusion with {@link #equals(Object)}
* its use is discouraged.
* <p>
* Since {@link #equals(Object)} and {@link #hashCode()} are overridden,
* Geometries can be used effectively in Java collections.
*
*@version 1.7
*/
public interface Geometry extends BoundingBoxProxy, Cloneable, Comparable<Object>, Emptyable,
GeometryFactoryProxy, Serializable, DataTypeProxy, Shape {
List<String> SORTED_GEOMETRY_TYPES = Arrays.asList("Point", "MultiPoint", "LineString",
"LinearRing", "MultiLineString", "Polygon", "MultiPolygon", "GeometryCollection");
/**
* Standard ordinate index values
*/
int X = 0;
int Y = 1;
int Z = 2;
int M = 3;
/**
* Throws an exception if the <code>geometry</code>'s is a {@link #isHeterogeneousGeometryCollection()}.
*
*
*@param geometry the <code>Geometry</code> to check
*@throws IllegalArgumentException if <code>geometry</code>'s is a {@link #isHeterogeneousGeometryCollection()}
*/
static void checkNotGeometryCollection(final Geometry geometry) {
if (geometry.isHeterogeneousGeometryCollection()) {
throw new IllegalArgumentException(
"This method does not support GeometryCollection arguments");
}
}
/**
* Returns the first non-zero result of <code>compareTo</code> encountered as
* the two <code>Collection</code>s are iterated over. If, by the time one of
* the iterations is complete, no non-zero result has been encountered,
* returns 0 if the other iteration is also complete. If <code>b</code>
* completes before <code>a</code>, a positive number is returned; if a
* before b, a negative number.
*
*@param a a <code>Collection</code> of <code>Comparable</code>s
*@param b a <code>Collection</code> of <code>Comparable</code>s
*@return the first non-zero <code>compareTo</code> result, if any;
* otherwise, zero
*/
@SuppressWarnings({
"rawtypes", "unchecked"
})
static int compare(final Collection a, final Collection b) {
final Iterator i = a.iterator();
final Iterator j = b.iterator();
while (i.hasNext() && j.hasNext()) {
final Comparable aElement = (Comparable)i.next();
final Comparable bElement = (Comparable)j.next();
final int comparison = aElement.compareTo(bElement);
if (comparison != 0) {
return comparison;
}
}
if (i.hasNext()) {
return 1;
}
if (j.hasNext()) {
return -1;
}
return 0;
}
static boolean equalsExact(final Object geometry1, final Object geometry2) {
return ((Geometry)geometry1).equalsExact((Geometry)geometry2);
}
static int getGeometryCount(final Iterable<Geometry> geometries) {
int totalGeometryCount = 0;
for (final Geometry geometry : geometries) {
final int geometryCount = geometry.getGeometryCount();
totalGeometryCount += geometryCount;
}
return totalGeometryCount;
}
static int getVertexIndex(final int[] index) {
final int length = index.length;
final int lastIndex = length - 1;
return index[lastIndex];
}
/**
* Returns true if the array contains any non-empty <code>Geometry</code>s.
*
*@param geometries an array of <code>Geometry</code>s; no elements may be
* <code>null</code>
*@return <code>true</code> if any of the <code>Geometry</code>s
* <code>isEmpty</code> methods return <code>false</code>
*/
static boolean hasNonEmptyElements(final Geometry... geometries) {
for (final Geometry geometry : geometries) {
if (!geometry.isEmpty()) {
return true;
}
}
return false;
}
/**
* Returns true if the array contains any <code>null</code> elements.
*
*@param array an array to validate
*@return <code>true</code> if any of <code>array</code>s elements are
* <code>null</code>
*/
public static boolean hasNullElements(final Object[] array) {
for (final Object element : array) {
if (element == null) {
return true;
}
}
return false;
}
/**
* Tests whether any representative of the target geometry
* intersects the test geometry.
* This is useful in A/A, A/L, A/P, L/P, and P/P cases.
* @param geometry TODO
* @param geom the test geometry
* @param repPts the representative points of the target geometry
*
* @return true if any component intersects the areal test geometry
*/
static boolean isAnyTargetComponentInTest(final Geometry geometry, final Geometry testGeom) {
final PointLocator locator = new PointLocator();
for (final Vertex vertex : geometry.vertices()) {
final boolean intersects = locator.intersects(vertex, testGeom);
if (intersects) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
static <G extends Geometry> G newGeometry(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Geometry) {
return (G)value;
} else {
final String string = DataTypes.toString(value);
return GeometryFactory.DEFAULT_3D.geometry(string, false);
}
}
static int[] newVertexId(final int[] partId, final int vertexIndex) {
final int[] vertexId = new int[partId.length + 1];
System.arraycopy(partId, 0, vertexId, 0, partId.length);
vertexId[partId.length] = vertexIndex;
return vertexId;
}
static int[] setVertexIndex(final int[] vertexId, final int vertexIndex) {
final int length = vertexId.length;
final int lastIndex = length - 1;
final int[] newVertextId = new int[length];
System.arraycopy(vertexId, 0, newVertextId, 0, lastIndex);
newVertextId[lastIndex] = vertexIndex;
return newVertextId;
}
default boolean addIsSimpleErrors(final List<GeometryValidationError> errors,
final boolean shortCircuit) {
return true;
}
<V extends Geometry> V appendVertex(Point newPoint, int... geometryId);
@SuppressWarnings("unchecked")
default <GIN extends Geometry, GRET extends Geometry> GRET applyGeometry(
final Function<? super GIN, ? super Geometry> function) {
if (!isEmpty()) {
return (GRET)function.apply((GIN)this);
}
return (GRET)this;
}
/**
* Computes a buffer area around this geometry having the given width. The
* buffer of a Geometry is the Minkowski sum or difference of the geometry
* with a disc of radius <code>abs(distance)</code>.
* <p>
* Mathematically-exact buffer area boundaries can contain circular arcs.
* To represent these arcs using linear geometry they must be approximated with line segments.
* The buffer geometry is constructed using 8 segments per quadrant to approximate
* the circular arcs.
* The end cap style is <code>CAP_ROUND</code>.
* <p>
* The buffer operation always returns a polygonal result. The negative or
* zero-distance buffer of lines and points is always an empty {@link Polygon}.
* This is also the result for the buffers of degenerate (zero-area) polygons.
*
* @param distance
* the width of the buffer (may be positive, negative or 0)
* @return a polygonal geometry representing the buffer region (which may be
* empty)
*
* @throws TopologyException
* if a robustness error occurs
*
* @see #buffer(double, int)
* @see #buffer(double, int, int)
*/
default Geometry buffer(final double distance) {
return buffer(distance, BufferParameters.DEFAULT_QUADRANT_SEGMENTS, LineCap.ROUND,
LineJoin.ROUND, BufferParameters.DEFAULT_MITRE_LIMIT);
}
default <G extends Geometry> G buffer(final double distance, final BufferParameters parameters) {
return Buffer.buffer(this, distance, parameters);
}
/**
* Computes a buffer area around this geometry having the given width and with
* a specified accuracy of approximation for circular arcs.
* <p>
* Mathematically-exact buffer area boundaries can contain circular arcs.
* To represent these arcs
* using linear geometry they must be approximated with line segments. The
* <code>quadrantSegments</code> argument allows controlling the accuracy of
* the approximation by specifying the number of line segments used to
* represent a quadrant of a circle
* <p>
* The buffer operation always returns a polygonal result. The negative or
* zero-distance buffer of lines and points is always an empty {@link Polygon}.
* This is also the result for the buffers of degenerate (zero-area) polygons.
*
* @param distance
* the width of the buffer (may be positive, negative or 0)
* @param quadrantSegments
* the number of line segments used to represent a quadrant of a
* circle
* @return a polygonal geometry representing the buffer region (which may be
* empty)
*
* @throws TopologyException
* if a robustness error occurs
*
* @see #buffer(double)
* @see #buffer(double, int, int)
*/
default Geometry buffer(final double distance, final int quadrantSegments) {
return buffer(distance, quadrantSegments, LineCap.ROUND, LineJoin.ROUND,
BufferParameters.DEFAULT_MITRE_LIMIT);
}
/**
* Computes a buffer area around this geometry having the given
* width and with a specified accuracy of approximation for circular arcs,
* and using a specified end cap style.
* <p>
* Mathematically-exact buffer area boundaries can contain circular arcs.
* To represent these arcs using linear geometry they must be approximated with line segments.
* The <code>quadrantSegments</code> argument allows controlling the
* accuracy of the approximation
* by specifying the number of line segments used to represent a quadrant of a circle
* <p>
* The end cap style specifies the buffer geometry that will be
* created at the ends of linestrings. The styles provided are:
* <ul>
* <li><code>{@link LineCap#ROUND}</code> - (default) a semi-circle
* <li><code>{@link LineCap#BUTT}</code> - a straight line perpendicular to the end segment
* <li><code>{@link LineCap#SQUARE}</code> - a half-square
* </ul>
* <p>
* The buffer operation always returns a polygonal result. The negative or
* zero-distance buffer of lines and points is always an empty {@link Polygon}.
* This is also the result for the buffers of degenerate (zero-area) polygons.
*
*@param distance the width of the buffer (may be positive, negative or 0)
*@param quadrantSegments the number of line segments used to represent a quadrant of a circle
*@param endCapStyle the end cap style to use
*@return a polygonal geometry representing the buffer region (which may be empty)
*
* @throws TopologyException if a robustness error occurs
*
* @see #buffer(double)
* @see #buffer(double, int)
*/
default Geometry buffer(final double distance, final int quadrantSegments,
final LineCap endCapStyle) {
return buffer(distance, quadrantSegments, endCapStyle, LineJoin.ROUND,
BufferParameters.DEFAULT_MITRE_LIMIT);
}
default <G extends Geometry> G buffer(final double distance, final int quadrantSegments,
final LineCap endCapStyle, final LineJoin joinStyle, final double mitreLimit) {
final BufferParameters parameters = new BufferParameters(quadrantSegments, endCapStyle,
joinStyle, mitreLimit);
return buffer(distance, parameters);
}
Geometry clone();
default int compareTo(final Geometry geometry) {
if (getClassSortIndex() != geometry.getClassSortIndex()) {
return getClassSortIndex() - geometry.getClassSortIndex();
} else if (isEmpty() && geometry.isEmpty()) {
return 0;
} else if (isEmpty()) {
return -1;
} else if (geometry.isEmpty()) {
return 1;
} else {
return compareToSameClass(geometry);
}
}
/**
* Returns whether this <code>Geometry</code> is greater than, equal to,
* or less than another <code>Geometry</code>. <P>
*
* If their classes are different, they are compared using the following
* ordering:
* <UL>
* <LI> Point (lowest)
* <LI> MultiPoint
* <LI> LineString
* <LI> LinearRing
* <LI> MultiLineString
* <LI> Polygon
* <LI> MultiPolygon
* <LI> GeometryCollection (highest)
* </UL>
* If the two <code>Geometry</code>s have the same class, their first
* elements are compared. If those are the same, the second elements are
* compared, etc.
*
*@param other a <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return a positive number, 0, or a negative number, depending on whether
* this object is greater than, equal to, or less than <code>o</code>, as
* defined in "Normal Form For Geometry" in the JTS Technical
* Specifications
*/
@Override
default int compareTo(final Object other) {
if (other instanceof Geometry) {
final Geometry geometry = (Geometry)other;
return compareTo(geometry);
} else {
return -1;
}
}
/**
* Returns whether this <code>Geometry</code> is greater than, equal to,
* or less than another <code>Geometry</code> having the same class.
*
*@param o a <code>Geometry</code> having the same class as this <code>Geometry</code>
*@return a positive number, 0, or a negative number, depending on whether
* this object is greater than, equal to, or less than <code>o</code>, as
* defined in "Normal Form For Geometry" in the JTS Technical
* Specifications
*/
int compareToSameClass(Geometry o);
/**
* In OGC terms this would be covers
*/
@Override
default boolean contains(final double x, final double y) {
return false;
}
@Override
default boolean contains(final double x, final double y, final double w, final double h) {
return false;
}
/**
* Tests whether this geometry contains the
* argument geometry.
* <p>
* The <code>contains</code> predicate has the following equivalent definitions:
* <ul>
* <li>Every point of the other geometry is a point of this geometry,
* and the interiors of the two geometries have at least one point in common.
* <li>The DE-9IM Intersection Matrix for the two geometries matches
* the pattern
* <code>[T*****FF*]</code>
* <li><code>g.within(this) = true</code>
* <br>(<code>contains</code> is the converse of {@link #within} )
* </ul>
* An implication of the definition is that "Geometries do not
* contain their boundary". In other words, if a geometry A is a subset of
* the points in the boundary of a geometry B, <code>B.contains(A) = false</code>.
* (As a concrete example, take A to be a LineString which lies in the boundary of a Polygon B.)
* For a predicate with similar behaviour but avoiding
* this subtle limitation, see {@link #covers}.
*
*@param geometry the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return <code>true</code> if this <code>Geometry</code> contains <code>g</code>
*
* @see Geometry#within
* @see Geometry#covers
*/
default boolean contains(final Geometry geometry) {
final BoundingBox boundingBox = getBoundingBox();
final BoundingBox otherBoundingBox = geometry.getBoundingBox();
if (boundingBox.covers(otherBoundingBox)) {
return relate(geometry).isContains();
} else {
return false;
}
}
@Override
default boolean contains(final Point2D point) {
final double x = point.getX();
final double y = point.getY();
return contains(x, y);
}
@Override
default boolean contains(final Rectangle2D rectangle) {
final double x = rectangle.getX();
final double y = rectangle.getY();
final double width = rectangle.getWidth();
final double height = rectangle.getHeight();
return contains(x, y, width, height);
}
default boolean containsProperly(final Geometry geometry) {
if (getBoundingBox().covers(geometry.getBoundingBox())) {
return relate(geometry, "T**FF*FF*");
} else {
return false;
}
}
@SuppressWarnings("unchecked")
default <G extends Geometry> G convertAxisCount(final int axisCount) {
if (getAxisCount() > axisCount) {
GeometryFactory geometryFactory = getGeometryFactory();
geometryFactory = geometryFactory.convertAxisCount(axisCount);
return (G)geometryFactory.geometry(this);
} else {
return (G)this;
}
}
@SuppressWarnings("unchecked")
default <V extends Geometry> V convertGeometry(final GeometryFactory geometryFactory) {
final GeometryFactory sourceGeometryFactory = getGeometryFactory();
if (geometryFactory == null || sourceGeometryFactory == geometryFactory) {
return (V)this;
} else {
return (V)newGeometry(geometryFactory);
}
}
@SuppressWarnings("unchecked")
default <V extends Geometry> V convertGeometry(GeometryFactory targetGeometryFactory,
final int targetAxisCount) {
if (targetGeometryFactory != null) {
targetGeometryFactory = targetGeometryFactory.convertAxisCount(targetAxisCount);
}
boolean copy = false;
if (targetGeometryFactory != null) {
final GeometryFactory sourceGeometryFactory = getGeometryFactory();
if (sourceGeometryFactory != targetGeometryFactory) {
if (sourceGeometryFactory.hasSameCoordinateSystem(targetGeometryFactory)) {
if (!targetGeometryFactory.isFloating()) {
final int sourceAxisCount = sourceGeometryFactory.getAxisCount();
final int minAxisCount = Math.min(sourceAxisCount, targetAxisCount);
for (int axisIndex = 0; axisIndex < minAxisCount; axisIndex++) {
final double sourceScale = sourceGeometryFactory.getScale(axisIndex);
final double targetScale = targetGeometryFactory.getScale(axisIndex);
if (!Doubles.equal(sourceScale, targetScale)) {
copy = true;
}
}
}
} else {
copy = true;
}
}
}
if (copy) {
return (V)newGeometry(targetGeometryFactory);
} else {
return (V)this;
}
}
default <V extends Geometry> V convertGeometry2dFloating() {
GeometryFactory geometryFactory = getGeometryFactory();
geometryFactory = geometryFactory.to2dFloating();
return convertGeometry(geometryFactory);
}
default <V extends Geometry> V convertScales(final double... scales) {
GeometryFactory geometryFactory = getGeometryFactory();
geometryFactory = geometryFactory.convertScales(scales);
return convertGeometry(geometryFactory);
}
/**
* Computes the smallest convex <code>Polygon</code> that contains all the
* points in the <code>Geometry</code>. This obviously applies only to <code>Geometry</code>
* s which contain 3 or more points; the results for degenerate cases are
* specified as follows:
* <TABLE>
* <TR>
* <TH> Number of <code>Point</code>s in argument <code>Geometry</code> </TH>
* <TH> <code>Geometry</code> class of result </TH>
* </TR>
* <TR>
* <TD> 0 </TD>
* <TD> empty <code>GeometryCollection</code> </TD>
* </TR>
* <TR> <TD> 1 </TD>
* <TD> <code>Point</code> </TD>
* </TR>
* <TR>
* <TD> 2 </TD>
* <TD> <code>LineString</code> </TD>
* </TR>
* <TR>
* <TD> 3 or more </TD>
* <TD> <code>Polygon</code> </TD>
* </TR>
* </TABLE>
*
*@return the minimum-area convex polygon containing this <code>Geometry</code>'
* s points
*/
default Geometry convexHull() {
return ConvexHull.convexHull(this);
}
/**
* Tests whether this geometry is covered by the
* argument geometry.
* <p>
* The <code>coveredBy</code> predicate has the following equivalent definitions:
* <ul>
* <li>Every point of this geometry is a point of the other geometry.
* <li>The DE-9IM Intersection Matrix for the two geometries matches
* at least one of the following patterns:
* <ul>
* <li><code>[T*F**F***]</code>
* <li><code>[*TF**F***]</code>
* <li><code>[**FT*F***]</code>
* <li><code>[**F*TF***]</code>
* </ul>
* <li><code>g.covers(this) = true</code>
* <br>(<code>coveredBy</code> is the converse of {@link #covers})
* </ul>
* If either geometry is empty, the value of this predicate is <code>false</code>.
* <p>
* This predicate is similar to {@link #within},
* but is more inclusive (i.e. returns <code>true</code> for more cases).
*
*@param g the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return <code>true</code> if this <code>Geometry</code> is covered by <code>g</code>
*
* @see Geometry#within
* @see Geometry#covers
*/
default boolean coveredBy(final Geometry g) {
return g.covers(this);
}
/**
* Tests whether this geometry covers the
* argument geometry.
* <p>
* The <code>covers</code> predicate has the following equivalent definitions:
* <ul>
* <li>Every point of the other geometry is a point of this geometry.
* <li>The DE-9IM Intersection Matrix for the two geometries matches
* at least one of the following patterns:
* <ul>
* <li><code>[T*****FF*]</code>
* <li><code>[*T****FF*]</code>
* <li><code>[***T**FF*]</code>
* <li><code>[****T*FF*]</code>
* </ul>
* <li><code>g.coveredBy(this) = true</code>
* <br>(<code>covers</code> is the converse of {@link #coveredBy})
* </ul>
* If either geometry is empty, the value of this predicate is <code>false</code>.
* <p>
* This predicate is similar to {@link #contains},
* but is more inclusive (i.e. returns <code>true</code> for more cases).
* In particular, unlike <code>contains</code> it does not distinguish between
* points in the boundary and in the interior of geometries.
* For most situations, <code>covers</code> should be used in preference to <code>contains</code>.
* As an added benefit, <code>covers</code> is more amenable to optimization,
* and hence should be more performant.
*
*@param g the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return <code>true</code> if this <code>Geometry</code> covers <code>g</code>
*
* @see Geometry#contains
* @see Geometry#coveredBy
*/
default boolean covers(final Geometry g) {
// short-circuit test
if (!getBoundingBox().covers(g.getBoundingBox())) {
return false;
}
// optimization for rectangle arguments
if (isRectangle()) {
// since we have already tested that the test boundingBox is covered
return true;
}
return relate(g).isCovers();
}
default boolean covers(final Point point) {
final double x = point.getX();
final double y = point.getY();
return contains(x, y);
}
/**
* Tests whether this geometry crosses the
* argument geometry.
* <p>
* The <code>crosses</code> predicate has the following equivalent definitions:
* <ul>
* <li>The geometries have some but not all interior points in common.
* <li>The DE-9IM Intersection Matrix for the two geometries matches
* one of the following patterns:
* <ul>
* <li><code>[T*T******]</code> (for P/L, P/A, and L/A situations)
* <li><code>[T*****T**]</code> (for L/P, A/P, and A/L situations)
* <li><code>[0********]</code> (for L/L situations)
* </ul>
* </ul>
* For any other combination of dimensions this predicate returns <code>false</code>.
* <p>
* The SFS defined this predicate only for P/L, P/A, L/L, and L/A situations.
* In order to make the relation symmetric,
* JTS extends the definition to apply to L/P, A/P and A/L situations as well.
*
*@param g the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return <code>true</code> if the two <code>Geometry</code>s cross.
*/
default boolean crosses(final Geometry g) {
// short-circuit test
if (!getBoundingBox().intersects(g.getBoundingBox())) {
return false;
}
return relate(g).isCrosses(getDimension(), g.getDimension());
}
<V extends Geometry> V deleteVertex(int... vertexId);
/**
* Computes a <code>Geometry</code> representing the closure of the point-set
* of the points contained in this <code>Geometry</code> that are not contained in
* the <code>other</code> Geometry.
* <p>
* If the result is empty, it is an atomic geometry
* with the dimension of the left-hand input.
* <p>{@link #isHeterogeneousGeometryCollection()} arguments are not supported.
*
*@param other the <code>Geometry</code> with which to compute the
* difference
*@return a Geometry representing the point-set difference of this <code>Geometry</code> with
* <code>other</code>
* @throws TopologyException if a robustness error occurs
* @throws IllegalArgumentException if either input is a non-empty GeometryCollection
*/
default Geometry difference(final Geometry other) {
// special case: if A.isEmpty ==> empty; if B.isEmpty ==> A
if (this.isEmpty()) {
return OverlayOp.newEmptyResult(OverlayOp.DIFFERENCE, this, other, getGeometryFactory());
} else if (other.isEmpty()) {
return clone();
} else {
checkNotGeometryCollection(this);
checkNotGeometryCollection(other);
return SnapIfNeededOverlayOp.overlayOp(this, other, OverlayOp.DIFFERENCE);
}
}
/**
* Tests whether this geometry is disjoint from the argument geometry.
* <p>
* The <code>disjoint</code> predicate has the following equivalent definitions:
* <ul>
* <li>The two geometries have no point in common
* <li>The DE-9IM Intersection Matrix for the two geometries matches
* <code>[FF*FF****]</code>
* <li><code>! g.intersects(this) = true</code>
* <br>(<code>disjoint</code> is the inverse of <code>intersects</code>)
* </ul>
*
*@param g the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return <code>true</code> if the two <code>Geometry</code>s are
* disjoint
*
* @see Geometry#intersects
*/
default boolean disjoint(final Geometry g) {
return !intersects(g);
}
/**
* Returns the minimum distance between this <code>Geometry</code>
* and another {@link Point}.
*
* @param x the x coordinate from which to compute the distance
* @param y the y coordinate from which to compute the distance
* @return the distance between the geometries or 0 if either input geometry is empty
* @throws IllegalArgumentException if g is null
*/
default double distance(final double x, final double y) {
return distance(x, y, 0.0);
}
/**
* Returns the minimum distance between this <code>Geometry</code>
* and another {@link Point}.
*
* @param x the x coordinate from which to compute the distance
* @param y the y coordinate from which to compute the distance
* @return the distance between the geometries or 0 if either input geometry is empty
* @throws IllegalArgumentException if g is null
*/
double distance(double x, double y, final double terminateDistance);
/**
* Returns the minimum distance between this <code>Geometry</code>
* and another <code>Geometry</code>.
*
* @param geometry the <code>Geometry</code> from which to compute the distance
* @return the distance between the geometries or 0 if either input geometry is empty
* @throws IllegalArgumentException if g is null
*/
default double distance(final Geometry geometry) {
return distance(geometry, 0.0);
}
/**
* Returns the minimum distance between this <code>Geometry</code>
* and another <code>Geometry</code>.
*
* @param geometry the <code>Geometry</code> from which to compute the distance
* @return the distance between the geometries or 0 if either input geometry is empty
* @throws IllegalArgumentException if g is null
*/
default double distance(Geometry geometry, final double terminateDistance) {
if (isEmpty()) {
return Double.POSITIVE_INFINITY;
} else if (Property.isEmpty(geometry)) {
return Double.POSITIVE_INFINITY;
} else if (geometry instanceof Point) {
final Point point = (Point)geometry;
return distance(point, terminateDistance);
} else {
final GeometryFactory geometryFactory = getGeometryFactory();
geometry = geometry.convertGeometry(geometryFactory, 2);
final DistanceOp distOp = new DistanceOp(this, geometry, terminateDistance);
final double distance = distOp.distance();
return distance;
}
}
/**
* Returns the minimum distance between this <code>Geometry</code>
* and another {@link Point}.
*
* @param point the point from which to compute the distance
* @return the distance between the geometries or 0 if either input geometry is empty
* @throws IllegalArgumentException if g is null
*/
default double distance(Point point, final double terminateDistance) {
if (isEmpty()) {
return Double.POSITIVE_INFINITY;
} else if (Property.isEmpty(point)) {
return Double.POSITIVE_INFINITY;
} else {
final GeometryFactory geometryFactory = getGeometryFactory();
point = point.convertPoint2d(geometryFactory);
final double x = point.getX();
final double y = point.getY();
return distance(x, y, terminateDistance);
}
}
/**
* Returns the minimum distance between this <code>Geometry</code>
* and another {@link Point}.
*
* @param point the point from which to compute the distance
* @return the distance between the geometries or 0 if either input geometry is empty
* @throws IllegalArgumentException if g is null
*/
default double distancePoint(final Point point) {
return distance(point, 0.0);
}
default boolean envelopeCovers(final Geometry geometry) {
if (getBoundingBox().covers(geometry.getBoundingBox())) {
return true;
} else {
return false;
}
}
default boolean envelopesIntersect(final Geometry geometry) {
if (getBoundingBox().intersects(geometry.getBoundingBox())) {
return true;
} else {
return false;
}
}
default boolean equal(final Point a, final Point b, final double tolerance) {
if (tolerance == 0) {
return a.equals(b);
} else {
return a.distancePoint(b) <= tolerance;
}
}
/**
* Tests whether this geometry is
* topologically equal to the argument geometry.
* <p>
* This method is included for backward compatibility reasons.
* It has been superseded by the {@link #equalsTopo(Geometry)} method,
* which has been named to clearly denote its functionality.
* <p>
* This method should NOT be confused with the method
* {@link #equals(Object)}, which implements
* an exact equality comparison.
*
*@param geometry the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return true if the two <code>Geometry</code>s are topologically equal
*
*@see #equalsTopo(Geometry)
*/
default boolean equals(final Geometry geometry) {
if (geometry == null) {
return false;
} else {
return equalsTopo(geometry);
}
}
boolean equals(int axisCount, Geometry geometry);
default boolean equalsExact(final Geometry geometry) {
if (geometry == null) {
return false;
} else {
final int axisCount = getAxisCount();
final int axisCount2 = geometry.getAxisCount();
if (axisCount == axisCount2) {
final int srid = getCoordinateSystemId();
final int otherSrid = geometry.getCoordinateSystemId();
if (srid == 0 || otherSrid == 0 || srid == otherSrid) {
return equals(axisCount, geometry);
}
}
}
return false;
}
/**
* Returns true if the two <code>Geometry</code>s are exactly equal,
* up to a specified distance tolerance.
* Two Geometries are exactly equal within a distance tolerance
* if and only if:
* <ul>
* <li>they have the same structure
* <li>they have the same values for their vertices,
* within the given tolerance distance, in exactly the same order.
* </ul>
* This method does <i>not</i>
* test the values of the <code>GeometryFactory</code>, the <code>SRID</code>,
* or the <code>userData</code> fields.
* <p>
* To properly test equality between different geometries,
* it is usually necessary to {@link #normalize()} them first.
*
* @param other the <code>Geometry</code> with which to compare this <code>Geometry</code>
* @param tolerance distance at or below which two <code>Coordinate</code>s
* are considered equal
* @return <code>true</code> if this and the other <code>Geometry</code>
* have identical structure and point values, up to the distance tolerance.
*
* @see #equals(2,Geometry)
* @see #normalize()
*/
boolean equalsExact(Geometry other, double tolerance);
default boolean equalsExactNormalize(final Geometry geometry) {
if (geometry == null) {
return false;
} else {
final Geometry geometry1 = normalize();
final Geometry geometry2 = geometry.normalize();
return geometry1.equalsExact(geometry2);
}
}
/**
* Tests whether two geometries are exactly equal
* in their normalized forms.
* This is a convenience method which creates normalized
* versions of both geometries before computing
* {@link #equals(2,Geometry)}.
* <p>
* This method is relatively expensive to compute.
* For maximum performance, the client
* should instead perform normalization on the individual geometries
* at an appropriate point during processing.
*
* @param g a Geometry
* @return true if the input geometries are exactly equal in their normalized form
*/
default boolean equalsNorm(final Geometry g) {
if (g == null) {
return false;
}
return normalize().equals(2, g.normalize());
}
/**
* Tests whether this geometry is topologically equal to the argument geometry
* as defined by the SFS <code>equals</code> predicate.
* <p>
* The SFS <code>equals</code> predicate has the following equivalent definitions:
* <ul>
* <li>The two geometries have at least one point in common,
* and no point of either geometry lies in the exterior of the other geometry.
* <li>The DE-9IM Intersection Matrix for the two geometries matches
* the pattern <code>T*F**FFF*</code>
* <pre>
* T*F
* **F
* FF*
* </pre>
* </ul>
* <b>Note</b> that this method computes <b>topologically equality</b>.
* For structural equality, see {@link #equals(2,Geometry)}.
*
*@param g the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return <code>true</code> if the two <code>Geometry</code>s are topologically equal
*
*@see #equals(2,Geometry)
*/
default boolean equalsTopo(final Geometry g) {
// short-circuit test
if (!getBoundingBox().equals(g.getBoundingBox())) {
return false;
}
return relate(g).isEquals(getDimension(), g.getDimension());
}
default Pair<GeometryComponent, Double> findClosestGeometryComponent(final double x,
final double y) {
if (isEmpty()) {
return new Pair<>();
} else {
GeometryComponent closestComponent = null;
double closestDistance = Double.POSITIVE_INFINITY;
for (final Segment segment : segments()) {
if (segment.isLineStart()) {
final Vertex from = segment.getGeometryVertex(0);
if (from.equalsVertex(x, y)) {
return new Pair<>(from, 0.0);
} else {
final double fromDistance = from.distance(x, y);
if (fromDistance < closestDistance || //
fromDistance == closestDistance && !(closestComponent instanceof Vertex)) {
closestDistance = fromDistance;
closestComponent = from.clone();
}
}
}
{
final Vertex to = segment.getGeometryVertex(1);
if (to.equalsVertex(x, y)) {
return new Pair<>(to, 0.0);
} else {
final double toDistance = to.distance(x, y);
if (toDistance < closestDistance || //
toDistance == closestDistance && !(closestComponent instanceof Vertex)) {
closestDistance = toDistance;
closestComponent = to.clone();
}
}
}
{
final double segmentDistance = segment.distance(x, y);
if (segmentDistance == 0) {
return new Pair<>(segment, 0.0);
} else if (segmentDistance < closestDistance || //
segmentDistance == closestDistance && !(closestComponent instanceof Vertex)) {
closestDistance = segmentDistance;
closestComponent = segment.clone();
}
}
}
if (Double.isFinite(closestDistance)) {
return new Pair<>(closestComponent, closestDistance);
} else {
return new Pair<>();
}
}
}
default Pair<GeometryComponent, Double> findClosestGeometryComponent(final double x,
final double y, final double maxDistance) {
if (isEmpty()) {
return new Pair<>();
} else {
GeometryComponent closestComponent = null;
double closestDistance = Double.POSITIVE_INFINITY;
for (final Segment segment : segments()) {
boolean matched = false;
if (segment.isLineStart()) {
final Vertex from = segment.getGeometryVertex(0);
if (from.equalsVertex(x, y)) {
return new Pair<>(from, 0.0);
} else {
final double fromDistance = from.distance(x, y);
if (fromDistance <= maxDistance) {
if (fromDistance < closestDistance || //
fromDistance == closestDistance && !(closestComponent instanceof Vertex)) {
closestDistance = fromDistance;
closestComponent = from.clone();
matched = true;
}
}
}
}
{
final Vertex to = segment.getGeometryVertex(1);
if (to.equalsVertex(x, y)) {
return new Pair<>(to, 0.0);
} else {
final double toDistance = to.distance(x, y);
if (toDistance <= maxDistance) {
if (toDistance < closestDistance || //
toDistance == closestDistance && !(closestComponent instanceof Vertex)) {
closestDistance = toDistance;
closestComponent = to.clone();
matched = true;
}
}
}
}
if (!matched) {
final double segmentDistance = segment.distance(x, y);
if (segmentDistance == 0) {
return new Pair<>(segment, 0.0);
} else if (segmentDistance <= maxDistance) {
if (segmentDistance < closestDistance || //
segmentDistance == closestDistance && !(closestComponent instanceof Vertex)) {
closestDistance = segmentDistance;
closestComponent = segment.clone();
}
}
}
}
if (Double.isFinite(closestDistance)) {
return new Pair<>(closestComponent, closestDistance);
} else {
return new Pair<>();
}
}
}
default Pair<GeometryComponent, Double> findClosestGeometryComponent(final Point point) {
return findClosestGeometryComponent(point, Double.POSITIVE_INFINITY);
}
default Pair<GeometryComponent, Double> findClosestGeometryComponent(Point point,
final double maxDistance) {
if (point.isEmpty()) {
return new Pair<>();
} else {
final GeometryFactory geometryFactory = getGeometryFactory().to2dFloating();
point = point.convertGeometry(geometryFactory);
final double x = point.getX();
final double y = point.getY();
return findClosestGeometryComponent(x, y, maxDistance);
}
}
default Iterable<Geometry> geometries() {
return getGeometries();
}
/**
* Returns the area of this <code>Geometry</code>.
* Areal Geometries have a non-zero area.
* They override this function to compute the area.
* Others return 0.0
*
*@return the area of the Geometry
*/
default double getArea() {
return 0.0;
}
default double getArea(final Unit<Area> unit) {
return 0.0;
}
int getAxisCount();
/**
* Returns the boundary, or an empty geometry of appropriate dimension
* if this <code>Geometry</code> is empty.
* (In the case of zero-dimensional geometries, '
* an empty GeometryCollection is returned.)
* For a discussion of this function, see the OpenGIS Simple
* Features Specification. As stated in SFS Section 2.1.13.1, "the boundary
* of a Geometry is a set of Geometries of the next lower dimension."
*
*@return the closure of the combinatorial boundary of this <code>Geometry</code>
*/
Geometry getBoundary();
/**
* Returns the dimension of this <code>Geometry</code>s inherent boundary.
*
*@return the dimension of the boundary of the class implementing this
* interface, whether or not this object is the empty geometry. Returns
* <code>Dimension.FALSE</code> if the boundary is the empty geometry.
*/
int getBoundaryDimension();
/**
* Gets an {@link BoundingBox} containing
* the minimum and maximum x and y values in this <code>Geometry</code>.
* If the geometry is empty, an empty <code>BoundingBox</code>
* is returned.
* <p>
* The returned object is a copy of the one maintained internally,
* to avoid aliasing issues.
* For best performance, clients which access this
* boundingBox frequently should cache the return value.
*
*@return the boundingBox of this <code>Geometry</code>.
*@return an empty BoundingBox if this Geometry is empty
*/
@Override
default BoundingBox getBoundingBox() {
return newBoundingBox();
}
@Override
default Rectangle getBounds() {
final Rectangle2D bounds2d = getBounds2D();
if (bounds2d == null) {
return null;
} else {
return bounds2d.getBounds();
}
}
@Override
default Rectangle2D getBounds2D() {
final BoundingBox boundingBox = getBoundingBox();
if (boundingBox.isEmpty()) {
return null;
} else {
final double x = boundingBox.getMinX();
final double y = boundingBox.getMinY();
final double width = boundingBox.getWidth();
final double height = boundingBox.getHeight();
return new Rectangle2D.Double(x, y, width, height);
}
}
/**
* Computes the centroid of this <code>Geometry</code>.
* The centroid
* is equal to the centroid of the set of component Geometries of highest
* dimension (since the lower-dimension geometries contribute zero
* "weight" to the centroid).
* <p>
* The centroid of an empty geometry is <code>POINT EMPTY</code>.
*
* @return a {@link Point} which is the centroid of this Geometry
*/
default Point getCentroid() {
if (isEmpty()) {
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.point();
} else {
return Centroid.getCentroid(this);
}
}
default int getClassSortIndex() {
final String geometryType = getGeometryType();
final int index = SORTED_GEOMETRY_TYPES.indexOf(geometryType);
return index;
}
/**
*
* @author Paul Austin <paul.austin@revolsys.com>
* @return
*/
@Override
default CoordinateSystem getCoordinateSystem() {
return getGeometryFactory().getCoordinateSystem();
}
@Override
default DataType getDataType() {
return DataTypes.GEOMETRY;
}
/**
* Returns the dimension of this geometry.
* The dimension of a geometry is is the topological
* dimension of its embedding in the 2-D Euclidean plane.
* In the JTS spatial model, dimension values are in the set {0,1,2}.
* <p>
* Note that this is a different concept to the dimension of
* the vertex {@link Coordinates}s.
* The geometry dimension can never be greater than the coordinate dimension.
* For example, a 0-dimensional geometry (e.g. a Point)
* may have a coordinate dimension of 3 (X,Y,Z).
*
*@return the topological dimension of this geometry.
*/
int getDimension();
/**
* Gets a Geometry representing the boundingBox (bounding box) of
* this <code>Geometry</code>.
* <p>
* If this <code>Geometry</code> is:
* <ul>
* <li>empty, returns an empty <code>Point</code>.
* <li>a point, returns a <code>Point</code>.
* <li>a line parallel to an axis, a two-vertex <code>LineString</code>
* <li>otherwise, returns a
* <code>Polygon</code> whose vertices are (minx miny, maxx miny,
* maxx maxy, minx maxy, minx miny).
* </ul>
*
*@return a Geometry representing the boundingBox of this Geometry
*
* @see GeometryFactory#toLineString(BoundingBox)
*/
default Geometry getEnvelope() {
return getBoundingBox().toGeometry();
}
@SuppressWarnings("unchecked")
default <V extends Geometry> List<V> getGeometries() {
return (List<V>)Arrays.asList(this);
}
@SuppressWarnings("unchecked")
default <V extends Geometry> List<V> getGeometries(final Class<V> geometryClass) {
final List<V> geometries = new ArrayList<>();
if (geometryClass.isAssignableFrom(getClass())) {
geometries.add((V)this);
}
return geometries;
}
default <V extends Geometry> V getGeometry(final Class<? extends Geometry> geometryClass) {
final List<? extends Geometry> geometries = getGeometries(geometryClass);
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.geometry(geometries);
}
/**
* Returns an element {@link Geometry} from a {@link GeometryCollection}
* (or <code>this</code>, if the geometry is not a collection).
*
* @param partIndex the index of the geometry element
* @return the n'th geometry contained in this geometry
*/
@SuppressWarnings("unchecked")
default <V extends Geometry> V getGeometry(final int partIndex) {
return (V)this;
}
@SuppressWarnings("unchecked")
default <V extends Geometry> List<V> getGeometryComponents(final Class<V> geometryClass) {
if (geometryClass.isAssignableFrom(getClass())) {
return Collections.singletonList((V)this);
} else {
return Collections.emptyList();
}
}
/**
* Returns the number of {@link Geometry}s in a {@link GeometryCollection}
* (or 1, if the geometry is not a collection).
*
* @return the number of geometries contained in this geometry
*/
default int getGeometryCount() {
if (isEmpty()) {
return 0;
} else {
return 1;
}
}
@Override
default GeometryFactory getGeometryFactory() {
final int axisCount = getAxisCount();
if (axisCount == 2) {
return GeometryFactory.DEFAULT_2D;
} else if (axisCount == 2) {
return GeometryFactory.DEFAULT_3D;
} else {
return GeometryFactory.floating(0, axisCount);
}
}
/**
* Returns the name of this Geometry's actual class.
*
*@return the name of this <code>Geometry</code>s actual class
*/
default String getGeometryType() {
return getDataType().toString();
}
/**
* Computes an interior point of this <code>Geometry</code>.
* An interior point is guaranteed to lie in the interior of the Geometry,
* if it possible to calculate such a point exactly. Otherwise,
* the point may lie on the boundary of the geometry.
* <p>
* The interior point of an empty geometry is <code>POINT EMPTY</code>.
*
* @return a {@link Point} which is in the interior of this Geometry
*/
default Point getInteriorPoint() {
final GeometryFactory geometryFactory = getGeometryFactory();
if (isEmpty()) {
return geometryFactory.point();
} else {
Point interiorPt = null;
final int dim = getDimension();
if (dim == 0) {
final Point centroid = getCentroid();
final double centroidX = centroid.getX();
final double centroidY = centroid.getY();
double minDistance = Double.MAX_VALUE;
Point interiorPoint = null;
for (final Point point : getGeometries(Point.class)) {
final double distance = point.distance(centroidX, centroidY);
if (distance < minDistance) {
interiorPoint = point;
minDistance = distance;
}
}
return interiorPoint;
} else if (dim == 1) {
final InteriorPointLine intPt = new InteriorPointLine(this);
interiorPt = intPt.getInteriorPoint();
} else {
final InteriorPointArea intPt = new InteriorPointArea(this);
interiorPt = intPt.getInteriorPoint();
}
return geometryFactory.point(interiorPt.getX(), interiorPt.getY());
}
}
default List<GeometryValidationError> getIsSimpleErrors() {
return getIsSimpleErrors(false);
}
default List<GeometryValidationError> getIsSimpleErrors(final boolean shortCircuit) {
final List<GeometryValidationError> errors = new ArrayList<>();
addIsSimpleErrors(errors, shortCircuit);
return errors;
}
/**
* Returns the length of this <code>Geometry</code>.
* Linear geometries return their length.
* Areal geometries return their perimeter.
* Others return 0.0
*
* @return the length of the Geometry
*/
default double getLength() {
return 0.0;
}
default double getLength(final Unit<Length> unit) {
return 0.0;
}
@Override
default GeometryFactory getNonZeroGeometryFactory(GeometryFactory geometryFactory) {
final GeometryFactory geometryFactoryThis = getGeometryFactory();
if (geometryFactory == null) {
return geometryFactoryThis;
} else {
final int srid = geometryFactory.getCoordinateSystemId();
if (srid == 0) {
final int geometrySrid = geometryFactoryThis.getCoordinateSystemId();
if (geometrySrid != 0) {
geometryFactory = geometryFactory.convertSrid(geometrySrid);
}
}
return geometryFactory;
}
}
@Override
default PathIterator getPathIterator(final AffineTransform transform) {
final Vertex vertex = vertices();
if (transform == null) {
return new VertexPathIterator(vertex);
} else {
return new VertexPathIteratorTransform(vertex, transform);
}
}
@Override
default PathIterator getPathIterator(final AffineTransform transform, final double flatness) {
return getPathIterator(transform);
}
/**
* Returns a vertex of this <code>Geometry</code>
* (usually, but not necessarily, the first one).
* The returned coordinate should not be assumed
* to be an actual Point object used in
* the internal representation.
*
*@return a {@link Coordinates} which is a vertex of this <code>Geometry</code>.
*@return null if this Geometry is empty
*/
Point getPoint();
Point getPointWithin();
/**
* <p>Get the {@link Segment} at the specified vertexId (see {@link Segment#getSegmentId()}).</p>
*
* @author Paul Austin <paul.austin@revolsys.com>
* @param vertexId The id of the vertex.
* @return The vertex or null if it does not exist.
*/
Segment getSegment(final int... segmentId);
default int getSegmentCount() {
return 0;
}
/**
* <p>Get the {@link Vertex} at the specified vertexId starting at the end of the geometry (see {@link Vertex#getVertexId()}).</p>
*
* @author Paul Austin <paul.austin@revolsys.com>
* @param vertexId The id of the vertex.
* @return The vertex or null if it does not exist.
*/
Vertex getToVertex(final int... vertexId);
/**
* <p>Get the {@link Vertex} at the specified vertexId (see {@link Vertex#getVertexId()}).</p>
*
* @author Paul Austin <paul.austin@revolsys.com>
* @param vertexId The id of the vertex.
* @return The vertex or null if it does not exist.
*/
Vertex getVertex(final int... vertexId);
/**
* Returns the count of this <code>Geometry</code>s vertices. The <code>Geometry</code>
* s contained by composite <code>Geometry</code>s must be
* Geometry's; that is, they must implement <code>getNumPoints</code>
*
*@return the number of vertices in this <code>Geometry</code>
*/
int getVertexCount();
boolean hasInvalidXyCoordinates();
<V extends Geometry> V insertVertex(Point newPoint, int... vertexId);
/**
* Computes a <code>Geometry</code> representing the point-set which is
* common to both this <code>Geometry</code> and the <code>other</code> Geometry.
* <p>
* The intersection of two geometries of different dimension produces a result
* geometry of dimension less than or equal to the minimum dimension of the input
* geometries.
* The result geometry may be a {@link #isHeterogeneousGeometryCollection()}.
* If the result is empty, it is an atomic geometry
* with the dimension of the lowest input dimension.
* <p>
* Intersection of {@link #isHomogeneousGeometryCollection()}s is supported
* only.
* <p>
* {@link #isHeterogeneousGeometryCollection()} arguments are not supported.
*
* @param geometry the <code>Geometry</code> with which to compute the intersection
* @return a Geometry representing the point-set common to the two <code>Geometry</code>s
* @throws TopologyException if a robustness error occurs
* @throws IllegalArgumentException if the argument is a non-empty heterogeneous <code>GeometryCollection</code>
*/
default Geometry intersection(final Geometry geometry) {
/**
* TODO: MD - add optimization for P-A case using Point-In-Polygon
*/
final GeometryFactory geometryFactory = getGeometryFactory();
if (this.isEmpty() || geometry.isEmpty()) {
// special case: if one input is empty ==> empty
return OverlayOp.newEmptyResult(OverlayOp.INTERSECTION, this, geometry, geometryFactory);
} else if (this.isHeterogeneousGeometryCollection()) {
final List<Geometry> geometries = new ArrayList<>();
for (final Geometry part : geometries()) {
final Geometry partIntersection = part.intersection(geometry);
if (!partIntersection.isEmpty()) {
if (partIntersection.isGeometryCollection()) {
geometries.addAll(partIntersection.getGeometries());
} else {
geometries.add(partIntersection);
}
}
}
return geometryFactory.geometry(geometries);
} else {
checkNotGeometryCollection(this);
checkNotGeometryCollection(geometry);
return SnapIfNeededOverlayOp.overlayOp(this, geometry, OverlayOp.INTERSECTION);
}
}
boolean intersects(BoundingBox boundingBox);
@Override
default boolean intersects(final double x, final double y, final double width,
final double height) {
final GeometryFactory geometryFactory = getGeometryFactory();
final BoundingBox boundingBox = geometryFactory.newBoundingBox(x, y, x + width, y + height);
return intersects(boundingBox);
}
/**
* Tests whether this geometry intersects the argument geometry.
* <p>
* The <code>intersects</code> predicate has the following equivalent definitions:
* <ul>
* <li>The two geometries have at least one point in common
* <li>The DE-9IM Intersection Matrix for the two geometries matches
* at least one of the patterns
* <ul>
* <li><code>[T********]</code>
* <li><code>[*T*******]</code>
* <li><code>[***T*****]</code>
* <li><code>[****T****]</code>
* </ul>
* <li><code>! g.disjoint(this) = true</code>
* <br>(<code>intersects</code> is the inverse of <code>disjoint</code>)
* </ul>
*
*@param g the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return <code>true</code> if the two <code>Geometry</code>s intersect
*
* @see Geometry#disjoint
*/
default boolean intersects(final Geometry g) {
// short-circuit boundingBox test
if (!getBoundingBox().intersects(g.getBoundingBox())) {
return false;
}
/**
* TODO: (MD) Add optimizations:
*
* - for P-A case:
* If P is in env(A), test for point-in-poly
*
* - for A-A case:
* If env(A1).overlaps(env(A2))
* test for overlaps via point-in-poly first (both ways)
* Possibly optimize selection of point to test by finding point of A1
* closest to centre of env(A2).
* (Is there a test where we shouldn't bother - e.g. if env A
* is much smaller than env B, maybe there's no point in testing
* pt(B) in env(A)?
*/
// optimization for rectangle arguments
if (isRectangle()) {
return RectangleIntersects.intersects((Polygon)this, g);
}
if (g.isRectangle()) {
return RectangleIntersects.intersects((Polygon)g, this);
}
// general case
return relate(g).isIntersects();
}
default boolean intersects(final Point point) {
return locate(point) != Location.EXTERIOR;
}
@Override
default boolean intersects(final Rectangle2D rectangle) {
final double x = rectangle.getX();
final double y = rectangle.getY();
final double width = rectangle.getWidth();
final double height = rectangle.getHeight();
return intersects(x, y, width, height);
}
boolean isContainedInBoundary(final BoundingBox boundingBox);
/**
* Returns whether the two <code>Geometry</code>s are equal, from the point
* of view of the <code>equalsExact</code> method. Called by <code>equalsExact</code>
* . In general, two <code>Geometry</code> classes are considered to be
* "equivalent" only if they are the same class. An exception is <code>LineString</code>
* , which is considered to be equivalent to its subclasses.
*
*@param other the <code>Geometry</code> with which to compare this <code>Geometry</code>
* for equality
*@return <code>true</code> if the classes of the two <code>Geometry</code>
* s are considered to be equal by the <code>equalsExact</code> method.
*/
default boolean isEquivalentClass(final Geometry other) {
return this.getClass().getName().equals(other.getClass().getName());
}
default boolean isGeometryCollection() {
return false;
}
default boolean isHeterogeneousGeometryCollection() {
if (isGeometryCollection()) {
return !isHomogeneousGeometryCollection();
} else {
return false;
}
}
/**
* Are all the elements of the collection the same type of geometry
*
* @return
*/
default boolean isHomogeneousGeometryCollection() {
return false;
}
/**
* Tests whether the distance from this <code>Geometry</code>
* to another is less than or equal to a specified value.
*
* @param geometry the Geometry to check the distance to
* @param distance the distance value to compare
* @return <code>true</code> if the geometries are less than <code>distance</code> apart.
*/
default boolean isLessThanDistance(Geometry geometry, final double distance) {
final GeometryFactory geometryFactory = getGeometryFactory();
geometry = geometry.convertGeometry(geometryFactory, 2);
final BoundingBox boundingBox = getBoundingBox();
final BoundingBox boundingBox2 = geometry.getBoundingBox();
final double bboxDistance = boundingBox.distance(boundingBox2);
if (bboxDistance > distance) {
return false;
} else {
final double geometryDistance = this.distance(geometry);
return geometryDistance < distance;
}
}
default boolean isRectangle() {
// Polygon overrides to check for actual rectangle
return false;
}
/**
* Tests whether this {@link Geometry} is simple.
* The SFS definition of simplicity
* follows the general rule that a Geometry is simple if it has no points of
* self-tangency, self-intersection or other anomalous points.
* <p>
* Simplicity is defined for each {@link Geometry} subclass as follows:
* <ul>
* <li>Valid polygonal geometries are simple, since their rings
* must not self-intersect. <code>isSimple</code>
* tests for this condition and reports <code>false</code> if it is not met.
* (This is a looser test than checking for validity).
* <li>Linear rings have the same semantics.
* <li>Linear geometries are simple iff they do not self-intersect at points
* other than boundary points.
* <li>Zero-dimensional geometries (points) are simple iff they have no
* repeated points.
* <li>Empty <code>Geometry</code>s are always simple.
* <ul>
*
* @return <code>true</code> if this <code>Geometry</code> is simple
* @see #isValid
*/
default boolean isSimple() {
final List<GeometryValidationError> errors = new ArrayList<>();
return addIsSimpleErrors(errors, true);
}
/**
* Tests whether this <code>Geometry</code>
* is topologically valid, according to the OGC SFS specification.
* <p>
* For validity rules see the Javadoc for the specific Geometry subclass.
*
*@return <code>true</code> if this <code>Geometry</code> is valid
*
* @see IsValidOp
*/
default boolean isValid() {
return IsValidOp.isValid(this);
}
/**
* Tests whether the distance from this <code>Geometry</code>
* to another is less than or equal to a specified value.
*
* @param geometry the Geometry to check the distance to
* @param distance the distance value to compare
* @return <code>true</code> if the geometries are less than <code>distance</code> apart.
*/
default boolean isWithinDistance(Geometry geometry, final double distance) {
final GeometryFactory geometryFactory = getGeometryFactory();
geometry = geometry.convertGeometry(geometryFactory, 2);
final BoundingBox boundingBox = getBoundingBox();
final BoundingBox boundingBox2 = geometry.getBoundingBox();
final double bboxDistance = boundingBox.distance(boundingBox2);
if (bboxDistance > distance) {
return false;
} else {
final double geometryDistance = this.distance(geometry);
return geometryDistance <= distance;
}
}
Location locate(Point point);
Geometry move(final double... deltas);
<V extends Geometry> V moveVertex(Point newPoint, int... vertexId);
/**
* Returns the minimum and maximum x and y values in this <code>Geometry</code>
* , or a null <code>BoundingBox</code> if this <code>Geometry</code> is empty.
* Unlike <code>getEnvelopeInternal</code>, this method calculates the <code>BoundingBox</code>
* each time it is called; <code>getEnvelopeInternal</code> caches the result
* of this method.
*
*@return this <code>Geometry</code>s bounding box; if the <code>Geometry</code>
* is empty, <code>BoundingBox#isNull</code> will return <code>true</code>
*/
default BoundingBox newBoundingBox() {
final GeometryFactory geometryFactory = getGeometryFactory();
if (isEmpty()) {
return geometryFactory.newBoundingBoxEmpty();
} else {
final Iterable<Vertex> vertices = vertices();
return geometryFactory.newBoundingBox(vertices);
}
}
/**
* Construct a new copy of the geometry to the required geometry factory. Projecting to the required
* coordinate system and applying the precision model.
*
* @param geometryFactory The geometry factory to convert the geometry to.
* @return The converted geometry
*/
Geometry newGeometry(GeometryFactory geometryFactory);
@SuppressWarnings("unchecked")
default <G extends Geometry> G newGeometry(GeometryFactory targetGeometryFactory,
final int axisCount) {
if (targetGeometryFactory == null) {
return newGeometry(axisCount);
} else {
targetGeometryFactory = targetGeometryFactory.convertAxisCount(2);
return (G)newGeometry(targetGeometryFactory);
}
}
@SuppressWarnings("unchecked")
default <G extends Geometry> G newGeometry(final int axisCount) {
final GeometryFactory geometryFactory = getGeometryFactory();
final GeometryFactory targetGeometryFactory = geometryFactory.convertAxisCount(2);
return (G)newGeometry(targetGeometryFactory);
}
default GeometryEditor newGeometryEditor() {
throw new UnsupportedOperationException();
}
default GeometryEditor newGeometryEditor(final int axisCount) {
final GeometryEditor geometryEditor = newGeometryEditor();
geometryEditor.setAxisCount(axisCount);
return geometryEditor;
}
/**
* Return a new geometry with the same coordinates but using the geometry factory. No projection will be performed.
*
* @param factory
* @return
*/
<G> G newUsingGeometryFactory(GeometryFactory factory);
@SuppressWarnings("unchecked")
default <G extends Geometry> G newValidGeometry() {
return (G)this;
}
/**
* Converts this <code>Geometry</code> to <b>normal form</b> (or <b>
* canonical form</b> ). Normal form is a unique representation for <code>Geometry</code>
* s. It can be used to test whether two <code>Geometry</code>s are equal
* in a way that is independent of the ordering of the coordinates within
* them. Normal form equality is a stronger condition than topological
* equality, but weaker than pointwise equality. The definitions for normal
* form use the standard lexicographical ordering for coordinates. "Sorted in
* order of coordinates" means the obvious extension of this ordering to
* sequences of coordinates.
*
* @return a normalized copy of this geometry.
*/
Geometry normalize();
/**
* Tests whether this geometry overlaps the
* specified geometry.
* <p>
* The <code>overlaps</code> predicate has the following equivalent definitions:
* <ul>
* <li>The geometries have at least one point each not shared by the other
* (or equivalently neither covers the other),
* they have the same dimension,
* and the intersection of the interiors of the two geometries has
* the same dimension as the geometries themselves.
* <li>The DE-9IM Intersection Matrix for the two geometries matches
* <code>[T*T***T**]</code> (for two points or two surfaces)
* or <code>[1*T***T**]</code> (for two curves)
* </ul>
* If the geometries are of different dimension this predicate returns <code>false</code>.
* This predicate is symmetric.
*
*@param g the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return <code>true</code> if the two <code>Geometry</code>s overlap.
*/
default boolean overlaps(final Geometry g) {
// short-circuit test
if (!getBoundingBox().intersects(g.getBoundingBox())) {
return false;
}
return relate(g).isOverlaps(getDimension(), g.getDimension());
}
/**
* Get vertices from any point component.
*
* @return
*/
default List<Vertex> pointVertices() {
return Collections.emptyList();
}
Geometry prepare();
/**
* Returns the DE-9IM {@link IntersectionMatrix} for the two <code>Geometry</code>s.
*
*@param g the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return an {@link IntersectionMatrix} describing the intersections of the interiors,
* boundaries and exteriors of the two <code>Geometry</code>s
*/
default IntersectionMatrix relate(final Geometry g) {
checkNotGeometryCollection(this);
checkNotGeometryCollection(g);
return RelateOp.relate(this, g);
}
/**
* Tests whether the elements in the DE-9IM
* {@link IntersectionMatrix} for the two <code>Geometry</code>s match the elements in <code>intersectionPattern</code>.
* The pattern is a 9-character string, with symbols drawn from the following set:
* <UL>
* <LI> 0 (dimension 0)
* <LI> 1 (dimension 1)
* <LI> 2 (dimension 2)
* <LI> T ( matches 0, 1 or 2)
* <LI> F ( matches FALSE)
* <LI> * ( matches any value)
* </UL>
* For more information on the DE-9IM, see the <i>OpenGIS Simple Features
* Specification</i>.
*
*@param g the <code>Geometry</code> with which to compare
* this <code>Geometry</code>
*@param intersectionPattern the pattern against which to check the
* intersection matrix for the two <code>Geometry</code>s
*@return <code>true</code> if the DE-9IM intersection
* matrix for the two <code>Geometry</code>s match <code>intersectionPattern</code>
* @see IntersectionMatrix
*/
default boolean relate(final Geometry g, final String intersectionPattern) {
return relate(g).matches(intersectionPattern);
}
Geometry removeDuplicatePoints();
/**
* Computes a new geometry which has all component coordinate sequences
* in reverse order (opposite orientation) to this one.
*
* @return a reversed geometry
*/
Geometry reverse();
default Iterable<Segment> segments() {
return Collections.emptyList();
}
/**
* Computes a <coe>Geometry </code> representing the closure of the point-set
* which is the union of the points in this <code>Geometry</code> which are not
* contained in the <code>other</code> Geometry,
* with the points in the <code>other</code> Geometry not contained in this
* <code>Geometry</code>.
* If the result is empty, it is an atomic geometry
* with the dimension of the highest input dimension.
* <p>
* {@link #isHeterogeneousGeometryCollection()} arguments are not supported.
*
*@param other the <code>Geometry</code> with which to compute the symmetric
* difference
*@return a Geometry representing the point-set symmetric difference of this <code>Geometry</code>
* with <code>other</code>
* @throws TopologyException if a robustness error occurs
* @throws IllegalArgumentException if either input is a non-empty GeometryCollection
*/
default Geometry symDifference(final Geometry other) {
// handle empty geometry cases
if (this.isEmpty() || other.isEmpty()) {
// both empty - check dimensions
if (this.isEmpty() && other.isEmpty()) {
return OverlayOp.newEmptyResult(OverlayOp.SYMDIFFERENCE, this, other, getGeometryFactory());
}
// special case: if either input is empty ==> result = other arg
if (this.isEmpty()) {
return other.clone();
}
if (other.isEmpty()) {
return clone();
}
}
checkNotGeometryCollection(this);
checkNotGeometryCollection(other);
return SnapIfNeededOverlayOp.overlayOp(this, other, OverlayOp.SYMDIFFERENCE);
}
@SuppressWarnings("unchecked")
default <G extends Geometry> G toClockDirection(final ClockDirection clockDirection) {
if (clockDirection.isClockwise()) {
return toClockwise();
} else if (clockDirection.isCounterClockwise()) {
return toCounterClockwise();
} else {
return (G)this;
}
}
@SuppressWarnings("unchecked")
default <G extends Geometry> G toClockwise() {
return (G)this;
}
@SuppressWarnings("unchecked")
default <G extends Geometry> G toCounterClockwise() {
return (G)this;
}
/**
* <p>Returns the Extended Well-known Text representation of this <code>Geometry</code>.
* For a definition of the Well-known Text format, see the OpenGIS Simple
* Features Specification.</p>
*
*@return the Well-known Text representation of this <code>Geometry</code>
*/
default String toEwkt() {
return EWktWriter.toString(this, true);
}
/**
* Tests whether this geometry touches the
* argument geometry.
* <p>
* The <code>touches</code> predicate has the following equivalent definitions:
* <ul>
* <li>The geometries have at least one point in common,
* but their interiors do not intersect.
* <li>The DE-9IM Intersection Matrix for the two geometries matches
* at least one of the following patterns
* <ul>
* <li><code>[FT*******]</code>
* <li><code>[F**T*****]</code>
* <li><code>[F***T****]</code>
* </ul>
* </ul>
* If both geometries have dimension 0, the predicate returns <code>false</code>,
* since points have only interiors.
* This predicate is symmetric.
*
*
*@param g the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return <code>true</code> if the two <code>Geometry</code>s touch;
* Returns <code>false</code> if both <code>Geometry</code>s are points
*/
default boolean touches(final Geometry g) {
// short-circuit test
if (!getBoundingBox().intersects(g.getBoundingBox())) {
return false;
}
return relate(g).isTouches(getDimension(), g.getDimension());
}
/**
* <p>Returns the Well-known Text representation of this <code>Geometry</code>.
* For a definition of the Well-known Text format, see the OpenGIS Simple
* Features Specification.</p>
*
*@return the Well-known Text representation of this <code>Geometry</code>
*/
default String toWkt() {
return EWktWriter.toString(this, false);
}
/**
* Computes the union of all the elements of this geometry.
* <p>
* This method supports
* {@link GeometryCollection}s
* (which the other overlay operations currently do not).
* <p>
* The result obeys the following contract:
* <ul>
* <li>Unioning a set of {@link LineString}s has the effect of fully noding
* and dissolving the linework.
* <li>Unioning a set of {@link Polygon}s always
* returns a {@link Polygonal} geometry (unlike {@link #union(Geometry)},
* which may return geometries of lower dimension if a topology collapse occurred).
* </ul>
*
* @return the union geometry
* @throws TopologyException if a robustness error occurs
*
* @see UnaryUnionOp
*/
default Geometry union() {
return UnaryUnionOp.union(this);
}
/**
* Computes a <code>Geometry</code> representing the point-set
* which is contained in both this
* <code>Geometry</code> and the <code>other</code> Geometry.
* <p>
* The union of two geometries of different dimension produces a result
* geometry of dimension equal to the maximum dimension of the input
* geometries.
* The result geometry may be a heterogenous
* {@link GeometryCollection}.
* If the result is empty, it is an atomic geometry
* with the dimension of the highest input dimension.
* <p>
* Unioning {@link LineString}s has the effect of
* <b>noding</b> and <b>dissolving</b> the input linework. In this context
* "noding" means that there will be a node or endpoint in the result for
* every endpoint or line segment crossing in the input. "Dissolving" means
* that any duplicate (i.e. coincident) line segments or portions of line
* segments will be reduced to a single line segment in the result.
* If <b>merged</b> linework is required, the {@link LineMerger}
* class can be used.
* <p>
* {@link #isHeterogeneousGeometryCollection()} arguments are not supported.
*
* @param other
* the <code>Geometry</code> with which to compute the union
* @return a point-set combining the points of this <code>Geometry</code> and the
* points of <code>other</code>
* @throws TopologyException
* if a robustness error occurs
* @throws IllegalArgumentException
* if either input is a non-empty GeometryCollection
* @see LineMerger
*/
default Geometry union(final Geometry other) {
// handle empty geometry cases
if (other == null) {
return this;
} else if (isEmpty()) {
if (other.isEmpty()) {
return OverlayOp.newEmptyResult(OverlayOp.UNION, this, other, getGeometryFactory());
} else {
return other;
}
} else if (other.isEmpty()) {
return this;
}
// TODO: optimize if envelopes of geometries do not intersect
checkNotGeometryCollection(this);
checkNotGeometryCollection(other);
return SnapIfNeededOverlayOp.overlayOp(this, other, OverlayOp.UNION);
}
/**
* <p>Get an {@link Iterable} that iterates over the {@link Vertex} of the geometry. For memory
* efficiency the {@link Vertex} returned is the same instance for each call to next
* on the iterator. If the vertex is required to track the previous vertex then the
* {@link Vertex#clone()} method must be called to get a copy of the vertex.</p>
*
* <p>The {@link Iterable#iterator()} method always returns the same {@link Iterator} instance.
* Therefore that method should not be called more than once.</p>
*
* @author Paul Austin <paul.austin@revolsys.com>
* @return The iterator over the vertices of the geometry.
*/
Vertex vertices();
/**
* Tests whether this geometry is within the
* specified geometry.
* <p>
* The <code>within</code> predicate has the following equivalent definitions:
* <ul>
* <li>Every point of this geometry is a point of the other geometry,
* and the interiors of the two geometries have at least one point in common.
* <li>The DE-9IM Intersection Matrix for the two geometries matches
* <code>[T*F**F***]</code>
* <li><code>g.contains(this) = true</code>
* <br>(<code>within</code> is the converse of {@link #contains})
* </ul>
* An implication of the definition is that
* "The boundary of a Geometry is not within the Geometry".
* In other words, if a geometry A is a subset of
* the points in the boundary of a geomtry B, <code>A.within(B) = false</code>
* (As a concrete example, take A to be a LineString which lies in the boundary of a Polygon B.)
* For a predicate with similar behaviour but avoiding
* this subtle limitation, see {@link #coveredBy}.
*
*@param g the <code>Geometry</code> with which to compare this <code>Geometry</code>
*@return <code>true</code> if this <code>Geometry</code> is within
* <code>g</code>
*
* @see Geometry#contains
* @see Geometry#coveredBy
*/
default boolean within(final Geometry g) {
return g.contains(this);
}
}