/*
* 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.LinkedList;
import java.util.List;
import java.util.function.Function;
import javax.measure.quantity.Area;
import javax.measure.unit.Unit;
import com.revolsys.datatype.DataType;
import com.revolsys.datatype.DataTypes;
import com.revolsys.geometry.graph.linemerge.LineMerger;
import com.revolsys.geometry.model.prep.PreparedMultiLineString;
import com.revolsys.geometry.model.segment.MultiLineStringSegment;
import com.revolsys.geometry.model.segment.Segment;
import com.revolsys.geometry.model.vertex.MultiLineStringVertex;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.geometry.operation.BoundaryOp;
import com.revolsys.geometry.operation.valid.GeometryValidationError;
import com.revolsys.util.Property;
/**
* Models a collection of (@link LineString}s.
* <p>
* Any collection of LineStrings is a valid MultiLineString.
*
*@version 1.7
*/
public interface MultiLineString extends GeometryCollection, Lineal {
@Override
default boolean addIsSimpleErrors(final List<GeometryValidationError> errors,
final boolean shortCircuit) {
return Lineal.addIsSimpleErrors(this, errors, shortCircuit);
}
@Override
default Lineal applyLineal(final Function<LineString, LineString> function) {
if (!isEmpty()) {
boolean changed = false;
final List<LineString> lines = new ArrayList<>();
for (final LineString line : lineStrings()) {
final LineString newLine = function.apply(line);
changed |= line != newLine;
lines.add(newLine);
}
if (changed) {
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.lineal(lines);
}
}
return this;
}
@Override
Lineal clone();
@Override
default double distance(Geometry geometry, final double terminateDistance) {
if (isEmpty()) {
return Double.POSITIVE_INFINITY;
} else if (Property.isEmpty(geometry)) {
return Double.POSITIVE_INFINITY;
} else {
final GeometryFactory geometryFactory = getGeometryFactory();
geometry = geometry.convertGeometry(geometryFactory, 2);
double minDistance = Double.MAX_VALUE;
for (final LineString line : lineStrings()) {
final double distance = geometry.distance(line);
if (distance < minDistance) {
minDistance = distance;
if (distance <= terminateDistance) {
return distance;
}
}
}
return minDistance;
}
}
@Override
default boolean equalsExact(final Geometry other, final double tolerance) {
if (!isEquivalentClass(other)) {
return false;
} else {
return GeometryCollection.super.equalsExact(other, tolerance);
}
}
@Override
default double getArea() {
return 0;
}
@Override
default double getArea(final Unit<Area> unit) {
return 0;
}
/**
* Gets the boundary of this geometry.
* The boundary of a lineal geometry is always a zero-dimensional geometry (which may be empty).
*
* @return the boundary geometry
* @see Geometry#getBoundary
*/
@Override
default Geometry getBoundary() {
return new BoundaryOp(this).getBoundary();
}
@Override
default int getBoundaryDimension() {
if (isClosed()) {
return Dimension.FALSE;
}
return 0;
}
@Override
default double getCoordinate(final int partIndex, final int vertexIndex, final int axisIndex) {
final LineString line = getGeometry(partIndex);
if (line == null) {
return Double.NaN;
} else {
return line.getCoordinate(vertexIndex, axisIndex);
}
}
@Override
default DataType getDataType() {
return DataTypes.MULTI_LINE_STRING;
}
@Override
default int getDimension() {
return 1;
}
@Override
default LineString getLineString(final int partIndex) {
return (LineString)getGeometry(partIndex);
}
@SuppressWarnings({
"unchecked", "rawtypes"
})
default <V extends LineString> List<V> getLineStrings() {
return (List)getGeometries();
}
@Override
default Segment getSegment(final int... segmentId) {
if (segmentId == null || segmentId.length != 2) {
return null;
} else {
final int partIndex = segmentId[0];
if (partIndex >= 0 && partIndex < getGeometryCount()) {
final LineString line = getLineString(partIndex);
final int segmentIndex = segmentId[1];
if (segmentIndex >= 0 && segmentIndex < line.getSegmentCount()) {
return new MultiLineStringSegment(this, partIndex, segmentIndex);
}
}
return null;
}
}
@Override
default int getSegmentCount() {
int segmentCount = 0;
for (final LineString line : lineStrings()) {
segmentCount += line.getSegmentCount();
}
return segmentCount;
}
@Override
default Vertex getToVertex(int... vertexId) {
if (vertexId == null || vertexId.length != 2) {
return null;
} else {
final int partIndex = vertexId[0];
if (partIndex >= 0 && partIndex < getGeometryCount()) {
final LineString line = getLineString(partIndex);
int vertexIndex = vertexId[1];
final int vertexCount = line.getVertexCount();
vertexIndex = vertexCount - 1 - vertexIndex;
if (vertexIndex <= vertexCount) {
while (vertexIndex < 0) {
vertexIndex += vertexCount - 1;
}
vertexId = Geometry.setVertexIndex(vertexId, vertexIndex);
return new MultiLineStringVertex(this, vertexId);
}
}
return null;
}
}
@Override
default Vertex getVertex(final int... vertexId) {
if (vertexId == null || vertexId.length != 2) {
return null;
} else {
final int partIndex = vertexId[0];
if (partIndex >= 0 && partIndex < getGeometryCount()) {
final LineString line = getLineString(partIndex);
int vertexIndex = vertexId[1];
final int vertexCount = line.getVertexCount();
if (vertexIndex <= vertexCount) {
while (vertexIndex < 0) {
vertexIndex += vertexCount - 1;
}
return new MultiLineStringVertex(this, vertexId);
}
}
return null;
}
}
@Override
default boolean hasInvalidXyCoordinates() {
for (final LineString line : lineStrings()) {
if (line.hasInvalidXyCoordinates()) {
return true;
}
}
return false;
}
@Override
default boolean isClosed() {
if (isEmpty()) {
return false;
} else {
for (final LineString line : getLineStrings()) {
if (line.isEmpty()) {
return false;
} else if (!line.isClosed()) {
return false;
}
}
return true;
}
}
@Override
default boolean isEquivalentClass(final Geometry other) {
return other instanceof MultiLineString;
}
@Override
default boolean isHomogeneousGeometryCollection() {
return true;
}
@Override
default Iterable<LineString> lineStrings() {
return getGeometries();
}
@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 Lineal");
} else {
final int partIndex = vertexId[0];
final int vertexIndex = vertexId[1];
final int partCount = getGeometryCount();
if (partIndex >= 0 && partIndex < partCount) {
final GeometryFactory geometryFactory = getGeometryFactory();
final LineString line = getLineString(partIndex);
final LineString newLine = line.moveVertex(newPoint, vertexIndex);
final List<LineString> lines = new ArrayList<>(getLineStrings());
lines.set(partIndex, newLine);
return (V)geometryFactory.lineal(lines);
} else {
throw new IllegalArgumentException(
"Part index must be between 0 and " + partCount + " not " + partIndex);
}
}
} else {
throw new IllegalArgumentException(
"Vertex id's for Lineals must have length 2. " + Arrays.toString(vertexId));
}
}
@Override
default Lineal newGeometry(final GeometryFactory geometryFactory) {
final List<LineString> lines = new ArrayList<>();
for (final LineString line : getLineStrings()) {
final LineString newLine = line.newGeometry(geometryFactory);
lines.add(newLine);
}
return geometryFactory.lineal(lines);
}
@SuppressWarnings("unchecked")
@Override
default <G> G newUsingGeometryFactory(final GeometryFactory factory) {
if (factory == getGeometryFactory()) {
return (G)this;
} else if (isEmpty()) {
return (G)factory.lineString();
} else {
final LineString[] lines = new LineString[getGeometryCount()];
for (int i = 0; i < getGeometryCount(); i++) {
LineString line = getLineString(i);
line = line.newUsingGeometryFactory(factory);
lines[i] = line;
}
return (G)factory.lineal(lines);
}
}
@Override
@SuppressWarnings("unchecked")
default <G extends Geometry> G newValidGeometry() {
if (isEmpty()) {
return (G)this;
} else if (isValid()) {
return (G)normalize();
} else {
final LineMerger lines = new LineMerger(this);
return (G)lines.getLineal();
}
}
@Override
default Lineal normalize() {
if (isEmpty()) {
return this;
} else {
final List<LineString> geometries = new ArrayList<>();
for (final LineString part : lineStrings()) {
final LineString normalizedPart = part.normalize();
geometries.add(normalizedPart);
}
Collections.sort(geometries);
final GeometryFactory geometryFactory = getGeometryFactory();
final Lineal normalizedGeometry = geometryFactory.lineal(geometries);
return normalizedGeometry;
}
}
@Override
default Lineal prepare() {
return new PreparedMultiLineString(this);
}
@Override
default Lineal removeDuplicatePoints() {
if (isEmpty()) {
return this;
} else {
final List<LineString> lines = new ArrayList<>();
for (final LineString line : getLineStrings()) {
if (line != null && !line.isEmpty()) {
lines.add(line.removeDuplicatePoints());
}
}
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.lineal(lines);
}
}
/**
* Creates a {@link Lineal} in the reverse
* order to this object.
* Both the order of the component LineStrings
* and the order of their coordinate sequences
* are reversed.
*
* @return a {@link Lineal} in the reverse order
*/
@Override
default Lineal reverse() {
final LinkedList<LineString> revLines = new LinkedList<>();
for (final Geometry geometry : geometries()) {
final LineString line = (LineString)geometry;
final LineString reverse = line.reverse();
revLines.addFirst(reverse);
}
final GeometryFactory geometryFactory = getGeometryFactory();
return geometryFactory.lineal(revLines);
}
@Override
default Iterable<Segment> segments() {
return new MultiLineStringSegment(this, 0, -1);
}
@SuppressWarnings("unchecked")
@Override
default <G extends Geometry> G toClockwise() {
return (G)applyLineal(LineString::toClockwise);
}
@SuppressWarnings("unchecked")
@Override
default <G extends Geometry> G toCounterClockwise() {
return (G)applyLineal(LineString::toCounterClockwise);
}
@Override
default Vertex vertices() {
return new MultiLineStringVertex(this, 0, -1);
}
}