org.geotools.data.collection.SpatialIndexFeatureSource Maven / Gradle / Ivy
Show all versions of gt-main Show documentation
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2019, 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.data.collection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.geotools.data.DataStore;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureListener;
import org.geotools.data.Query;
import org.geotools.data.QueryCapabilities;
import org.geotools.data.ResourceInfo;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.store.EmptyFeatureCollection;
import org.geotools.data.store.ReTypingFeatureCollection;
import org.geotools.data.store.ReprojectingFeatureCollection;
import org.geotools.feature.collection.MaxSimpleFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.And;
import org.opengis.filter.Filter;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.sort.SortBy;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.BinarySpatialOperator;
import org.opengis.filter.spatial.Contains;
import org.opengis.filter.spatial.Crosses;
import org.opengis.filter.spatial.DWithin;
import org.opengis.filter.spatial.Equals;
import org.opengis.filter.spatial.Intersects;
import org.opengis.filter.spatial.Overlaps;
import org.opengis.filter.spatial.Touches;
import org.opengis.filter.spatial.Within;
/**
* A FeatureSource using a spatial index to hold on to features and serve them up for fast display.
*
* This is a port of Andrea's CachingFeatureSource (which is slightly more compliced and rebuilds
* the cache as an origional feature source changes). Our implementation here knows up front that
* the features are in memory and does its best to take advantage of the fact. A caching feature
* source for fast data access.
*
*
Please note that this FeatureSource is strictly "read-only" and thus does not support feature
* events.
*/
public class SpatialIndexFeatureSource implements SimpleFeatureSource {
SpatialIndexFeatureCollection contents;
private static final Set supportedFilterTypes =
new HashSet(
Arrays.asList(
BBOX.class,
Contains.class,
Crosses.class,
DWithin.class,
Equals.class,
Intersects.class,
Overlaps.class,
Touches.class,
Within.class));
public SpatialIndexFeatureSource(SpatialIndexFeatureCollection original) {
this.contents = original;
}
public void addFeatureListener(FeatureListener listener) {}
public void removeFeatureListener(FeatureListener listener) {}
public DataStore getDataStore() {
return null; // not applicable
}
public ReferencedEnvelope getBounds() throws IOException {
return contents.getBounds();
}
public ReferencedEnvelope getBounds(Query query) throws IOException {
return getFeatures(query).getBounds();
}
public int getCount(Query query) throws IOException {
return getFeatures(query).size();
}
public SimpleFeatureType getSchema() {
return contents.getSchema();
}
public SimpleFeatureCollection getFeatures() throws IOException {
return contents;
}
public SimpleFeatureCollection getFeatures(Filter filter) throws IOException {
Query query = new Query(getSchema().getName().getLocalPart(), filter);
return getFeatures(query);
}
public SimpleFeatureCollection getFeatures(Query query) throws IOException {
Envelope bounds = getEnvelope(query.getFilter());
return getFeatureCollection(query, bounds);
}
private SimpleFeatureCollection getFeatureCollection(Query query, Envelope bounds)
throws IOException {
query = DataUtilities.resolvePropertyNames(query, getSchema());
final int offset = query.getStartIndex() != null ? query.getStartIndex() : 0;
if (offset > 0 && query.getSortBy() == null) {
if (!getQueryCapabilities().supportsSorting(query.getSortBy())) {
throw new IllegalStateException(
"Feature source does not support this sorting "
+ "so there is no way a stable paging (offset/limit) can be performed");
}
Query copy = new Query(query);
copy.setSortBy(new SortBy[] {SortBy.NATURAL_ORDER});
query = copy;
}
SimpleFeatureCollection collection;
// step one filter
if (query.getFilter() != null && query.getFilter().equals(Filter.EXCLUDE)) {
return new EmptyFeatureCollection(getSchema());
}
if (query.getFilter() != null && query.getFilter().equals(Filter.INCLUDE)) {
collection = contents;
}
if (query.getFilter() != null && query.getFilter().equals(Filter.INCLUDE)) {
collection = contents;
} else {
collection = contents.subCollection(query.getFilter());
}
// step two: reproject
if (query.getCoordinateSystemReproject() != null) {
collection =
new ReprojectingFeatureCollection(
collection, query.getCoordinateSystemReproject());
}
// step two sort! (note this makes a sorted copy)
if (query.getSortBy() != null && query.getSortBy().length != 0) {
SimpleFeature array[] = collection.toArray(new SimpleFeature[collection.size()]);
// Arrays sort is stable (not resorting equal elements)
for (SortBy sortBy : query.getSortBy()) {
Comparator comparator = DataUtilities.sortComparator(sortBy);
Arrays.sort(array, comparator);
}
ArrayList list = new ArrayList(Arrays.asList(array));
collection = new ListFeatureCollection(getSchema(), list);
}
// step three skip to start and return max number of fetaures
if (offset > 0 || !query.isMaxFeaturesUnlimited()) {
long max = Long.MAX_VALUE;
if (!query.isMaxFeaturesUnlimited()) {
max = query.getMaxFeatures();
}
collection = new MaxSimpleFeatureCollection(collection, offset, max);
}
// step four - retyping
// (It would be nice to do this earlier so as to not have all the baggage
// of unneeded attributes)
if (query.getPropertyNames() != Query.ALL_NAMES) {
// rebuild the type and wrap the reader
SimpleFeatureType schema = collection.getSchema();
SimpleFeatureType target =
SimpleFeatureTypeBuilder.retype(schema, query.getPropertyNames());
if (!target.equals(schema)) {
collection = new ReTypingFeatureCollection(collection, target);
}
}
return collection;
}
Envelope getEnvelope(Filter filter) {
Envelope result = new Envelope();
if (filter instanceof And) {
Envelope bounds = new Envelope();
for (Iterator iter = ((And) filter).getChildren().iterator(); iter.hasNext(); ) {
Filter f = (Filter) iter.next();
Envelope e = getEnvelope(f);
if (e == null) {
return null;
} else {
bounds.expandToInclude(e);
}
}
result = bounds;
} else if (filter instanceof BinarySpatialOperator) {
BinarySpatialOperator gf = (BinarySpatialOperator) filter;
if (supportedFilterTypes.contains(gf.getClass())) {
Expression lg = gf.getExpression1();
Expression rg = gf.getExpression2();
if (lg instanceof Literal) {
Geometry g = (Geometry) ((Literal) lg).getValue();
if (rg instanceof PropertyName) {
result = g.getEnvelopeInternal();
}
} else if (rg instanceof Literal) {
Geometry g = (Geometry) ((Literal) rg).getValue();
if (lg instanceof PropertyName) {
result = g.getEnvelopeInternal();
}
}
}
}
return result;
}
public ResourceInfo getInfo() {
return null;
}
public Name getName() {
return contents.getSchema().getName();
}
public QueryCapabilities getQueryCapabilities() {
return new QueryCapabilities() {
@Override
public boolean isOffsetSupported() {
return true;
}
};
}
public Set getSupportedHints() {
HashSet hints = new HashSet();
return hints;
}
}