/*
* 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.Level;
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.ReferencedEnvelope;
import org.geotools.process.spatialstatistics.core.DataUtils;
import org.geotools.referencing.CRS;
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 org.opengis.referencing.crs.CoordinateReferenceSystem;
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;
import com.vividsolutions.jts.operation.union.CascadedPolygonUnion;
/**
* Difference SimpleFeatureCollection Implementation
*
* @author Minpa Lee, MangoSystem
*
* @source $URL$
*/
public class DifferenceFeatureCollection extends GXTSimpleFeatureCollection {
protected static final Logger LOGGER = Logging.getLogger(DifferenceFeatureCollection.class);
private SimpleFeatureCollection differenceFeatures;
private SimpleFeatureType targetSchema;
public DifferenceFeatureCollection(SimpleFeatureCollection delegate,
SimpleFeatureCollection differenceFeatures) {
super(delegate);
// check coordinate reference system
CoordinateReferenceSystem crsT = delegate.getSchema().getCoordinateReferenceSystem();
CoordinateReferenceSystem crsS = differenceFeatures.getSchema()
.getCoordinateReferenceSystem();
if (crsT != null && crsS != null && !CRS.equalsIgnoreMetadata(crsT, crsS)) {
differenceFeatures = new ReprojectFeatureCollection(differenceFeatures, crsS, crsT,
true);
LOGGER.log(Level.WARNING, "reprojecting features");
}
// use SpatialIndexFeatureCollection
this.differenceFeatures = DataUtils.toSpatialIndexFeatureCollection(differenceFeatures);
this.targetSchema = buildTargetSchema(delegate.getSchema());
}
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();
}
public SimpleFeatureType getSchema() {
return targetSchema;
}
@Override
public SimpleFeatureIterator features() {
return new DifferenceFeatureIterator(delegate.features(), getSchema(), differenceFeatures);
}
@Override
public ReferencedEnvelope getBounds() {
return DataUtilities.bounds(features());
}
@Override
public SimpleFeatureCollection subCollection(Filter filter) {
if (filter == Filter.INCLUDE) {
return this;
}
return new SubFeatureCollection(this, filter);
}
public int size() {
return DataUtilities.count(features());
}
static class DifferenceFeatureIterator implements SimpleFeatureIterator {
private SimpleFeatureIterator delegate;
private SimpleFeatureCollection differenceFeatures;
private ReferencedEnvelope bounds;
private String geomField;
private SimpleFeatureBuilder builder;
private SimpleFeature next;
private Class<?> target;
public DifferenceFeatureIterator(SimpleFeatureIterator delegate, SimpleFeatureType schema,
SimpleFeatureCollection differenceFeatures) {
this.delegate = delegate;
this.differenceFeatures = differenceFeatures;
this.bounds = differenceFeatures.getBounds();
this.geomField = differenceFeatures.getSchema().getGeometryDescriptor().getLocalName();
this.builder = new SimpleFeatureBuilder(schema);
this.target = schema.getGeometryDescriptor().getType().getBinding();
}
public void close() {
delegate.close();
}
public boolean hasNext() {
while (next == null && delegate.hasNext()) {
SimpleFeature feature = delegate.next();
Geometry geometry = (Geometry) feature.getDefaultGeometry();
if (geometry == null || geometry.isEmpty()) {
continue;
}
Geometry diffGeom = geometry; // default
if (bounds.intersects(geometry.getEnvelopeInternal())) {
Filter filter = getIntersectsFilter(geomField, geometry);
// finally difference using union geometries(intersection features)
List<Geometry> geometries = new ArrayList<Geometry>();
SimpleFeatureIterator difIter = differenceFeatures.subCollection(filter)
.features();
try {
while (difIter.hasNext()) {
SimpleFeature diffFeature = difIter.next();
geometries.add((Geometry) diffFeature.getDefaultGeometry());
}
} finally {
difIter.close();
}
if (geometries.size() > 0) {
Geometry unionGeometry = new CascadedPolygonUnion(geometries).union();
if (unionGeometry != null && !unionGeometry.isEmpty()) {
diffGeom = difference(geometry, unionGeometry, target);
}
if (diffGeom == null || diffGeom.isEmpty()) {
continue;
}
}
}
for (Object attribute : feature.getAttributes()) {
if (attribute instanceof Geometry) {
builder.add(diffGeom);
} else {
builder.add(attribute);
}
}
next = builder.buildFeature(feature.getID());
}
return next != null;
}
private Geometry difference(Geometry geom, Geometry overlay, Class<?> target) {
Geometry difference = geom.difference(overlay);
// empty difference?
if (difference == null || difference.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 = difference;
} else if (MultiLineString.class.isAssignableFrom(target)
|| LineString.class.isAssignableFrom(target)) {
final List<LineString> geoms = new ArrayList<LineString>();
difference.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[] ls = (LineString[]) geoms.toArray(new LineString[geoms.size()]);
result = geom.getFactory().createMultiLineString(ls);
}
} else if (MultiPolygon.class.isAssignableFrom(target)
|| Polygon.class.isAssignableFrom(target)) {
final List<Polygon> geoms = new ArrayList<Polygon>();
difference.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[] ps = (Polygon[]) geoms.toArray(new Polygon[geoms.size()]);
result = geom.getFactory().createMultiPolygon(ps);
}
} else {
throw new RuntimeException("Unrecognized target type " + target.getCanonicalName());
}
return result;
}
public SimpleFeature next() throws NoSuchElementException {
if (!hasNext()) {
throw new NoSuchElementException("hasNext() returned false!");
}
SimpleFeature result = next;
next = null;
return result;
}
}
}