/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2006 Vivid Solutions
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.geometry.iso.topograph2D;
/**
* A Dimensionally Extended Nine-Intersection Model (DE-9IM) matrix. This class
* can used to represent both computed DE-9IM's (like 212FF1FF2) as well as
* patterns for matching them (like T*T******).
* <P>
*
* 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, see the <A
* HREF="http://www.opengis.org/techno/specs.htm">OpenGIS Simple Features
* Specification for SQL</A> .
*
*
* @source $URL$
*/
public class IntersectionMatrix implements Cloneable {
/**
* Internal representation of this <code>IntersectionMatrix</code>.
*/
private int[][] matrix;
/**
* Creates an <code>IntersectionMatrix</code> with <code>FALSE</code>
* dimension values.
*/
public IntersectionMatrix() {
matrix = new int[3][3];
setAll(Dimension.FALSE);
}
/**
* Creates an <code>IntersectionMatrix</code> with the given dimension
* symbols.
*
* @param elements
* a String of nine dimension symbols in row major order
*/
public IntersectionMatrix(String elements) {
this();
set(elements);
}
/**
* Creates an <code>IntersectionMatrix</code> with the same elements as
* <code>other</code>.
*
* @param other
* an <code>IntersectionMatrix</code> to copy
*/
public IntersectionMatrix(IntersectionMatrix other) {
this();
matrix[Location.INTERIOR][Location.INTERIOR] = other.matrix[Location.INTERIOR][Location.INTERIOR];
matrix[Location.INTERIOR][Location.BOUNDARY] = other.matrix[Location.INTERIOR][Location.BOUNDARY];
matrix[Location.INTERIOR][Location.EXTERIOR] = other.matrix[Location.INTERIOR][Location.EXTERIOR];
matrix[Location.BOUNDARY][Location.INTERIOR] = other.matrix[Location.BOUNDARY][Location.INTERIOR];
matrix[Location.BOUNDARY][Location.BOUNDARY] = other.matrix[Location.BOUNDARY][Location.BOUNDARY];
matrix[Location.BOUNDARY][Location.EXTERIOR] = other.matrix[Location.BOUNDARY][Location.EXTERIOR];
matrix[Location.EXTERIOR][Location.INTERIOR] = other.matrix[Location.EXTERIOR][Location.INTERIOR];
matrix[Location.EXTERIOR][Location.BOUNDARY] = other.matrix[Location.EXTERIOR][Location.BOUNDARY];
matrix[Location.EXTERIOR][Location.EXTERIOR] = other.matrix[Location.EXTERIOR][Location.EXTERIOR];
}
/**
* 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(IntersectionMatrix im) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
setAtLeast(i, j, im.get(i, j));
}
}
}
/**
* Returns true 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 encompasses the dimension value
*/
public static boolean matches(int actualDimensionValue,
char requiredDimensionSymbol) {
if (requiredDimensionSymbol == '*') {
return true;
}
if (requiredDimensionSymbol == 'T'
&& (actualDimensionValue >= 0 || actualDimensionValue == Dimension.TRUE)) {
return true;
}
if (requiredDimensionSymbol == 'F'
&& actualDimensionValue == Dimension.FALSE) {
return true;
}
if (requiredDimensionSymbol == '0'
&& actualDimensionValue == Dimension.P) {
return true;
}
if (requiredDimensionSymbol == '1'
&& actualDimensionValue == Dimension.L) {
return true;
}
if (requiredDimensionSymbol == '2'
&& actualDimensionValue == Dimension.A) {
return true;
}
return false;
}
/**
* Returns true if each of the actual dimension symbols satisfies the
* corresponding required dimension symbol.
*
* @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(String actualDimensionSymbols,
String requiredDimensionSymbols) {
IntersectionMatrix m = new IntersectionMatrix(actualDimensionSymbols);
return m.matches(requiredDimensionSymbols);
}
/**
* 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(int row, int column, int dimensionValue) {
matrix[row][column] = 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(String dimensionSymbols) {
for (int i = 0; i < dimensionSymbols.length(); i++) {
int row = i / 3;
int col = i % 3;
matrix[row][col] = Dimension.toDimensionValue(dimensionSymbols
.charAt(i));
}
}
/**
* 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(int row, int column, int minimumDimensionValue) {
if (matrix[row][column] < minimumDimensionValue) {
matrix[row][column] = minimumDimensionValue;
}
}
/**
* 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(int row, int column, int minimumDimensionValue) {
if (row >= 0 && column >= 0) {
setAtLeast(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(String minimumDimensionSymbols) {
for (int i = 0; i < minimumDimensionSymbols.length(); i++) {
int row = i / 3;
int col = i % 3;
setAtLeast(row, col, Dimension
.toDimensionValue(minimumDimensionSymbols.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(int dimensionValue) {
for (int ai = 0; ai < 3; ai++) {
for (int bi = 0; bi < 3; bi++) {
matrix[ai][bi] = dimensionValue;
}
}
}
/**
* Returns 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>
* @return the dimension value at the given matrix position.
*/
public int get(int row, int column) {
return matrix[row][column];
}
/**
* 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(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(matrix[ai][bi], requiredDimensionSymbols.charAt(3
* ai + bi))) {
return false;
}
}
}
return true;
}
/**
* Transposes this IntersectionMatrix.
*
* @return this <code>IntersectionMatrix</code> as a convenience
*/
public IntersectionMatrix transpose() {
int temp = matrix[1][0];
matrix[1][0] = matrix[0][1];
matrix[0][1] = temp;
temp = matrix[2][0];
matrix[2][0] = matrix[0][2];
matrix[0][2] = temp;
temp = matrix[2][1];
matrix[2][1] = matrix[1][2];
matrix[1][2] = temp;
return this;
}
// /**
// * 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 matrix[Location.INTERIOR][Location.INTERIOR] == Dimension.FALSE
// && matrix[Location.INTERIOR][Location.BOUNDARY] == Dimension.FALSE
// && matrix[Location.BOUNDARY][Location.INTERIOR] == Dimension.FALSE
// && matrix[Location.BOUNDARY][Location.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
// * 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(int dimensionOfGeometryA, 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 matrix[Location.INTERIOR][Location.INTERIOR] == Dimension.FALSE
// && (matches(matrix[Location.INTERIOR][Location.BOUNDARY], 'T')
// || matches(matrix[Location.BOUNDARY][Location.INTERIOR], 'T')
// || matches(matrix[Location.BOUNDARY][Location.BOUNDARY], 'T'));
// }
// return false;
// }
//
// /**
// * Returns <code>true</code> if 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(int dimensionOfGeometryA, int dimensionOfGeometryB) {
// if ((dimensionOfGeometryA == Dimension.P && dimensionOfGeometryB == Dimension.L)
// || (dimensionOfGeometryA == Dimension.P && dimensionOfGeometryB == Dimension.A)
// || (dimensionOfGeometryA == Dimension.L && dimensionOfGeometryB == Dimension.A)) {
// return matches(matrix[Location.INTERIOR][Location.INTERIOR], 'T')
// && matches(matrix[Location.INTERIOR][Location.EXTERIOR],
// 'T');
// }
// if ((dimensionOfGeometryA == Dimension.L && dimensionOfGeometryB == Dimension.P)
// || (dimensionOfGeometryA == Dimension.A && dimensionOfGeometryB == Dimension.P)
// || (dimensionOfGeometryA == Dimension.A && dimensionOfGeometryB == Dimension.L)) {
// return matches(matrix[Location.INTERIOR][Location.INTERIOR], 'T')
// && matches(matrix[Location.EXTERIOR][Location.INTERIOR],
// 'T');
// }
// if (dimensionOfGeometryA == Dimension.L
// && dimensionOfGeometryB == Dimension.L) {
// return matrix[Location.INTERIOR][Location.INTERIOR] == 0;
// }
// return false;
// }
//
// /**
// * Returns <code>true</code> if 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 matches(matrix[Location.INTERIOR][Location.INTERIOR], 'T')
// && matrix[Location.INTERIOR][Location.EXTERIOR] == Dimension.FALSE
// && matrix[Location.BOUNDARY][Location.EXTERIOR] == Dimension.FALSE;
// }
//
// /**
// * Returns <code>true</code> if this <code>IntersectionMatrix</code> is
// * T*****FF*.
// *
// * @return <code>true</code> if the first <code>Geometry</code> contains
// * the second
// */
// public boolean isContains() {
// return matches(matrix[Location.INTERIOR][Location.INTERIOR], 'T')
// && matrix[Location.EXTERIOR][Location.INTERIOR] == Dimension.FALSE
// && matrix[Location.EXTERIOR][Location.BOUNDARY] == 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() {
// boolean hasPointInCommon = matches(
// matrix[Location.INTERIOR][Location.INTERIOR], 'T')
// || matches(matrix[Location.INTERIOR][Location.BOUNDARY], 'T')
// || matches(matrix[Location.BOUNDARY][Location.INTERIOR], 'T')
// || matches(matrix[Location.BOUNDARY][Location.BOUNDARY], 'T');
//
// return hasPointInCommon
// && matrix[Location.EXTERIOR][Location.INTERIOR] == Dimension.FALSE
// && matrix[Location.EXTERIOR][Location.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() {
// boolean hasPointInCommon = matches(
// matrix[Location.INTERIOR][Location.INTERIOR], 'T')
// || matches(matrix[Location.INTERIOR][Location.BOUNDARY], 'T')
// || matches(matrix[Location.BOUNDARY][Location.INTERIOR], 'T')
// || matches(matrix[Location.BOUNDARY][Location.BOUNDARY], 'T');
//
// return hasPointInCommon
// && matrix[Location.INTERIOR][Location.EXTERIOR] == Dimension.FALSE
// && matrix[Location.BOUNDARY][Location.EXTERIOR] == Dimension.FALSE;
// }
//
// /**
// * Returns <code>true</code> if this <code>IntersectionMatrix</code> is
// * T*F**FFF*.
// *
// * @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 for this
// * function to return <code>true</code>
// */
// public boolean isEquals(int dimensionOfGeometryA, int dimensionOfGeometryB) {
// if (dimensionOfGeometryA != dimensionOfGeometryB) {
// return false;
// }
// return matches(matrix[Location.INTERIOR][Location.INTERIOR], 'T')
// && matrix[Location.EXTERIOR][Location.INTERIOR] == Dimension.FALSE
// && matrix[Location.INTERIOR][Location.EXTERIOR] == Dimension.FALSE
// && matrix[Location.EXTERIOR][Location.BOUNDARY] == Dimension.FALSE
// && matrix[Location.BOUNDARY][Location.EXTERIOR] == Dimension.FALSE;
// }
//
// /**
// * 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(int dimensionOfGeometryA, int dimensionOfGeometryB) {
// if ((dimensionOfGeometryA == Dimension.P && dimensionOfGeometryB == Dimension.P)
// || (dimensionOfGeometryA == Dimension.A && dimensionOfGeometryB == Dimension.A)) {
// return matches(matrix[Location.INTERIOR][Location.INTERIOR], 'T')
// && matches(matrix[Location.INTERIOR][Location.EXTERIOR],
// 'T')
// && matches(matrix[Location.EXTERIOR][Location.INTERIOR],
// 'T');
// }
// if (dimensionOfGeometryA == Dimension.L
// && dimensionOfGeometryB == Dimension.L) {
// return matrix[Location.INTERIOR][Location.INTERIOR] == 1
// && matches(matrix[Location.INTERIOR][Location.EXTERIOR],
// 'T')
// && matches(matrix[Location.EXTERIOR][Location.INTERIOR],
// 'T');
// }
// return false;
// }
/**
* 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.
*/
public String toString() {
StringBuffer buf = new StringBuffer("123456789");
for (int ai = 0; ai < 3; ai++) {
for (int bi = 0; bi < 3; bi++) {
buf.setCharAt(3 * ai + bi, Dimension
.toDimensionSymbol(matrix[ai][bi]));
}
}
return buf.toString();
}
}