/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.featureinfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.FeatureInfoRequestParameters;
import org.geoserver.wms.MapLayerInfo;
import org.geoserver.wms.WMS;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.store.FilteringFeatureCollection;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.filter.Filters;
import org.geotools.filter.IllegalFilterException;
import org.geotools.filter.visitor.SimplifyingFilterVisitor;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.renderer.lite.MetaBufferEstimator;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Rule;
import org.geotools.styling.Style;
import org.geotools.util.logging.Logging;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.Or;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Polygon;
/**
* An identifier for vector layers that will take into account the filters, viewparams, styles and
* build a bbox (plus extra filters) query against the database
*
* @author Andrea Aime - GeoSolutions
*/
public class VectorBasicLayerIdentifier extends AbstractVectorLayerIdentifier {
static final Logger LOGGER = Logging.getLogger(VectorBasicLayerIdentifier.class);
protected static final int MIN_BUFFER_SIZE = Integer.getInteger("org.geoserver.wms.featureinfo.minBuffer", 5);
private WMS wms;
public VectorBasicLayerIdentifier(final WMS wms) {
this.wms = wms;
}
public List<FeatureCollection> identify(FeatureInfoRequestParameters params, int maxFeatures) throws Exception {
LOGGER.log(Level.FINER, "Appliying bbox based feature info identifier");
final MapLayerInfo layer = params.getLayer();
final Filter filter = params.getFilter();
final Style style = params.getStyle();
// ok, internally rendered layer then, we check the style to see what's active
final List<Rule> rules = getActiveRules(style, params.getScaleDenominator());
if (rules.size() == 0) {
return null;
}
// compute the request radius
double radius = getSearchRadius(params, layer, rules);
// compute the bbox for the request
ReferencedEnvelope queryEnvelope = getEnvelopeFilter(params, radius);
CoordinateReferenceSystem requestedCRS = params.getRequestedCRS();
CoordinateReferenceSystem dataCRS = layer.getCoordinateReferenceSystem();
if ((requestedCRS != null) && !CRS.equalsIgnoreMetadata(dataCRS, requestedCRS)) {
if (dataCRS.getCoordinateSystem().getDimension() == 3
&& requestedCRS.getCoordinateSystem().getDimension() == 2) {
queryEnvelope = JTS.transformTo3D(queryEnvelope, dataCRS, true, 10);
} else {
queryEnvelope = queryEnvelope.transform(dataCRS, true);
}
}
final FeatureSource<? extends FeatureType, ? extends Feature> featureSource;
featureSource = layer.getFeatureSource(false);
FeatureType schema = featureSource.getSchema();
Filter getFInfoFilter = null;
FilterFactory2 ff = params.getFilterFactory();
try {
GeometryDescriptor geometryDescriptor = schema.getGeometryDescriptor();
String localName = geometryDescriptor.getLocalName();
Polygon queryPolygon = JTS.toGeometry(queryEnvelope);
getFInfoFilter = ff.intersects(ff.property(localName), ff.literal(queryPolygon));
} catch (IllegalFilterException e) {
e.printStackTrace();
throw new ServiceException("Internal error : " + e.getMessage(), e);
}
// include the eventual layer definition filter
if (filter != null) {
getFInfoFilter = ff.and(getFInfoFilter, filter);
}
// see if we can include the rule filters as well, if too many we'll do them in
// memory
Filter postFilter = Filter.INCLUDE;
Filter rulesFilters = buildRulesFilter(ff, rules);
if (!(featureSource.getSchema() instanceof SimpleFeatureType)
|| !(rulesFilters instanceof Or)
|| (rulesFilters instanceof Or && ((Or) rulesFilters).getChildren().size() <= 20)) {
getFInfoFilter = ff.and(getFInfoFilter, rulesFilters);
} else {
postFilter = rulesFilters;
}
// handle time/elevation
Filter timeElevationFilter = wms.getTimeElevationToFilter(params.getTimes(),
params.getElevations(), layer.getFeature());
getFInfoFilter = Filters.and(ff, getFInfoFilter, timeElevationFilter);
// simplify the filter
SimplifyingFilterVisitor simplifier = new SimplifyingFilterVisitor();
getFInfoFilter = (Filter) getFInfoFilter.accept(simplifier, null);
// build the query
String typeName = schema.getName().getLocalPart();
Query q = new Query(typeName, null, getFInfoFilter, maxFeatures, params.getPropertyNames(),
null);
// handle sql view params
final Map<String, String> viewParams = params.getViewParams();
if (viewParams != null && viewParams.size() > 0) {
q.setHints(new Hints(Hints.VIRTUAL_TABLE_PARAMETERS, viewParams));
}
FeatureCollection match;
System.out.println(q);
match = featureSource.getFeatures(q);
// if we could not include the rules filter into the query, post process in
// memory
if (!Filter.INCLUDE.equals(postFilter)) {
match = new FilteringFeatureCollection(match, postFilter);
}
return Collections.singletonList(match);
}
private double getSearchRadius(FeatureInfoRequestParameters params, final MapLayerInfo layer,
final List<Rule> rules) {
double radius;
int buffer = params.getBuffer();
if (buffer <= 0) {
Integer layerBuffer = null;
final LayerInfo layerInfo = layer.getLayerInfo();
if (layerInfo != null) { // it is a local layer
layerBuffer = layerInfo.getMetadata().get(LayerInfo.BUFFER, Integer.class);
}
if (layerBuffer != null && layerBuffer > 0) {
radius = layerBuffer / 2.0;
} else {
// estimate the radius given the currently active rules
MetaBufferEstimator estimator = new MetaBufferEstimator();
for (Rule rule : rules) {
rule.accept(estimator);
}
int estimatedRadius = estimator.getBuffer() / 2;
if (estimatedRadius < MIN_BUFFER_SIZE) {
radius = MIN_BUFFER_SIZE;
} else {
radius = estimatedRadius;
}
}
} else {
radius = buffer;
}
// make sure we don't go overboard, the admin might have set a maximum
int maxRadius = wms.getMaxBuffer();
if (maxRadius > 0 && radius > maxRadius) {
radius = maxRadius;
}
return radius;
}
private Filter buildRulesFilter(org.opengis.filter.FilterFactory ff, List<Rule> rules) {
// build up a or of all the rule filters
List<Filter> filters = new ArrayList<Filter>();
for (Rule rule : rules) {
if (rule.getFilter() == null || rule.isElseFilter())
return Filter.INCLUDE;
filters.add(rule.getFilter());
}
// not or and and simplify (if there is any include/exclude we'll get
// a very simple result ;-)
Filter or = ff.or(filters);
SimplifyingFilterVisitor simplifier = new SimplifyingFilterVisitor();
return (Filter) or.accept(simplifier, null);
}
private ReferencedEnvelope getEnvelopeFilter(FeatureInfoRequestParameters params, double radius) {
final int x = params.getX();
final int y = params.getY();
final ReferencedEnvelope bbox = params.getRequestedBounds();
final int width = params.getWidth();
final int height = params.getHeight();
Coordinate upperLeft = WMS.pixelToWorld(x - radius, y - radius, bbox, width, height);
Coordinate lowerRight = WMS.pixelToWorld(x + radius, y + radius, bbox, width, height);
return new ReferencedEnvelope(upperLeft.x, lowerRight.x, lowerRight.y, upperLeft.y,
bbox.getCoordinateReferenceSystem());
}
}