/*
* 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.util;
import java.util.ArrayList;
import java.util.List;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Lineal;
import com.revolsys.geometry.model.LinearRing;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.Polygon;
import com.revolsys.geometry.model.Polygonal;
import com.revolsys.geometry.model.Punctual;
import com.revolsys.util.Property;
/**
* A framework for processes which transform an input {@link Geometry} into
* an output {@link Geometry}, possibly changing its structure and type(s).
* This class is a framework for implementing subclasses
* which perform transformations on
* various different Geometry subclasses.
* It provides an easy way of applying specific transformations
* to given geometry types, while allowing unhandled types to be simply copied.
* Also, the framework ensures that if subcomponents change type
* the parent geometries types change appropriately to maintain valid structure.
* Subclasses will override whichever <code>transformX</code> methods
* they need to to handle particular Geometry types.
* <p>
* A typically usage would be a transformation class that transforms <tt>Polygons</tt> into
* <tt>Polygons</tt>, <tt>LineStrings</tt> or <tt>Points</tt>, depending on the geometry of the input
* (For instance, a simplification operation).
* This class would likely need to override the {@link #transformMultiPolygon(MultiPolygon, Geometry)transformMultiPolygon}
* method to ensure that if input Polygons change type the result is a <tt>GeometryCollection</tt>,
* not a <tt>MultiPolygon</tt>.
* <p>
* The default behaviour of this class is simply to recursively transform
* each Geometry component into an identical object by deep copying down
* to the level of, but not including, coordinates.
* <p>
* All <code>transformX</code> methods may return <code>null</code>,
* to avoid creating empty or invalid geometry objects. This will be handled correctly
* by the transformer. <code>transform<i>XXX</i></code> methods should always return valid
* geometry - if they cannot do this they should return <code>null</code>
* (for instance, it may not be possible for a transformLineString implementation
* to return at least two points - in this case, it should return <code>null</code>).
* The {@link #transform(Geometry)transform} method itself will always
* return a non-null Geometry object (but this may be empty).
*
* @version 1.7
*
*/
public abstract class GeometryTransformer {
protected GeometryFactory factory = null;
/**
* Possible extensions:
* getParent() method to return immediate parent e.g. of LinearRings in Polygons
*/
private Geometry inputGeom;
/**
* <code>true</code> if the type of the input should be preserved
*/
private final boolean preserveType = false;
public GeometryTransformer() {
}
/**
* Convenience method which provides statndard way of copying {@link LineString}s
* @param seq the sequence to copy
* @return a deep copy of the sequence
*/
protected final LineString copy(final LineString seq) {
return seq.clone();
}
/**
* Utility function to make input geometry available
*
* @return the input geometry
*/
public Geometry getInputGeometry() {
return this.inputGeom;
}
public boolean isPreserveType() {
return this.preserveType;
}
public final Geometry transform(final Geometry inputGeom) {
this.inputGeom = inputGeom;
this.factory = inputGeom.getGeometryFactory();
if (inputGeom instanceof Point) {
return transformPoint((Point)inputGeom);
} else if (inputGeom instanceof Punctual) {
return transformMultiPoint((Punctual)inputGeom);
} else if (inputGeom instanceof LinearRing) {
return transformLinearRing((LinearRing)inputGeom, null);
} else if (inputGeom instanceof LineString) {
return transformLineString((LineString)inputGeom);
} else if (inputGeom instanceof Lineal) {
return transformMultiLineString((Lineal)inputGeom);
} else if (inputGeom instanceof Polygon) {
return transformPolygon((Polygon)inputGeom, null);
} else if (inputGeom instanceof Polygonal) {
return transformMultiPolygon((Polygonal)inputGeom, null);
} else if (inputGeom.isGeometryCollection()) {
return transformGeometryCollection(inputGeom, null);
} else {
throw new IllegalArgumentException(
"Unknown Geometry subtype: " + inputGeom.getClass().getName());
}
}
/**
* Transforms a {@link LineString}.
* This method should always return a valid coordinate list for
* the desired result type. (E.g. a coordinate list for a LineString
* must have 0 or at least 2 points).
* If this is not possible, return an empty sequence -
* this will be pruned out.
*
* @param coords the coordinates to transform
* @param parent the parent geometry
* @return the transformed coordinates
*/
protected LineString transformCoordinates(final LineString coords, final Geometry parent) {
return copy(coords);
}
protected Geometry transformGeometryCollection(final Geometry geom, final Geometry parent) {
final List<Geometry> newGeometries = new ArrayList<>();
for (final Geometry geometry : geom.geometries()) {
final Geometry transformGeom = transform(geometry);
if (transformGeom == null || transformGeom.isEmpty()) {
} else {
newGeometries.add(transformGeom);
}
}
return this.factory.geometry(newGeometries);
}
/**
* Transforms a LinearRing.
* The transformation of a LinearRing may result in a coordinate sequence
* which does not form a structurally valid ring (i.e. a degnerate ring of 3 or fewer points).
* In this case a LineString is returned.
* Subclasses may wish to override this method and check for this situation
* (e.g. a subclass may choose to eliminate degenerate linear rings)
*
* @param geom the ring to simplify
* @param parent the parent geometry
* @return a LinearRing if the transformation resulted in a structurally valid ring
* @return a LineString if the transformation caused the LinearRing to collapse to 3 or fewer points
*/
protected Geometry transformLinearRing(final LinearRing geometry, final Geometry parent) {
if (geometry == null) {
return this.factory.linearRing();
} else {
final LineString points = transformCoordinates(geometry, geometry);
if (points == null) {
return this.factory.linearRing();
} else {
final int seqSize = points.getVertexCount();
// ensure a valid LinearRing
if (seqSize > 0 && seqSize < 4 && !this.preserveType) {
return this.factory.lineString(points);
} else {
return this.factory.linearRing(points);
}
}
}
}
/**
* Transforms a {@link LineString} geometry.
*
* @param line
* @return
*/
protected LineString transformLineString(final LineString line) {
// should check for 1-point sequences and downgrade them to points
return this.factory.lineString(transformCoordinates(line, line));
}
protected Lineal transformMultiLineString(final Lineal lineal) {
boolean updated = false;
final List<LineString> newLines = new ArrayList<>();
for (final LineString line : lineal.lineStrings()) {
final LineString newLine = transformLineString(line);
if (newLine == null || newLine.isEmpty()) {
updated = true;
} else {
newLines.add(newLine);
if (newLine != line) {
updated = true;
}
}
}
if (updated) {
return lineal;
} else {
return this.factory.lineal(newLines);
}
}
protected Punctual transformMultiPoint(final Punctual punctual) {
boolean updated = false;
final List<Point> newPoints = new ArrayList<>();
for (final Point point : punctual.points()) {
final Point newPoint = transformPoint(point);
if (newPoint == null || newPoint.isEmpty()) {
updated = true;
} else {
newPoints.add(newPoint);
if (newPoint != point) {
updated = true;
}
}
}
if (updated) {
return punctual;
} else {
return this.factory.punctual(newPoints);
}
}
protected Geometry transformMultiPolygon(final Polygonal polygonal, final Geometry parent) {
final List<Geometry> transGeomList = new ArrayList<>();
for (final Polygon polygon : polygonal.polygons()) {
final Geometry transformGeom = transformPolygon(polygon, polygonal);
if (transformGeom == null) {
continue;
}
if (transformGeom.isEmpty()) {
continue;
}
transGeomList.add(transformGeom);
}
return this.factory.buildGeometry(transGeomList);
}
protected Point transformPoint(final Point point) {
return point;
}
protected Geometry transformPolygon(final Polygon polygon, final Geometry parent) {
boolean isAllValidLinearRings = true;
final Geometry newShell = transformLinearRing(polygon.getShell(), polygon);
if (newShell == null || !(newShell instanceof LinearRing) || newShell.isEmpty()) {
isAllValidLinearRings = false;
}
final List<Geometry> components = new ArrayList<>();
components.add(newShell);
for (final LinearRing hole : polygon.holes()) {
final Geometry newHole = transformLinearRing(hole, polygon);
if (Property.hasValue(newHole)) {
if (!(newHole instanceof LinearRing)) {
} else {
isAllValidLinearRings = false;
}
components.add(newHole);
}
}
if (isAllValidLinearRings) {
return this.factory.polygon(components);
} else {
return this.factory.buildGeometry(components);
}
}
}