/* * 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.util.ArrayList; import java.util.Arrays; import java.util.Collections; 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.DataTypes; import com.revolsys.geometry.algorithm.RayCrossingCounter; import com.revolsys.geometry.model.editor.PolygonEditor; import com.revolsys.geometry.model.impl.PointDoubleXY; import com.revolsys.geometry.model.prep.PreparedPolygon; import com.revolsys.geometry.model.segment.LineSegment; import com.revolsys.geometry.model.segment.LineSegmentDouble; import com.revolsys.geometry.model.segment.PolygonSegment; import com.revolsys.geometry.model.segment.Segment; import com.revolsys.geometry.model.vertex.PolygonVertex; import com.revolsys.geometry.model.vertex.Vertex; import com.revolsys.geometry.operation.polygonize.Polygonizer; import com.revolsys.geometry.operation.valid.GeometryValidationError; import com.revolsys.util.Property; /** * Represents a polygon with linear edges, which may include holes. * The outer boundary (shell) * and inner boundaries (holes) of the polygon are represented by {@link LinearRing}s. * The boundary rings of the polygon may have any orientation. * Polygons are closed, simple geometries by definition. * <p> * The polygon model conforms to the assertions specified in the * <A HREF="http://www.opengis.org/techno/specs.htm">OpenGIS Simple Features * Specification for SQL</A>. * <p> * A <code>Polygon</code> is topologically valid if and only if: * <ul> * <li>the coordinates which define it are valid coordinates * <li>the linear rings for the shell and holes are valid * (i.e. are closed and do not self-intersect) * <li>holes touch the shell or another hole at at most one point * (which implies that the rings of the shell and holes must not cross) * <li>the interior of the polygon is connected, * or equivalently no sequence of touching holes * makes the interior of the polygon disconnected * (i.e. effectively split the polygon into two pieces). * </ul> * *@version 1.7 */ public interface Polygon extends Polygonal { @SuppressWarnings("unchecked") static <G extends Geometry> G newPolygon(final Object value) { if (value == null) { return null; } else if (value instanceof Polygon) { return (G)value; } else if (value instanceof Geometry) { throw new IllegalArgumentException( ((Geometry)value).getGeometryType() + " cannot be converted to a Polygon"); } else { final String string = DataTypes.toString(value); return (G)GeometryFactory.DEFAULT_3D.geometry(string, false); } } @Override default boolean addIsSimpleErrors(final List<GeometryValidationError> errors, final boolean shortCircuit) { for (final LinearRing ring : rings()) { if (!ring.addIsSimpleErrors(errors, shortCircuit) && shortCircuit) { return false; } } return errors.isEmpty(); } @Override @SuppressWarnings("unchecked") default <V extends Geometry> V appendVertex(final Point newPoint, final int... geometryId) { if (newPoint == null || newPoint.isEmpty()) { return (V)this; } else if (geometryId.length == 1) { if (isEmpty()) { throw new IllegalArgumentException("Cannot move vertex for empty Polygon"); } else { final int ringIndex = geometryId[0]; final int ringCount = getRingCount(); if (ringIndex >= 0 && ringIndex < ringCount) { final GeometryFactory geometryFactory = getGeometryFactory(); final LinearRing ring = getRing(ringIndex); final LinearRing newRing = ring.appendVertex(newPoint); final List<LinearRing> rings = new ArrayList<>(getRings()); rings.set(ringIndex, newRing); return (V)geometryFactory.polygon(rings); } else { throw new IllegalArgumentException( "Ring index must be between 0 and " + ringCount + " not " + ringIndex); } } } else { throw new IllegalArgumentException( "Geometry id's for Polygons must have length 1. " + Arrays.toString(geometryId)); } } @Override default Polygon applyPolygonal(final Function<Polygon, Polygon> function) { if (!isEmpty()) { final Polygon newPolygon = function.apply(this); return newPolygon; } return this; } @Override Polygon clone(); @Override default int compareToSameClass(final Geometry geometry) { final LinearRing thisShell = getShell(); final Polygon ploygon2 = (Polygon)geometry; final LinearRing otherShell = ploygon2.getShell(); return thisShell.compareToSameClass(otherShell); } @Override default boolean contains(final Geometry geometry) { // short-circuit test final BoundingBox boundingBox = getBoundingBox(); final BoundingBox otherBoundingBox = geometry.getBoundingBox(); if (!boundingBox.covers(otherBoundingBox)) { return false; } // optimization for rectangle arguments if (isRectangle()) { return boundingBox.containsSFS(geometry); } else { // general case return relate(geometry).isContains(); } } @Override default Geometry convexHull() { return getShell().convexHull(); } @Override @SuppressWarnings("unchecked") default <V extends Geometry> V deleteVertex(final int... vertexId) { if (vertexId.length == 2) { if (isEmpty()) { throw new IllegalArgumentException("Cannot move vertex for empty Polygon"); } else { final int ringIndex = vertexId[0]; final int vertexIndex = vertexId[1]; final int ringCount = getRingCount(); if (ringIndex >= 0 && ringIndex < ringCount) { final GeometryFactory geometryFactory = getGeometryFactory(); final LinearRing ring = getRing(ringIndex); final LinearRing newRing = ring.deleteVertex(vertexIndex); final List<LinearRing> rings = new ArrayList<>(getRings()); rings.set(ringIndex, newRing); return (V)geometryFactory.polygon(rings); } else { throw new IllegalArgumentException( "Ring index must be between 0 and " + ringCount + " not " + ringIndex); } } } else { throw new IllegalArgumentException( "Vertex id's for Polygons must have length 2. " + Arrays.toString(vertexId)); } } @Override default double distance(final double x, final double y, final double terminateDistance) { if (isEmpty()) { return Double.POSITIVE_INFINITY; } else { // TODO implement intersects for x,y if (intersects(new PointDoubleXY(x, y))) { return 0.0; } else { double minDistance = Double.MAX_VALUE; for (final LinearRing ring : rings()) { final double distance = ring.distance(x, y, terminateDistance); if (distance < minDistance) { minDistance = distance; if (distance <= terminateDistance) { return distance; } } } return minDistance; } } } @Override default double distance(final 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 if (geometry instanceof LineString) { return Polygonal.super.distance(geometry, terminateDistance); } else if (geometry instanceof Polygon) { return Polygonal.super.distance(geometry, terminateDistance); } else { return geometry.distance(this, terminateDistance); } } @Override default boolean equals(final int axisCount, final Geometry geometry) { if (geometry == this) { return true; } else if (geometry == null) { return false; } else if (axisCount < 2) { throw new IllegalArgumentException("Axis Count must be >=2"); } else if (isEquivalentClass(geometry)) { if (isEmpty()) { return geometry.isEmpty(); } else if (geometry.isEmpty()) { return false; } else { final Polygon polygon = (Polygon)geometry; final int ringCount = getRingCount(); final int ringCount2 = polygon.getRingCount(); if (ringCount == ringCount2) { for (int i = 0; i < ringCount; i++) { final LinearRing ring1 = getRing(i); final LinearRing ring2 = polygon.getRing(i); if (!ring1.equals(axisCount, ring2)) { return false; } } return true; } } } return false; } @Override default boolean equalsExact(final Geometry other, final double tolerance) { if (!isEquivalentClass(other)) { return false; } else { final Polygon otherPolygon = (Polygon)other; final int ringCount = getRingCount(); if (ringCount != otherPolygon.getRingCount()) { return false; } else { for (int i = 0; i < ringCount; i++) { final LinearRing ring = getRing(i); final LinearRing otherRing = otherPolygon.getRing(i); if (!ring.equalsExact(otherRing, tolerance)) { return false; } } return true; } } } /** * Returns the area of this <code>Polygon</code> * *@return the area of the polygon */ @Override default double getArea() { double totalArea = 0; final LinearRing shell = getShell(); if (shell != null) { totalArea += shell.getPolygonArea(); } for (final LinearRing hole : holes()) { final double area = hole.getPolygonArea(); totalArea -= area; } return totalArea; } /** * Returns the area of this <code>Polygon</code> * *@return the area of the polygon */ @Override default double getArea(final Unit<Area> unit) { final LinearRing shell = getShell(); double totalArea = shell.getPolygonArea(unit); for (final LinearRing hole : holes()) { final double area = hole.getPolygonArea(unit); totalArea -= area; } return totalArea; } /** * Computes the boundary of this geometry * * @return a lineal geometry (which may be empty) * @see Geometry#getBoundary */ @Override default Geometry getBoundary() { final GeometryFactory geometryFactory = getGeometryFactory(); if (isEmpty()) { return geometryFactory.lineString(); } else { if (getRingCount() == 1) { return geometryFactory.linearRing(getShell()); } else { return geometryFactory.lineal(getRings()); } } } @Override default int getBoundaryDimension() { return 1; } default double getCoordinate(final int ringIndex, final int vertexIndex, final int axisIndex) { final LinearRing ring = getRing(ringIndex); if (ring == null) { return Double.NaN; } else { return ring.getCoordinate(vertexIndex, axisIndex); } } @Override default double getCoordinate(final int partIndex, final int ringIndex, final int vertexIndex, final int axisIndex) { if (partIndex == 0) { return getCoordinate(ringIndex, vertexIndex, axisIndex); } else { return Double.NaN; } } @Override default DataType getDataType() { return DataTypes.POLYGON; } @Override default int getDimension() { return 2; } @Override @SuppressWarnings("unchecked") default <V extends Geometry> List<V> getGeometryComponents(final Class<V> geometryClass) { final List<V> geometries = new ArrayList<>(); if (geometryClass.isAssignableFrom(getClass())) { geometries.add((V)this); } for (final LinearRing ring : rings()) { if (ring != null) { final List<V> ringGeometries = ring.getGeometries(geometryClass); geometries.addAll(ringGeometries); } } return geometries; } default LinearRing getHole(final int ringIndex) { return getRing(ringIndex + 1); } default int getHoleCount() { if (isEmpty()) { return 0; } else { return getRingCount() - 1; } } /** * Returns the perimeter of this <code>Polygon</code> * *@return the perimeter of the polygon */ @Override default double getLength() { double totalLength = 0.0; for (final LinearRing ring : rings()) { final double length = ring.getLength(); totalLength += length; } return totalLength; } @Override default double getLength(final Unit<Length> unit) { double totalLength = 0; for (final LinearRing ring : rings()) { final double length = ring.getLength(unit); totalLength += length; } return totalLength; } default double getM(final int ringIndex, final int vertexIndex) { return getCoordinate(ringIndex, vertexIndex, M); } @Override default Point getPoint() { if (isEmpty()) { return null; } else { return getShell().getPoint(); } } @Override default Point getPointWithin() { if (isEmpty()) { final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.point(); } else { if (isValid()) { final Point centroid = getCentroid(); boolean within = false; try { within = centroid.within(this); } catch (final TopologyException e) { } if (within) { return centroid; } else { final BoundingBox boundingBox = getBoundingBox(); final double x1 = centroid.getX(); final double y1 = centroid.getY(); for (final double x2 : new double[] { boundingBox.getMinX(), boundingBox.getMaxX() }) { for (final double y2 : new double[] { boundingBox.getMinY(), boundingBox.getMaxY() }) { final LineSegment line = new LineSegmentDouble(2, x1, y1, x2, y2); try { final Geometry intersection = intersection(line); if (!intersection.isEmpty()) { return intersection.getPointWithin(); } } catch (final TopologyException e) { } } } return getPoint(); } } else { final Geometry validGeometry = newValidGeometry(); if (validGeometry == this) { return getPoint(); } else { return validGeometry.getPointWithin(); } } } } @Override default Polygon getPolygon(final int partIndex) { if (partIndex == 1) { return this; } else { return null; } } LinearRing getRing(int ringIndex); int getRingCount(); List<LinearRing> getRings(); @Override default Segment getSegment(final int... segmentId) { if (segmentId == null || segmentId.length != 2) { return null; } else { final int ringIndex = segmentId[0]; if (ringIndex >= 0 && ringIndex < getRingCount()) { final LinearRing ring = getRing(ringIndex); final int segmentIndex = segmentId[1]; if (segmentIndex >= 0 && segmentIndex < ring.getSegmentCount()) { return new PolygonSegment(this, ringIndex, segmentIndex); } } return null; } } @Override default int getSegmentCount() { int segmentCount = 0; for (final LinearRing ring : rings()) { segmentCount += ring.getSegmentCount(); } return segmentCount; } default LinearRing getShell() { if (isEmpty()) { return null; } else { return getRing(0); } } @Override default Vertex getToVertex(int... vertexId) { if (vertexId == null || vertexId.length != 2) { return null; } else { final int ringIndex = vertexId[0]; if (ringIndex >= 0 && ringIndex < getRingCount()) { final LinearRing ring = getRing(ringIndex); int vertexIndex = vertexId[1]; final int vertexCount = ring.getVertexCount(); vertexIndex = vertexCount - 2 - vertexIndex; vertexId = Geometry.setVertexIndex(vertexId, vertexIndex); if (vertexIndex >= 0 && vertexIndex < vertexCount) { return new PolygonVertex(this, vertexId); } } return null; } } @Override default Vertex getVertex(final int... vertexId) { if (vertexId == null || vertexId.length > 2) { return null; } else if (vertexId.length == 1) { int vertexIndex = vertexId[0]; while (vertexIndex < 0) { vertexIndex += getVertexCount() - 1; } int totalVertexCount = 0; final int ringIndex = 0; for (final LinearRing ring : rings()) { final int ringVertexCount = ring.getVertexCount(); final int ringVertexIndex = vertexIndex - totalVertexCount; if (ringVertexIndex <= ringVertexCount) { return new PolygonVertex(this, ringIndex, ringVertexIndex); } else { totalVertexCount += ringVertexCount; } } return null; } else { final int ringIndex = vertexId[0]; if (ringIndex >= 0 && ringIndex < getRingCount()) { final LinearRing ring = getRing(ringIndex); int vertexIndex = vertexId[1]; final int vertexCount = ring.getVertexCount(); if (vertexIndex <= vertexCount) { while (vertexIndex < 0) { vertexIndex += vertexCount - 1; } return new PolygonVertex(this, vertexId); } } return null; } } @Override default int getVertexCount() { int vertexCount = 0; for (final LinearRing ring : rings()) { vertexCount += ring.getVertexCount(); } return vertexCount; } default double getX(final int ringIndex, final int vertexIndex) { return getCoordinate(ringIndex, vertexIndex, X); } default double getY(final int ringIndex, final int vertexIndex) { return getCoordinate(ringIndex, vertexIndex, Y); } default double getZ(final int ringIndex, final int vertexIndex) { return getCoordinate(ringIndex, vertexIndex, Z); } @Override default boolean hasInvalidXyCoordinates() { for (final LinearRing ring : rings()) { if (ring.hasInvalidXyCoordinates()) { return true; } } return false; } default Iterable<LinearRing> holes() { if (getHoleCount() == 0) { return Collections.emptyList(); } else { final List<LinearRing> holes = new ArrayList<>(); for (int i = 0; i < getHoleCount(); i++) { final LinearRing ring = getHole(i); holes.add(ring); } return holes; } } @Override @SuppressWarnings("unchecked") default <V extends Geometry> V insertVertex(final Point newPoint, final int... vertexId) { if (newPoint == null || newPoint.isEmpty()) { return (V)this; } else if (vertexId.length == 2) { if (isEmpty()) { throw new IllegalArgumentException("Cannot move vertex for empty Polygon"); } else { final int ringIndex = vertexId[0]; final int vertexIndex = vertexId[1]; final int ringCount = getRingCount(); if (ringIndex >= 0 && ringIndex < ringCount) { final GeometryFactory geometryFactory = getGeometryFactory(); final LinearRing ring = getRing(ringIndex); final LinearRing newRing = ring.insertVertex(newPoint, vertexIndex); final List<LinearRing> rings = new ArrayList<>(getRings()); rings.set(ringIndex, newRing); return (V)geometryFactory.polygon(rings); } else { throw new IllegalArgumentException( "Ring index must be between 0 and " + ringCount + " not " + ringIndex); } } } else { throw new IllegalArgumentException( "Vertex id's for Polygons must have length 2. " + Arrays.toString(vertexId)); } } @Override default boolean intersects(final BoundingBox boundingBox) { if (isEmpty() || boundingBox.isEmpty()) { return false; } else { final BoundingBox thisBoundingBox = getBoundingBox(); if (thisBoundingBox.intersects(boundingBox)) { return intersects(boundingBox.toPolygon(getGeometryFactory(), 10)); } // for (final LinearRing ring : rings()) { // if (ring.intersects(boundingBox)) { // return true; // } // } return false; } } @Override default boolean isContainedInBoundary(final BoundingBox boundingBox) { return false; } @Override default boolean isEmpty() { return getRingCount() == 0; } @Override default boolean isEquivalentClass(final Geometry other) { return other instanceof Polygon; } @Override default boolean isRectangle() { if (isEmpty()) { return false; } else if (getHoleCount() != 0) { return false; } else { final LinearRing shell = getShell(); if (shell.getVertexCount() != 5) { return false; } else { // check vertices have correct values final BoundingBox boundingBox = getBoundingBox(); for (int i = 0; i < 5; i++) { final double x = shell.getX(i); if (!(x == boundingBox.getMinX() || x == boundingBox.getMaxX())) { return false; } final double y = shell.getY(i); if (!(y == boundingBox.getMinY() || y == boundingBox.getMaxY())) { return false; } } // check vertices are in right order double prevX = shell.getX(0); double prevY = shell.getY(0); for (int i = 1; i <= 4; i++) { final double x = shell.getX(i); final double y = shell.getY(i); final boolean xChanged = x != prevX; final boolean yChanged = y != prevY; if (xChanged == yChanged) { return false; } prevX = x; prevY = y; } return true; } } } @Override default Location locate(Point point) { if (isEmpty() || point.isEmpty()) { return Location.EXTERIOR; } else { final GeometryFactory geometryFactory = getGeometryFactory(); point = point.convertGeometry(geometryFactory); final LinearRing shell = getShell(); final Location shellLocation = RayCrossingCounter.locatePointInRing(point, shell); if (shellLocation == Location.EXTERIOR) { return Location.EXTERIOR; } else if (shellLocation == Location.BOUNDARY) { return Location.BOUNDARY; } else { for (final LinearRing hole : holes()) { final Location holeLocation = RayCrossingCounter.locatePointInRing(point, hole); if (holeLocation == Location.INTERIOR) { return Location.EXTERIOR; } else if (holeLocation == Location.BOUNDARY) { return Location.BOUNDARY; } } } return Location.INTERIOR; } } @Override default Polygon move(final double... deltas) { if (deltas == null || isEmpty()) { return this; } else { final List<LinearRing> rings = new ArrayList<>(); for (final LinearRing part : rings()) { final LinearRing movedPart = part.move(deltas); rings.add(movedPart); } final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.polygon(rings); } } @Override @SuppressWarnings("unchecked") default <V extends Geometry> V moveVertex(final Point newPoint, final int... vertexId) { if (newPoint == null || newPoint.isEmpty()) { return (V)this; } else if (vertexId.length == 2) { if (isEmpty()) { throw new IllegalArgumentException("Cannot move vertex for empty Polygon"); } else { final int ringIndex = vertexId[0]; final int vertexIndex = vertexId[1]; final int ringCount = getRingCount(); if (ringIndex >= 0 && ringIndex < ringCount) { final GeometryFactory geometryFactory = getGeometryFactory(); final LinearRing ring = getRing(ringIndex); final LinearRing newRing = ring.moveVertex(newPoint, vertexIndex); final List<LinearRing> rings = new ArrayList<>(getRings()); rings.set(ringIndex, newRing); return (V)geometryFactory.polygon(rings); } else { throw new IllegalArgumentException( "Ring index must be between 0 and " + ringCount + " not " + ringIndex); } } } else { throw new IllegalArgumentException( "Vertex id's for Polygons must have length 2. " + Arrays.toString(vertexId)); } } @Override default Polygon newGeometry(final GeometryFactory geometryFactory) { final List<LinearRing> rings = new ArrayList<>(); for (final LinearRing ring : rings()) { final LinearRing newRing = ring.newGeometry(geometryFactory); rings.add(newRing); } return geometryFactory.polygon(rings); } @Override default PolygonEditor newGeometryEditor() { return new PolygonEditor(this); } @Override default PolygonEditor newGeometryEditor(final int axisCount) { final PolygonEditor geometryEditor = newGeometryEditor(); geometryEditor.setAxisCount(axisCount); return geometryEditor; } default Polygon newPolygon(final GeometryFactory geometryFactory, final LinearRing... rings) { return geometryFactory.polygon(rings); } default Polygon newPolygon(final LinearRing... rings) { final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.polygon(rings); } default Polygon newPolygonWithoutHoles() { if (isEmpty()) { return this; } else if (getRingCount() == 1) { return this; } else { final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.polygon(getShell()); } } @SuppressWarnings("unchecked") @Override default <G> G newUsingGeometryFactory(final GeometryFactory factory) { if (factory == getGeometryFactory()) { return (G)this; } else if (isEmpty()) { return (G)factory.polygon(); } else { final int ringCount = getRingCount(); final LinearRing[] rings = new LinearRing[ringCount]; for (int i = 0; i < ringCount; i++) { LinearRing ring = getRing(i); ring = ring.newUsingGeometryFactory(factory); rings[i] = ring; } return (G)factory.polygon(rings); } } @Override @SuppressWarnings("unchecked") default <G extends Geometry> G newValidGeometry() { if (isEmpty()) { return (G)this; } else if (isValid()) { return (G)normalize(); } else { final Polygonizer polygonizer = new Polygonizer(); polygonizer.addPolygon(this); final Polygonal polygonal = polygonizer.getPolygonal(); if (polygonal.isEmpty()) { return (G)this; } else { return (G)polygonal; } } } @Override default Polygon normalize() { if (isEmpty()) { return this; } else { final LinearRing exteriorRing = getShell().normalize(true); final List<LinearRing> rings = new ArrayList<>(); for (final LinearRing hole : holes()) { final LinearRing normalizedHole = hole.normalize(false); rings.add(normalizedHole); } Collections.sort(rings); rings.add(0, exteriorRing); final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.polygon(rings); } } @Override default Iterable<Polygon> polygons() { return Collections.singleton(this); } @Override @Deprecated default Polygon prepare() { return new PreparedPolygon(this); } @Override default Polygon removeDuplicatePoints() { if (isEmpty()) { return this; } else { final List<LinearRing> rings = new ArrayList<>(); for (final LinearRing ring : rings()) { final LinearRing newRing = ring.removeDuplicatePoints(); rings.add(newRing); } final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.polygon(rings); } } @Override default Polygon reverse() { final List<LinearRing> rings = new ArrayList<>(); for (final LinearRing ring : rings()) { rings.add(ring.reverse()); } final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.polygon(rings); } default Iterable<LinearRing> rings() { if (isEmpty()) { return Collections.emptyList(); } else { return getRings(); } } @Override default Iterable<Segment> segments() { return new PolygonSegment(this, 0, -1); } @Override @SuppressWarnings("unchecked") default <G extends Geometry> G toClockwise() { if (!isEmpty()) { boolean changed = false; final List<Geometry> rings = new ArrayList<>(); boolean exterior = true; for (LinearRing ring : rings()) { if (ring.isClockwise() != exterior) { ring = ring.reverse(); changed = true; } exterior = false; rings.add(ring); } if (changed) { final GeometryFactory geometryFactory = getGeometryFactory(); return (G)geometryFactory.polygon(rings); } } return (G)this; } @Override @SuppressWarnings("unchecked") default <G extends Geometry> G toCounterClockwise() { if (!isEmpty()) { boolean changed = false; final List<Geometry> rings = new ArrayList<>(); boolean exterior = true; for (LinearRing ring : rings()) { if (ring.isClockwise() == exterior) { ring = ring.reverse(); changed = true; } exterior = false; rings.add(ring); } if (changed) { final GeometryFactory geometryFactory = getGeometryFactory(); return (G)geometryFactory.polygon(rings); } } return (G)this; } @Override default PolygonVertex vertices() { return new PolygonVertex(this, 0, -1); } }