/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2014, 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.process.spatialstatistics.transformation; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.logging.Logger; import org.geotools.data.DataUtilities; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.feature.collection.SubFeatureCollection; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.geometry.jts.GeometryClipper; import org.geotools.util.logging.Logging; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.filter.Filter; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryComponentFilter; 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; /** * Extracts input features that overlay the clip geometry. * * @author Andrea Aime - GeoSolutions * @modifier Minpa Lee, MangoSystem * * @reference org.geotools.process.vector.ClipProcess.java * * @source $URL$ */ public class ClipWithGeometryFeatureCollection extends GXTSimpleFeatureCollection { protected static final Logger LOGGER = Logging .getLogger(ClipWithGeometryFeatureCollection.class); private Geometry clip; private SimpleFeatureType targetSchema; public ClipWithGeometryFeatureCollection(SimpleFeatureCollection delegate, Geometry clip) { super(delegate); this.clip = clip; this.targetSchema = buildTargetSchema(delegate.getSchema()); } /** * When clipping lines and polygons can turn into multilines and multipolygons */ private SimpleFeatureType buildTargetSchema(SimpleFeatureType schema) { SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); for (AttributeDescriptor ad : schema.getAttributeDescriptors()) { if (ad instanceof GeometryDescriptor) { GeometryDescriptor gd = (GeometryDescriptor) ad; Class<?> binding = ad.getType().getBinding(); if (Point.class.isAssignableFrom(binding) || GeometryCollection.class.isAssignableFrom(binding)) { tb.add(ad); } else { Class<?> target; if (LineString.class.isAssignableFrom(binding)) { target = MultiLineString.class; } else if (Polygon.class.isAssignableFrom(binding)) { target = MultiPolygon.class; } else { throw new RuntimeException("Don't know how to handle geometries of type " + binding.getCanonicalName()); } tb.minOccurs(ad.getMinOccurs()); tb.maxOccurs(ad.getMaxOccurs()); tb.nillable(ad.isNillable()); tb.add(ad.getLocalName(), target, gd.getCoordinateReferenceSystem()); } } else { tb.add(ad); } } tb.setName(schema.getName()); return tb.buildFeatureType(); } @Override public SimpleFeatureIterator features() { return new ClipWithGeometryFeatureIterator(delegate.features(), clip, getSchema()); } @Override public SimpleFeatureType getSchema() { return targetSchema; } @Override public SimpleFeatureCollection subCollection(Filter filter) { if (filter == Filter.INCLUDE) { return this; } return new SubFeatureCollection(this, filter); } @Override public int size() { return DataUtilities.count(features()); } static class ClipWithGeometryFeatureIterator implements SimpleFeatureIterator { private SimpleFeatureIterator delegate; private GeometryClipper clipper; private SimpleFeatureBuilder builder; private SimpleFeature next; private Geometry clip; public ClipWithGeometryFeatureIterator(SimpleFeatureIterator delegate, Geometry clip, SimpleFeatureType schema) { this.delegate = delegate; // can we use the fast clipper? if (clip.getEnvelope().equals(clip)) { this.clipper = new GeometryClipper(clip.getEnvelopeInternal()); } else { this.clip = clip; } builder = new SimpleFeatureBuilder(schema); } public void close() { delegate.close(); } public boolean hasNext() { while (next == null && delegate.hasNext()) { boolean clippedOut = false; // try building the clipped feature out of the original feature, if the // default geometry is clipped out, skip it SimpleFeature feature = delegate.next(); GeometryDescriptor gds = feature.getFeatureType().getGeometryDescriptor(); Object cliped = clipGeometry((Geometry) feature.getDefaultGeometry(), gds.getType() .getBinding()); if (cliped == null) { clippedOut = true; } if (!clippedOut) { // build the next feature for (Object attribute : feature.getAttributes()) { if (attribute instanceof Geometry) { attribute = cliped; } builder.add(attribute); } next = builder.buildFeature(feature.getID()); } builder.reset(); } return next != null; } public SimpleFeature next() throws NoSuchElementException { if (!hasNext()) { throw new NoSuchElementException("hasNext() returned false!"); } SimpleFeature result = next; next = null; return result; } private Object clipGeometry(Geometry geom, Class<?> target) { // first off, clip Geometry clipped = null; if (clipper != null) { clipped = clipper.clip(geom, true); } else { if (geom.getEnvelopeInternal().intersects(clip.getEnvelopeInternal())) { clipped = clip.intersection(geom); } } // empty intersection? if (clipped == null || clipped.getNumGeometries() == 0) { return null; } // map the result to the target output type, removing the spurious lower dimensional // elements that might result out of the intersection Geometry result; if (Point.class.isAssignableFrom(target) || MultiPoint.class.isAssignableFrom(target) || GeometryCollection.class.equals(target)) { result = clipped; } else if (MultiLineString.class.isAssignableFrom(target) || LineString.class.isAssignableFrom(target)) { final List<LineString> geoms = new ArrayList<LineString>(); clipped.apply(new GeometryComponentFilter() { @Override public void filter(Geometry geom) { if (geom instanceof LineString) { geoms.add((LineString) geom); } } }); if (geoms.size() == 0) { result = null; } else { LineString[] lsArray = (LineString[]) geoms .toArray(new LineString[geoms.size()]); result = geom.getFactory().createMultiLineString(lsArray); } } else if (MultiPolygon.class.isAssignableFrom(target) || Polygon.class.isAssignableFrom(target)) { final List<Polygon> geoms = new ArrayList<Polygon>(); clipped.apply(new GeometryComponentFilter() { @Override public void filter(Geometry geom) { if (geom instanceof Polygon) { geoms.add((Polygon) geom); } } }); if (geoms.size() == 0) { result = null; } else { Polygon[] lsArray = (Polygon[]) geoms.toArray(new Polygon[geoms.size()]); result = geom.getFactory().createMultiPolygon(lsArray); } } else { throw new RuntimeException("Unrecognized target type " + target.getCanonicalName()); } return result; } } }