/******************************************************************************* * $Id$ * $Source: /cvs/ctree/LiteGO1/src/jar/com/polexis/lite/spatialschema/geometry/GeometryImpl.java,v $ * Copyright (C) 2003 Open GIS Consortium, Inc. All Rights Reserved. * http://www.opengis.org/Legal/ ******************************************************************************/ package org.geotoolkit.geometry.isoonjts.spatialschema.geometry; import java.io.Serializable; import java.util.*; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.apache.sis.geometry.DirectPosition2D; import org.apache.sis.geometry.GeneralDirectPosition; import org.geotoolkit.geometry.isoonjts.JTSUtils; import org.geotoolkit.geometry.isoonjts.spatialschema.geometry.primitive.JTSCurveBoundary; import org.geotoolkit.geometry.isoonjts.spatialschema.geometry.primitive.JTSPoint; import org.geotoolkit.geometry.isoonjts.spatialschema.geometry.primitive.JTSSurfaceBoundary; import org.geotoolkit.internal.jaxb.CoordinateReferenceSystemAdapter; import org.apache.sis.referencing.CRS; import org.geotoolkit.util.Cloneable; import org.opengis.util.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.OperationNotFoundException; import org.opengis.referencing.operation.TransformException; import org.opengis.geometry.Boundary; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.Envelope; import org.opengis.geometry.Geometry; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.geometry.Precision; import org.opengis.geometry.TransfiniteSet; import org.opengis.geometry.complex.Complex; import org.opengis.geometry.primitive.Ring; /** * Base class for our JTS-based implementation of the various ISO 19107 geometry classes. * * @author Johann Sorel (Geomatys) * @module */ @XmlAccessorType(XmlAccessType.NONE) public abstract class AbstractJTSGeometry implements Geometry, Serializable, Cloneable, JTSGeometry { /** * True if we're allowing changes to the geometry. False if not. */ private boolean mutable; /** * CRS for this geometry. */ @XmlAttribute(name="srsName") @XmlJavaTypeAdapter(CoordinateReferenceSystemAdapter.class) private CoordinateReferenceSystem coordinateReferenceSystem; /** * The JTS equivalent of this geometry. This gets set to null whenever we * make changes to the geometry so that we can recompute it. */ private com.vividsolutions.jts.geom.Geometry jtsPeer; /** * If this object is part of a composite, this this member should hold a * pointer to that composite so that when our JTS geometry is invalidated, * we can also invalidate that of our parent. */ private JTSGeometry parent; /** * Precision model */ private Precision precision; /** * Creates a new mutable {@code GeometryImpl} with a null CRS. */ public AbstractJTSGeometry() { this(null); } /** * Creates a new mutable {@code GeometryImpl}. * @param coordinateReferenceSystem CRS for this geometry's vertices. */ public AbstractJTSGeometry(final CoordinateReferenceSystem coordinateReferenceSystem) { this(coordinateReferenceSystem, true); } /** * Creates a new {@code GeometryImpl}. * * @param coordinateReferenceSystem CRS for this geometry's vertices. * @param mutable Whether or not changes will be allowed. */ public AbstractJTSGeometry(final CoordinateReferenceSystem coordinateReferenceSystem, final boolean mutable) { this.coordinateReferenceSystem = coordinateReferenceSystem; this.mutable = mutable; } public void setParent(final JTSGeometry parent) { this.parent = parent; } @Override public Precision getPrecision() { return precision; } /** * Subclasses must override this method to compute the JTS equivalent of * this geometry. */ protected abstract com.vividsolutions.jts.geom.Geometry computeJTSPeer(); /** * This method must be called by subclasses whenever the user makes a change * to the geometry so that the cached JTS object can be recreated. */ @Override public final void invalidateCachedJTSPeer() { jtsPeer = null; if (parent != null) { parent.invalidateCachedJTSPeer(); } } /** * This method is meant to be invoked by the JTSUtils utility class when it * creates a Geometry from a JTS geometry. This prevents the Geometry from * having to recompute the JTS peer the first time. */ protected final void setJTSPeer(final com.vividsolutions.jts.geom.Geometry g) { jtsPeer = g; } /** * Returns the JTS version of this geometry. If the geometry has not * changed since the last time this method was called, it will return the * exact same object. */ @Override public final com.vividsolutions.jts.geom.Geometry getJTSGeometry() { if (jtsPeer == null) { jtsPeer = computeJTSPeer(); } return jtsPeer; } /** * Returns the CRS that was given to the constructor. */ @Override public final CoordinateReferenceSystem getCoordinateReferenceSystem() { return coordinateReferenceSystem; } public final void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs) { this.coordinateReferenceSystem = crs; } /** * Returns a Geometry that represents the minimum bounding region of this * geometry. */ @Override public final Geometry getMbRegion() { com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); return JTSUtils.toISO(jtsGeom.getEnvelope(), getCoordinateReferenceSystem()); } /** * Returns a point interior to the geometry. */ @Override public final DirectPosition getRepresentativePoint() { com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); com.vividsolutions.jts.geom.Point p = jtsGeom.getInteriorPoint(); return JTSUtils.pointToDirectPosition(p, getCoordinateReferenceSystem()); } /** * Returns the boundary of this geometry. Returns null if the boundary is * empty. */ @Override public Boundary getBoundary() { // PENDING(CSD): // Need to find out if MultiPrimitives are handled correctly. (I think // they are, but 19107's boundary semantics for multi-primitives are // not well-specified.) // Need to find out if GeometryCollections are handled correctly. (I // don't think they are, but it's not clear what it would mean, nor is // it obvious why anyone would call it in the first place.) com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); // PENDING(CSD): // As far as I could tell, it's not defined what it would mean to // compute the boundary of a collection object in 19107. if (jtsGeom instanceof com.vividsolutions.jts.geom.GeometryCollection) { throw new UnsupportedOperationException( "Boundary cannot be computed for multi-primitives."); } com.vividsolutions.jts.geom.Geometry jtsBoundary = jtsGeom.getBoundary(); int d = jtsGeom.getDimension(); if (d == 0) { // If d is zero, then our geometry is a point. So the boundary is // empty. ISO 19107 defines the boundary of a point to // be NULL. return null; } else if (d == 1) { // If d is 1, then the boundary is either empty (if it's a ring) or // it's two points at either end of the curve. // We've ruled out the possibility of multi-primitives (see the // instanceof check above), so we know that the boundary can't be // more than 2 points. com.vividsolutions.jts.geom.Coordinate[] coords = jtsBoundary.getCoordinates(); // If coords is emtpy, then this geometry is a ring. So we return // an empty CurveBoundary object (i.e. one with both points set to // null). if ((coords == null) || (coords.length == 0)) { JTSCurveBoundary result = new JTSCurveBoundary( getCoordinateReferenceSystem(), null, null); return result; } else { // If it wasn't empty, then return a CurveBoundary with the two // endpoints. if (coords.length != 2) { // Should this be an assert instead? throw new RuntimeException("ERROR: One dimensional " + "primitive had wrong number of boundary points (" + coords.length + ")"); } CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); JTSCurveBoundary result = new JTSCurveBoundary(crs, new JTSPoint(JTSUtils.coordinateToDirectPosition( coords[0], crs)), new JTSPoint(JTSUtils.coordinateToDirectPosition( coords[1], crs))); return result; } } else if (d == 2) { // If d == 2, then the boundary is a collection of rings. // In particular, the JTS tests indicate that it'll be a // MultiLineString. com.vividsolutions.jts.geom.MultiLineString mls = (com.vividsolutions.jts.geom.MultiLineString) jtsBoundary; int n = mls.getNumGeometries(); CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); Ring exteriorRing = JTSUtils.linearRingToRing( (com.vividsolutions.jts.geom.LineString) mls.getGeometryN(0), crs); Ring[] interiorRings = new Ring[n - 1]; for (int i = 1; i < n; i++) { interiorRings[n - 1] = JTSUtils.linearRingToRing( (com.vividsolutions.jts.geom.LineString) mls.getGeometryN(i), crs); } JTSSurfaceBoundary result = new JTSSurfaceBoundary(crs, exteriorRing, interiorRings); return result; } else { throw new UnsupportedOperationException("Computing the boundary " + "for geometries of dimension larger than 2 is not " + "supported."); } } /** * This method is not implemented. Always throws an * UnsupportedOperationException. */ @Override public final Complex getClosure() { throw new UnsupportedOperationException("Closure not supported"); } /** * Returns true if this object does not cross itself. */ @Override public final boolean isSimple() { com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); return jtsGeom.isSimple(); } @Override public final boolean isCycle() { com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); com.vividsolutions.jts.geom.Geometry jtsBoundary = jtsGeom.getBoundary(); return jtsBoundary.isEmpty(); } /** * Returns the distance between the given geometry and this geometry. Note * that this distance is in units the same as the units of the coordinate * reference system, and thus may not have any physical meaning (such as * when the coordinate system is a latitude/longitude system). */ public final double getDistance(final Geometry geometry) { com.vividsolutions.jts.geom.Geometry jtsGeom1 = getJTSGeometry(); com.vividsolutions.jts.geom.Geometry jtsGeom2 = ((JTSGeometry) geometry).getJTSGeometry(); return JTSUtils.distance(jtsGeom1, jtsGeom2); } /** * Returns the manifold dimension of the geometry at the given point. The * point must lie on the geometry. * * For geometries that consist of multiple parts, this returns the dimension * of the part intersecting the given point. When multiple parts coincide * at the given point, this returns the least dimension of those geometries. * Returns Integer.MAX_VALUE if the given point is not on this geometry. */ @Override public final int getDimension(final DirectPosition point) { com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); if (jtsGeom instanceof com.vividsolutions.jts.geom.GeometryCollection) { com.vividsolutions.jts.geom.Point p = JTSUtils.directPositionToPoint(point); return getDimension(p, (com.vividsolutions.jts.geom.GeometryCollection) jtsGeom); } else { return jtsGeom.getDimension(); } } private static final int getDimension( final com.vividsolutions.jts.geom.Point p, final com.vividsolutions.jts.geom.GeometryCollection gc) { int min = Integer.MAX_VALUE; int n = gc.getNumGeometries(); for (int i = 0; i < n; i++) { int d = Integer.MAX_VALUE; com.vividsolutions.jts.geom.Geometry g = gc.getGeometryN(i); if (g instanceof com.vividsolutions.jts.geom.GeometryCollection) { // If it was a nested GeometryCollection, then just recurse // until we get down to non-collections. d = getDimension(p, (com.vividsolutions.jts.geom.GeometryCollection) g); } else { if (g.intersects(p)) { d = g.getDimension(); } } if (d < min) { min = d; } } return min; } /** * Returns the dimension of the coordinates in this geometry. This * delegates to the coordinate reference system, so it may throw a null * pointer exception if this geometry has no coordinate reference system. */ @Override public final int getCoordinateDimension() { return getCoordinateReferenceSystem().getCoordinateSystem().getDimension(); } /** * This impementation of geometry does not support traversing this * association in this direction as it would require every geometry to know * about all of the larger geometries of which it is a part. This would * add some memory usage and bookkeeping headaches for functionality that * will rarely, if ever, be used. This this method always returns null. */ @Override public final Set getMaximalComplex() { return null; } /** * Attempts to find a transform from the current CRS to the new CRS and * creates a new geometry by invoking that transform on each control point * of this geometry. */ @Override public final Geometry transform(final CoordinateReferenceSystem newCRS) throws TransformException { try { MathTransform mt = CRS.findOperation(getCoordinateReferenceSystem(), newCRS, null).getMathTransform(); return transform(newCRS, mt); } catch (OperationNotFoundException e) { throw new TransformException("Unable to find an operation", e); } catch (FactoryException e) { throw new TransformException("Factory exception", e); } } /** * Creates a new Geometry out of this one by invoking the given transform * on each control point of this geometry. */ @Override public final Geometry transform(final CoordinateReferenceSystem newCRS, final MathTransform transform) throws TransformException { // Get the JTS geometry com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); // Make a copy since we're going to modify its values jtsGeom = (com.vividsolutions.jts.geom.Geometry) jtsGeom.clone(); // Get a local variable that has the src CRS CoordinateReferenceSystem oldCRS = getCoordinateReferenceSystem(); // Do the actual work of transforming the vertices jtsGeom.apply(new MathTransformFilter(transform, oldCRS, newCRS)); // Then convert back to a GO1 geometry return JTSUtils.toISO(jtsGeom, getCoordinateReferenceSystem()); } /** * @inheritDoc * @see org.opengis.geometry.coordinate.#getEnvelope() */ @Override public final Envelope getEnvelope() { com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); com.vividsolutions.jts.geom.Envelope jtsEnv = jtsGeom.getEnvelopeInternal(); CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); DirectPosition2D lower = new DirectPosition2D(jtsEnv.getMinX(), jtsEnv.getMinY()); lower.setCoordinateReferenceSystem(crs); DirectPosition2D upper = new DirectPosition2D(jtsEnv.getMaxX(), jtsEnv.getMaxY()); upper.setCoordinateReferenceSystem(crs); Envelope result = new JTSEnvelope(lower, upper); return result; } /** * Returns the centroid of this geometry. */ @Override public final DirectPosition getCentroid() { com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); com.vividsolutions.jts.geom.Point jtsCentroid = jtsGeom.getCentroid(); return JTSUtils.pointToDirectPosition(jtsCentroid, getCoordinateReferenceSystem()); } /** * Returns the geometric convex hull of this geometry. */ @Override public final Geometry getConvexHull() { final com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); final com.vividsolutions.jts.geom.Geometry jtsHull = jtsGeom.convexHull(); return JTSUtils.toISO(jtsHull, getCoordinateReferenceSystem()); } /** * Returns an approximate buffer around this object. */ @Override public final Geometry getBuffer(final double distance) { final com.vividsolutions.jts.geom.Geometry jtsGeom = getJTSGeometry(); final com.vividsolutions.jts.geom.Geometry jtsBuffer = jtsGeom.buffer(distance); return JTSUtils.toISO(jtsBuffer, getCoordinateReferenceSystem()); } /** * Returns true if this geometry can be changed. */ @Override public final boolean isMutable() { return mutable; } /** * Creates an immutable copy of this object or just returns this object if * it's already immutable. */ @Override public final Geometry toImmutable() { if (isMutable()) { AbstractJTSGeometry result = clone(); result.mutable = false; return result; } else { return this; } } /** * Returns a deep copy of this geometric object. Subclasses must override * to make deep copies of members that are themselves mutable objects. Note * that all of the (private) members of GeometryImpl are already immutable * so this method simply delegates to the superclass (Object) clone. */ @Override public AbstractJTSGeometry clone() { try { return (AbstractJTSGeometry) super.clone(); } catch (CloneNotSupportedException cnse) { throw new AssertionError(cnse); } } /** * Returns true if the given position lies in this geometry within the * tolerance of the floating point representation. */ @Override public boolean contains(final DirectPosition point) { com.vividsolutions.jts.geom.Geometry jtsGeom1 = getJTSGeometry(); com.vividsolutions.jts.geom.Geometry jtsGeom2 = JTSUtils.directPositionToPoint(point); return JTSUtils.contains(jtsGeom1, jtsGeom2); } /** * Returns true if this geometry completely contains the given geometry. */ @Override public boolean contains(final TransfiniteSet pointSet) { com.vividsolutions.jts.geom.Geometry jtsGeom1 = getJTSGeometry(); com.vividsolutions.jts.geom.Geometry jtsGeom2 = ((JTSGeometry) pointSet).getJTSGeometry(); return JTSUtils.contains(jtsGeom1, jtsGeom2); } @Override public double distance(final Geometry otherGeometry) { return getDistance(otherGeometry); } @Override public TransfiniteSet difference(final TransfiniteSet pointSet) { com.vividsolutions.jts.geom.Geometry jtsGeom1 = getJTSGeometry(); com.vividsolutions.jts.geom.Geometry jtsGeom2 = ((JTSGeometry) pointSet).getJTSGeometry(); return JTSUtils.toISO(JTSUtils.difference(jtsGeom1, jtsGeom2), getCoordinateReferenceSystem()); } @Override public boolean equals(final TransfiniteSet pointSet) { com.vividsolutions.jts.geom.Geometry jtsGeom1 = getJTSGeometry(); com.vividsolutions.jts.geom.Geometry jtsGeom2 = ((JTSGeometry) pointSet).getJTSGeometry(); return JTSUtils.equals(jtsGeom1, jtsGeom2); } @Override public TransfiniteSet intersection(final TransfiniteSet pointSet) { com.vividsolutions.jts.geom.Geometry jtsGeom1 = getJTSGeometry(); com.vividsolutions.jts.geom.Geometry jtsGeom2 = ((JTSGeometry) pointSet).getJTSGeometry(); return JTSUtils.toISO(jtsGeom1.intersection(jtsGeom2), getCoordinateReferenceSystem()); } @Override public boolean intersects(final TransfiniteSet pointSet) { com.vividsolutions.jts.geom.Geometry jtsGeom1 = getJTSGeometry(); com.vividsolutions.jts.geom.Geometry jtsGeom2 = ((JTSGeometry) pointSet).getJTSGeometry(); return JTSUtils.intersects(jtsGeom1, jtsGeom2); } @Override public TransfiniteSet symmetricDifference(final TransfiniteSet pointSet) { com.vividsolutions.jts.geom.Geometry jtsGeom1 = getJTSGeometry(); com.vividsolutions.jts.geom.Geometry jtsGeom2 = ((JTSGeometry) pointSet).getJTSGeometry(); return JTSUtils.toISO(JTSUtils.symmetricDifference(jtsGeom1, jtsGeom2), getCoordinateReferenceSystem()); } @Override public TransfiniteSet union(final TransfiniteSet pointSet) { com.vividsolutions.jts.geom.Geometry jtsGeom1 = getJTSGeometry(); com.vividsolutions.jts.geom.Geometry jtsGeom2 = ((JTSGeometry) pointSet).getJTSGeometry(); return JTSUtils.toISO(JTSUtils.union(jtsGeom1, jtsGeom2), getCoordinateReferenceSystem()); } public static Set listAsSet(final List list) { return new Set() { @Override public int size() { return list.size(); } @Override public void clear() { list.clear(); } @Override public boolean isEmpty() { return list.isEmpty(); } @Override public Object[] toArray() { return list.toArray(); } @Override public boolean add(Object o) { return list.add(o); } @Override public boolean contains(Object o) { return list.contains(o); } @Override public boolean remove(Object o) { return list.remove(o); } @Override public boolean addAll(Collection c) { return list.addAll(c); } @Override public boolean containsAll(Collection c) { return list.containsAll(c); } @Override public boolean removeAll(Collection c) { return list.removeAll(c); } @Override public boolean retainAll(Collection c) { return list.retainAll(c); } @Override public Iterator iterator() { return list.iterator(); } @Override public Object[] toArray(Object[] a) { return list.toArray(a); } }; } /** * This class implements JTS's CoordinateFilter interface using a Types * MathTransform object to actually perform the work. */ public static class MathTransformFilter implements com.vividsolutions.jts.geom.CoordinateFilter { private MathTransform transform; private DirectPosition src; private DirectPosition dst; public MathTransformFilter(final MathTransform transform, final CoordinateReferenceSystem oldCRS, final CoordinateReferenceSystem newCRS) { this.transform = transform; src = new GeneralDirectPosition(oldCRS); dst = new GeneralDirectPosition(newCRS); } @Override public void filter(final com.vividsolutions.jts.geom.Coordinate coord) { // Load the input into a DirectPosition JTSUtils.coordinateToDirectPosition(coord, src); try { // Do the transform math. transform.transform(src, dst); } catch (MismatchedDimensionException e) { throw new RuntimeException(e); } catch (TransformException e) { throw new RuntimeException(e); } // Load the result back into the Coordinate. JTSUtils.directPositionToCoordinate(dst, coord); } } @Override public boolean equals(final Object object) { if (object == this) return true; if (object instanceof AbstractJTSGeometry) { AbstractJTSGeometry that = (AbstractJTSGeometry) object; return Objects.equals(this.coordinateReferenceSystem, that.coordinateReferenceSystem) && Objects.equals(this.parent, that.parent) && Objects.equals(this.precision, that.precision); } return false; } @Override public int hashCode() { int hash = 7; hash = 41 * hash + (this.coordinateReferenceSystem != null ? this.coordinateReferenceSystem.hashCode() : 0); hash = 41 * hash + (this.parent != null ? this.parent.hashCode() : 0); hash = 41 * hash + (this.precision != null ? this.precision.hashCode() : 0); return hash; } @Override public String toString() { StringBuilder sb = new StringBuilder("[").append(this.getClass().getName()).append(']'); sb.append('\n'); if (coordinateReferenceSystem != null) sb.append("crs: ").append(coordinateReferenceSystem).append('\n'); if (jtsPeer != null) sb.append("jtspeer: ").append(jtsPeer).append('\n'); sb.append("mutable: ").append(mutable).append('\n'); if (parent != null) sb.append("parent:").append(parent).append('\n'); if (precision != null) sb.append("precision:").append(precision).append('\n'); return sb.toString(); } }