/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2006-2008, Open Source Geospatial Foundation (OSGeo) * * 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; * version 2.1 of the License. * * 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. */ package org.geotools.geometry.iso.primitive; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.geotools.geometry.iso.aggregate.MultiSurfaceImpl; import org.geotools.geometry.iso.coordinate.EnvelopeImpl; import org.geotools.geometry.iso.coordinate.PolygonImpl; import org.geotools.geometry.iso.coordinate.SurfacePatchImpl; import org.geotools.geometry.iso.io.GeometryToString; import org.geotools.geometry.iso.operation.IsSimpleOp; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.Envelope; import org.opengis.geometry.Geometry; import org.opengis.geometry.TransfiniteSet; import org.opengis.geometry.aggregate.MultiSurface; import org.opengis.geometry.complex.CompositeSurface; import org.opengis.geometry.primitive.OrientableSurface; import org.opengis.geometry.primitive.PrimitiveFactory; import org.opengis.geometry.primitive.Ring; import org.opengis.geometry.primitive.Surface; import org.opengis.geometry.primitive.SurfaceBoundary; import org.opengis.geometry.primitive.SurfacePatch; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; /** * * Surface (Figure 12) a subclass of Primitive and is the basis for * 2-dimensional geometry. Unorientable surfaces such as the Möbius band are not * allowed. The orientation of a surface chooses an "up" direction through the * choice of the upward normal, which, if the surface is not a cycle, is the * side of the surface from which the exterior boundary appears * counterclockwise. Reversal of the surface orientation reverses the curve * orientation of each boundary component, and interchanges the conceptual "up" * and "down" direction of the surface. If the surface is the boundary of a * solid, the "up" direction is usually outward. For closed surfaces, which have * no boundary, the up direction is that of the surface patches, which must be * consistent with one another. Its included SurfacePatches describe the * interior structure of a Surface * * NOTE Other than the restriction on orientability, no other "validity" * condition is required for Surface. * * @author Jackson Roehrig & Sanjay Jena * * * * @source $URL$ */ public class SurfaceImpl extends OrientableSurfaceImpl implements Surface { private static final long serialVersionUID = 2431540523002962079L; /** * The "Segmentation" association relates this Surface to a set of * SurfacePatches that shall be joined together to form this Surface. * Depending on the interpolation method, the set of patches may require * significant additional structure. In general, the form of the patches * shall be defined in the application schema. * * Surface::patch [1..n] : SurfacePatch SurfacePatch::surface [0,1] : * Reference<Surface> * * If the Surface.coordinateDimension is 2, then the entire Surface is one * logical patch defined by linear interpolation from the boundary. * * NOTE In this standard, surface patches do not appear except in the * context of a surface, and therefore the cardinality of the “surface” role * in this association could be “1” which would preclude the use of surface * patches except in this manner. While this would not affect this Standard, * leaving the cardinality as “0..1” allows other standards based on this * one to use surface patches in a more open-ended manner. */ protected ArrayList<? extends SurfacePatch> patch = null; private SurfaceBoundary boundary = null; private Envelope envelope; // /** // * Constructor without arguments // * Surface Patches have to be setted after // * @param factory // */ // public SurfaceImpl(GeometryFactoryImpl factory) { // super(factory); // this.patch = null; // } /** * Constructor The first version of the constructor for Surface takes a list * of SurfacePatches with the appropriate side-toside relationships and * creates a Surface. * * Surface::Surface(patch[1..n] : SurfacePatch) : Surface * * @param crs * @param patch */ public SurfaceImpl(CoordinateReferenceSystem crs, List<? extends SurfacePatch> patch) { super(crs, null, null, null); this.initializeSurface(patch); } /** * Constructor The second version, which is guaranteed to work always in 2D * coordinate spaces, constructs a Surface by indicating its boundary as a * collection of Curves organized into a SurfaceBoundary. In 3D coordinate * spaces, this second version of the constructor shall require all of the * defining boundary Curve instances to be coplanar (lie in a single plane) * which will define the surface interior. * * Surface::Surface(bdy : SurfaceBoundary) : Surface * @param boundary * The SurfaceBoundary which defines the Surface */ public SurfaceImpl(SurfaceBoundary boundary) { super(boundary.getCoordinateReferenceSystem(), null, null, null); // Set Boundary this.boundary = boundary; // Set Envelope this.envelope = boundary.getEnvelope(); // TODO Is it really necessary to create the surface patches? // Create Surface Patch on basis of the Boundary ArrayList<SurfacePatch> newPatchList = new ArrayList<SurfacePatch>(); newPatchList.add(new PolygonImpl((SurfaceBoundaryImpl)boundary, (SurfaceImpl)this)); this.patch = newPatchList; } /** * Initializes the Surface: * - Sets the surface patches * - Sets the Boundary, or calculates it if doesn´t exist * * @param patch * List of SurfacePatch´s * @param surfaceBoundary * SurfaceBoundary; will be calculated if this parameter is NULL */ private void initializeSurface(List<? extends SurfacePatch> patch) { if (patch == null) throw new IllegalArgumentException("Empty array SurfacePatch."); //$NON-NLS-1$ if (patch.isEmpty()) throw new IllegalArgumentException("Empty array SurfacePatch."); //$NON-NLS-1$ // Calculate the boundary for the SurfacePatches. The continuity of the SurfacePatches is checked within the creation of the surface boundary this.boundary = this.createBoundary(patch); /* Add patches to patch list */ ArrayList<SurfacePatch> newPatchList = new ArrayList<SurfacePatch>(); for (SurfacePatch p : patch) { if (p != null) newPatchList.add(p); } this.patch = newPatchList; // Build the envelope for the Surface based on the SurfacePatch envelopes SurfacePatchImpl tFirstPatch = (SurfacePatchImpl) patch.get(0); //this.envelope = new EnvelopeImpl(tFirstPatch.getEnvelope()); this.envelope = new EnvelopeImpl(tFirstPatch.getEnvelope()); for (SurfacePatch p : patch) ((EnvelopeImpl) this.envelope).expand(((SurfacePatchImpl) p) .getEnvelope()); } /** * Creates a SurfaceBoundary by a list of neigboured patches * * @param patches List of surface patches that represent the surface * @return the SurfaceBoundary of the surface represented by the set of surface patches * @throws IllegalArgument Exception if the union of the surface patches is not a surface. That means that the patches are not continuous. */ private SurfaceBoundaryImpl createBoundary(List<? extends SurfacePatch> patches) { if (patches.isEmpty()) return null; SurfacePatch firstPatch = patches.get(0); if (patches.size() == 1) return (SurfaceBoundaryImpl) firstPatch.getBoundary(); Surface firstPatchSurface = new SurfaceImpl(firstPatch.getBoundary()); Set<OrientableSurface> surfaceList = new HashSet<OrientableSurface>(); for (int i=1; i<patches.size(); i++) { SurfacePatch nextPatch = patches.get(i); surfaceList.add(new SurfaceImpl(nextPatch.getBoundary())); } MultiSurface ms = new MultiSurfaceImpl(getCoordinateReferenceSystem(), surfaceList); TransfiniteSet unionResultSurface = firstPatchSurface.union(ms); if (! (unionResultSurface instanceof SurfaceImpl)) throw new IllegalArgumentException("Surface patches are not continuous"); return (SurfaceBoundaryImpl) ((SurfaceImpl)unionResultSurface).getBoundary(); } /* * (non-Javadoc) * * @see org.geotools.geometry.featgeom.primitive.PrimitiveImpl#getBoundary() */ public SurfaceBoundaryImpl getBoundary() { // ok // Return the Boundary of this surface return (SurfaceBoundaryImpl) boundary; } /** * Sets the Boundary of the Surface * * @param boundary * The boundary to set. */ public void setBoundary(SurfaceBoundaryImpl boundary) { this.boundary = boundary; } /** * Sets the Surface Patches and Boundary for the Surface * * @param surfacePatches - * ArrayList of Surface Patches, which represent the Surface * @param surfaceBoundary - * Surface Boundary of the Surface */ protected void setPatches(List<? extends SurfacePatch> surfacePatches) { this.initializeSurface(surfacePatches); } /* * (non-Javadoc) * * @see org.opengis.geometry.primitive.Surface#getPatches() */ public List<? extends SurfacePatch> getPatches() { return this.patch; } /* * (non-Javadoc) * * @see org.geotools.geometry.featgeom.root.GeometryImpl#getEnvelope() */ public Envelope getEnvelope() { // TODO documentation return this.envelope; } /* * (non-Javadoc) * * @see org.stdss.fgeo.primitive.OrientablePrimitive#createMate() */ protected OrientablePrimitiveImpl createProxy() { // TODO semantic SJ, JR // TODO implementation // TODO test // TODO documentation return null; } // Not used // /** // * @param distance // */ // public void splitBoundary(double distance) { // this.getBoundary().split(distance); // } /* * (non-Javadoc) * * @see org.geotools.geometry.featgeom.root.GeometryImpl#clone() */ public SurfaceImpl clone() throws CloneNotSupportedException { // Test OK // Clone SurfaceBoundary and use it to create new Surface SurfaceBoundary newBoundary = (SurfaceBoundary) this.boundary.clone(); return new SurfaceImpl(newBoundary); } /* * (non-Javadoc) * * @see org.opengis.geometry.primitive.OrientableSurface#getComposite() */ public CompositeSurface getComposite() { // TODO semantic SJ, JR // TODO implementation // TODO test // TODO documentation return null; } public Surface getPrimitive() { return null; } public OrientableSurface[] getProxy() { return null; } /* * (non-Javadoc) * * @see org.opengis.geometry.coordinate.root.Geometry#isSimple() */ public boolean isSimple() { // Test OK // Test simplicity by building a topological graph and testing for self-intersection // Is Simple, if the exterior ring and the interior rings does not have selfintersections // and the exterior ring and the interior rings don´t touch or intersect each other. IsSimpleOp simpleOp = new IsSimpleOp(); return simpleOp.isSimple(this); } /* * (non-Javadoc) * * @see org.opengis.geometry.coordinate.GenericSurface#getUpNormal(org.opengis.geometry.coordinate.DirectPosition) */ public double[] getUpNormal(DirectPosition point) { // TODO semantic SJ, JR // TODO implementation // TODO test // TODO documentation return null; } /* * (non-Javadoc) * * @see org.opengis.geometry.coordinate.GenericSurface#getPerimeter() */ public double getPerimeter() { // TODO semantic SJ, JR // TODO implementation // TODO test // TODO documentation return 0; } /* * (non-Javadoc) * * @see org.opengis.geometry.coordinate.GenericSurface#getArea() */ public double getArea() { // TODO semantic SJ, JR // TODO implementation // TODO test // TODO documentation return 0; } /* * (non-Javadoc) * * @see org.geotools.geometry.featgeom.root.GeometryImpl#getDimension(org.opengis.geometry.coordinate.DirectPosition) */ public int getDimension(DirectPosition point) { // TODO semantic SJ, JR // TODO implementation // TODO test // TODO documentation return 2; } public String toString() { return GeometryToString.getString(this); } /** * Returns a list of the rings which define the surface: First element is * the exterior ring (island), the following elements, if exist, define the * interior rings (holes) * * @return List of Ring: First element is the exterior ring (island), * the following elements, if exist, define the interior rings * (holes) */ public List<Ring> getBoundaryRings() { List<Ring> rList = new ArrayList(); rList.add(this.boundary.getExterior()); Iterator tInteriorRings = this.boundary.getInteriors().iterator(); while (tInteriorRings.hasNext()) { rList.add((Ring) tInteriorRings.next()); } return rList; } /* (non-Javadoc) * @see org.geotools.geometry.featgeom.root.GeometryImpl#getRepresentativePoint() */ public DirectPosition getRepresentativePoint() { // Return the representative point of the surface´s boundary // TODO Note: This solution is not correct, since the representative point of the surface boundary may not be on the surface return this.getBoundary().getRepresentativePoint(); } @Override public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + ((boundary == null) ? 0 : boundary.hashCode()); result = PRIME * result + ((envelope == null) ? 0 : envelope.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final SurfaceImpl other = (SurfaceImpl) obj; if (boundary == null) { if (other.boundary != null) return false; } else if (!boundary.equals(other.boundary)) return false; /* Envelope.class doesn't have equals implemented if (envelope == null) { if (other.envelope != null) return false; } else if (!envelope.equals(other.envelope)) return false; */ return true; } /* * (non-Javadoc) * * @see org.opengis.geometry.coordinate.root.Geometry#transform(org.opengis.referencing.crs.CoordinateReferenceSystem, * org.opengis.referencing.operation.MathTransform) */ public Geometry transform(CoordinateReferenceSystem newCRS, MathTransform transform) throws TransformException { // loop through each ring in this Surface and transform it to the new CRS, then // use the new rings to build a new Surface and return that. PrimitiveFactory primitiveFactory = new PrimitiveFactoryImpl(newCRS, getPositionFactory()); List<Ring> currentRings = this.getBoundaryRings(); Iterator<Ring> iter = currentRings.iterator(); Ring newExterior = null; List<Ring> newInteriors = new ArrayList<Ring>(); while (iter.hasNext()) { Ring thisRing = (Ring) iter.next(); // exterior Ring should be first element in the list if (newExterior == null) { newExterior = (Ring) thisRing.transform(newCRS, transform ); } else { newInteriors.add((Ring) thisRing.transform(newCRS, transform)); } } // use the new Ring list to build a new Surface and return it SurfaceBoundaryImpl surfaceBoundary = (SurfaceBoundaryImpl) primitiveFactory.createSurfaceBoundary(newExterior, newInteriors); SurfaceImpl newSurface = (SurfaceImpl) primitiveFactory.createSurface(surfaceBoundary); return newSurface; } }