Package org.geoserver.wms.featureinfo

Source Code of org.geoserver.wms.featureinfo.VectorRenderingLayerIdentifier$FeatureInfoFeatureSource

/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 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.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageTypeSpecifier;

import org.geoserver.catalog.LayerInfo;
import org.geoserver.platform.ExtensionPriority;
import org.geoserver.platform.ServiceException;
import org.geoserver.security.decorators.DecoratingFeatureSource;
import org.geoserver.wms.FeatureInfoRequestParameters;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.MapLayerInfo;
import org.geoserver.wms.RenderingVariables;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.map.RenderedImageMapOutputFormat;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.QueryCapabilities;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.FeatureLayer;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.renderer.RenderListener;
import org.geotools.renderer.lite.GraphicsAwareDpiRescaleStyleVisitor;
import org.geotools.renderer.lite.MetaBufferEstimator;
import org.geotools.renderer.lite.RendererUtilities;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.Rule;
import org.geotools.styling.Style;
import org.geotools.styling.StyleAttributeExtractor;
import org.geotools.styling.visitor.DpiRescaleStyleVisitor;
import org.geotools.styling.visitor.UomRescaleStyleVisitor;
import org.geotools.util.logging.Logging;
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.FeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.spatial.BBOX;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.Envelope;

/**
* Painting based layer identifier: this method actually paints a reduced version of the map to find
* out which features really intercept the clicked point
*
* @author Andrea Aime - GeoSolutions
*
*/
public class VectorRenderingLayerIdentifier extends AbstractVectorLayerIdentifier implements
        ExtensionPriority {

    static final Logger LOGGER = Logging.getLogger(VectorRenderingLayerIdentifier.class);
    private static final String FEAUTURE_INFO_RENDERING_ENABLED_KEY = "org.geoserver.wms.featureinfo.render.enabled";
    protected static final int MIN_BUFFER_SIZE = Integer.getInteger("org.geoserver.wms.featureinfo.render.minBuffer", 3);
    protected static boolean RENDERING_FEATUREINFO_ENABLED;
   
    private WMS wms;
    private VectorBasicLayerIdentifier fallback;
    private static final FilterFactory2 FF = CommonFactoryFinder.getFilterFactory2();
   
    static {
        String value = System.getProperty(FEAUTURE_INFO_RENDERING_ENABLED_KEY, "true");
        RENDERING_FEATUREINFO_ENABLED = Boolean.valueOf(value);
        if(!RENDERING_FEATUREINFO_ENABLED) {
            LOGGER.info("Rendering based GetFeatureInfo disabled since " + FEAUTURE_INFO_RENDERING_ENABLED_KEY + " is set to " + value);
        }
    }

    public VectorRenderingLayerIdentifier(WMS wms, VectorBasicLayerIdentifier fallback) {
        this.wms = wms;
        this.fallback = fallback;
    }
   
    @Override
    public boolean canHandle(MapLayerInfo layer) {
        // selectively disable based on system settings
        if(!RENDERING_FEATUREINFO_ENABLED) {
            return false;
        }
       
        return super.canHandle(layer);
    }

    @Override
    public List<FeatureCollection> identify(FeatureInfoRequestParameters params,
            final int maxFeatures) throws Exception {
        LOGGER.log(Level.FINER, "Appliying rendering based feature info identifier");
       
        // at the moment the new identifier works only with simple features due to a limitation
        // in the StreamingRenderer
        if(!(params.getLayer().getFeatureSource(true).getSchema() instanceof SimpleFeatureType)) {
            return fallback.identify(params, maxFeatures);
        }
       
        final Style style = preprocessStyle(params.getStyle(), params.getLayer().getFeature().getFeatureType());
        final int userBuffer = params.getBuffer() > 0 ? params.getBuffer() : MIN_BUFFER_SIZE;
        final int buffer = Math.min(userBuffer, wms.getMaxBuffer());

        // check the style to see what's active
        final List<Rule> rules = getActiveRules(style, params.getScaleDenominator());
        if (rules.size() == 0) {
            return null;
        }
        GetMapRequest getMap = params.getGetMapRequest();
        WMSMapContent mc = new WMSMapContent(getMap);
        try {
            // prepare the fake web map content
            mc.setTransparent(true);
            mc.setBuffer(params.getBuffer());
            mc.getViewport().setBounds(new ReferencedEnvelope(getMap.getBbox(), getMap.getCrs()));
            mc.setMapWidth(getMap.getWidth());
            mc.setMapHeight(getMap.getHeight());
            FeatureLayer layer = getLayer(params, style);
            mc.addLayer(layer);
            // setup the env variables just like in the original GetMap
            RenderingVariables.setupEnvironmentVariables(mc);
           
            // setup the transformation from screen to world space
            AffineTransform worldToScreen = RendererUtilities.worldToScreenTransform(
                    params.getRequestedBounds(), new Rectangle(params.getWidth(), params.getHeight()));
            AffineTransform screenToWorld = worldToScreen.createInverse();
           
            // apply uom rescale on the rules
            rescaleRules(rules, params);
           
            // setup the area we are actually going to paint
            int radius = getSearchRadius(params, rules, layer, getMap, screenToWorld);
            if(radius < buffer) {
                radius = buffer;
            }
            Envelope targetRasterSpace = new Envelope(params.getX() - radius, params.getX() + radius,
                    params.getY() - radius, params.getY() + radius);
            Envelope targetModelSpace = JTS.transform(targetRasterSpace, new AffineTransform2D(screenToWorld));
           
            // prepare the image we are going to check rendering against
            int paintAreaSize = radius * 2 + 1;
            final BufferedImage image = ImageTypeSpecifier.createFromBufferedImageType(
                    BufferedImage.TYPE_INT_ARGB).createBufferedImage(paintAreaSize,
                    paintAreaSize);
            image.setAccelerationPriority(0);
   
            // and now the listener that will check for painted pixels
            int mid = radius;
            int hitAreaSize = buffer * 2 + 1;
            Rectangle hitArea = new Rectangle(mid - buffer, mid - buffer, hitAreaSize, hitAreaSize);
            final FeatureInfoRenderListener featureInfoListener = new FeatureInfoRenderListener(
                    image, hitArea, maxFeatures, params.getPropertyNames());

            // update the map context
            mc.getViewport().setBounds(new ReferencedEnvelope(targetModelSpace, getMap.getCrs()));
            mc.setMapWidth(paintAreaSize);
            mc.setMapHeight(paintAreaSize);
           
            // and now run the rendering _almost_ like a GetMap
            RenderedImageMapOutputFormat rim = new RenderedImageMapOutputFormat(wms) {
   
                private Graphics2D graphics;

                @Override
                protected RenderedImage prepareImage(int width, int height, IndexColorModel palette,
                        boolean transparent) {
                    return image;
                }

                @Override
                protected Graphics2D getGraphics(boolean transparent, Color bgColor,
                        RenderedImage preparedImage, Map<Key, Object> hintsMap) {
                    graphics = super.getGraphics(transparent, bgColor, preparedImage,
                            hintsMap);
                    return graphics;
                }
   
                @Override
                protected void onBeforeRender(StreamingRenderer renderer) {
                    // force the renderer into serial painting mode, as we need to check what
                    // was painted to decide which features to include in the results
                    Map hints = renderer.getRendererHints();
                    hints.put(StreamingRenderer.OPTIMIZE_FTS_RENDERING_KEY, Boolean.FALSE);
                    // disable antialiasing to speed up rendering
                    hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
   
                    // TODO: should we disable the screenmap as well?
                    featureInfoListener.setGraphics(graphics);
                    featureInfoListener.setRenderer(renderer);
                    renderer.addRenderListener(featureInfoListener);
                }
            };
            rim.produceMap(mc);
           
            List<SimpleFeature> features = featureInfoListener.getFeatures();
            return aggregateByFeatureType(features);
        } finally {
            mc.dispose();
        }
    }

    private void rescaleRules(List<Rule> rules, FeatureInfoRequestParameters params) {
        Map<Object, Object> rendererParams = new HashMap<Object, Object>();
        Integer requestedDpi = ((Integer) params.getGetMapRequest().getFormatOptions().get("dpi"));
        if(requestedDpi != null) {
            rendererParams.put(StreamingRenderer.DPI_KEY, requestedDpi);
        }
       
        // apply dpi rescale if necessary
        double standardDpi = RendererUtilities.getDpi(rendererParams);
        if(requestedDpi != null && standardDpi != requestedDpi) {
            double scaleFactor = requestedDpi / standardDpi;
            DpiRescaleStyleVisitor dpiVisitor = new GraphicsAwareDpiRescaleStyleVisitor(scaleFactor);
            for (int i = 0; i < rules.size(); i++) {
                rules.get(i).accept(dpiVisitor);
                Rule rescaled = (Rule) dpiVisitor.getCopy();
                rules.set(i, rescaled);
            }
        }

        // apply UOM rescaling
        double pixelsPerMeters = RendererUtilities.calculatePixelsPerMeterRatio(params.getScaleDenominator(), rendererParams);
        UomRescaleStyleVisitor uomVisitor = new UomRescaleStyleVisitor(pixelsPerMeters);
        for (int i = 0; i < rules.size(); i++) {
            rules.get(i).accept(uomVisitor);
            Rule rescaled = (Rule) uomVisitor.getCopy();
            rules.set(i, rescaled);
        }
    }

    private Style preprocessStyle(Style style, FeatureType schema) {
        FeatureInfoStylePreprocessor preprocessor = new FeatureInfoStylePreprocessor(schema);
        style.accept(preprocessor);
        Style result = (Style) preprocessor.getCopy();
       
        return result;
    }

    private List<FeatureCollection> aggregateByFeatureType(List<? extends Feature> features) {
        // group by feature type (rendering transformations might cause us to get more
        // than one type from the original layer)
        Map<FeatureType, List<Feature>> map = new HashMap<FeatureType, List<Feature>>();
        for (Feature f : features) {
            FeatureType type = f.getType();
            List<Feature> list = map.get(type);
            if (list == null) {
                list = new ArrayList<Feature>();
                map.put(type, list);
            }
            list.add(f);
        }

        // build a feature collection for each group
        List<FeatureCollection> result = new ArrayList<FeatureCollection>();
        for (Map.Entry<FeatureType, List<Feature>> entry : map.entrySet()) {
            FeatureType type = entry.getKey();
            List<Feature> list = entry.getValue();
            if(type instanceof SimpleFeatureType) {
                result.add(new ListFeatureCollection((SimpleFeatureType) type, new ArrayList<SimpleFeature>((List) list)));
            } else {
                result.add(new ListComplexFeatureCollection(type, list));
            }
        }
       
        return result;
    }

    private FeatureLayer getLayer(FeatureInfoRequestParameters params, Style style) throws IOException {
        // build the full filter
        List<Object> times = params.getTimes();
        List<Object> elevations = params.getElevations();
        Filter layerFilter = params.getFilter();
        MapLayerInfo layer = params.getLayer();
        Filter dimensionFilter = wms.getTimeElevationToFilter(times, elevations, layer.getFeature());
        Filter filter;
        if(layerFilter == null) {
            filter = dimensionFilter;
        } else if(dimensionFilter == null) {
            filter = layerFilter;
        } else {
            filter = FF.and(Arrays.asList(layerFilter, dimensionFilter));
        }

        GetMapRequest getMap = params.getGetMapRequest();
        FeatureSource<? extends FeatureType, ? extends Feature> featureSource = layer
                .getFeatureSource(true);
        final Query definitionQuery = new Query(featureSource.getSchema().getName().getLocalPart());
        definitionQuery.setVersion(getMap.getFeatureVersion());
        definitionQuery.setFilter(filter);
        Map<String, String> viewParams = params.getViewParams();
        if (viewParams != null) {
            definitionQuery.setHints(new Hints(Hints.VIRTUAL_TABLE_PARAMETERS, viewParams));
        }

        // check for startIndex + offset
        final Integer startIndex = getMap.getStartIndex();
        if (startIndex != null) {
            QueryCapabilities queryCapabilities = featureSource.getQueryCapabilities();
            if (queryCapabilities.isOffsetSupported()) {
                // fsource is required to support
                // SortBy.NATURAL_ORDER so we don't bother checking
                definitionQuery.setStartIndex(startIndex);
            } else {
                // source = new PagingFeatureSource(source,
                // request.getStartIndex(), limit);
                throw new ServiceException("startIndex is not supported for the " + layer.getName()
                        + " layer");
            }
        }

        int maxFeatures = getMap.getMaxFeatures() != null ? getMap.getMaxFeatures()
                : Integer.MAX_VALUE;
        definitionQuery.setMaxFeatures(maxFeatures);

        FeatureLayer result = new FeatureLayer(new FeatureInfoFeatureSource(featureSource,
                params.getPropertyNames()), style);
        result.setQuery(definitionQuery);

        return result;
    }

    private int getSearchRadius(FeatureInfoRequestParameters params, List<Rule> rules, FeatureLayer layer, GetMapRequest getMap, AffineTransform screenToWorld) throws TransformException, FactoryException, IOException {
        // is it part of the request params?
        int requestBuffer = params.getBuffer();
        if(requestBuffer > 0) {
            return (int) Math.ceil(requestBuffer / 2.0);
        }
       
        // was it manually configured?
        Integer layerBuffer = null;
        final LayerInfo layerInfo = params.getLayer().getLayerInfo();
        if (layerInfo != null) {
            // it is a local layer
            layerBuffer = layerInfo.getMetadata().get(LayerInfo.BUFFER, Integer.class);
        }
        if (layerBuffer != null && layerBuffer > 0) {
            return (int) Math.round(layerBuffer / 2.0);
        }
       
        // estimate the radius given the currently active rules
        MetaBufferEstimator estimator = new MetaBufferEstimator();
        for (Rule rule : rules) {
            rule.accept(estimator);
        }

        // easy case, the style is static, we can just use size computed from the style
        int estimatedRadius = estimator.getBuffer() / 2;
        if (estimator.isEstimateAccurate()) {
            if (estimatedRadius < MIN_BUFFER_SIZE) {
                return MIN_BUFFER_SIZE;
            } else {
                return estimatedRadius;
            }
        } else {
            // ok, so we have an estimate based on the static portion of the style,
            // let's extract the dynamic one
            DynamicSizeStyleExtractor extractor = new DynamicSizeStyleExtractor();
            final List<Rule> dynamicRules = new ArrayList<Rule>();
            for (Rule rule : rules) {
                rule.accept(extractor);
                Rule copy = (Rule) extractor.getCopy();
                if(copy != null) {
                    dynamicRules.add(copy);
                }
            }
           
            // this can happen, the meta buffer estimator can get tripped by
            // graphic fills using dynamic sizes for their strokes
            if(dynamicRules.size() == 0) {
                if(LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.fine("No dynamic rules found, even if the estimator initially though so, "
                            + "using the static analysis result: " + estimatedRadius);
                }
                return estimatedRadius;
            }
           
            // TODO: verify what expressions are used, if they are simple links to attributes
            // or direct proportionalities we could just compute the max value of the fields
            // involved
            FeatureSource<?, ?> fs = layer.getFeatureSource();
            Envelope targetRasterSpace = new Envelope(-estimatedRadius, params.getWidth() + estimatedRadius,
                    - estimatedRadius, params.getWidth() + estimatedRadius);
            Envelope expanded = JTS.transform(targetRasterSpace, new AffineTransform2D(screenToWorld));
            ReferencedEnvelope renderingBBOX = new ReferencedEnvelope(expanded, getMap.getCrs());
            ReferencedEnvelope queryBBOX = renderingBBOX.transform(fs.getSchema().getCoordinateReferenceSystem(), true);
           
            // setup the query
            Query query = layer.getQuery();
            BBOX bbox = FF.bbox(FF.property(""), queryBBOX);
            if(query.getFilter() == null || query.getFilter() == Filter.INCLUDE) {
                query.setFilter(bbox);
            } else {
                Filter and = FF.and(query.getFilter(), bbox);
                query.setFilter(and);
            }
            String[] dynamicProperties = getDynamicProperties(dynamicRules);
            query.setPropertyNames(dynamicProperties);
           
            // visit all features and evaluate buffer size
            final DynamicBufferEstimator dbe = new DynamicBufferEstimator();
            fs.getFeatures(query).accepts(new FeatureVisitor() {
               
                @Override
                public void visit(Feature feature) {
                    dbe.setFeature(feature);
                    for (Rule rule : dynamicRules) {
                        rule.accept(dbe);
                    }
                   
                }
            }, null);
           
            int dynamicBuffer = dbe.getBuffer();
            return Math.max(dynamicBuffer / 2, estimatedRadius);
        }
    }

    private String[] getDynamicProperties(List<Rule> dynamicRules) {
        StyleAttributeExtractor extractor = new StyleAttributeExtractor();
        for (Rule rule : dynamicRules) {
            rule.accept(extractor);
        }
       
        return extractor.getAttributeNames();
    }

    /**
     * Returns a priority higher than the default, but still allows for overrides
     */
    @Override
    public int getPriority() {
        return (ExtensionPriority.LOWEST + ExtensionPriority.HIGHEST) / 2;
    }

    /**
     * Checks if the features just rendered hit the target area, and collects them.
     * Stops the rendering once enough features are collected
     *
     * @author Andrea Aime - GeoSolutions
     */
    static final class FeatureInfoRenderListener implements RenderListener {
        private final int scanlineStride;

        private Rectangle hitArea;

        List<SimpleFeature> features = new ArrayList<SimpleFeature>();

        String[] propertyNames;

        SimpleFeatureBuilder retypeBuilder;

        private int maxFeatures;
       
        ColorModel cm;
       
        BufferedImage bi;
       
        StreamingRenderer renderer;
       
        Feature previous;

        Graphics2D graphics;

        public FeatureInfoRenderListener(BufferedImage bi, Rectangle hitArea, int maxFeatures,
                String[] propertyNames) {
            verifyColorModel(bi);
            Raster raster = getRaster(bi);
            this.scanlineStride = raster.getDataBuffer().getSize() / raster.getHeight();
            this.hitArea = hitArea;
            this.maxFeatures = maxFeatures;
            this.cm = bi.getColorModel();
            this.bi = bi;
        }

        public void setGraphics(Graphics2D graphics) {
            this.graphics = graphics;
        }

        public void setRenderer(StreamingRenderer renderer) {
            this.renderer = renderer;
        }

        public List<SimpleFeature> getFeatures() {
            return features;
        }

        private void verifyColorModel(BufferedImage bi) {
            ColorModel cm = bi.getColorModel();
            if (!(cm instanceof DirectColorModel)) {
                throw new IllegalArgumentException(
                        "Invalid color model, it should be a DirectColorModel");
            }
            DirectColorModel dcm = (DirectColorModel) cm;
            if (dcm.getNumColorComponents() != 3 || !dcm.hasAlpha()) {
                throw new IllegalArgumentException(
                        "Invalid color model, it should be a 3 bands DirectColorModel with alpha");
            }
        }

        private Raster getRaster(BufferedImage image) {
            // in case the raster has a parent, this is likely a subimage, we have to force
            // a copy of the raster to get a data buffer we can scroll over without issues
            Raster raster = image.getRaster();
            if (raster.getParent() != null) {
                throw new IllegalArgumentException(
                        "The provided raster is a child of another image");
            } else {
                return raster;
            }
        }

        @Override
        public void featureRenderer(SimpleFeature feature) {
            // TODO: handle the case the feature became a grid due to rendering transformations?
           
            // feature caught by more than one rule?
            if(feature == previous) {
                // clean the hit area anyways before returning, as the feature might
                // have been rendered twice in a row coloring the hit area twice
                cleanHitArea();
                return;
            }
           
            // note: we need to extract the raster here, caching it will make us
            // get the old version of it if hw acceleration kicks in
            Raster raster = getRaster(bi);
            int[] pixels = ((java.awt.image.DataBufferInt) raster.getDataBuffer()).getData();
           
            // scan and clean the hit area, bail out early if we find a hit
            boolean hit = false;
            for (int row = hitArea.y; row < (hitArea.y + hitArea.height) && !hit; row++) {
                int idx = row * scanlineStride + hitArea.x;
                for (int col = hitArea.x; col < (hitArea.x + hitArea.width) && !hit; col++) {
                    final int color = pixels[idx];
                    final int alpha = cm.getAlpha(color);
                    if (!hit && alpha > 0) {
                        hit = true;
                    }
                    idx++;
                }
            }
           
            if (hit) {
                previous = feature;
                if(features.size() < maxFeatures) {
                    SimpleFeature retyped = retype(feature);
                    features.add(retyped);
                } else {
                    // we're done, stop rendering
                    renderer.stopRendering();
                }
            }

            // clean the hit area to prepare for next feature
            cleanHitArea();
        }

        private SimpleFeature retype(SimpleFeature feature) {
            if (propertyNames == null) {
                return feature;
            } else {
                if (retypeBuilder == null) {
                    SimpleFeatureType targetType = SimpleFeatureTypeBuilder.retype(
                            feature.getFeatureType(), propertyNames);
                    retypeBuilder = new SimpleFeatureBuilder(targetType);
                }
                return SimpleFeatureBuilder.retype(feature, retypeBuilder);
            }
        }

        private void cleanHitArea() {
            Composite oldComposite = graphics.getComposite();
            graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
            graphics.setColor(new Color(0, true));
            graphics.fillRect(hitArea.x, hitArea.y, hitArea.width, hitArea.height);
            graphics.setComposite(oldComposite);
        }

        @Override
        public void errorOccurred(Exception e) {
            // nothing to do here, there are other listeners handling this
        }

    }
   
    /**
     * A tiny wrapper that forces the attributes needed by getfeatureinfo to be returned: the
     * renderer normally tries to get only the attributes it needs for performance reasons
     *
     * @author Andrea Aime - GeoSolutions
     *
     * @param <T>
     * @param <F>
     */
    static class FeatureInfoFeatureSource extends DecoratingFeatureSource<FeatureType, Feature> {

        String[] propertyNames;

        public FeatureInfoFeatureSource(FeatureSource delegate, String[] propertyNames) {
            super(delegate);
            this.propertyNames = propertyNames;
        }
       
        @Override
        public FeatureCollection getFeatures(Query query) throws IOException {
            Query q = new Query(query);
            // we made the renderer believe we support the screenmap, but we don't want
            // it really be applied, so remove it
            if(query.getHints() != null) {
                Hints newHints = new Hints(query.getHints());
                newHints.remove(Hints.SCREENMAP);
                q.setHints(newHints);
            }
            if (propertyNames == null || propertyNames.length == 0) {
                // no property selection, we return them all
                q.setProperties(Query.ALL_PROPERTIES);
            } else {
                // properties got selected, mix them with the ones needed by the renderer
                if (query.getPropertyNames() == null || query.getPropertyNames().length == 0) {
                    q.setPropertyNames(propertyNames);
                } else {
                    Set<String> names = new LinkedHashSet<>(Arrays.asList(propertyNames));
                    names.addAll(Arrays.asList(q.getPropertyNames()));
                    String[] newNames = names.toArray(new String[names.size()]);
                    q.setPropertyNames(newNames);
                }
            }
            return super.getFeatures(q);
        }
       
        @Override
        public Set<Key> getSupportedHints() {
            // force cloning, and make streaming renderer believe we do support
            // the screenmap
            Set<Key> hints = delegate.getSupportedHints();
            Set<Key> result;
            if(hints == null) {
                result = new HashSet<RenderingHints.Key>();
            } else {
                result = new HashSet<RenderingHints.Key>(hints);
            }
            result.remove(Hints.FEATURE_DETACHED);
            result.add(Hints.SCREENMAP);
            return result;
        }
       
    }

}
TOP

Related Classes of org.geoserver.wms.featureinfo.VectorRenderingLayerIdentifier$FeatureInfoFeatureSource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.