org.geotools.data.view.DefaultView Maven / Gradle / Ivy
Show all versions of gt-main Show documentation
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-2008, 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.view;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataStore;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureListener;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.QueryCapabilities;
import org.geotools.data.ResourceInfo;
import org.geotools.data.crs.ForceCoordinateSystemFeatureResults;
import org.geotools.data.crs.ReprojectFeatureResults;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.SchemaException;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* Wrapper for SimpleFeatureSource constrained by a Query.
*
* Support SimpleFeatureSource decorator that takes care of mapping a Query & SimpleFeatureSource
* with the schema and definition query configured for it.
*
*
Because GeoServer requires that attributes always be returned in the same order we need a way
* to smoothly inforce this. Could we use this class to do so?
*
*
WARNING: this class is a placeholder for ideas right now - it may not always impement
* FeatureSource.
*
* @author Gabriel Rold�n
*/
public class DefaultView implements SimpleFeatureSource {
/** Shared package logger */
private static final Logger LOGGER =
org.geotools.util.logging.Logging.getLogger(DefaultView.class);
/** SimpleFeatureSource being served up */
protected SimpleFeatureSource source;
/** Schema generated by provided constraintQuery */
private SimpleFeatureType schema;
/** Query provided as a constraint */
private Query constraintQuery;
/**
* Creates a new GeoServerFeatureSource object.
*
*
Grabs the following from query:
*
*
* - typeName - only used if client does not supply
*
- cs - only used if client does not supply
*
- csForce - only used if client does not supply
*
- filter - combined with client filter
*
- propertyNames - combined with client filter (indicate property names that *must* be
* included)
*
*
* Schema is generated based on this information.
*
* @param source a FeatureSource
* @param query Filter used to limit results
*/
public DefaultView(SimpleFeatureSource source, Query query) throws SchemaException {
this.source = source;
this.constraintQuery = query;
SimpleFeatureType origionalType = source.getSchema();
CoordinateReferenceSystem cs = null;
if (query.getCoordinateSystemReproject() != null) {
cs = query.getCoordinateSystemReproject();
} else if (query.getCoordinateSystem() != null) {
cs = query.getCoordinateSystem();
}
schema =
DataUtilities.createSubType(
origionalType, query.getPropertyNames(), cs, query.getTypeName(), null);
}
/**
* @see FeatureSource#getName()
* @since 2.5
*/
public Name getName() {
return getSchema().getName();
}
/**
* Factory that make the correct decorator for the provided featureSource.
*
* This factory method is public and will be used to create all required subclasses. By
* comparison the constructors for this class have package visibiliy. TODO: revisit this - I am
* not sure I want write access to views (especially if they do reprojection).
*/
public static SimpleFeatureSource create(SimpleFeatureSource source, Query query)
throws SchemaException {
return new DefaultView(source, query);
}
/**
* Takes a query and adapts it to match re definitionQuery filter configured for a feature type.
* It won't handle coordinate system changes
*
*
Grabs the following from query:
*
*
* - typeName - only used if client does not supply
*
- filter - combined with client filter
*
- propertyNames - combined with client filter (indicate property names that *must* be
* included)
*
*
* @param query Query against this DataStore
* @return Query restricted to the limits of definitionQuery
* @throws IOException See DataSourceException
* @throws DataSourceException If query could not meet the restrictions of definitionQuery
*/
protected Query makeDefinitionQuery(Query query) throws IOException {
if ((query == Query.ALL) || query.equals(Query.ALL)) {
return new Query(constraintQuery);
}
try {
String[] propNames = extractAllowedAttributes(query);
String typeName = query.getTypeName();
if (typeName == null) {
typeName = constraintQuery.getTypeName();
}
URI namespace = query.getNamespace();
if (namespace == null || namespace == Query.NO_NAMESPACE) {
namespace = constraintQuery.getNamespace();
}
Filter filter = makeDefinitionFilter(query.getFilter());
int maxFeatures = Math.min(query.getMaxFeatures(), constraintQuery.getMaxFeatures());
String handle = query.getHandle();
if (handle == null) {
handle = constraintQuery.getHandle();
} else if (constraintQuery.getHandle() != null) {
handle = handle + "(" + constraintQuery.getHandle() + ")";
}
Query Query = new Query(typeName, namespace, filter, maxFeatures, propNames, handle);
Query.setSortBy(query.getSortBy());
return Query;
} catch (Exception ex) {
throw new DataSourceException(
"Could not restrict the query to the definition criteria: " + ex.getMessage(),
ex);
}
}
/**
* List of allowed attributes.
*
* Creates a list of FeatureTypeInfo's attribute names based on the attributes requested by
* query
and making sure they not contain any non exposed attribute.
*
*
Exposed attributes are those configured in the "attributes" element of the
* FeatureTypeInfo's configuration
*
* @param query User's origional query
* @return List of allowed attribute types
*/
private String[] extractAllowedAttributes(Query query) {
String[] propNames = null;
if (query.retrieveAllProperties()) {
propNames = new String[schema.getAttributeCount()];
for (int i = 0; i < schema.getAttributeCount(); i++) {
propNames[i] = schema.getDescriptor(i).getLocalName();
}
} else {
String[] queriedAtts = query.getPropertyNames();
int queriedAttCount = queriedAtts.length;
List allowedAtts = new LinkedList();
for (int i = 0; i < queriedAttCount; i++) {
if (schema.getDescriptor(queriedAtts[i]) != null) {
allowedAtts.add(queriedAtts[i]);
} else {
LOGGER.info(
"queried a not allowed property: "
+ queriedAtts[i]
+ ". Ommitting it from query");
}
}
propNames = (String[]) allowedAtts.toArray(new String[allowedAtts.size()]);
}
return propNames;
}
/**
* If a definition query has been configured for the FeatureTypeInfo, makes and return a new
* Filter that contains both the query's filter and the layer's definition one, by logic AND'ing
* them.
*
* @param filter Origional user supplied Filter
* @return Filter adjusted to the limitations of definitionQuery
* @throws DataSourceException If the filter could not meet the limitations of definitionQuery
*/
protected Filter makeDefinitionFilter(Filter filter) throws DataSourceException {
Filter newFilter = filter;
Filter constraintFilter = constraintQuery.getFilter();
try {
if (constraintFilter != Filter.INCLUDE) {
FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
newFilter = ff.and(constraintFilter, filter);
}
} catch (Exception ex) {
throw new DataSourceException("Can't create the constraint filter", ex);
}
return newFilter;
}
/**
* Implement getDataStore.
*
*
Description ...
*
* @return @see org.geotools.data.FeatureSource#getDataStore()
*/
public DataStore getDataStore() {
return (DataStore) source.getDataStore();
}
/**
* Implement addFeatureListener.
*
*
Description ...
*
* @see org.geotools.data.FeatureSource#addFeatureListener(org.geotools.data.FeatureListener)
*/
public void addFeatureListener(FeatureListener listener) {
source.addFeatureListener(listener);
}
/**
* Implement removeFeatureListener.
*
*
Description ...
*
* @see org.geotools.data.FeatureSource#removeFeatureListener(org.geotools.data.FeatureListener)
*/
public void removeFeatureListener(FeatureListener listener) {
source.removeFeatureListener(listener);
}
/**
* Implement getFeatures.
*
*
Description ...
*
* @see org.geotools.data.FeatureSource#getFeatures(org.geotools.data.Query)
*/
public SimpleFeatureCollection getFeatures(Query query) throws IOException {
Query mergedQuery = makeDefinitionQuery(query);
SimpleFeatureCollection results = source.getFeatures(mergedQuery);
// Get all the coordinate systems involved in the two queries
CoordinateReferenceSystem cCs = constraintQuery.getCoordinateSystem();
CoordinateReferenceSystem cCsr = constraintQuery.getCoordinateSystemReproject();
CoordinateReferenceSystem qCs = query.getCoordinateSystem();
CoordinateReferenceSystem qCsr = query.getCoordinateSystemReproject();
/*
* Here we create all the needed transformations. We assume for the
* moment that the data stores are incapable of any kind of cs
* transformation and neither capable of forcing cs. We also assume that
* concatenating multiple forced and reprojected wrappers is inexpensive
* since they are optimized to recognize each other and to avoid useless
* object creation
*/
try {
if (qCsr != null && cCsr != null) {
if (cCs != null) results = new ForceCoordinateSystemFeatureResults(results, cCs);
results = new ReprojectFeatureResults(results, cCsr);
if (qCs != null) results = new ForceCoordinateSystemFeatureResults(results, qCs);
results = new ReprojectFeatureResults(results, qCsr);
} else if (qCs != null && cCsr != null) {
// complex case 2, reprojected then forced
// mergedQuery.setCoordinateSystem(cCs);
// mergedQuery.setCoordinateSystemReproject(cCsr);
try {
if (cCs != null)
results = new ForceCoordinateSystemFeatureResults(results, cCs);
results = new ReprojectFeatureResults(source.getFeatures(mergedQuery), cCsr);
results = new ForceCoordinateSystemFeatureResults(results, qCs);
} catch (SchemaException e) {
throw new DataSourceException("This should not happen", e);
}
} else {
// easy case, we can just put toghether one forced cs and one
// reprojection cs
// in the mixed query and let it go
// mergedQuery.setCoordinateSystem(qCs != null ? qCs : cCs);
// mergedQuery.setCoordinateSystemReproject(qCsr != null ? qCsr
// : cCsr);
CoordinateReferenceSystem forcedCS = qCs != null ? qCs : cCs;
CoordinateReferenceSystem reprojectCS = qCsr != null ? qCsr : cCsr;
if (forcedCS != null)
results = new ForceCoordinateSystemFeatureResults(results, forcedCS);
if (reprojectCS != null)
results = new ReprojectFeatureResults(results, reprojectCS);
}
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new DataSourceException(
"A problem occurred while handling forced "
+ "coordinate systems and reprojection",
e);
}
return results;
}
/**
* Implement getFeatures.
*
*
Description ...
*/
public SimpleFeatureCollection getFeatures(Filter filter) throws IOException {
return getFeatures(new Query(schema.getTypeName(), filter));
}
/**
* Implement getFeatures.
*
*
Description ...
*
* @see org.geotools.data.FeatureSource#getFeatures()
*/
public SimpleFeatureCollection getFeatures() throws IOException {
return getFeatures(Query.ALL);
}
/**
* Implement getSchema.
*
*
Description ...
*
* @return @see org.geotools.data.FeatureSource#getSchema()
*/
public SimpleFeatureType getSchema() {
return schema;
}
public ResourceInfo getInfo() {
return new ResourceInfo() {
final Set words = new HashSet();
{
words.add("features");
words.add("view");
words.add(DefaultView.this.getSchema().getTypeName());
}
public ReferencedEnvelope getBounds() {
try {
return DefaultView.this.getBounds();
} catch (IOException e) {
return null;
}
}
public CoordinateReferenceSystem getCRS() {
return DefaultView.this.getSchema().getCoordinateReferenceSystem();
}
public String getDescription() {
return null;
}
public Set getKeywords() {
return words;
}
public String getName() {
return DefaultView.this.getSchema().getTypeName();
}
public URI getSchema() {
Name name = DefaultView.this.getSchema().getName();
URI namespace;
try {
namespace = new URI(name.getNamespaceURI());
return namespace;
} catch (URISyntaxException e) {
return null;
}
}
public String getTitle() {
Name name = DefaultView.this.getSchema().getName();
return name.getLocalPart();
}
};
}
/**
* Retrieves the total extent of this FeatureSource.
*
* Please note this extent will reflect the provided definitionQuery.
*
* @return Extent of this FeatureSource, or null
if no optimizations exist.
* @throws IOException If bounds of definitionQuery
*/
public ReferencedEnvelope getBounds() throws IOException {
if (constraintQuery.getCoordinateSystemReproject() == null) {
if (constraintQuery.getFilter() == null
|| constraintQuery.getFilter() == Filter.INCLUDE
|| Filter.INCLUDE.equals(constraintQuery.getFilter())) {
return source.getBounds();
}
return source.getBounds(constraintQuery);
}
// this will create a feature results that can reproject the
// features, and will
// properly compute the bouds
return getFeatures().getBounds();
}
/**
* Retrive the extent of the Query.
*
*
This method provides access to an optimized getBounds opperation. If no optimized
* opperation is available null
will be returned.
*
*
You may still make use of getFeatures( Query ).getCount() which will return the correct
* answer (even if it has to itterate through all the results to do so.
*
* @param query User's query
* @return Extend of Query or null
if no optimization is available
* @throws IOException If a problem is encountered with source
*/
public ReferencedEnvelope getBounds(Query query) throws IOException {
if (constraintQuery.getCoordinateSystemReproject() == null) {
try {
query = makeDefinitionQuery(query);
} catch (IOException ex) {
return null;
}
return source.getBounds(query);
}
// this will create a feature results that can reproject the
// features, and will
// properly compute the bouds
return getFeatures(query).getBounds();
}
/**
* Adjust query and forward to source.
*
*
This method provides access to an optimized getCount opperation. If no optimized
* opperation is available -1
will be returned.
*
*
You may still make use of getFeatures( Query ).getCount() which will return the correct
* answer (even if it has to itterate through all the results to do so).
*
* @param query User's query.
* @return Number of Features for Query, or -1 if no optimization is available.
*/
public int getCount(Query query) {
try {
query = makeDefinitionQuery(query);
} catch (IOException ex) {
return -1;
}
try {
return source.getCount(query);
} catch (IOException e) {
return 0;
}
}
public Set getSupportedHints() {
return source.getSupportedHints();
}
public QueryCapabilities getQueryCapabilities() {
return source.getQueryCapabilities();
}
}