/*
* 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
* multiPolygon 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 com.revolsys.datatype.DataType;
import com.revolsys.datatype.DataTypes;
import com.revolsys.geometry.model.segment.MultiPolygonSegment;
import com.revolsys.geometry.model.segment.Segment;
import com.revolsys.geometry.model.vertex.MultiPolygonVertex;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.geometry.operation.polygonize.Polygonizer;
/**
* Models a collection of {@link Polygon}s.
* <p>
* As per the OGC SFS specification,
* the Polygons in a MultiPolygon may not overlap,
* and may only touch at single points.
* This allows the topological point-set semantics
* to be well-defined.
*
*
*@version 1.7
*/
public interface MultiPolygon extends GeometryCollection, Polygonal {
@Override
default Polygonal applyPolygonal(final Function<Polygon, Polygon> function) {
if (!isEmpty()) {
boolean changed = false;
final Polygon[] polygons = new Polygon[getGeometryCount()];
int i = 0;
for (final Polygon polygon : polygons()) {
final Polygon newPolygon = function.apply(polygon);
changed |= polygon != newPolygon;
polygons[i++] = newPolygon;
}
if (changed) {
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.polygonal(polygons);
}
}
return this;
}
@Override
Polygonal clone();
@Override
default boolean equalsExact(final Geometry other, final double tolerance) {
if (!isEquivalentClass(other)) {
return false;
}
return GeometryCollection.super.equalsExact(other, tolerance);
}
/**
* 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 {
final List<LineString> allRings = new ArrayList<>();
for (final Polygon polygon : polygons()) {
final Geometry rings = polygon.getBoundary();
for (int j = 0; j < rings.getGeometryCount(); j++) {
final LineString ring = (LineString)rings.getGeometry(j);
allRings.add(ring);
}
}
return geometryFactory.lineal(allRings);
}
}
@Override
default int getBoundaryDimension() {
return 1;
}
@Override
default DataType getDataType() {
return DataTypes.MULTI_POLYGON;
}
@Override
default int getDimension() {
return 2;
}
@Override
default Polygon getPolygon(final int partIndex) {
return (Polygon)getGeometry(partIndex);
}
@Override
default Segment getSegment(final int... segmentId) {
if (segmentId == null || segmentId.length != 3) {
return null;
} else {
final int partIndex = segmentId[0];
if (partIndex >= 0 && partIndex < getGeometryCount()) {
final Polygon polygon = getPolygon(partIndex);
final int ringIndex = segmentId[1];
if (ringIndex >= 0 && ringIndex < polygon.getRingCount()) {
final LinearRing ring = polygon.getRing(ringIndex);
final int segmentIndex = segmentId[2];
if (segmentIndex >= 0 && segmentIndex < ring.getSegmentCount()) {
return new MultiPolygonSegment(this, partIndex, ringIndex, segmentIndex);
}
}
}
return null;
}
}
@Override
default int getSegmentCount() {
int segmentCount = 0;
for (final Polygon polygon : polygons()) {
segmentCount += polygon.getSegmentCount();
}
return segmentCount;
}
@Override
default Vertex getToVertex(int... vertexId) {
if (vertexId == null || vertexId.length != 3) {
return null;
} else {
final int partIndex = vertexId[0];
if (partIndex >= 0 && partIndex < getGeometryCount()) {
final Polygon polygon = getPolygon(partIndex);
final int ringIndex = vertexId[1];
if (ringIndex >= 0 && ringIndex < polygon.getRingCount()) {
final LinearRing ring = polygon.getRing(ringIndex);
int vertexIndex = vertexId[2];
final int vertexCount = ring.getVertexCount();
vertexIndex = vertexCount - 2 - vertexIndex;
if (vertexIndex <= vertexCount) {
while (vertexIndex < 0) {
vertexIndex += vertexCount - 1;
}
vertexId = Geometry.setVertexIndex(vertexId, vertexIndex);
return new MultiPolygonVertex(this, vertexId);
}
}
}
return null;
}
}
@Override
default Vertex getVertex(final int... vertexId) {
if (vertexId == null || vertexId.length != 3) {
return null;
} else {
final int partIndex = vertexId[0];
if (partIndex >= 0 && partIndex < getGeometryCount()) {
final Polygon polygon = getPolygon(partIndex);
final int ringIndex = vertexId[1];
if (ringIndex >= 0 && ringIndex < polygon.getRingCount()) {
final LinearRing ring = polygon.getRing(ringIndex);
int vertexIndex = vertexId[2];
final int vertexCount = ring.getVertexCount();
if (vertexIndex <= vertexCount) {
while (vertexIndex < 0) {
vertexIndex += vertexCount - 1;
}
return new MultiPolygonVertex(this, vertexId);
}
}
}
return null;
}
}
@Override
default boolean hasInvalidXyCoordinates() {
for (final Polygon polygon : polygons()) {
if (polygon.hasInvalidXyCoordinates()) {
return true;
}
}
return false;
}
@Override
default boolean isContainedInBoundary(final BoundingBox boundingBox) {
return false;
}
@Override
default boolean isEquivalentClass(final Geometry other) {
return other instanceof MultiPolygon;
}
@Override
default boolean isHomogeneousGeometryCollection() {
return true;
}
@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 == 3) {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot move vertex for empty MultiPolygon");
} else {
final int partIndex = vertexId[0];
final int ringIndex = vertexId[1];
final int vertexIndex = vertexId[2];
final int partCount = getGeometryCount();
if (partIndex >= 0 && partIndex < partCount) {
final GeometryFactory geometryFactory = getGeometryFactory();
final Polygon polygon = getPolygon(partIndex);
final Polygon newPolygon = polygon.moveVertex(newPoint, ringIndex, vertexIndex);
final List<Polygon> polygons = new ArrayList<>(getPolygons());
polygons.set(partIndex, newPolygon);
return (V)geometryFactory.polygonal(polygons);
} else {
throw new IllegalArgumentException(
"Part index must be between 0 and " + partCount + " not " + partIndex);
}
}
} else {
throw new IllegalArgumentException(
"Vertex id's for MultiPolygons must have length 3. " + Arrays.toString(vertexId));
}
}
@Override
default Polygonal newGeometry(final GeometryFactory geometryFactory) {
final List<Polygon> polygons = new ArrayList<>();
for (final Polygon polygon : polygons()) {
final Polygon newPolygon = polygon.newGeometry(geometryFactory);
polygons.add(newPolygon);
}
return geometryFactory.polygonal(polygons);
}
@SuppressWarnings("unchecked")
@Override
default <G> G newUsingGeometryFactory(final GeometryFactory factory) {
if (factory == getGeometryFactory()) {
return (G)this;
} else if (isEmpty()) {
return (G)factory.polygonal();
} else {
final Polygon[] polygons = new Polygon[getGeometryCount()];
for (int i = 0; i < getGeometryCount(); i++) {
Polygon polygon = getPolygon(i);
polygon = polygon.newUsingGeometryFactory(factory);
polygons[i] = polygon;
}
return (G)factory.polygonal(polygons);
}
}
@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();
for (final Polygon polygon : polygons()) {
polygonizer.addPolygon(polygon);
}
final Polygonal polygonal = polygonizer.getPolygonal();
if (polygonal.isEmpty()) {
return (G)this;
} else {
return (G)polygonal;
}
}
}
@Override
default Polygonal normalize() {
if (isEmpty()) {
return this;
} else {
final List<Polygon> geometries = new ArrayList<>();
for (final Geometry part : geometries()) {
final Polygon normalizedPart = (Polygon)part.normalize();
geometries.add(normalizedPart);
}
Collections.sort(geometries);
final GeometryFactory geometryFactory = getGeometryFactory();
final Polygonal normalizedGeometry = geometryFactory.polygonal(geometries);
return normalizedGeometry;
}
}
@Override
default Polygonal removeDuplicatePoints() {
return applyPolygonal(Polygon::removeDuplicatePoints);
}
/**
* Creates a {@link Polygonal} with
* every component reversed.
* The order of the components in the collection are not reversed.
*
* @return a MultiPolygon in the reverse order
*/
@Override
default Polygonal reverse() {
final List<Polygon> polygons = new ArrayList<>();
for (final Polygon polygon : polygons()) {
final Polygon reverse = polygon.reverse();
polygons.add(reverse);
}
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.polygonal(polygons);
}
@Override
default Iterable<Segment> segments() {
return new MultiPolygonSegment(this, 0, 0, -1);
}
@SuppressWarnings("unchecked")
@Override
default <G extends Geometry> G toClockwise() {
return (G)applyPolygonal(Polygon::toClockwise);
}
@SuppressWarnings("unchecked")
@Override
default <G extends Geometry> G toCounterClockwise() {
return (G)applyPolygonal(Polygon::toCounterClockwise);
}
@Override
default MultiPolygonVertex vertices() {
return new MultiPolygonVertex(this, 0, 0, -1);
}
}