/*
* The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
* for visualizing and manipulating spatial features with geometry and attributes.
*
* Copyright (C) 2003 Vivid Solutions
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; 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.vividsolutions.jump.workbench.ui;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineSegment;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.util.Assert;
import com.vividsolutions.jump.I18N;
import com.vividsolutions.jump.util.CoordinateArrays;
/**
* Geometry objects are unmodifiable; this class allows you to "modify" a Geometry
* in a sense -- the modified Geometry is returned as a new Geometry.
* The new Geometry's #isValid should be checked.
*/
public class GeometryEditor {
private GeometryFactory factory = new GeometryFactory();
public GeometryEditor() {}
public Geometry edit(Geometry geometry, GeometryEditorOperation operation) {
if (geometry instanceof GeometryCollection) {
return editGeometryCollection((GeometryCollection) geometry, operation);
}
if (geometry instanceof Polygon) {
return editPolygon((Polygon) geometry, operation);
}
if (geometry instanceof Point) {
return operation.edit(geometry);
}
if (geometry instanceof LineString) {
return operation.edit(geometry);
}
Assert.shouldNeverReachHere(
I18N.get("ui.GeometryEditor.unsupported-geometry-classes-should-be-caught-in-the-GeometryEditorOperation"));
return null;
}
private Polygon editPolygon(Polygon polygon, GeometryEditorOperation operation) {
Polygon newPolygon = (Polygon) operation.edit(polygon);
if (newPolygon.isEmpty()) {
//RemoveSelectedPlugIn relies on this behaviour. [Jon Aquino]
return newPolygon;
}
LinearRing shell = (LinearRing) edit(newPolygon.getExteriorRing(), operation);
if (shell.isEmpty()) {
//RemoveSelectedPlugIn relies on this behaviour. [Jon Aquino]
return factory.createPolygon(null, null);
}
ArrayList holes = new ArrayList();
for (int i = 0; i < newPolygon.getNumInteriorRing(); i++) {
LinearRing hole =
(LinearRing) edit(newPolygon.getInteriorRingN(i), operation);
if (hole.isEmpty()) {
continue;
}
holes.add(hole);
}
return factory.createPolygon(
shell,
(LinearRing[]) holes.toArray(new LinearRing[] {}));
}
private GeometryCollection editGeometryCollection(
GeometryCollection collection,
GeometryEditorOperation operation) {
GeometryCollection newCollection =
(GeometryCollection) operation.edit(collection);
ArrayList geometries = new ArrayList();
for (int i = 0; i < newCollection.getNumGeometries(); i++) {
Geometry geometry = edit(newCollection.getGeometryN(i), operation);
if (geometry.isEmpty()) {
continue;
}
geometries.add(geometry);
}
if (newCollection.getClass() == MultiPoint.class) {
return factory.createMultiPoint((Point[]) geometries.toArray(new Point[] {}));
}
if (newCollection.getClass() == MultiLineString.class) {
return factory.createMultiLineString(
(LineString[]) geometries.toArray(new LineString[] {}));
}
if (newCollection.getClass() == MultiPolygon.class) {
return factory.createMultiPolygon(
(Polygon[]) geometries.toArray(new Polygon[] {}));
}
return factory.createGeometryCollection(
(Geometry[]) geometries.toArray(new Geometry[] {}));
}
/**
* The input and output Geometries may share some Coordinate arrays.
*/
public Geometry removeRepeatedPoints(Geometry geometry) {
if (geometry.isEmpty()) {
return geometry;
}
return edit(geometry, new CoordinateOperation() {
public Coordinate[] edit(Coordinate[] coordinates, boolean linearRing) {
//May return the same Coordinate array. [Jon Aquino]
return com.vividsolutions.jts.geom.CoordinateArrays.removeRepeatedPoints(
coordinates);
}
});
}
/**
* @return null if parent == itemToRemove
*/
public Geometry remove(Geometry g, final Geometry itemToRemove) {
return edit(g, new GeometryEditorOperation() {
public Geometry edit(Geometry geometry) {
if (geometry == itemToRemove) {
return createNullGeometry(geometry.getClass());
}
return geometry;
}
});
}
private Geometry createNullGeometry(Class geometryClass) {
if (geometryClass == MultiPolygon.class) {
return factory.createMultiPolygon(null);
}
if (geometryClass == MultiLineString.class) {
return factory.createMultiLineString(null);
}
if (geometryClass == MultiPoint.class) {
return factory.createMultiPoint((Coordinate[]) null);
}
if (geometryClass == GeometryCollection.class) {
return factory.createGeometryCollection(null);
}
if (geometryClass == Polygon.class) {
return factory.createPolygon(null, null);
}
if (geometryClass == LinearRing.class) {
return factory.createLinearRing((Coordinate[])null);
}
if (geometryClass == LineString.class) {
return factory.createLineString((Coordinate[])null);
}
if (geometryClass == Point.class) {
return factory.createPoint((Coordinate)null);
}
Assert.shouldNeverReachHere();
return null;
}
/**
* The vertex will be inserted at the point closest to the target.
*/
public Geometry insertVertex(
Geometry geometry,
Coordinate target,
Geometry ignoreSegmentsOutside) {
LineString closestSegment = null;
Point targetPoint = factory.createPoint(target);
for (Iterator i = CoordinateArrays.toCoordinateArrays(geometry, false).iterator();
i.hasNext();
) {
Coordinate[] coordinates = (Coordinate[]) i.next();
if (coordinates.length < 2) {
continue;
}
for (int j = 1; j < coordinates.length; j++) { //1
LineString candidate =
factory.createLineString(
new Coordinate[] { coordinates[j], coordinates[j - 1] });
if (!candidate.intersects(ignoreSegmentsOutside)) {
continue;
}
if (closestSegment == null) {
closestSegment = candidate;
} else if (
candidate.distance(targetPoint)
< closestSegment.distance(targetPoint)) {
closestSegment = candidate;
}
}
}
if (closestSegment == null) {
return null;
}
return insertVertex(
geometry,
closestSegment.getCoordinateN(0),
closestSegment.getCoordinateN(1),
new LineSegment(
closestSegment.getCoordinateN(0),
closestSegment.getCoordinateN(1)).closestPoint(
target));
}
/**
* Inserts v on the line segment with endpoints equal to existing1 and existing2
*/
public Geometry insertVertex(
Geometry geometry,
final Coordinate existing1,
final Coordinate existing2,
final Coordinate v) {
if (geometry.isEmpty()) {
return geometry;
}
return edit(geometry, new CoordinateOperation() {
private boolean vertexInserted = false;
public Coordinate[] edit(Coordinate[] coordinates, boolean linearRing) {
if (vertexInserted) {
return coordinates;
}
for (int i = 1; i < coordinates.length; i++) { //1
if ((coordinates[i - 1].equals(existing1)
&& coordinates[i].equals(existing2))
|| (coordinates[i - 1].equals(existing2)
&& coordinates[i].equals(existing1))) {
Coordinate[] newCoordinates =
new Coordinate[coordinates.length + 1];
System.arraycopy(coordinates, 0, newCoordinates, 0, i);
newCoordinates[i] = v;
System.arraycopy(
coordinates,
i,
newCoordinates,
i + 1,
coordinates.length - i);
vertexInserted = true;
return newCoordinates;
}
}
return coordinates;
}
});
}
/**
* Deletes the given vertices (matched using ==, not #equals).
*/
public Geometry deleteVertices(Geometry geometry, final Collection vertices) {
return edit(geometry, new CoordinateOperation() {
public Coordinate[] edit(Coordinate[] coordinates, boolean linearRing) {
List newCoordinates = new ArrayList(Arrays.asList(coordinates));
boolean firstCoordinateDeleted = false;
int j = -1;
for (Iterator i = newCoordinates.iterator(); i.hasNext();) {
Coordinate c = (Coordinate) i.next();
j++;
if (containsReference(vertices, c)) {
i.remove();
if (j == 0) {
firstCoordinateDeleted = true;
}
}
}
if (linearRing && firstCoordinateDeleted) {
newCoordinates.remove(newCoordinates.size() - 1);
}
if (linearRing
&& firstCoordinateDeleted
&& !newCoordinates.isEmpty()
&& !newCoordinates.get(0).equals(
newCoordinates.get(newCoordinates.size() - 1))) {
newCoordinates.add(
new Coordinate((Coordinate) newCoordinates.get(0)));
}
return (Coordinate[]) newCoordinates.toArray(new Coordinate[] {});
}
});
}
public boolean containsReference(Collection collection, Object o) {
//Inefficient. [Jon Aquino]
for (Iterator i = collection.iterator(); i.hasNext();) {
Object item = (Object) i.next();
if (item == o) {
return true;
}
}
return false;
}
public interface GeometryEditorOperation {
/**
* "Modifies" a Geometry by returning a new Geometry with a modification.
* The returned Geometry might be the same as the Geometry passed in.
*/
public Geometry edit(Geometry geometry);
}
private Coordinate[] atLeastNCoordinatesOrNothing(int n, Coordinate[] c) {
return c.length >= n ? c : new Coordinate[] {};
}
private abstract class CoordinateOperation implements GeometryEditorOperation {
public Geometry edit(Geometry geometry) {
if (geometry instanceof LinearRing) {
return factory.createLinearRing(
atLeastNCoordinatesOrNothing(
4,
edit(geometry.getCoordinates(), true)));
}
if (geometry instanceof LineString) {
return factory.createLineString(
atLeastNCoordinatesOrNothing(
2,
edit(geometry.getCoordinates(), false)));
}
if (geometry instanceof Point) {
Coordinate[] newCoordinates = edit(geometry.getCoordinates(), false);
Assert.isTrue(newCoordinates.length < 2);
return factory.createPoint(
(newCoordinates.length > 0) ? newCoordinates[0] : null);
}
return geometry;
}
public abstract Coordinate[] edit(Coordinate[] coordinates, boolean linearRing);
}
}