/*
* 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;
/**
* Models a <b>Dimensionally Extended Nine-Intersection Model (DE-9IM)</b> matrix.
* DE-9IM matrices (such as "212FF1FF2")
* specify the topological relationship between two {@link Geometry}s.
* This class can also represent matrix patterns (such as "T*T******")
* which are used for matching instances of DE-9IM matrices.
*
* Methods are provided to:
* <UL>
* <LI> set and query the elements of the matrix in a convenient fashion
* <LI> convert to and from the standard string representation (specified in
* SFS Section 2.1.13.2).
* <LI> test to see if a matrix matches a given pattern string.
* </UL>
* <P>
*
* For a description of the DE-9IM and the spatial predicates derived from it,
* see the <i><A
* HREF="http://www.opengis.org/techno/specs.htm">OGC 99-049 OpenGIS Simple Features
* Specification for SQL</A></i>, as well as
* <i>OGC 06-103r4 OpenGIS
* Implementation Standard for Geographic information -
* Simple feature access - Part 1: Common architecture</i>
* (which provides some further details on certain predicate specifications).
* <p>
* The entries of the matrix are defined by the constants in the {@link Dimension} class.
* The indices of the matrix represent the topological locations
* that occur in a geometry (Interior, Boundary, Exterior).
* These are provided as constants in the {@link Location} class.
*
*
*@version 1.7
*/
public class IntersectionMatrix implements Cloneable {
public static final int BOUNDARY = Location.BOUNDARY.getIndex();
public static final int EXTERIOR = Location.EXTERIOR.getIndex();
public static final int INTERIOR = Location.INTERIOR.getIndex();
/**
* Tests if the dimension value matches <tt>TRUE</tt>
* (i.e. has value 0, 1, 2 or TRUE).
*
*@param actualDimensionValue a number that can be stored in the <code>IntersectionMatrix</code>
* . Possible values are <code>{TRUE, FALSE, DONTCARE, 0, 1, 2}</code>.
*@return true if the dimension value matches TRUE
*/
public static boolean isTrue(final int actualDimensionValue) {
if (actualDimensionValue >= 0 || actualDimensionValue == Dimension.TRUE) {
return true;
}
return false;
}
/**
* Tests if the dimension value satisfies the dimension symbol.
*
*@param actualDimensionValue a number that can be stored in the <code>IntersectionMatrix</code>
* . Possible values are <code>{TRUE, FALSE, DONTCARE, 0, 1, 2}</code>.
*@param requiredDimensionSymbol a character used in the string
* representation of an <code>IntersectionMatrix</code>. Possible values
* are <code>{T, F, * , 0, 1, 2}</code>.
*@return true if the dimension symbol matches
* the dimension value
*/
public static boolean matches(final int actualDimensionValue,
final char requiredDimensionSymbol) {
if (requiredDimensionSymbol == Dimension.SYM_DONTCARE) {
return true;
}
if (requiredDimensionSymbol == Dimension.SYM_TRUE
&& (actualDimensionValue >= 0 || actualDimensionValue == Dimension.TRUE)) {
return true;
}
if (requiredDimensionSymbol == Dimension.SYM_FALSE && actualDimensionValue == Dimension.FALSE) {
return true;
}
if (requiredDimensionSymbol == Dimension.SYM_P && actualDimensionValue == Dimension.P) {
return true;
}
if (requiredDimensionSymbol == Dimension.SYM_L && actualDimensionValue == Dimension.L) {
return true;
}
if (requiredDimensionSymbol == Dimension.SYM_A && actualDimensionValue == Dimension.A) {
return true;
}
return false;
}
/**
* Tests if each of the actual dimension symbols in a matrix string satisfies the
* corresponding required dimension symbol in a pattern string.
*
*@param actualDimensionSymbols nine dimension symbols to validate.
* Possible values are <code>{T, F, * , 0, 1, 2}</code>.
*@param requiredDimensionSymbols nine dimension symbols to validate
* against. Possible values are <code>{T, F, * , 0, 1, 2}</code>.
*@return true if each of the required dimension
* symbols encompass the corresponding actual dimension symbol
*/
public static boolean matches(final String actualDimensionSymbols,
final String requiredDimensionSymbols) {
final IntersectionMatrix m = new IntersectionMatrix(actualDimensionSymbols);
return m.matches(requiredDimensionSymbols);
}
/**
* Internal representation of this <code>IntersectionMatrix</code>.
*/
private final int[][] matrix;
/**
* Creates an <code>IntersectionMatrix</code> with <code>FALSE</code>
* dimension values.
*/
public IntersectionMatrix() {
this.matrix = new int[3][3];
setAll(Dimension.FALSE);
}
/**
* Creates an <code>IntersectionMatrix</code> with the same elements as
* <code>other</code>.
*
*@param other an <code>IntersectionMatrix</code> to copy
*/
public IntersectionMatrix(final IntersectionMatrix other) {
this();
this.matrix[INTERIOR][INTERIOR] = other.matrix[INTERIOR][INTERIOR];
this.matrix[INTERIOR][BOUNDARY] = other.matrix[INTERIOR][BOUNDARY];
this.matrix[INTERIOR][EXTERIOR] = other.matrix[INTERIOR][EXTERIOR];
this.matrix[BOUNDARY][INTERIOR] = other.matrix[BOUNDARY][INTERIOR];
this.matrix[BOUNDARY][BOUNDARY] = other.matrix[BOUNDARY][BOUNDARY];
this.matrix[BOUNDARY][EXTERIOR] = other.matrix[BOUNDARY][EXTERIOR];
this.matrix[EXTERIOR][INTERIOR] = other.matrix[EXTERIOR][INTERIOR];
this.matrix[EXTERIOR][BOUNDARY] = other.matrix[EXTERIOR][BOUNDARY];
this.matrix[EXTERIOR][EXTERIOR] = other.matrix[EXTERIOR][EXTERIOR];
}
/**
* Creates an <code>IntersectionMatrix</code> with the given dimension
* symbols.
*
*@param elements a String of nine dimension symbols in row major order
*/
public IntersectionMatrix(final String elements) {
this();
set(elements);
}
/**
* Adds one matrix to another.
* Addition is defined by taking the maximum dimension value of each position
* in the summand matrices.
*
* @param im the matrix to add
*/
public void add(final IntersectionMatrix im) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
setAtLeast(i, j, im.get(i, j));
}
}
}
/**
* Returns the value of one of this matrix
* entries.
* The value of the provided index is one of the
* values from the {@link Location} class.
* The value returned is a constant
* from the {@link Dimension} class.
*
*@param row the row of this <code>IntersectionMatrix</code>, indicating
* the interior, boundary or exterior of the first <code>Geometry</code>
*@param column the column of this <code>IntersectionMatrix</code>,
* indicating the interior, boundary or exterior of the second <code>Geometry</code>
*@return the dimension value at the given matrix position.
*/
public int get(final int row, final int column) {
return this.matrix[row][column];
}
/**
* Tests whether this <code>IntersectionMatrix</code> is
* T*****FF*.
*
*@return <code>true</code> if the first <code>Geometry</code> contains the
* second
*/
public boolean isContains() {
return isTrue(this.matrix[INTERIOR][INTERIOR])
&& this.matrix[EXTERIOR][INTERIOR] == Dimension.FALSE
&& this.matrix[EXTERIOR][BOUNDARY] == Dimension.FALSE;
}
/**
* Returns <code>true</code> if this <code>IntersectionMatrix</code> is
* <code>T*F**F***</code>
* or <code>*TF**F***</code>
* or <code>**FT*F***</code>
* or <code>**F*TF***</code>
*
*@return <code>true</code> if the first <code>Geometry</code>
* is covered by the second
*/
public boolean isCoveredBy() {
final boolean hasPointInCommon = isTrue(this.matrix[INTERIOR][INTERIOR])
|| isTrue(this.matrix[INTERIOR][BOUNDARY]) || isTrue(this.matrix[BOUNDARY][INTERIOR])
|| isTrue(this.matrix[BOUNDARY][BOUNDARY]);
return hasPointInCommon && this.matrix[INTERIOR][EXTERIOR] == Dimension.FALSE
&& this.matrix[BOUNDARY][EXTERIOR] == Dimension.FALSE;
}
/**
* Returns <code>true</code> if this <code>IntersectionMatrix</code> is
* <code>T*****FF*</code>
* or <code>*T****FF*</code>
* or <code>***T**FF*</code>
* or <code>****T*FF*</code>
*
*@return <code>true</code> if the first <code>Geometry</code> covers the
* second
*/
public boolean isCovers() {
final boolean hasPointInCommon = isTrue(this.matrix[INTERIOR][INTERIOR])
|| isTrue(this.matrix[INTERIOR][BOUNDARY]) || isTrue(this.matrix[BOUNDARY][INTERIOR])
|| isTrue(this.matrix[BOUNDARY][BOUNDARY]);
return hasPointInCommon && this.matrix[EXTERIOR][INTERIOR] == Dimension.FALSE
&& this.matrix[EXTERIOR][BOUNDARY] == Dimension.FALSE;
}
/**
* Tests whether this geometry crosses the
* specified 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 is
* <ul>
* <li>T*T****** (for P/L, P/A, and L/A situations)
* <li>T*****T** (for L/P, L/A, and A/L situations)
* <li>0******** (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.
* JTS extends the definition to apply to L/P, A/P and A/L situations as well.
* This makes the relation symmetric.
*
*@param dimensionOfGeometryA the dimension of the first <code>Geometry</code>
*@param dimensionOfGeometryB the dimension of the second <code>Geometry</code>
*@return <code>true</code> if the two <code>Geometry</code>s
* related by this <code>IntersectionMatrix</code> cross.
*/
public boolean isCrosses(final int dimensionOfGeometryA, final int dimensionOfGeometryB) {
if (dimensionOfGeometryA == Dimension.P && dimensionOfGeometryB == Dimension.L
|| dimensionOfGeometryA == Dimension.P && dimensionOfGeometryB == Dimension.A
|| dimensionOfGeometryA == Dimension.L && dimensionOfGeometryB == Dimension.A) {
return isTrue(this.matrix[INTERIOR][INTERIOR]) && isTrue(this.matrix[INTERIOR][EXTERIOR]);
}
if (dimensionOfGeometryA == Dimension.L && dimensionOfGeometryB == Dimension.P
|| dimensionOfGeometryA == Dimension.A && dimensionOfGeometryB == Dimension.P
|| dimensionOfGeometryA == Dimension.A && dimensionOfGeometryB == Dimension.L) {
return isTrue(this.matrix[INTERIOR][INTERIOR]) && isTrue(this.matrix[EXTERIOR][INTERIOR]);
}
if (dimensionOfGeometryA == Dimension.L && dimensionOfGeometryB == Dimension.L) {
return this.matrix[INTERIOR][INTERIOR] == 0;
}
return false;
}
/**
* Returns <code>true</code> if this <code>IntersectionMatrix</code> is
* FF*FF****.
*
*@return <code>true</code> if the two <code>Geometry</code>s related by
* this <code>IntersectionMatrix</code> are disjoint
*/
public boolean isDisjoint() {
return this.matrix[INTERIOR][INTERIOR] == Dimension.FALSE
&& this.matrix[INTERIOR][BOUNDARY] == Dimension.FALSE
&& this.matrix[BOUNDARY][INTERIOR] == Dimension.FALSE
&& this.matrix[BOUNDARY][BOUNDARY] == Dimension.FALSE;
}
/**
* Tests whether the argument dimensions are equal and
* this <code>IntersectionMatrix</code> matches
* the pattern <tt>T*F**FFF*</tt>.
* <p>
* <b>Note:</b> This pattern differs from the one stated in
* <i>Simple feature access - Part 1: Common architecture</i>.
* That document states the pattern as <tt>TFFFTFFFT</tt>. This would
* specify that
* two identical <tt>POINT</tt>s are not equal, which is not desirable behaviour.
* The pattern used here has been corrected to compute equality in this situation.
*
*@param dimensionOfGeometryA the dimension of the first <code>Geometry</code>
*@param dimensionOfGeometryB the dimension of the second <code>Geometry</code>
*@return <code>true</code> if the two <code>Geometry</code>s
* related by this <code>IntersectionMatrix</code> are equal; the
* <code>Geometry</code>s must have the same dimension to be equal
*/
public boolean isEquals(final int dimensionOfGeometryA, final int dimensionOfGeometryB) {
if (dimensionOfGeometryA != dimensionOfGeometryB) {
return false;
}
return isTrue(this.matrix[INTERIOR][INTERIOR])
&& this.matrix[INTERIOR][EXTERIOR] == Dimension.FALSE
&& this.matrix[BOUNDARY][EXTERIOR] == Dimension.FALSE
&& this.matrix[EXTERIOR][INTERIOR] == Dimension.FALSE
&& this.matrix[EXTERIOR][BOUNDARY] == Dimension.FALSE;
}
/**
* Returns <code>true</code> if <code>isDisjoint</code> returns false.
*
*@return <code>true</code> if the two <code>Geometry</code>s related by
* this <code>IntersectionMatrix</code> intersect
*/
public boolean isIntersects() {
return !isDisjoint();
}
/**
* Returns <code>true</code> if this <code>IntersectionMatrix</code> is
* <UL>
* <LI> T*T***T** (for two points or two surfaces)
* <LI> 1*T***T** (for two curves)
* </UL>.
*
*@param dimensionOfGeometryA the dimension of the first <code>Geometry</code>
*@param dimensionOfGeometryB the dimension of the second <code>Geometry</code>
*@return <code>true</code> if the two <code>Geometry</code>s
* related by this <code>IntersectionMatrix</code> overlap. For this
* function to return <code>true</code>, the <code>Geometry</code>s must
* be two points, two curves or two surfaces.
*/
public boolean isOverlaps(final int dimensionOfGeometryA, final int dimensionOfGeometryB) {
if (dimensionOfGeometryA == Dimension.P && dimensionOfGeometryB == Dimension.P
|| dimensionOfGeometryA == Dimension.A && dimensionOfGeometryB == Dimension.A) {
return isTrue(this.matrix[INTERIOR][INTERIOR]) && isTrue(this.matrix[INTERIOR][EXTERIOR])
&& isTrue(this.matrix[EXTERIOR][INTERIOR]);
}
if (dimensionOfGeometryA == Dimension.L && dimensionOfGeometryB == Dimension.L) {
return this.matrix[INTERIOR][INTERIOR] == 1 && isTrue(this.matrix[INTERIOR][EXTERIOR])
&& isTrue(this.matrix[EXTERIOR][INTERIOR]);
}
return false;
}
/**
* Returns <code>true</code> if this <code>IntersectionMatrix</code> is
* FT*******, F**T***** or F***T****.
*
*@param dimensionOfGeometryA the dimension of the first <code>Geometry</code>
*@param dimensionOfGeometryB the dimension of the second <code>Geometry</code>
*@return <code>true</code> if the two <code>Geometry</code>
* s related by this <code>IntersectionMatrix</code> touch; Returns false
* if both <code>Geometry</code>s are points.
*/
public boolean isTouches(final int dimensionOfGeometryA, final int dimensionOfGeometryB) {
if (dimensionOfGeometryA > dimensionOfGeometryB) {
// no need to get transpose because pattern matrix is symmetrical
return isTouches(dimensionOfGeometryB, dimensionOfGeometryA);
}
if (dimensionOfGeometryA == Dimension.A && dimensionOfGeometryB == Dimension.A
|| dimensionOfGeometryA == Dimension.L && dimensionOfGeometryB == Dimension.L
|| dimensionOfGeometryA == Dimension.L && dimensionOfGeometryB == Dimension.A
|| dimensionOfGeometryA == Dimension.P && dimensionOfGeometryB == Dimension.A
|| dimensionOfGeometryA == Dimension.P && dimensionOfGeometryB == Dimension.L) {
return this.matrix[INTERIOR][INTERIOR] == Dimension.FALSE
&& (isTrue(this.matrix[INTERIOR][BOUNDARY]) || isTrue(this.matrix[BOUNDARY][INTERIOR])
|| isTrue(this.matrix[BOUNDARY][BOUNDARY]));
}
return false;
}
/**
* Tests whether this <code>IntersectionMatrix</code> is
* T*F**F***.
*
*@return <code>true</code> if the first <code>Geometry</code> is within
* the second
*/
public boolean isWithin() {
return isTrue(this.matrix[INTERIOR][INTERIOR])
&& this.matrix[INTERIOR][EXTERIOR] == Dimension.FALSE
&& this.matrix[BOUNDARY][EXTERIOR] == Dimension.FALSE;
}
/**
* Returns whether the elements of this <code>IntersectionMatrix</code>
* satisfies the required dimension symbols.
*
*@param requiredDimensionSymbols nine dimension symbols with which to
* compare the elements of this <code>IntersectionMatrix</code>. Possible
* values are <code>{T, F, * , 0, 1, 2}</code>.
*@return <code>true</code> if this <code>IntersectionMatrix</code>
* matches the required dimension symbols
*/
public boolean matches(final String requiredDimensionSymbols) {
if (requiredDimensionSymbols.length() != 9) {
throw new IllegalArgumentException("Should be length 9: " + requiredDimensionSymbols);
}
for (int ai = 0; ai < 3; ai++) {
for (int bi = 0; bi < 3; bi++) {
if (!matches(this.matrix[ai][bi], requiredDimensionSymbols.charAt(3 * ai + bi))) {
return false;
}
}
}
return true;
}
/**
* Changes the value of one of this <code>IntersectionMatrix</code>s
* elements.
*
*@param row the row of this <code>IntersectionMatrix</code>,
* indicating the interior, boundary or exterior of the first <code>Geometry</code>
*@param column the column of this <code>IntersectionMatrix</code>,
* indicating the interior, boundary or exterior of the second <code>Geometry</code>
*@param dimensionValue the new value of the element
*/
public void set(final int row, final int column, final int dimensionValue) {
this.matrix[row][column] = dimensionValue;
}
public void set(final Location row, final Location column, final int dimensionValue) {
set(row.getIndex(), column.getIndex(), dimensionValue);
}
/**
* Changes the elements of this <code>IntersectionMatrix</code> to the
* dimension symbols in <code>dimensionSymbols</code>.
*
*@param dimensionSymbols nine dimension symbols to which to set this <code>IntersectionMatrix</code>
* s elements. Possible values are <code>{T, F, * , 0, 1, 2}</code>
*/
public void set(final String dimensionSymbols) {
for (int i = 0; i < dimensionSymbols.length(); i++) {
final int row = i / 3;
final int col = i % 3;
this.matrix[row][col] = Dimension.toDimensionValue(dimensionSymbols.charAt(i));
}
}
/**
* Changes the elements of this <code>IntersectionMatrix</code> to <code>dimensionValue</code>
* .
*
*@param dimensionValue the dimension value to which to set this <code>IntersectionMatrix</code>
* s elements. Possible values <code>{TRUE, FALSE, DONTCARE, 0, 1, 2}</code>
* .
*/
public void setAll(final int dimensionValue) {
for (int ai = 0; ai < 3; ai++) {
for (int bi = 0; bi < 3; bi++) {
this.matrix[ai][bi] = dimensionValue;
}
}
}
/**
* Changes the specified element to <code>minimumDimensionValue</code> if the
* element is less.
*
*@param row the row of this <code>IntersectionMatrix</code>
* , indicating the interior, boundary or exterior of the first <code>Geometry</code>
*@param column the column of this <code>IntersectionMatrix</code>
* , indicating the interior, boundary or exterior of the second <code>Geometry</code>
*@param minimumDimensionValue the dimension value with which to compare the
* element. The order of dimension values from least to greatest is
* <code>{DONTCARE, TRUE, FALSE, 0, 1, 2}</code>.
*/
public void setAtLeast(final int row, final int column, final int minimumDimensionValue) {
if (this.matrix[row][column] < minimumDimensionValue) {
this.matrix[row][column] = minimumDimensionValue;
}
}
/**
* For each element in this <code>IntersectionMatrix</code>, changes the
* element to the corresponding minimum dimension symbol if the element is
* less.
*
*@param minimumDimensionSymbols nine dimension symbols with which to
* compare the elements of this <code>IntersectionMatrix</code>. The
* order of dimension values from least to greatest is <code>{DONTCARE, TRUE, FALSE, 0, 1, 2}</code>
* .
*/
public void setAtLeast(final String minimumDimensionSymbols) {
for (int i = 0; i < minimumDimensionSymbols.length(); i++) {
final int row = i / 3;
final int col = i % 3;
setAtLeast(row, col, Dimension.toDimensionValue(minimumDimensionSymbols.charAt(i)));
}
}
/**
* If row >= 0 and column >= 0, changes the specified element to <code>minimumDimensionValue</code>
* if the element is less. Does nothing if row <0 or column < 0.
*
*@param row the row of this <code>IntersectionMatrix</code>
* , indicating the interior, boundary or exterior of the first <code>Geometry</code>
*@param column the column of this <code>IntersectionMatrix</code>
* , indicating the interior, boundary or exterior of the second <code>Geometry</code>
*@param minimumDimensionValue the dimension value with which to compare the
* element. The order of dimension values from least to greatest is
* <code>{DONTCARE, TRUE, FALSE, 0, 1, 2}</code>.
*/
public void setAtLeastIfValid(final int row, final int column, final int minimumDimensionValue) {
if (row >= 0 && column >= 0) {
setAtLeast(row, column, minimumDimensionValue);
}
}
public void setAtLeastIfValid(final Location row, final Location column,
final int minimumDimensionValue) {
setAtLeastIfValid(row.getIndex(), column.getIndex(), minimumDimensionValue);
}
/**
* Returns a nine-character <code>String</code> representation of this <code>IntersectionMatrix</code>
* .
*
*@return the nine dimension symbols of this <code>IntersectionMatrix</code>
* in row-major order.
*/
@Override
public String toString() {
final StringBuilder buf = new StringBuilder("123456789");
for (int ai = 0; ai < 3; ai++) {
for (int bi = 0; bi < 3; bi++) {
buf.setCharAt(3 * ai + bi, Dimension.toDimensionSymbol(this.matrix[ai][bi]));
}
}
return buf.toString();
}
/**
* Transposes this IntersectionMatrix.
*
*@return this <code>IntersectionMatrix</code> as a convenience
*/
public IntersectionMatrix transpose() {
int temp = this.matrix[1][0];
this.matrix[1][0] = this.matrix[0][1];
this.matrix[0][1] = temp;
temp = this.matrix[2][0];
this.matrix[2][0] = this.matrix[0][2];
this.matrix[0][2] = temp;
temp = this.matrix[2][1];
this.matrix[2][1] = this.matrix[1][2];
this.matrix[1][2] = temp;
return this;
}
}