/* Copyright (c) 2001 - 2008 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, available at the root
*/
package org.geoserver.filter.function;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.feature.FeatureIterator;
import org.geotools.filter.FunctionImpl;
import org.geotools.filter.capability.FunctionNameImpl;
import org.geotools.filter.text.ecql.ECQL;
import org.opengis.feature.Feature;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryComponentFilter;
/**
* Queries a GeoServer layer and extracts the value(s) of an attribute TODO: add sorting
*
* @author Andrea Aime - GeoSolutions
*/
public class QueryFunction extends FunctionImpl {
Catalog catalog;
int maxResults;
boolean single;
public QueryFunction(Name name, Catalog catalog, List<Expression> args, Literal fallback,
boolean single, int maxResults) {
this.catalog = catalog;
this.maxResults = maxResults;
this.single = single;
functionName = new FunctionNameImpl(name, args != null ? args.size() : -1);
setName(name.getLocalPart());
setFallbackValue(fallback);
setParameters(args);
if (args.size() < 3 || args.size() > 4) {
throw new IllegalArgumentException(
"QuerySingle function requires 3 or 4 arguments (feature type qualified name, "
+ "cql filter, extracted attribute name and sort by clause");
}
}
@Override
public Object evaluate(Object object) {
FeatureIterator fi = null;
try {
// extract layer
String layerName = getParameters().get(0).evaluate(object, String.class);
if (layerName == null) {
throw new IllegalArgumentException(
"The first argument should be a vector layer name");
}
FeatureTypeInfo ft = catalog.getFeatureTypeByName(layerName);
if (ft == null) {
throw new IllegalArgumentException("Could not find vector layer " + layerName
+ " in the GeoServer catalog");
}
// extract and check the attribute
String attribute = getParameters().get(1).evaluate(object, String.class);
if (attribute == null) {
throw new IllegalArgumentException("The second argument of the query "
+ "function should be the attribute name");
}
CoordinateReferenceSystem crs = null;
PropertyDescriptor ad = ft.getFeatureType().getDescriptor(attribute);
if (ad == null) {
throw new IllegalArgumentException("Attribute " + attribute
+ " could not be found in layer " + layerName);
} else if(ad instanceof GeometryDescriptor) {
crs = ((GeometryDescriptor) ad).getCoordinateReferenceSystem();
if(crs == null) {
crs = ft.getCRS();
}
}
// extract and check the filter
String cql = getParameters().get(2).evaluate(object, String.class);
if (cql == null) {
throw new IllegalArgumentException("The third argument of the query "
+ "function should be a valid (E)CQL filter");
}
Filter filter;
try {
filter = (Filter) ECQL.toFilter(cql);
} catch (Exception e) {
throw new IllegalArgumentException("The third argument of the query "
+ "function should be a valid (E)CQL filter", e);
}
// perform the query
Query query = new Query(null, filter, new String[] { attribute });
// .. just enough to judge if we went beyond the limit
query.setMaxFeatures(maxResults + 1);
FeatureSource fs = ft.getFeatureSource(null, null);
fi = fs.getFeatures(query).features();
List<Object> results = new ArrayList<Object>(maxResults);
while (fi.hasNext()) {
Feature f = fi.next();
Object value = f.getProperty(attribute).getValue();
if(value instanceof Geometry && crs != null) {
// if the crs is not associated with the geometry do so, this
// way other code will get to know the crs (e.g. for reprojection purposes)
Geometry geom = (Geometry) value;
geom.apply(new GeometryCRSFilter(crs));
}
results.add(value);
}
if (results.size() == 0) {
return null;
}
if (maxResults > 0 && results.size() > maxResults && !single) {
throw new IllegalStateException("The query in " + getName() + " returns too many "
+ "features, the limit is " + maxResults);
}
if (maxResults == 1) {
return results.get(0);
} else {
return results;
}
} catch (IOException e) {
throw new RuntimeException("Failed to evaluated the query: " + e.getMessage(), e);
} finally {
if (fi != null) {
fi.close();
}
}
}
/**
* Applies the CRS to all geometry components
* @author aaime
*
*/
static final class GeometryCRSFilter implements GeometryComponentFilter {
CoordinateReferenceSystem crs;
public GeometryCRSFilter(CoordinateReferenceSystem crs) {
this.crs = crs;
}
@Override
public void filter(Geometry g) {
g.setUserData(crs);
}
}
}