/* Spatial Operations & Editing Tools for uDig * * Axios Engineering under a funding contract with: * Diputación Foral de Gipuzkoa, Ordenación Territorial * * http://b5m.gipuzkoa.net * http://www.axios.es * * (C) 2006, Diputación Foral de Gipuzkoa, Ordenación Territorial (DFG-OT). * DFG-OT agrees to licence under Lesser General Public License (LGPL). * * 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 es.axios.udig.ui.commons.util; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import org.geotools.feature.FeatureCollection; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; 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.operation.linemerge.LineMerger; import com.vividsolutions.jts.operation.polygonize.Polygonizer; import es.axios.udig.ui.commons.internal.i18n.Messages; /** * Geometry util methods * <p> * Collection of method which gets feature or feature collection to applay geometry operations * </p> * * @author Mauricio Pazos (www.axios.es) * @author Gabriel Roldan (www.axios.es) * @since 1.1.0 */ public class GeometryUtil { /** * unused */ private GeometryUtil() { // util class } /** * Returns a geometry which is the union of all the non null default geometries from the * features in <code>featureCollection</code> * * @param featureCollection * @param expectedGeometryClass * @return Gemetry Union */ public static Geometry geometryUnion( final FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection ) { Geometry resultGeom = null; SimpleFeature currFeature; Geometry featureGeom; try { for( Iterator<SimpleFeature> iterator = featureCollection.iterator(); iterator.hasNext(); ) { currFeature = iterator.next(); featureGeom = (Geometry) currFeature.getDefaultGeometry(); if (featureGeom != null) { featureGeom.normalize(); if (resultGeom == null) { resultGeom = featureGeom; } else { resultGeom = resultGeom.union(featureGeom); } } } } finally { // ask feature collection to close potentially still open iterators featureCollection.purge(); } return resultGeom; // // TODO: iterar sobre featurecollection y crear la unión incrementalmente. Obtener el // // array de geometries es potencialmente demasiado costoso // Geometry[] geometries = extractGeometries(featureCollection); // assert geometries.length >= 2; // // final GeometryFactory geomFactory = geometries[0].getFactory(); // // if (Polygon.class.equals(expectedGeometryClass)) { // // does buffer of collection for efficient union // // GeometryCollection geometryCollection = geomFactory // .createGeometryCollection(geometries); // // resultGeom = geometryCollection.buffer(0); // // } else { // other geometries // Geometry unionGeom = geometries[0]; // for( int i = 1; i < geometries.length; i++ ) { // Geometry geom = geometries[i]; // // if (GeometryCollection.class.equals(unionGeom.getClass())) { // break; // cannot do the union // } // unionGeom = unionGeom.union(geom); // // } // // if could not do union because exists a geometry collection in union geometry // if (GeometryCollection.class.equals(unionGeom.getClass())) { // // makes a geometry collection // resultGeom = geomFactory.createGeometryCollection(geometries); // } else { // // the result is the union // resultGeom = unionGeom; // } // } // assert !resultGeom.isEmpty(); // // return resultGeom; } /** * Extracts the geometries and makes a geometry array. * <p> * Note the resulting array size might be lower than the featureCollection size, as it will not * contain null geometries. * </p> * * @param featureCollection * @return Geometry[] geometries present in features */ public static Geometry[] extractGeometries( final FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection ) { Iterator iter = null; try { ArrayList<Geometry> geometries = new ArrayList<Geometry>(featureCollection.size()); int finalSize = 0; iter = featureCollection.iterator(); while( iter.hasNext() ) { SimpleFeature feature = (SimpleFeature) iter.next(); Geometry geometry = (Geometry) feature.getDefaultGeometry(); if (geometry == null) { continue; } else if (geometry instanceof GeometryCollection) { GeometryCollection geomSet = (GeometryCollection) geometry; final int size = geomSet.getNumGeometries(); for( int j = 0; j < size; j++ ) { geometries.add(geomSet.getGeometryN(j)); } finalSize += size; } else { geometries.add(geometry); finalSize++; } } return geometries.toArray(new Geometry[finalSize]); } finally { if (iter != null) { featureCollection.close(iter); } } } /** * Existe intersection between features. * * @param features * @param expectedGeometryClass * @return boolean true */ // public static boolean intersects( final FeatureCollection<SimpleFeatureType, SimpleFeature> features, // final Class expectedGeometryClass ) { // // TODO: search an option to optimize this implementation. // Geometry result = geometryUnion(features, expectedGeometryClass); // // return !(result instanceof GeometryCollection); // } /** * Adapts a Geometry <code>geom</code> to another type of geometry given the desired geometry * class. * <p> * Currently implemented adaptations: * <ul> * <li>Point -> MultiPoint. Wraps the Point on a single part MultiPoint. * <li>Polygon -> MultiPolygon. Wraps the Polygon on a single part MultiPolygon. * <li>LineString -> MultiLineString. Wraps the LineString on a single part MultiLineString. * <li>MultiLineString -> String. Succeeds if merging the parts result in a single LineString, * fails otherwise. * <li>MultiPolygon -> Polygon. Succeeds if merging the parts result in a single Polygon, fails * otherwise. * <li>* -> GeometryCollection * </ul> * </p> * TODO: add more adaptations on an as needed basis * * @param inputGeom * @param adaptTo * @return Geometry adapted * @throws IllegalArgumentException if <code>geom</code> cannot be adapted as * <code>adapTo</code> */ public static Geometry adapt( final Geometry inputGeom, final Class< ? extends Geometry> adaptTo ) { assert inputGeom != null : "inputGeom can't be null"; assert adaptTo != null : "adaptTo can't be null";; final Class geomClass = inputGeom.getClass(); if (Geometry.class.equals(adaptTo)) { return inputGeom; } final GeometryFactory gf = inputGeom.getFactory(); if (MultiPoint.class.equals(adaptTo) && Point.class.equals(geomClass)) { return gf.createMultiPoint(new Point[]{(Point) inputGeom}); } if (Polygon.class.equals(adaptTo)) { if (adaptTo.equals(geomClass)) { return inputGeom; } Polygonizer polygonnizer = new Polygonizer(); polygonnizer.add(inputGeom); Collection polys = polygonnizer.getPolygons(); Polygon[] polygons = new ArrayList<Polygon>(polys).toArray(new Polygon[polys.size()]); if (polygons.length == 1) { return polygons[0]; } } if (MultiPolygon.class.equals(adaptTo)) { if (adaptTo.equals(geomClass)) { return inputGeom; } if (Polygon.class.equals(geomClass)) { return gf.createMultiPolygon(new Polygon[]{(Polygon) inputGeom}); } /* * Polygonizer polygonnizer = new Polygonizer(); polygonnizer.add(inputGeom); Collection * polys = polygonnizer.getPolygons(); Polygon[] polygons = new ArrayList<Polygon>(polys).toArray(new * Polygon[polys.size()]); if (MultiPolygon.class.equals(adaptTo)) { return * gf.createMultiPolygon(polygons); } if (polygons.length == 1) { return polygons[0]; } */ } if (GeometryCollection.class.equals(adaptTo)) { return gf.createGeometryCollection(new Geometry[]{inputGeom}); } if (MultiLineString.class.equals(adaptTo) || LineString.class.equals(adaptTo)) { LineMerger merger = new LineMerger(); merger.add(inputGeom); Collection mergedLineStrings = merger.getMergedLineStrings(); ArrayList<LineString> lineList = new ArrayList<LineString>(mergedLineStrings); LineString[] lineStrings = lineList.toArray(new LineString[mergedLineStrings.size()]); if (MultiLineString.class.equals(adaptTo)) { MultiLineString line = gf.createMultiLineString(lineStrings); return line; } if (lineStrings.length == 1) { Geometry mergedResult = (Geometry) lineStrings[0]; return mergedResult; } } final String msg = MessageFormat.format(Messages.GeometryUtil_DonotKnowHowAdapt, geomClass.getSimpleName(), adaptTo.getSimpleName()); throw new IllegalArgumentException(msg); } /** * @param geomClass * @return the geometry class's dimension */ public static int getDimension( final Class geomClass ) { if ((Point.class.equals(geomClass)) || (MultiPoint.class.equals(geomClass))) { return 0; } else if ((LineString.class.equals(geomClass)) || (MultiLineString.class.equals(geomClass))) { return 1; } else if ((Polygon.class.equals(geomClass)) || (MultiPolygon.class.equals(geomClass))) { return 2; } else { final String msg = MessageFormat.format(Messages.GeometryUtil_CannotGetDimension, geomClass.getName()); throw new IllegalArgumentException(msg); } } /** * @param simpleGeometry Point, LineString or Polygon class * @return a compatible Geometry collection for the simple geometry */ public static Class getCompatibleCollection( final Class simpleGeometry ) { if (Point.class.equals(simpleGeometry)) { return MultiPoint.class; } else if (LineString.class.equals(simpleGeometry)) { return MultiLineString.class; } else if (Polygon.class.equals(simpleGeometry)) { return MultiPolygon.class; } else { throw new IllegalArgumentException(Messages.GeometryUtil_ExpectedSimpleGeometry); } } }