/*
* 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.Set;
import java.util.TreeSet;
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.PointLocator;
import com.revolsys.geometry.model.segment.GeometryCollectionSegment;
import com.revolsys.geometry.model.segment.Segment;
import com.revolsys.geometry.model.vertex.GeometryCollectionVertex;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.geometry.operation.polygonize.Polygonizer;
import com.revolsys.geometry.operation.valid.GeometryValidationError;
/**
* Models a collection of {@link Geometry}s of
* arbitrary type and dimension.
*
*
*@version 1.7
*/
public interface GeometryCollection extends Geometry {
static Geometry newGeometryCollection(final Object value) {
if (value == null) {
return null;
} else if (value instanceof Geometry) {
return (Geometry)value;
} else {
final String string = DataTypes.toString(value);
return GeometryFactory.DEFAULT_3D.geometry(string, false);
}
}
@Override
default boolean addIsSimpleErrors(final List<GeometryValidationError> errors,
final boolean shortCircuit) {
for (final Geometry geometry : geometries()) {
if (!geometry.addIsSimpleErrors(errors, shortCircuit) && shortCircuit) {
return false;
}
}
return errors.isEmpty();
}
default void addPointVertices(final List<Vertex> vertices,
final GeometryCollection geometryCollection, final int... parentId) {
for (int partIndex = 0; partIndex < getGeometryCount(); partIndex++) {
final Geometry part = getGeometry(partIndex);
if (part instanceof Point) {
final int[] vertexId = new int[parentId.length + 1];
System.arraycopy(parentId, 0, vertexId, 0, parentId.length);
vertexId[parentId.length] = partIndex;
final Vertex vertex = getVertex(vertexId);
vertices.add(vertex);
}
}
}
@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 > 0) {
final GeometryFactory geometryFactory = getGeometryFactory();
if (isEmpty()) {
return (V)newPoint.newGeometry(geometryFactory);
} else {
final int partIndex = geometryId[0];
final int partCount = getGeometryCount();
if (partIndex >= 0 && partIndex < partCount) {
final int[] subId = new int[geometryId.length - 1];
System.arraycopy(geometryId, 1, subId, 0, subId.length);
final Geometry geometry = getGeometry(partIndex);
final Geometry newGeometry = geometry.appendVertex(newPoint, subId);
final List<Geometry> geometries = new ArrayList<>(getGeometries());
geometries.set(partIndex, newGeometry);
return (V)geometryFactory.geometryCollection(geometries);
} else {
throw new IllegalArgumentException(
"Part index must be between 0 and " + partCount + " not " + partIndex);
}
}
} else {
throw new IllegalArgumentException(
"Vertex id's for GeometryCollection must have length > 1. " + Arrays.toString(geometryId));
}
}
@Override
@SuppressWarnings("unchecked")
default <GIN extends Geometry, GRET extends Geometry> GRET applyGeometry(
final Function<? super GIN, ? super Geometry> function) {
if (!isEmpty()) {
boolean changed = false;
final List<Geometry> geometries = new ArrayList<>();
for (final Geometry geometry : geometries()) {
final Geometry newGeometry = (Geometry)function.apply((GIN)geometry);
changed |= geometry != newGeometry;
geometries.add(newGeometry);
}
if (changed) {
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.geometry(geometries);
}
}
return (GRET)this;
}
default Geometry applyGeometryCollection(final Function<Geometry, Geometry> function) {
if (!isEmpty()) {
boolean changed = false;
final List<Geometry> geometries = new ArrayList<>();
for (final Geometry geometry : geometries()) {
final Geometry newGeometry = function.apply(geometry);
changed |= geometry != newGeometry;
geometries.add(newGeometry);
}
if (changed) {
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.geometryCollection(geometries);
}
}
return this;
}
@Override
default int compareToSameClass(final Geometry geometry) {
final Set<Geometry> theseElements = new TreeSet<>(getGeometries());
final Set<Geometry> otherElements = new TreeSet<>(geometry.getGeometries());
return Geometry.compare(theseElements, otherElements);
}
@Override
@SuppressWarnings("unchecked")
default <V extends Geometry> V deleteVertex(final int... vertexId) {
if (vertexId.length > 1) {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot delete vertex for empty MultiPoint");
} else {
final int partIndex = vertexId[0];
final int partCount = getGeometryCount();
if (partIndex >= 0 && partIndex < partCount) {
final GeometryFactory geometryFactory = getGeometryFactory();
final int[] subId = new int[vertexId.length - 1];
System.arraycopy(vertexId, 1, subId, 0, subId.length);
final Geometry geometry = getGeometry(partIndex);
final Geometry newGeometry = geometry.deleteVertex(subId);
final List<Geometry> geometries = new ArrayList<>(getGeometries());
geometries.set(partIndex, newGeometry);
return (V)geometryFactory.geometryCollection(geometries);
} else {
throw new IllegalArgumentException(
"Part index must be between 0 and " + partCount + " not " + partIndex);
}
}
} else {
throw new IllegalArgumentException(
"Vertex id's for GeometryCollection must have length > 1. " + Arrays.toString(vertexId));
}
}
@Override
default double distance(final double x, final double y, final double terminateDistance) {
if (isEmpty()) {
return Double.POSITIVE_INFINITY;
} else {
double minDistance = Double.MAX_VALUE;
for (final Geometry geometry : geometries()) {
final double distance = geometry.distance(x, y);
if (distance < minDistance) {
minDistance = distance;
if (distance <= terminateDistance) {
return distance;
}
}
}
return minDistance;
}
}
@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 int geometryCount1 = getGeometryCount();
final int geometryCount2 = geometry.getGeometryCount();
if (geometryCount1 == geometryCount2) {
for (int i = 0; i < geometryCount1; i++) {
final Geometry part1 = getGeometry(i);
final Geometry part2 = geometry.getGeometry(i);
if (!part1.equals(axisCount, part2)) {
return false;
}
}
return true;
}
}
}
return false;
}
@Override
default boolean equalsExact(final Geometry other, final double tolerance) {
if (!isEquivalentClass(other)) {
return false;
}
if (getGeometryCount() != other.getGeometryCount()) {
return false;
}
int i = 0;
for (final Geometry geometry : geometries()) {
if (!geometry.equalsExact(other.getGeometry(i), tolerance)) {
return false;
}
i++;
}
return true;
}
@Override
default Iterable<Geometry> geometries() {
return getGeometries();
}
/**
* Returns the area of this <code>GeometryCollection</code>
*
* @return the area of the polygon
*/
@Override
default double getArea() {
double totalArea = 0.0;
for (final Geometry geometry : geometries()) {
final double area = geometry.getArea();
totalArea += area;
}
return totalArea;
}
@Override
default double getArea(final Unit<Area> unit) {
double totalArea = 0.0;
for (final Geometry geometry : geometries()) {
final double area = geometry.getArea(unit);
totalArea += area;
}
return totalArea;
}
@Override
default Geometry getBoundary() {
throw new IllegalArgumentException("This method does not support GeometryCollection arguments");
}
@Override
default int getBoundaryDimension() {
int dimension = Dimension.FALSE;
for (final Geometry geometry : geometries()) {
dimension = Math.max(dimension, geometry.getBoundaryDimension());
}
return dimension;
}
@Override
default DataType getDataType() {
return DataTypes.GEOMETRY_COLLECTION;
}
@Override
default int getDimension() {
int dimension = Dimension.FALSE;
for (final Geometry geometry : geometries()) {
dimension = Math.max(dimension, geometry.getDimension());
}
return dimension;
}
@Override
default <V extends Geometry> List<V> getGeometries(final Class<V> geometryClass) {
final List<V> geometries = Geometry.super.getGeometries(geometryClass);
for (final Geometry geometry : geometries()) {
if (geometry != null) {
final List<V> partGeometries = geometry.getGeometries(geometryClass);
geometries.addAll(partGeometries);
}
}
return geometries;
}
@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 Geometry geometry : geometries()) {
if (geometry != null) {
final List<V> partGeometries = geometry.getGeometryComponents(geometryClass);
geometries.addAll(partGeometries);
}
}
return geometries;
}
@Override
default double getLength() {
double totalLength = 0.0;
for (final Geometry geometry : geometries()) {
final double length = geometry.getLength();
totalLength += length;
}
return totalLength;
}
@Override
default double getLength(final Unit<Length> unit) {
double totalLength = 0.0;
for (final Geometry geometry : geometries()) {
final double length = geometry.getLength(unit);
totalLength += length;
}
return totalLength;
}
@Override
default Point getPoint() {
if (isEmpty()) {
return null;
} else {
return getGeometry(0).getPoint();
}
}
@Override
default Point getPointWithin() {
if (!isEmpty()) {
for (final Geometry geometry : geometries()) {
final Point point = geometry.getPointWithin();
if (!point.isEmpty()) {
return point;
}
}
return getPoint();
}
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.point();
}
@Override
default Segment getSegment(final int... segmentId) {
return new GeometryCollectionSegment(this, segmentId);
}
@Override
default Vertex getToVertex(final int... vertexId) {
return new GeometryCollectionVertex(this, vertexId);
}
@Override
default Vertex getVertex(final int... vertexId) {
return new GeometryCollectionVertex(this, vertexId);
}
@Override
default int getVertexCount() {
int numPoints = 0;
for (final Geometry geometry : geometries()) {
numPoints += geometry.getVertexCount();
}
return numPoints;
}
@Override
default boolean hasInvalidXyCoordinates() {
for (final Geometry geometry : geometries()) {
if (geometry.hasInvalidXyCoordinates()) {
return true;
}
}
return false;
}
@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 > 1) {
final GeometryFactory geometryFactory = getGeometryFactory();
if (isEmpty()) {
return newPoint.convertGeometry(geometryFactory);
} else {
final int partIndex = vertexId[0];
final int partCount = getGeometryCount();
if (partIndex >= 0 && partIndex < partCount) {
final int[] subId = new int[vertexId.length - 1];
System.arraycopy(vertexId, 1, subId, 0, subId.length);
final Geometry geometry = getGeometry(partIndex);
final Geometry newGeometry = geometry.insertVertex(newPoint, subId);
final List<Geometry> geometries = new ArrayList<>(getGeometries());
geometries.set(partIndex, newGeometry);
return (V)geometryFactory.geometryCollection(geometries);
} else {
throw new IllegalArgumentException(
"Part index must be between 0 and " + partCount + " not " + partIndex);
}
}
} else {
throw new IllegalArgumentException("Vertex id's for " + getGeometryType()
+ " must have length > 1. " + Arrays.toString(vertexId));
}
}
@Override
default boolean intersects(final BoundingBox boundingBox) {
if (isEmpty() || boundingBox.isEmpty()) {
return false;
} else {
for (final Geometry geometry : geometries()) {
if (geometry.intersects(boundingBox)) {
return true;
}
}
return false;
}
}
@Override
default boolean isContainedInBoundary(final BoundingBox boundingBox) {
for (final Geometry geometry : geometries()) {
if (!geometry.isContainedInBoundary(boundingBox)) {
return false;
}
}
return true;
}
@Override
default boolean isEmpty() {
if (getGeometryCount() == 0) {
return true;
} else {
for (final Geometry geometry : geometries()) {
if (!geometry.isEmpty()) {
return false;
}
}
return true;
}
}
@Override
default boolean isEquivalentClass(final Geometry other) {
return other instanceof GeometryCollection;
}
@Override
default boolean isGeometryCollection() {
return true;
}
@Override
default Location locate(final Point point) {
return new PointLocator().locate(point, this);
}
@Override
default Geometry move(final double... deltas) {
if (deltas == null || isEmpty()) {
return this;
} else {
final List<Geometry> parts = new ArrayList<>();
for (final Geometry part : geometries()) {
final Geometry movedPart = part.move(deltas);
parts.add(movedPart);
}
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.geometryCollection(parts);
}
}
@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 > 1) {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot move vertex for empty " + getGeometryType());
} else {
final int partIndex = vertexId[0];
final int partCount = getGeometryCount();
if (partIndex >= 0 && partIndex < partCount) {
final GeometryFactory geometryFactory = getGeometryFactory();
final int[] subId = new int[vertexId.length - 1];
System.arraycopy(vertexId, 1, subId, 0, subId.length);
final Geometry geometry = getGeometry(partIndex);
final Geometry newGeometry = geometry.moveVertex(newPoint, subId);
final List<Geometry> geometries = new ArrayList<>(getGeometries());
geometries.set(partIndex, newGeometry);
return (V)geometryFactory.geometryCollection(geometries);
} else {
throw new IllegalArgumentException(
"Part index must be between 0 and " + partCount + " not " + partIndex);
}
}
} else {
throw new IllegalArgumentException("Vertex id's for " + getGeometryType()
+ " must have length > 1. " + Arrays.toString(vertexId));
}
}
@Override
default Geometry newGeometry(final GeometryFactory geometryFactory) {
final List<Geometry> geometries = new ArrayList<>();
for (final Geometry geometry : geometries()) {
geometries.add(geometry.newGeometry(geometryFactory));
}
return geometryFactory.geometryCollection(geometries);
}
@SuppressWarnings("unchecked")
@Override
default <G> G newUsingGeometryFactory(final GeometryFactory factory) {
if (factory == getGeometryFactory()) {
return (G)this;
} else if (isEmpty()) {
return (G)factory.geometryCollection();
} else {
final List<Geometry> geometries = new ArrayList<>(getGeometryCount());
for (final Geometry part : geometries()) {
final Geometry newPart = part.newUsingGeometryFactory(factory);
geometries.add(newPart);
}
return (G)factory.geometryCollection(geometries);
}
}
@Override
@SuppressWarnings("unchecked")
default <G extends Geometry> G newValidGeometry() {
if (isEmpty()) {
return (G)this;
} else if (isValid()) {
return (G)normalize();
} else {
final List<Geometry> geometries = new ArrayList<>();
final Polygonizer polygonizer = new Polygonizer();
for (final Geometry geometry : geometries()) {
if (geometry instanceof Polygon) {
final Polygon polygon = (Polygon)geometry;
polygonizer.addPolygon(polygon);
} else {
geometries.add(geometry.newValidGeometry());
}
}
geometries.addAll(polygonizer.getPolygonal().getGeometries());
return (G)getGeometryFactory().geometry(geometries).union();
}
}
@Override
default Geometry normalize() {
final List<Geometry> geometries = new ArrayList<>();
for (final Geometry part : geometries()) {
final Geometry normalizedPart = part.normalize();
geometries.add(normalizedPart);
}
Collections.sort(geometries);
final GeometryFactory geometryFactory = getGeometryFactory();
final Geometry normalizedGeometry = geometryFactory.geometryCollection(geometries);
return normalizedGeometry;
}
@Override
default List<Vertex> pointVertices() {
if (isEmpty()) {
return Collections.emptyList();
} else {
final int vertexCount = getVertexCount();
final List<Vertex> vertices = new ArrayList<>(vertexCount);
addPointVertices(vertices, this);
return vertices;
}
}
@Override
default Geometry removeDuplicatePoints() {
if (isEmpty()) {
return this;
} else {
final List<Geometry> geometries = new ArrayList<>();
for (final Geometry geometry : geometries()) {
if (geometry != null && !geometry.isEmpty()) {
geometries.add(geometry.removeDuplicatePoints());
}
}
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.geometryCollection(geometries);
}
}
/**
* Creates a {@link Geometry} with
* every component reversed.
* The order of the components in the collection are not reversed.
*
* @return a {@link Geometry} in the reverse order
*/
@Override
default Geometry reverse() {
final List<Geometry> revGeoms = new ArrayList<>();
for (final Geometry geometry : geometries()) {
if (!geometry.isEmpty()) {
final Geometry reverse = geometry.reverse();
revGeoms.add(reverse);
}
}
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.geometryCollection(revGeoms);
}
@Override
default Iterable<Segment> segments() {
return new GeometryCollectionSegment(this, -1);
}
@SuppressWarnings("unchecked")
@Override
default <G extends Geometry> G toClockwise() {
return (G)applyGeometryCollection(Geometry::toClockwise);
}
@SuppressWarnings("unchecked")
@Override
default <G extends Geometry> G toCounterClockwise() {
return (G)applyGeometryCollection(Geometry::toCounterClockwise);
}
@Override
default Vertex vertices() {
final GeometryCollectionVertex vertex = new GeometryCollectionVertex(this, -1);
return vertex;
}
}