Package org.geoserver.kml.utils

Source Code of org.geoserver.kml.utils.KMLFeatureAccessor

/* (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.kml.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.geoserver.catalog.Catalog;
import org.geoserver.kml.KmlEncodingContext;
import org.geoserver.kml.regionate.RegionatingStrategy;
import org.geoserver.kml.regionate.RegionatingStrategyFactory;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent;
import org.geotools.data.DataUtilities;
import org.geotools.data.Query;
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.filter.IllegalFilterException;
import org.geotools.filter.visitor.SimplifyingFilterVisitor;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.Layer;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.styling.Style;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.Envelope;

/**
* Class encapsulating the logic to query features for a given layer in the current GetMap KML
* request
*
* @author Andrea Aime - GeoSolutions
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
*/
public class KMLFeatureAccessor {

    static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.kml");

    /**
     * Factory used to create filter objects
     */
    private static FilterFactory filterFactory = (FilterFactory) CommonFactoryFinder
            .getFilterFactory(null);

    /**
     * Loads the feature collection based on the current styling and the scale denominator. If no
     * feature is going to be returned a null feature collection will be returned instead
     *
     * @param layer
     * @param mapContent
     * @param wms
     * @param scaleDenominator
     * @return
     * @throws Exception
     */
    public SimpleFeatureCollection loadFeatureCollection(Layer layer, WMSMapContent mapContent,
            WMS wms, double scaleDenominator) throws Exception {
        SimpleFeatureSource featureSource = (SimpleFeatureSource) layer.getFeatureSource();
        Query q = getFeaturesQuery(layer, mapContent, wms, scaleDenominator);

        // make sure we output in 4326 since that's what KML mandates
        CoordinateReferenceSystem wgs84;
        try {
            wgs84 = CRS.decode("EPSG:4326", true);
        } catch (Exception e) {
            throw new RuntimeException(
                    "Cannot decode EPSG:4326, the CRS subsystem must be badly broken...", e);
        }
        SimpleFeatureCollection features = featureSource.getFeatures(q);
        SimpleFeatureType schema = featureSource.getSchema();
        CoordinateReferenceSystem nativeCRS = schema.getCoordinateReferenceSystem();
        if (nativeCRS != null && !CRS.equalsIgnoreMetadata(wgs84, nativeCRS)) {
            features = new ReprojectFeatureResults(features, wgs84);
        }

        return features;
    }

    /**
     * Counts how many features will be returned for the specified layer in the current request
     *
     * @param layer
     * @param mapContent
     * @param wms
     * @param scaleDenominator
     * @return
     * @throws Exception
     */
    public int getFeatureCount(Layer layer, WMSMapContent mapContent, WMS wms,
            double scaleDenominator) throws Exception {
        Query q = getFeaturesQuery(layer, mapContent, wms, scaleDenominator);

        SimpleFeatureSource featureSource = (SimpleFeatureSource) layer.getFeatureSource();
        int count = featureSource.getCount(q);
        if (count == -1) {
            count = featureSource.getFeatures(q).size();
        }

        return count;
    }

    /**
     * Builds the Query object that will return the features for the specified layer and scale
     * denominator, based also on the current WMS configuration
     *
     * @param layer
     * @param mapContent
     * @param wms
     * @param scaleDenominator
     * @param schema
     * @return
     * @throws TransformException
     * @throws FactoryException
     */
    private Query getFeaturesQuery(Layer layer, WMSMapContent mapContent, WMS wms,
            double scaleDenominator) throws TransformException, FactoryException {
        SimpleFeatureType schema = ((SimpleFeatureSource) layer.getFeatureSource()).getSchema();

        // create a bbox filter in the source crs (from the GetMap request bbox)
        ReferencedEnvelope aoi = mapContent.getRenderingArea();
        Filter filter = createBBoxFilter(schema, aoi);

        // now build the query using only the attributes and the bounding box needed
        Query q = new Query(schema.getTypeName());
        q.setFilter(filter);

        // now, if a definition query has been established for this layer,
        // be sure to respect it by combining it with the bounding box one.
        q = DataUtilities.mixQueries(q, layer.getQuery(), "KMLEncoder");

        // check the regionating strategy
        RegionatingStrategy regionatingStrategy = null;
        String stratname = (String) mapContent.getRequest().getFormatOptions().get("regionateBy");
        if (("auto").equals(stratname)) {
            Catalog catalog = wms.getGeoServer().getCatalog();
            Name name = layer.getFeatureSource().getName();
            stratname = catalog.getFeatureTypeByName(name).getMetadata()
                    .get("kml.regionateStrategy", String.class);
            if (stratname == null || "".equals(stratname)) {
                stratname = "best_guess";
                LOGGER.log(Level.FINE, "No default regionating strategy has been configured in "
                        + name + "; using automatic best-guess strategy.");
            }
        }

        Filter regionatingFilter = Filter.INCLUDE;
        if (stratname != null) {
            regionatingStrategy = findStrategyByName(stratname);

            // if a strategy was specified but we did not find it, let the user
            // know
            if (regionatingStrategy == null) {
                throw new ServiceException("Unknown regionating strategy " + stratname);
            } else {
                regionatingFilter = regionatingStrategy.getFilter(mapContent, layer);
            }
        }

        // try to load less features by leveraging regionating strategy and the SLD
        Filter ruleFilter = getStyleFilter(schema, layer.getStyle(), scaleDenominator);
        Filter finalFilter = joinFilters(q.getFilter(), ruleFilter, regionatingFilter);
        if (finalFilter == Filter.EXCLUDE) {
            // if we don't have any feature to return
            return null;
        }
        q.setFilter(finalFilter);
        return q;
    }

    private RegionatingStrategy findStrategyByName(String name) {
        List<RegionatingStrategyFactory> factories = GeoServerExtensions
                .extensions(RegionatingStrategyFactory.class);
        Iterator<RegionatingStrategyFactory> it = factories.iterator();
        while (it.hasNext()) {
            RegionatingStrategyFactory factory = it.next();
            if (factory.canHandle(name)) {
                return factory.createStrategy();
            }
        }

        return null;
    }

    private Filter getStyleFilter(SimpleFeatureType schema, Style style, double scaleDenominator) {
        // first, simplify the style and get only the applicable rules
        ScaleStyleVisitor sdSimplifier = new ScaleStyleVisitor(scaleDenominator, schema);
        style.accept(sdSimplifier);
        Style simplified = sdSimplifier.getSimplifiedStyle();
        // then collect the filter equivalent to all the rules
        RuleFiltersCollector collector = new RuleFiltersCollector();
        simplified.accept(collector);
        return collector.getSummaryFilter();
    }

    /**
     * Creates the bounding box filters (one for each geometric attribute) needed to query a
     * <code>Layer</code>'s feature source to return just the features for the target rendering
     * extent
     *
     * @param schema the layer's feature source schema
     * @param bbox the expression holding the target rendering bounding box
     * @throws IllegalFilterException if something goes wrong creating the filter
     */
    private Filter createBBoxFilter(SimpleFeatureType schema, ReferencedEnvelope aoi)
            throws IllegalFilterException {
       
        // Google earth likes to make requests that go beyond 180 when zoomed out
        // fix them
        List<ReferencedEnvelope> envelopes = new ArrayList<ReferencedEnvelope>();
        if(KmlEncodingContext.WORLD_BOUNDS_WGS84.contains((Envelope) aoi)) {
            envelopes.add(aoi);
        } else {
            Envelope intersection = KmlEncodingContext.WORLD_BOUNDS_WGS84.intersection((Envelope) aoi);
            if(intersection.getWidth() > 0) {
                envelopes.add(new ReferencedEnvelope(intersection, DefaultGeographicCRS.WGS84));
            }
            // look for the portion beyond +180
            if(aoi.getMaxX() > 180) {
                // GE never sends values larger than 360
                double maxx = aoi.getMaxX() - 360;
                double minx = aoi.getMinX() > 180 ? aoi.getMinX() - 360 : -180;
                envelopes.add(new ReferencedEnvelope(minx, maxx, aoi.getMinY(),
                        aoi.getMaxY(), DefaultGeographicCRS.WGS84));
            }
        }
       
        List<ReferencedEnvelope> sourceEnvelopes = new ArrayList<ReferencedEnvelope>();
        CoordinateReferenceSystem sourceCrs = schema.getCoordinateReferenceSystem();
        if ((sourceCrs != null)
                && !CRS.equalsIgnoreMetadata(aoi.getCoordinateReferenceSystem(), sourceCrs)) {
            for (ReferencedEnvelope re : envelopes) {
                try {
                    ReferencedEnvelope se = re.transform(sourceCrs, true);
                    sourceEnvelopes.add(se);
                } catch(Exception e) {
                    // in case of failure it means we are going beyond the projectable area
                    // of the source system, meaning that we are asking for an area that's too
                    // large -> don't do spatial filtering then
                   
                    return Filter.INCLUDE;
                }
            }
        } else {
            sourceEnvelopes.addAll(envelopes);
        }
       
        GeometryDescriptor gd = schema.getGeometryDescriptor();
        if(sourceEnvelopes.size() == 0) {
            return Filter.INCLUDE;
        } else if(sourceEnvelopes.size() == 1) {
            ReferencedEnvelope se = sourceEnvelopes.get(0);
            return filterFactory.bbox(gd.getLocalName(), se.getMinX(),
                    se.getMinY(), se.getMaxX(), se.getMaxY(), null);
        } else {
            // we have to OR the multiple source envelopes
            List<Filter> filters = new ArrayList<Filter>();
            for (ReferencedEnvelope se : sourceEnvelopes) {
                filters.add(filterFactory.bbox(gd.getLocalName(), se.getMinX(),
                        se.getMinY(), se.getMaxX(), se.getMaxY(), null));
            }
            return filterFactory.or(filters);
        }
    }

    /**
     * Joins the provided filters in a single one by and-ing them (and then, simplifying them)
     *
     * @param filters
     * @return
     */
    private Filter joinFilters(Filter... filters) {
        if (filters == null || filters.length == 0) {
            return Filter.EXCLUDE;
        }

        Filter result = null;
        if (filters.length > 0) {
            FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
            result = ff.and(Arrays.asList(filters));
        } else if (filters.length == 1) {
            result = filters[0];
        }

        SimplifyingFilterVisitor visitor = new SimplifyingFilterVisitor();
        return (Filter) result.accept(visitor, null);
    }
}
TOP

Related Classes of org.geoserver.kml.utils.KMLFeatureAccessor

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.