001:
002: /*
003: * The JTS Topology Suite is a collection of Java classes that
004: * implement the fundamental operations required to validate a given
005: * geo-spatial data set to a known topological specification.
006: *
007: * Copyright (C) 2001 Vivid Solutions
008: *
009: * This library is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU Lesser General Public
011: * License as published by the Free Software Foundation; either
012: * version 2.1 of the License, or (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017: * Lesser General Public License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022: *
023: * For more information, contact:
024: *
025: * Vivid Solutions
026: * Suite #1A
027: * 2328 Government Street
028: * Victoria BC V8T 5G5
029: * Canada
030: *
031: * (250)385-6040
032: * www.vividsolutions.com
033: */
034: package com.vividsolutions.jts.geom;
035:
036: import java.util.Arrays;
037:
038: import com.vividsolutions.jts.algorithm.*;
039:
040: /**
041: * Represents a linear polygon, which may include holes.
042: * The shell and holes of the polygon are represented by {@link LinearRing}s.
043: * In a valid polygon, holes may touch the shell or other holes at a single point.
044: * However, no sequence of touching holes may split the polygon into two pieces.
045: * The orientation of the rings in the polygon does not matter.
046: * <p>
047: * The shell and holes must conform to the assertions specified in the <A
048: * HREF="http://www.opengis.org/techno/specs.htm">OpenGIS Simple Features
049: * Specification for SQL</A> .
050: *
051: *@version 1.7
052: */
053: public class Polygon extends Geometry {
054: private static final long serialVersionUID = -3494792200821764533L;
055:
056: /**
057: * The exterior boundary,
058: * or <code>null</code> if this <code>Polygon</code>
059: * is empty.
060: */
061: protected LinearRing shell = null;
062:
063: /**
064: * The interior boundaries, if any.
065: * This instance var is never null.
066: * If there are no holes, the array is of zero length.
067: */
068: protected LinearRing[] holes;
069:
070: /**
071: * Constructs a <code>Polygon</code> with the given exterior boundary.
072: *
073: *@param shell the outer boundary of the new <code>Polygon</code>,
074: * or <code>null</code> or an empty <code>LinearRing</code> if the empty
075: * geometry is to be created.
076: *@param precisionModel the specification of the grid of allowable points
077: * for this <code>Polygon</code>
078: *@param SRID the ID of the Spatial Reference System used by this
079: * <code>Polygon</code>
080: * @deprecated Use GeometryFactory instead
081: */
082: public Polygon(LinearRing shell, PrecisionModel precisionModel,
083: int SRID) {
084: this (shell, new LinearRing[] {}, new GeometryFactory(
085: precisionModel, SRID));
086: }
087:
088: /**
089: * Constructs a <code>Polygon</code> with the given exterior boundary and
090: * interior boundaries.
091: *
092: *@param shell the outer boundary of the new <code>Polygon</code>,
093: * or <code>null</code> or an empty <code>LinearRing</code> if the empty
094: * geometry is to be created.
095: *@param holes the inner boundaries of the new <code>Polygon</code>
096: * , or <code>null</code> or empty <code>LinearRing</code>s if the empty
097: * geometry is to be created.
098: *@param precisionModel the specification of the grid of allowable points
099: * for this <code>Polygon</code>
100: *@param SRID the ID of the Spatial Reference System used by this
101: * <code>Polygon</code>
102: * @deprecated Use GeometryFactory instead
103: */
104: public Polygon(LinearRing shell, LinearRing[] holes,
105: PrecisionModel precisionModel, int SRID) {
106: this (shell, holes, new GeometryFactory(precisionModel, SRID));
107: }
108:
109: /**
110: * Constructs a <code>Polygon</code> with the given exterior boundary and
111: * interior boundaries.
112: *
113: *@param shell the outer boundary of the new <code>Polygon</code>,
114: * or <code>null</code> or an empty <code>LinearRing</code> if the empty
115: * geometry is to be created.
116: *@param holes the inner boundaries of the new <code>Polygon</code>
117: * , or <code>null</code> or empty <code>LinearRing</code>s if the empty
118: * geometry is to be created.
119: */
120: public Polygon(LinearRing shell, LinearRing[] holes,
121: GeometryFactory factory) {
122: super (factory);
123: if (shell == null) {
124: shell = getFactory().createLinearRing(
125: (CoordinateSequence) null);
126: }
127: if (holes == null) {
128: holes = new LinearRing[] {};
129: }
130: if (hasNullElements(holes)) {
131: throw new IllegalArgumentException(
132: "holes must not contain null elements");
133: }
134: if (shell.isEmpty() && hasNonEmptyElements(holes)) {
135: throw new IllegalArgumentException(
136: "shell is empty but holes are not");
137: }
138: this .shell = shell;
139: this .holes = holes;
140: }
141:
142: public Coordinate getCoordinate() {
143: return shell.getCoordinate();
144: }
145:
146: public Coordinate[] getCoordinates() {
147: if (isEmpty()) {
148: return new Coordinate[] {};
149: }
150: Coordinate[] coordinates = new Coordinate[getNumPoints()];
151: int k = -1;
152: Coordinate[] shellCoordinates = shell.getCoordinates();
153: for (int x = 0; x < shellCoordinates.length; x++) {
154: k++;
155: coordinates[k] = shellCoordinates[x];
156: }
157: for (int i = 0; i < holes.length; i++) {
158: Coordinate[] childCoordinates = holes[i].getCoordinates();
159: for (int j = 0; j < childCoordinates.length; j++) {
160: k++;
161: coordinates[k] = childCoordinates[j];
162: }
163: }
164: return coordinates;
165: }
166:
167: public int getNumPoints() {
168: int numPoints = shell.getNumPoints();
169: for (int i = 0; i < holes.length; i++) {
170: numPoints += holes[i].getNumPoints();
171: }
172: return numPoints;
173: }
174:
175: public int getDimension() {
176: return 2;
177: }
178:
179: public int getBoundaryDimension() {
180: return 1;
181: }
182:
183: public boolean isEmpty() {
184: return shell.isEmpty();
185: }
186:
187: /**
188: * Tests if a valid polygon is simple.
189: * This method always returns true, since a valid polygon is always simple
190: *
191: * @return <code>true</code>
192: */
193: public boolean isSimple() {
194: return true;
195: }
196:
197: public boolean isRectangle() {
198: if (getNumInteriorRing() != 0)
199: return false;
200: if (shell == null)
201: return false;
202: if (shell.getNumPoints() != 5)
203: return false;
204:
205: CoordinateSequence seq = shell.getCoordinateSequence();
206:
207: // check vertices have correct values
208: Envelope env = getEnvelopeInternal();
209: for (int i = 0; i < 5; i++) {
210: double x = seq.getX(i);
211: if (!(x == env.getMinX() || x == env.getMaxX()))
212: return false;
213: double y = seq.getY(i);
214: if (!(y == env.getMinY() || y == env.getMaxY()))
215: return false;
216: }
217:
218: // check vertices are in right order
219: double prevX = seq.getX(0);
220: double prevY = seq.getY(0);
221: for (int i = 1; i <= 4; i++) {
222: double x = seq.getX(i);
223: double y = seq.getY(i);
224: boolean xChanged = x != prevX;
225: boolean yChanged = y != prevY;
226: if (xChanged == yChanged)
227: return false;
228: prevX = x;
229: prevY = y;
230: }
231: return true;
232: }
233:
234: public LineString getExteriorRing() {
235: return shell;
236: }
237:
238: public int getNumInteriorRing() {
239: return holes.length;
240: }
241:
242: public LineString getInteriorRingN(int n) {
243: return holes[n];
244: }
245:
246: public String getGeometryType() {
247: return "Polygon";
248: }
249:
250: /**
251: * Returns the area of this <code>Polygon</code>
252: *
253: *@return the area of the polygon
254: */
255: public double getArea() {
256: double area = 0.0;
257: area += Math.abs(CGAlgorithms
258: .signedArea(shell.getCoordinates()));
259: for (int i = 0; i < holes.length; i++) {
260: area -= Math.abs(CGAlgorithms.signedArea(holes[i]
261: .getCoordinates()));
262: }
263: return area;
264: }
265:
266: /**
267: * Returns the perimeter of this <code>Polygon</code>
268: *
269: *@return the perimeter of the polygon
270: */
271: public double getLength() {
272: double len = 0.0;
273: len += shell.getLength();
274: for (int i = 0; i < holes.length; i++) {
275: len += holes[i].getLength();
276: }
277: return len;
278: }
279:
280: /**
281: * Computes the boundary of this geometry
282: *
283: * @return a lineal geometry (which may be empty)
284: * @see Geometry#getBoundary
285: */
286: public Geometry getBoundary() {
287: if (isEmpty()) {
288: return getFactory().createMultiLineString(null);
289: }
290: LinearRing[] rings = new LinearRing[holes.length + 1];
291: rings[0] = shell;
292: for (int i = 0; i < holes.length; i++) {
293: rings[i + 1] = holes[i];
294: }
295: // create LineString or MultiLineString as appropriate
296: if (rings.length <= 1)
297: return getFactory().createLinearRing(
298: rings[0].getCoordinateSequence());
299: return getFactory().createMultiLineString(rings);
300: }
301:
302: protected Envelope computeEnvelopeInternal() {
303: return shell.getEnvelopeInternal();
304: }
305:
306: public boolean equalsExact(Geometry other, double tolerance) {
307: if (!isEquivalentClass(other)) {
308: return false;
309: }
310: Polygon otherPolygon = (Polygon) other;
311: Geometry this Shell = shell;
312: Geometry otherPolygonShell = otherPolygon.shell;
313: if (!this Shell.equalsExact(otherPolygonShell, tolerance)) {
314: return false;
315: }
316: if (holes.length != otherPolygon.holes.length) {
317: return false;
318: }
319: if (holes.length != otherPolygon.holes.length) {
320: return false;
321: }
322: for (int i = 0; i < holes.length; i++) {
323: if (!((Geometry) holes[i]).equalsExact(
324: otherPolygon.holes[i], tolerance)) {
325: return false;
326: }
327: }
328: return true;
329: }
330:
331: public void apply(CoordinateFilter filter) {
332: shell.apply(filter);
333: for (int i = 0; i < holes.length; i++) {
334: holes[i].apply(filter);
335: }
336: }
337:
338: public void apply(CoordinateSequenceFilter filter) {
339: shell.apply(filter);
340: if (!filter.isDone()) {
341: for (int i = 0; i < holes.length; i++) {
342: holes[i].apply(filter);
343: if (filter.isDone())
344: break;
345: }
346: }
347: if (filter.isGeometryChanged())
348: geometryChanged();
349: }
350:
351: public void apply(GeometryFilter filter) {
352: filter.filter(this );
353: }
354:
355: public void apply(GeometryComponentFilter filter) {
356: filter.filter(this );
357: shell.apply(filter);
358: for (int i = 0; i < holes.length; i++) {
359: holes[i].apply(filter);
360: }
361: }
362:
363: /**
364: * Creates and returns a full copy of this {@link Polygon} object.
365: * (including all coordinates contained by it).
366: *
367: * @return a clone of this instance
368: */
369: public Object clone() {
370: Polygon poly = (Polygon) super .clone();
371: poly.shell = (LinearRing) shell.clone();
372: poly.holes = new LinearRing[holes.length];
373: for (int i = 0; i < holes.length; i++) {
374: poly.holes[i] = (LinearRing) holes[i].clone();
375: }
376: return poly;// return the clone
377: }
378:
379: public Geometry convexHull() {
380: return getExteriorRing().convexHull();
381: }
382:
383: public void normalize() {
384: normalize(shell, true);
385: for (int i = 0; i < holes.length; i++) {
386: normalize(holes[i], false);
387: }
388: Arrays.sort(holes);
389: }
390:
391: protected int compareToSameClass(Object o) {
392: LinearRing this Shell = shell;
393: LinearRing otherShell = ((Polygon) o).shell;
394: return this Shell.compareToSameClass(otherShell);
395: }
396:
397: protected int compareToSameClass(Object o,
398: CoordinateSequenceComparator comp) {
399: Polygon poly = (Polygon) o;
400:
401: LinearRing this Shell = shell;
402: LinearRing otherShell = poly.shell;
403: int shellComp = this Shell.compareToSameClass(otherShell, comp);
404: if (shellComp != 0)
405: return shellComp;
406:
407: int nHole1 = getNumInteriorRing();
408: int nHole2 = poly.getNumInteriorRing();
409: int i = 0;
410: while (i < nHole1 && i < nHole2) {
411: LinearRing this Hole = (LinearRing) getInteriorRingN(i);
412: LinearRing otherHole = (LinearRing) poly
413: .getInteriorRingN(i);
414: int holeComp = this Hole.compareToSameClass(otherHole, comp);
415: if (holeComp != 0)
416: return holeComp;
417: i++;
418: }
419: if (i < nHole1)
420: return 1;
421: if (i < nHole2)
422: return -1;
423: return 0;
424: }
425:
426: private void normalize(LinearRing ring, boolean clockwise) {
427: if (ring.isEmpty()) {
428: return;
429: }
430: Coordinate[] uniqueCoordinates = new Coordinate[ring
431: .getCoordinates().length - 1];
432: System.arraycopy(ring.getCoordinates(), 0, uniqueCoordinates,
433: 0, uniqueCoordinates.length);
434: Coordinate minCoordinate = CoordinateArrays.minCoordinate(ring
435: .getCoordinates());
436: CoordinateArrays.scroll(uniqueCoordinates, minCoordinate);
437: System.arraycopy(uniqueCoordinates, 0, ring.getCoordinates(),
438: 0, uniqueCoordinates.length);
439: ring.getCoordinates()[uniqueCoordinates.length] = uniqueCoordinates[0];
440: if (CGAlgorithms.isCCW(ring.getCoordinates()) == clockwise) {
441: CoordinateArrays.reverse(ring.getCoordinates());
442: }
443: }
444:
445: }
|