/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2011, 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.geoserver.data.geogit;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.geogit.api.ObjectId;
import org.geogit.api.Ref;
import org.geogit.api.RevTree;
import org.geogit.api.SpatialRef;
import org.geogit.storage.ObjectDatabase;
import org.geogit.storage.ObjectReader;
import org.geogit.storage.WrappedSerialisingFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.factory.Hints;
import org.geotools.feature.CollectionListener;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureVisitor;
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.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.sort.SortBy;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.BinarySpatialOperator;
import org.opengis.filter.spatial.BoundedSpatialOperator;
import org.opengis.geometry.BoundingBox;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.ProgressListener;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterators;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
public class GeoGitSimpleFeatureCollection implements SimpleFeatureCollection {
private final SimpleFeatureType type;
private final Filter filter;
private final ObjectDatabase odb;
private final FeatureReprojector reprojector;
private Integer cachedSize;
private ReferencedEnvelope cachedBounds;
private GeometryFactory geometryFactory;
private final RevTree typeTree;
private Integer maxFeatures;
public GeoGitSimpleFeatureCollection(final SimpleFeatureType type,
final Filter filter, final ObjectDatabase odb,
final RevTree typeTree) {
this.type = type;
this.filter = filter;
this.odb = odb;
this.typeTree = typeTree;
this.reprojector = new FeatureReprojector(type);
}
public void setGeometryFactory(GeometryFactory geometryFactory) {
this.geometryFactory = geometryFactory;
}
public void setMaxFeatures(Integer maxFeatures) {
this.maxFeatures = maxFeatures;
}
private static class FeatureReprojector {
private final GeometryDescriptor[] geometryDescriptors;
private GeometryDescriptor mainGeometry;
public FeatureReprojector(final SimpleFeatureType type) {
this.mainGeometry = type.getGeometryDescriptor();
List<GeometryDescriptor> list = new ArrayList<GeometryDescriptor>(2);
for (AttributeDescriptor att : type.getAttributeDescriptors()) {
if (att instanceof GeometryDescriptor) {
list.add((GeometryDescriptor) att);
}
}
this.geometryDescriptors = list.toArray(new GeometryDescriptor[list
.size()]);
}
/**
* Expands {@code bounds} to include {@code featureBounds}
*
* @throws TransformException
*/
public void expandToInclude(ReferencedEnvelope target,
final BoundingBox featureBounds) throws TransformException {
final CoordinateReferenceSystem crs = target
.getCoordinateReferenceSystem();
final BoundingBox reprojected = ensureCompatibleCrs(featureBounds,
crs);
target.include(reprojected);
}
public BoundingBox ensureCompatibleCrs(final BoundingBox featureBounds,
final CoordinateReferenceSystem targetCrs)
throws TransformException {
final CoordinateReferenceSystem featureCrs = featureBounds
.getCoordinateReferenceSystem();
if (CRS.equalsIgnoreMetadata(targetCrs, featureCrs)) {
return featureBounds;
}
return featureBounds.toBounds(targetCrs);
}
public SimpleFeature reproject(SimpleFeature feature) throws Exception {
GeometryDescriptor geometryDescriptor;
CoordinateReferenceSystem sourceCrs;
CoordinateReferenceSystem targetCrs;
String name;
for (int i = 0; i < geometryDescriptors.length; i++) {
geometryDescriptor = geometryDescriptors[i];
targetCrs = geometryDescriptor.getCoordinateReferenceSystem();
name = geometryDescriptor.getLocalName();
Geometry geometry = (Geometry) feature.getAttribute(name);
if (geometry != null) {
sourceCrs = (CoordinateReferenceSystem) geometry
.getUserData();
if (sourceCrs != null
&& !CRS.equalsIgnoreMetadata(sourceCrs, targetCrs)) {
MathTransform mathTransform = CRS.findMathTransform(
sourceCrs, targetCrs, true);
geometry = JTS.transform((Geometry) geometry,
mathTransform);
geometry.setUserData(targetCrs);
feature.setAttribute(name, geometry);
}
}
}
return feature;
}
}
/**
* @see org.geotools.feature.FeatureCollection#getSchema()
*/
@Override
public SimpleFeatureType getSchema() {
return type;
}
/**
* @see org.geotools.feature.FeatureCollection#getID()
*/
@Override
public String getID() {
return null;
}
/**
* @see org.geotools.feature.FeatureCollection#purge()
*/
@Override
public void purge() {
}
/**
* @see org.geotools.feature.FeatureCollection#getBounds()
*/
@Override
public ReferencedEnvelope getBounds() {
if (this.cachedBounds != null) {
return this.cachedBounds;
}
final CoordinateReferenceSystem crs = type
.getCoordinateReferenceSystem();
ReferencedEnvelope bounds = new ReferencedEnvelope(crs);
if (BigInteger.ZERO.equals(typeTree.size())) {
return bounds;
}
final FeatureRefIterator refs = new FeatureRefIterator(typeTree, filter);
BoundingBox featureBounds;
try {
if (refs.isFullySupported()) {
while (refs.hasNext()) {
Ref ref = refs.next();
if (ref instanceof SpatialRef) {
SpatialRef sp = (SpatialRef) ref;
featureBounds = sp.getBounds();
reprojector.expandToInclude(bounds, featureBounds);
}
}
} else {
Iterator<SimpleFeature> features = new GeoGitFeatureIterator(
refs, type, filter, odb);
while (features.hasNext()) {
featureBounds = features.next().getBounds();
reprojector.expandToInclude(bounds, featureBounds);
}
}
} catch (TransformException e) {
throw new RuntimeException(e);
}
this.cachedBounds = bounds;
return bounds;
}
/**
* @see org.geotools.feature.FeatureCollection#size()
*/
@Override
public int size() {
if (this.cachedSize != null) {
return this.cachedSize.intValue();
}
if (Filter.INCLUDE.equals(filter)) {
final BigInteger size = typeTree.size();
return size.intValue();
}
final FeatureRefIterator refs = new FeatureRefIterator(typeTree, filter);
int size;
if (refs.isFullySupported()) {
size = Iterators.size(refs);
} else {
Iterator<SimpleFeature> features = new GeoGitFeatureIterator(refs,
type, filter, odb);
size = Iterators.size(features);
}
this.cachedSize = Integer.valueOf(size);
return size;
}
/**
* @see org.geotools.feature.FeatureCollection#iterator()
*/
@Override
public Iterator<SimpleFeature> iterator() {
final FeatureRefIterator refs = new FeatureRefIterator(typeTree, filter);
Iterator<SimpleFeature> features = new GeoGitFeatureIterator(refs,
type, filter, odb);
if (maxFeatures != null) {
features = Iterators.limit(features, maxFeatures.intValue());
}
return features;
}
/**
* @see org.geotools.data.simple.SimpleFeatureCollection#features()
* @see #iterator()
*/
@Override
public SimpleFeatureIterator features() {
final Iterator<SimpleFeature> iterator = iterator();
return new SimpleFeatureIterator() {
@Override
public SimpleFeature next() throws NoSuchElementException {
return iterator.next();
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public void close() {
// nothing to do?
}
};
}
/**
* @author groldan
*/
private class BBOXPredicate implements Predicate<Ref> {
private BoundingBox filter;
public BBOXPredicate(final BinarySpatialOperator boundedFilter) {
Expression right = boundedFilter.getExpression2();
if (right instanceof Literal) {
Object literal = right.evaluate(null);
if (literal instanceof BoundingBox) {
this.filter = (BoundingBox) literal;
} else if (literal instanceof Geometry) {
Geometry geom = (Geometry) literal;
CoordinateReferenceSystem crs = null;
if (geom.getUserData() instanceof CoordinateReferenceSystem) {
crs = (CoordinateReferenceSystem) geom.getUserData();
} else if (type.getGeometryDescriptor() != null) {
crs = type.getGeometryDescriptor()
.getCoordinateReferenceSystem();
} else {
throw new IllegalStateException(
"Can't determine CRS of filter geometry");
}
this.filter = new ReferencedEnvelope(
geom.getEnvelopeInternal(), crs);
} else {
throw new IllegalArgumentException(
"Right operand of BBOX filter can't be resolved to a BoundingBox: "
+ right);
}
}
}
@Override
public boolean apply(final Ref featureRef) {
if (!(featureRef instanceof SpatialRef)) {
return false;
}
final CoordinateReferenceSystem targetCrs = filter
.getCoordinateReferenceSystem();
BoundingBox bounds;
try {
bounds = reprojector.ensureCompatibleCrs(
((SpatialRef) featureRef).getBounds(), targetCrs);
final boolean apply = filter.intersects(bounds);
return apply;
} catch (TransformException e) {
throw new RuntimeException(e);
}
}
}
/**
* @author groldan
*/
private class FeatureRefIterator extends AbstractIterator<Ref> {
private final Iterator<Ref> refs;
private final Filter filter;
public FeatureRefIterator(final RevTree typeTree, final Filter filter) {
this.filter = filter;
if (Filter.INCLUDE.equals(filter)) {
refs = typeTree.iterator(null);
return;
}
if (filter instanceof BoundedSpatialOperator
&& filter instanceof BinarySpatialOperator) {
refs = typeTree.iterator(new BBOXPredicate(
(BinarySpatialOperator) filter));
return;
}
// can't optimize here
refs = typeTree.iterator(null);
}
public boolean isFullySupported() {
return Filter.INCLUDE.equals(filter) || filter instanceof BBOX;
}
@Override
protected Ref computeNext() {
if (!refs.hasNext()) {
return endOfData();
}
return refs.next();
}
}
private class GeoGitFeatureIterator extends AbstractIterator<SimpleFeature> {
private final Iterator<Ref> featureRefs;
private final SimpleFeatureType type;
private final Filter filter;
private final ObjectDatabase odb;
final WrappedSerialisingFactory serialisingFactory;
public GeoGitFeatureIterator(final Iterator<Ref> featureRefs,
final SimpleFeatureType type, final Filter filter,
final ObjectDatabase odb) {
this.featureRefs = featureRefs;
this.type = type;
this.filter = filter;
this.odb = odb;
this.serialisingFactory = WrappedSerialisingFactory.getInstance();
}
@Override
protected SimpleFeature computeNext() {
Hints hints = new Hints();
if (null != geometryFactory) {
hints.put(Hints.GEOMETRY_FACTORY, geometryFactory);
}
try {
while (featureRefs.hasNext()) {
Ref featureRef = featureRefs.next();
String featureId = featureRef.getName();
ObjectId contentId = featureRef.getObjectId();
SimpleFeature feature;
ObjectReader<Feature> featureReader = serialisingFactory
.createFeatureReader(type, featureId, hints);
feature = (SimpleFeature) odb.get(contentId, featureReader);
feature = reprojector.reproject(feature);
if (filter.evaluate(feature)) {
return feature;
}
}
} catch (Exception e) {
Throwables.propagate(e);
}
return endOfData();
}
}
@Override
public void accepts(FeatureVisitor visitor, ProgressListener progress)
throws IOException {
// TODO Auto-generated method stub
}
@Override
public void close(FeatureIterator<SimpleFeature> close) {
// TODO Auto-generated method stub
}
@Override
public void close(Iterator<SimpleFeature> close) {
// TODO Auto-generated method stub
}
/**
* @see org.geotools.feature.FeatureCollection#addListener(org.geotools.feature.CollectionListener)
*/
@Override
public void addListener(CollectionListener listener)
throws NullPointerException {
// TODO Auto-generated method stub
}
/**
* @see org.geotools.feature.FeatureCollection#removeListener(org.geotools.feature.CollectionListener)
*/
@Override
public void removeListener(CollectionListener listener)
throws NullPointerException {
// TODO Auto-generated method stub
}
/**
* @see org.geotools.feature.FeatureCollection#isEmpty()
*/
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean add(SimpleFeature obj) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends SimpleFeature> collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(
FeatureCollection<? extends SimpleFeatureType, ? extends SimpleFeature> resource) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public boolean contains(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(Collection<?> o) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public Object[] toArray() {
throw new UnsupportedOperationException();
}
@Override
public <O> O[] toArray(O[] a) {
throw new UnsupportedOperationException();
}
@Override
public SimpleFeatureCollection subCollection(Filter filter) {
throw new UnsupportedOperationException();
}
@Override
public SimpleFeatureCollection sort(SortBy order) {
throw new UnsupportedOperationException();
}
}