Class Polygon

  1  
  2  
  3 /*
  4  * Copyright (c) 2016 Vivid Solutions.
  5  *
  6  * All rights reserved. This program and the accompanying materials
  7  * are made available under the terms of the Eclipse Public License 2.0
  8  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  9  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 10  * and the Eclipse Distribution License is available at
 11  *
 12  * http://www.eclipse.org/org/documents/edl-v10.php.
 13  */
 14 package org.locationtech.jts.geom;
 15  
 16 import java.util.Arrays;
 17  
 18 import org.locationtech.jts.algorithm.Area;
 19 import org.locationtech.jts.algorithm.Orientation;
 20  
 21  
 22 /**
 23  * Represents a polygon with linear edges, which may include holes.
 24  * The outer boundary (shell)
 25  * and inner boundaries (holes) of the polygon are represented by {@link LinearRing}s.
 26  * The boundary rings of the polygon may have any orientation.
 27  * Polygons are closed, simple geometries by definition.
 28  * <p>
 29  * The polygon model conforms to the assertions specified in the
 30  * <A HREF="http://www.opengis.org/techno/specs.htm">OpenGIS Simple Features
 31  * Specification for SQL</A>.
 32  * <p>
 33  * A <code>Polygon</code> is topologically valid if and only if:
 34  * <ul>
 35  * <li>the coordinates which define it are valid coordinates
 36  * <li>the linear rings for the shell and holes are valid
 37  * (i.e. are closed and do not self-intersect)
 38  * <li>holes touch the shell or another hole at at most one point
 39  * (which implies that the rings of the shell and holes must not cross)
 40  * <li>the interior of the polygon is connected,
 41  * or equivalently no sequence of touching holes
 42  * makes the interior of the polygon disconnected
 43  * (i.e. effectively split the polygon into two pieces).
 44  * </ul>
 45  *
 46  *@version 1.7
 47  */
 48 public class Polygon
 49     extends Geometry
 50     implements Polygonal
 51 {
 52   private static final long serialVersionUID = -3494792200821764533L;
 53  
 54   /**
 55    *  The exterior boundary,
 56    * or <code>null</code> if this <code>Polygon</code>
 57    *  is empty.
 58    */
 59   protected LinearRing shell = null;
 60  
 61   /**
 62    * The interior boundaries, if any.
 63    * This instance var is never null.
 64    * If there are no holes, the array is of zero length.
 65    */
 66   protected LinearRing[] holes;
 67  
 68   /**
 69    *  Constructs a <code>Polygon</code> with the given exterior boundary.
 70    *
 71    *@param  shell           the outer boundary of the new <code>Polygon</code>,
 72    *      or <code>null</code> or an empty <code>LinearRing</code> if the empty
 73    *      geometry is to be created.
 74    *@param  precisionModel  the specification of the grid of allowable points
 75    *      for this <code>Polygon</code>
 76    *@param  SRID            the ID of the Spatial Reference System used by this
 77    *      <code>Polygon</code>
 78    * @deprecated Use GeometryFactory instead
 79    */
 80   public Polygon(LinearRing shell, PrecisionModel precisionModel, int SRID) {
 81     this(shell, new LinearRing[]{}, new GeometryFactory(precisionModel, SRID));
 82   }
 83  
 84   /**
 85    *  Constructs a <code>Polygon</code> with the given exterior boundary and
 86    *  interior boundaries.
 87    *
 88    *@param  shell           the outer boundary of the new <code>Polygon</code>,
 89    *      or <code>null</code> or an empty <code>LinearRing</code> if the empty
 90    *      geometry is to be created.
 91    *@param  holes           the inner boundaries of the new <code>Polygon</code>
 92    *      , or <code>null</code> or empty <code>LinearRing</code>s if the empty
 93    *      geometry is to be created.
 94    *@param  precisionModel  the specification of the grid of allowable points
 95    *      for this <code>Polygon</code>
 96    *@param  SRID            the ID of the Spatial Reference System used by this
 97    *      <code>Polygon</code>
 98    * @deprecated Use GeometryFactory instead
 99    */
100   public Polygon(LinearRing shell, LinearRing[] holes, PrecisionModel precisionModel, int SRID) {
101       this(shell, holes, new GeometryFactory(precisionModel, SRID));
102   }
103  
104   /**
105    *  Constructs a <code>Polygon</code> with the given exterior boundary and
106    *  interior boundaries.
107    *
108    *@param  shell           the outer boundary of the new <code>Polygon</code>,
109    *      or <code>null</code> or an empty <code>LinearRing</code> if the empty
110    *      geometry is to be created.
111    *@param  holes           the inner boundaries of the new <code>Polygon</code>
112    *      , or <code>null</code> or empty <code>LinearRing</code>s if the empty
113    *      geometry is to be created.
114    */
115   public Polygon(LinearRing shell, LinearRing[] holes, GeometryFactory factory) {
116     super(factory);
117     if (shell == null) {
118       shell = getFactory().createLinearRing();
119     }
120     if (holes == null) {
121       holes = new LinearRing[]{};
122     }
123     if (hasNullElements(holes)) {
124       throw new IllegalArgumentException("holes must not contain null elements");
125     }
126     if (shell.isEmpty() && hasNonEmptyElements(holes)) {
127       throw new IllegalArgumentException("shell is empty but holes are not");
128     }
129     this.shell = shell;
130     this.holes = holes;
131   }
132  
133   public Coordinate getCoordinate() {
134     return shell.getCoordinate();
135   }
136  
137   public Coordinate[] getCoordinates() {
138     if (isEmpty()) {
139       return new Coordinate[]{};
140     }
141     Coordinate[] coordinates = new Coordinate[getNumPoints()];
142     int k = -1;
143     Coordinate[] shellCoordinates = shell.getCoordinates();
144     for (int x = 0; x < shellCoordinates.length; x++) {
145       k++;
146       coordinates[k] = shellCoordinates[x];
147     }
148     for (int i = 0; i < holes.length; i++) {
149       Coordinate[] childCoordinates = holes[i].getCoordinates();
150       for (int j = 0; j < childCoordinates.length; j++) {
151         k++;
152         coordinates[k] = childCoordinates[j];
153       }
154     }
155     return coordinates;
156   }
157  
158   public int getNumPoints() {
159     int numPoints = shell.getNumPoints();
160     for (int i = 0; i < holes.length; i++) {
161       numPoints += holes[i].getNumPoints();
162     }
163     return numPoints;
164   }
165  
166   public int getDimension() {
167     return 2;
168   }
169  
170   public int getBoundaryDimension() {
171     return 1;
172   }
173  
174   public boolean isEmpty() {
175     return shell.isEmpty();
176   }
177  
178   public boolean isRectangle()
179   {
180     if (getNumInteriorRing() != 0return false;
181     if (shell == nullreturn false;
182     if (shell.getNumPoints() != 5return false;
183  
184     CoordinateSequence seq = shell.getCoordinateSequence();
185  
186     // check vertices have correct values
187     Envelope env = getEnvelopeInternal();
188     for (int i = 0; i < 5; i++) {
189       double x = seq.getX(i);
190       if (! (x == env.getMinX() || x == env.getMaxX())) return false;
191       double y = seq.getY(i);
192       if (! (y == env.getMinY() || y == env.getMaxY())) return false;
193     }
194  
195     // check vertices are in right order
196     double prevX = seq.getX(0);
197     double prevY = seq.getY(0);
198     for (int i = 1; i <= 4; i++) {
199       double x = seq.getX(i);
200       double y = seq.getY(i);
201       boolean xChanged = x != prevX;
202       boolean yChanged = y != prevY;
203       if (xChanged == yChanged)
204         return false;
205       prevX = x;
206       prevY = y;
207     }
208     return true;
209   }
210  
211   public LinearRing getExteriorRing() {
212     return shell;
213   }
214  
215   public int getNumInteriorRing() {
216     return holes.length;
217   }
218  
219   public LinearRing getInteriorRingN(int n) {
220     return holes[n];
221   }
222  
223   public String getGeometryType() {
224     return Geometry.TYPENAME_POLYGON;
225   }
226  
227   /**
228    *  Returns the area of this <code>Polygon</code>
229    *
230    *@return the area of the polygon
231    */
232   public double getArea()
233   {
234     double area = 0.0;
235     area += Area.ofRing(shell.getCoordinateSequence());
236     for (int i = 0; i < holes.length; i++) {
237       area -= Area.ofRing(holes[i].getCoordinateSequence());
238     }
239     return area;
240   }
241  
242   /**
243    *  Returns the perimeter of this <code>Polygon</code>
244    *
245    *@return the perimeter of the polygon
246    */
247   public double getLength()
248   {
249     double len = 0.0;
250     len += shell.getLength();
251     for (int i = 0; i < holes.length; i++) {
252       len += holes[i].getLength();
253     }
254     return len;
255   }
256  
257   /**
258    * Computes the boundary of this geometry
259    *
260    * @return a lineal geometry (which may be empty)
261    * @see Geometry#getBoundary
262    */
263   public Geometry getBoundary() {
264     if (isEmpty()) {
265       return getFactory().createMultiLineString();
266     }
267     LinearRing[] rings = new LinearRing[holes.length + 1];
268     rings[0] = shell;
269     for (int i = 0; i < holes.length; i++) {
270       rings[i + 1] = holes[i];
271     }
272     // create LineString or MultiLineString as appropriate
273     if (rings.length <= 1)
274       return getFactory().createLinearRing(rings[0].getCoordinateSequence());
275     return getFactory().createMultiLineString(rings);
276   }
277  
278   protected Envelope computeEnvelopeInternal() {
279     return shell.getEnvelopeInternal();
280   }
281  
282   public boolean equalsExact(Geometry other, double tolerance) {
283     if (!isEquivalentClass(other)) {
284       return false;
285     }
286     Polygon otherPolygon = (Polygon) other;
287     Geometry thisShell = shell;
288     Geometry otherPolygonShell = otherPolygon.shell;
289     if (!thisShell.equalsExact(otherPolygonShell, tolerance)) {
290       return false;
291     }
292     if (holes.length != otherPolygon.holes.length) {
293       return false;
294     }
295     for (int i = 0; i < holes.length; i++) {
296       if (!((Geometry) holes[i]).equalsExact(otherPolygon.holes[i], tolerance)) {
297         return false;
298       }
299     }
300     return true;
301   }
302  
303   public void apply(CoordinateFilter filter) {
304         shell.apply(filter);
305         for (int i = 0; i < holes.length; i++) {
306           holes[i].apply(filter);
307         }
308       }
309  
310   public void apply(CoordinateSequenceFilter filter)
311   {
312         shell.apply(filter);
313       if (! filter.isDone()) {
314         for (int i = 0; i < holes.length; i++) {
315           holes[i].apply(filter);
316           if (filter.isDone())
317             break;
318         }
319       }
320       if (filter.isGeometryChanged())
321         geometryChanged();
322       }
323  
324   public void apply(GeometryFilter filter) {
325     filter.filter(this);
326   }
327  
328   public void apply(GeometryComponentFilter filter) {
329     filter.filter(this);
330     shell.apply(filter);
331     for (int i = 0; i < holes.length; i++) {
332       holes[i].apply(filter);
333     }
334   }
335  
336   /**
337    * Creates and returns a full copy of this {@link Polygon} object.
338    * (including all coordinates contained by it).
339    *
340    * @return a clone of this instance
341    * @deprecated
342    */
343   public Object clone() {
344  
345     return copy();
346   }
347  
348   protected Polygon copyInternal() {
349     LinearRing shellCopy = (LinearRing) shell.copy();
350     LinearRing[] holeCopies = new LinearRing[this.holes.length];
351     for (int i = 0; i < holes.length; i++) {
352         holeCopies[i] = (LinearRing) holes[i].copy();
353     }
354     return new Polygon(shellCopy, holeCopies, factory);
355   }
356  
357   public Geometry convexHull() {
358     return getExteriorRing().convexHull();
359   }
360  
361   public void normalize() {
362     shell = normalized(shell, true);
363     for (int i = 0; i < holes.length; i++) {
364       holes[i] = normalized(holes[i], false);
365     }
366     Arrays.sort(holes);
367   }
368  
369   protected int compareToSameClass(Object o) {
370     LinearRing thisShell = shell;
371     LinearRing otherShell = ((Polygon) o).shell;
372     return thisShell.compareToSameClass(otherShell);
373   }
374  
375   protected int compareToSameClass(Object o, CoordinateSequenceComparator comp) {
376     Polygon poly = (Polygon) o;
377  
378     LinearRing thisShell = shell;
379     LinearRing otherShell = poly.shell;
380     int shellComp = thisShell.compareToSameClass(otherShell, comp);
381     if (shellComp != 0return shellComp;
382  
383     int nHole1 = getNumInteriorRing();
384     int nHole2 = poly.getNumInteriorRing();
385     int i = 0;
386     while (i < nHole1 && i < nHole2) {
387       LinearRing thisHole = (LinearRing) getInteriorRingN(i);
388       LinearRing otherHole = (LinearRing) poly.getInteriorRingN(i);
389       int holeComp = thisHole.compareToSameClass(otherHole, comp);
390       if (holeComp != 0return holeComp;
391       i++;
392     }
393     if (i < nHole1) return 1;
394     if (i < nHole2) return -1;
395     return 0;
396   }
397   
398   protected int getTypeCode() {
399     return Geometry.TYPECODE_POLYGON;
400   }
401  
402   private LinearRing normalized(LinearRing ring, boolean clockwise) {
403     LinearRing res = (LinearRing) ring.copy();
404     normalize(res, clockwise);
405     return res;
406   }
407  
408   private void normalize(LinearRing ring, boolean clockwise) {
409     if (ring.isEmpty()) {
410       return;
411     }
412  
413     CoordinateSequence seq = ring.getCoordinateSequence();
414     int minCoordinateIndex = CoordinateSequences.minCoordinateIndex(seq, 0, seq.size()-2);
415     CoordinateSequences.scroll(seq, minCoordinateIndex, true);
416     if (Orientation.isCCW(seq) == clockwise)
417       CoordinateSequences.reverse(seq);
418   }
419  
420   public Polygon reverse() {
421     return (Polygon) super.reverse();
422   }
423  
424   protected Polygon reverseInternal()
425   {
426     LinearRing shell = (LinearRing) getExteriorRing().reverse();
427     LinearRing[] holes = new LinearRing[getNumInteriorRing()];
428     for (int i = 0; i < holes.length; i++) {
429       holes[i] = (LinearRing) getInteriorRingN(i).reverse();
430     }
431  
432     return getFactory().createPolygon(shell, holes);
433   }
434 }
435  
436