Package org.vfny.geoserver.wms.responses.map.kml

Source Code of org.vfny.geoserver.wms.responses.map.kml.KMLUtils

/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.vfny.geoserver.wms.responses.map.kml;

import java.awt.Color;
import java.util.ArrayList;
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.platform.GeoServerExtensions;
import org.geoserver.wms.util.WMSRequests;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.crs.ReprojectFeatureResults;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureTypes;
import org.geotools.filter.IllegalFilterException;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.MapLayer;
import org.geotools.referencing.CRS;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Rule;
import org.geotools.styling.Style;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
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.crs.CoordinateReferenceSystem;
import org.vfny.geoserver.wms.WMSMapContext;
import org.vfny.geoserver.wms.WmsException;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;

import com.vividsolutions.jts.geom.Envelope;


/**
* Some convenience methods used by the kml transformers.
*
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
* @deprecated use {@link WMSRequests}.
*/
public class KMLUtils {
    /**
     * logger
     */
    static Logger LOGGER = org.geotools.util.logging.Logging
            .getLogger("org.geoserver.kml");

    /**
     * Tolerance used to compare doubles for equality
     */
    static final double TOLERANCE = 1e-6;

    private static final CoordinateReferenceSystem WGS84;
    private static final int RULES = 0;
    private static final int ELSE_RULES = 1;

    static {
        try {
            WGS84 = CRS.decode("EPSG:4326");
        } catch (Exception e) {
            throw new RuntimeException(
                    "Cannot decode EPSG:4326, the CRS subsystem must be badly broken...");
        }
    }

    public final static Envelope WORLD_BOUNDS_WGS84 = new Envelope(-180,180,-90,90);   
   
    private final static double[] TILE_RESOLUTIONS = new double[100];

    static {
        for (int i = 0; i < TILE_RESOLUTIONS.length; i++) {
            TILE_RESOLUTIONS[i] = WORLD_BOUNDS_WGS84.getWidth() / ((0x01 << i) * 256);
        }
    }

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

    /**
     * Encodes the url of a GetMap request from a map context + map layer.
     * <p>
     * If the <tt>mapLayer</tt> argument is <code>null</code>, the request is
     * made including all layers in the <tt>mapContexT</tt>.
     * </p>
     * <p>
     * If the <tt>bbox</tt> argument is <code>null</code>. {@link WMSMapContext#getAreaOfInterest()}
     * is used for the bbox parameter.
     * </p>
     *
     * @param mapContext The map context.
     * @param mapLayer The Map layer, may be <code>null</code>.
     * @param layerIndex The index of the layer in the request.
     * @param bbox The bounding box of the request, may be <code>null</code>.
     * @param kvp Additional or overiding kvp parameters, may be <code>null</code>
     * @param tile Flag controlling whether the request should be made against tile cache
     *
     * @return The full url for a getMap request.
     * @deprecated use {@link WMSRequests#getGetMapUrl(WMSMapContext, MapLayer, Envelope, String[])}
     */
    public static String getMapUrl(
            WMSMapContext mapContext,
            MapLayer mapLayer,
            int layerIndex,
            Envelope bbox,
            String[] kvp,
            boolean tile) {
      
        if ( tile ) {
            return WMSRequests.getTiledGetMapUrl( mapContext.getRequest(), mapLayer, layerIndex, bbox, kvp );
        }
       
        return WMSRequests.getGetMapUrl(
                    mapContext.getRequest(),
                    mapLayer,
                    layerIndex,
                    bbox,
                    kvp
                );
    }

    /**
     * Encodes the url of a GetMap request from a map context + map layer.
     * <p>
     * If the <tt>mapLayer</tt> argument is <code>null</code>, the request is
     * made including all layers in the <tt>mapContexT</tt>.
     * </p>
     * @param mapContext The map context.
     * @param mapLayer The Map layer, may be <code>null</code>
     * @param layerIndex The index of the layer in the request.
     * @param kvp Additional or overidding kvp parameters, may be <code>null</code>
     * @param tile Flag controlling wether the request should be made against tile cache
     *
     * @return The full url for a getMap request.
     * @deprecated use {@link WMSRequests#getGetMapUrl(WMSMapContext, MapLayer, int, Envelope, String[])}
     */
    public static String getMapUrl(WMSMapContext mapContext, MapLayer mapLayer, int layerIndex, boolean tile) {
        return getMapUrl(mapContext, mapLayer, layerIndex, mapContext.getAreaOfInterest(), null, tile);
    }

    /**
     * Encodes the url for a GetLegendGraphic request from a map context + map layer.
     *
     * @param mapContext The map context.
     * @param mapLayer The map layer.
     * @param kvp Additional or overidding kvp parameters, may be <code>null</code>
     *
     * @return A map containing all the key value pairs for a GetLegendGraphic request.
     * @deprecated use {@link WMSRequests#getGetLegendGraphicUrl(WMSMapContext, MapLayer, String[])
     */
    public static String getLegendGraphicUrl(WMSMapContext mapContext, MapLayer mapLayer,
        String[] kvp) {
        return WMSRequests.getGetLegendGraphicUrl(mapContext.getRequest(), mapLayer, kvp);
    }

    /**
     * Creates sax attributes from an array of key value pairs.
     *
     * @param nameValuePairs Alternating key value pair array.
     *
     */
    public static Attributes attributes(String[] nameValuePairs) {
        AttributesImpl attributes = new AttributesImpl();

        for (int i = 0; i < nameValuePairs.length; i += 2) {
            String name = nameValuePairs[i];
            String value = nameValuePairs[i + 1];

            attributes.addAttribute("", name, name, "", value);
        }

        return attributes;
    }
   
    /**
     * Filters the rules of <code>featureTypeStyle</code> returnting only
     * those that apply to <code>feature</code>.
     * <p>
     * This method returns rules for which:
     * <ol>
     <li><code>rule.getFilter()</code> matches <code>feature</code>, or:
     *  <li>the rule defines an "ElseFilter", and the feature matches no
     *  other rules.
     * </ol>
     * This method returns an empty array in the case of which no rules
     * match.
     * </p>
     * @param featureTypeStyle The feature type style containing the rules.
     * @param feature The feature being filtered against.
     *
     */
    public static Rule[] filterRules(FeatureTypeStyle featureTypeStyle,
            SimpleFeature feature, double scaleDenominator) {
        Rule[] rules = featureTypeStyle.getRules();

        if ((rules == null) || (rules.length == 0)) {
            return new Rule[0];
        }

        ArrayList filtered = new ArrayList(rules.length);

        //process the rules, keep track of the need to apply an else filters
        boolean match = false;
        boolean hasElseFilter = false;

        for (int i = 0; i < rules.length; i++) {
            Rule rule = rules[i];
            LOGGER.finer(new StringBuffer("Applying rule: ").append(
                    rule.toString()).toString());

            //does this rule have an else filter
            if (rule.hasElseFilter()) {
                hasElseFilter = true;

                continue;
            }

            //is this rule within scale?
            if (!isWithInScale(rule, scaleDenominator)) {
                continue;
            }

            //does this rule have a filter which applies to the feature
            Filter filter = rule.getFilter();

            if ((filter == null) || filter.evaluate(feature)) {
                match = true;

                filtered.add(rule);
            }
        }

        //if no rules mached the feautre, re-run through the rules applying
        // any else filters
        if (!match && hasElseFilter) {
            //loop through again and apply all the else rules
            for (int i = 0; i < rules.length; i++) {
                Rule rule = rules[i];

                //is this rule within scale?
                if (!isWithInScale(rule, scaleDenominator)) {
                    continue;
                }

                if (rule.hasElseFilter()) {
                    filtered.add(rule);
                }
            }
        }

        return (Rule[]) filtered.toArray(new Rule[filtered.size()]);
    }

    /**
     * Checks if a rule can be triggered at the current scale level
     *
     * @param r
     *            The rule
     * @return true if the scale is compatible with the rule settings
     */
    public static boolean isWithInScale(Rule r, double scaleDenominator) {
        return ((r.getMinScaleDenominator() - TOLERANCE) <= scaleDenominator)
                && ((r.getMaxScaleDenominator() + TOLERANCE) > scaleDenominator);
    }
   
    public static int findZoomLevel(Envelope extent){
        double resolution = Math.max(extent.getWidth()/256d, extent.getHeight() / 256d);
       
        int i;
       
        for (i = 1; i < TILE_RESOLUTIONS.length; i++){
            if (resolution > TILE_RESOLUTIONS[i]) {
                i--;
                break;
            }
        }
       
        return i;
    }

    public static Envelope expandToTile(Envelope extent){
        double resolution = Math.max(extent.getWidth() / 256d, extent.getHeight() / 256d);
       
        int i = findZoomLevel(extent);
        
        while (i > 0) {
            resolution = TILE_RESOLUTIONS[i];

            double tilelon = resolution * 256;
            double tilelat = resolution * 256;

            double lon0 = extent.getMinX() - WORLD_BOUNDS_WGS84.getMinX();
            double lon1 = extent.getMaxX() - WORLD_BOUNDS_WGS84.getMinX();

            int col0 = (int) Math.floor(lon0 / tilelon);
            int col1 = (int) Math.floor((lon1 / tilelon) - 1E-9);

            double lat0 = extent.getMinY() - WORLD_BOUNDS_WGS84.getMinY();
            double lat1 = extent.getMaxY() - WORLD_BOUNDS_WGS84.getMinY();

            int row0 = (int) Math.floor(lat0 / tilelat);
            int row1 = (int) Math.floor((lat1 / tilelat) - 1E-9);

            if ((col0 == col1) && (row0 == row1)) {
                double tileoffsetlon = WORLD_BOUNDS_WGS84.getMinX() + (col0 * tilelon);
                double tileoffsetlat = WORLD_BOUNDS_WGS84.getMinY() + (row0 * tilelat);

                return new Envelope(tileoffsetlon, tileoffsetlon + tilelon, tileoffsetlat,
                        tileoffsetlat + tilelat);
            } else {
                i--;
            }
        }
       
        return WORLD_BOUNDS_WGS84;
    }
   
    /**
     * Utility method to convert an int into hex, padded to two characters.
     * handy for generating colour strings.
     *
     * @param i Int to convert
     * @return String a two character hex representation of i
     * NOTE: this is a utility method and should be put somewhere more useful.
     */
    public static String intToHex(int i) {
        String prelim = Integer.toHexString(i);

        if (prelim.length() < 2) {
            prelim = "0" + prelim;
        }

        return prelim;
    }

    /**
     * Utility method to convert a Color and opacity (0,1.0) into a KML
     * color ref.
     *
     * @param c The color to convert.
     * @param opacity Opacity / alpha, double from 0 to 1.0.
     *
     * @return A String of the form "AABBGGRR".
     */
    public static String colorToHex(Color c, double opacity) {
        return new StringBuffer().append(
                intToHex(new Float(255 * opacity).intValue())).append(
                intToHex(c.getBlue())).append(intToHex(c.getGreen())).append(
                intToHex(c.getRed())).toString();
    }

    /**
     * Filters the feature type styles of <code>style</code> returning only
     * those that apply to <code>featureType</code>
     * <p>
     * This methods returns feature types for which
     * <code>featureTypeStyle.getFeatureTypeName()</code> matches the name
     * of the feature type of <code>featureType</code>, or matches the name of
     * any parent type of the feature type of <code>featureType</code>. This
     * method returns an empty array in the case of which no rules match.
     * </p>
     * @param style The style containing the feature type styles.
     * @param featureType The feature type being filtered against.
     *
     */
    public static FeatureTypeStyle[] filterFeatureTypeStyles(Style style,
            SimpleFeatureType featureType) {
        FeatureTypeStyle[] featureTypeStyles = style.getFeatureTypeStyles();

        if ((featureTypeStyles == null) || (featureTypeStyles.length == 0)) {
            return new FeatureTypeStyle[0];
        }

        ArrayList filtered = new ArrayList(featureTypeStyles.length);

        for (int i = 0; i < featureTypeStyles.length; i++) {
            FeatureTypeStyle featureTypeStyle = featureTypeStyles[i];
            String featureTypeName = featureTypeStyle.getFeatureTypeName();

            //does this style have any rules
            if (featureTypeStyle.getRules() == null
                    || featureTypeStyle.getRules().length == 0) {
                continue;
            }

            //does this style apply to the feature collection
            if (featureType.getTypeName().equalsIgnoreCase(featureTypeName)
                    || FeatureTypes.isDecendedFrom(featureType, null,
                            featureTypeName)) {
                filtered.add(featureTypeStyle);
            }
        }

        return (FeatureTypeStyle[]) filtered
                .toArray(new FeatureTypeStyle[filtered.size()]);
    }

    public static FeatureCollection<SimpleFeatureType, SimpleFeature> loadFeatureCollection(
            FeatureSource<SimpleFeatureType, SimpleFeature> featureSource,
            MapLayer layer, WMSMapContext mapContext) throws Exception {
        SimpleFeatureType schema = featureSource.getSchema();

        Envelope envelope = mapContext.getAreaOfInterest();
        ReferencedEnvelope aoi = new ReferencedEnvelope(envelope, mapContext
                .getCoordinateReferenceSystem());
        CoordinateReferenceSystem sourceCrs = schema
                .getCoordinateReferenceSystem();

        boolean reprojectBBox = (sourceCrs != null)
                && !CRS.equalsIgnoreMetadata(
                        aoi.getCoordinateReferenceSystem(), sourceCrs);
        if (reprojectBBox) {
            aoi = aoi.transform(sourceCrs, true);
        }

        Filter filter = createBBoxFilter(schema, aoi);

        // now build the query using only the attributes and the bounding
        // box needed
        DefaultQuery q = new DefaultQuery(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.
        Query definitionQuery = layer.getQuery();

        if (definitionQuery != Query.ALL) {
            if (q == Query.ALL) {
                q = (DefaultQuery) definitionQuery;
            } else {
                q = (DefaultQuery) DataUtilities.mixQueries(definitionQuery, q,
                        "KMLEncoder");
            }
        }

        // handle startIndex requested by client query
        q.setStartIndex(definitionQuery.getStartIndex());

        // check the regionating strategy
        RegionatingStrategy regionatingStrategy = null;
        String stratname = (String) mapContext.getRequest().getFormatOptions()
                .get("regionateBy");
        if (("auto").equals(stratname)) {
            Catalog catalog = mapContext.getRequest().getWMS().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."
                    );
            }
        }

        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 WmsException("Unknown regionating strategy " + stratname);
        }

        // try to load less features by leveraging regionating strategy and the
        // SLD
        Filter regionatingFilter = Filter.INCLUDE;

        if (regionatingStrategy != null)
            regionatingFilter = regionatingStrategy.getFilter(mapContext, layer);

        Filter ruleFilter = summarizeRuleFilters(getLayerRules(featureSource
                .getSchema(), layer.getStyle()));
        Filter finalFilter = joinFilters(q.getFilter(), joinFilters(ruleFilter,
                regionatingFilter));
        q.setFilter(finalFilter);

        // make sure we output in 4326 since that's what KML mandates
        if (sourceCrs != null && !CRS.equalsIgnoreMetadata(WGS84, sourceCrs)) {
            return new ReprojectFeatureResults(featureSource.getFeatures(q),
                    WGS84);
        }

        return featureSource.getFeatures(q);
    }

    public static 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;
    }

    /**
     * Creates the bounding box filters (one for each geometric attribute)
     * needed to query a <code>MapLayer</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
     * @return an or'ed list of bbox filters, one for each geometric attribute
     *         in <code>attributes</code>. If there are just one geometric
     *         attribute, just returns its corresponding
     *         <code>GeometryFilter</code>.
     * @throws IllegalFilterException
     *             if something goes wrong creating the filter
     */
    private static Filter createBBoxFilter(SimpleFeatureType schema,
            Envelope bbox) throws IllegalFilterException {
        List filters = new ArrayList();
        for (int j = 0; j < schema.getAttributeCount(); j++) {
            AttributeDescriptor attType = schema.getDescriptor(j);

            if (attType instanceof GeometryDescriptor) {
                Filter gfilter = filterFactory.bbox(attType.getLocalName(),
                        bbox.getMinX(), bbox.getMinY(), bbox.getMaxX(), bbox
                                .getMaxY(), null);
                filters.add(gfilter);
            }
        }

        if (filters.size() == 0)
            return Filter.INCLUDE;
        else if (filters.size() == 1)
            return (Filter) filters.get(0);
        else
            return filterFactory.or(filters);
    }

    private static List[] getLayerRules(SimpleFeatureType ftype, Style style) {
        List[] result = new List[] { new ArrayList(), new ArrayList() };

        final String typeName = ftype.getTypeName();
        FeatureTypeStyle[] featureStyles = style.getFeatureTypeStyles();
        final int length = featureStyles.length;
        for (int i = 0; i < length; i++) {
            // getting feature styles
            FeatureTypeStyle fts = featureStyles[i];

            // check if this FTS is compatible with this FT.
            if ((typeName != null)
                    && FeatureTypes.isDecendedFrom(ftype, null, fts
                            .getFeatureTypeName())) {

                // get applicable rules at the current scale
                Rule[] ftsRules = fts.getRules();
                for (int j = 0; j < ftsRules.length; j++) {
                    // getting rule
                    Rule r = ftsRules[j];

                    if (KMLUtils.isWithInScale(r, 1)) {
                        if (r.hasElseFilter()) {
                            result[ELSE_RULES].add(r);
                        } else {
                            result[RULES].add(r);
                        }
                    }
                }
            }
        }

        return result;
    }

    private static Filter joinFilters(Filter first, Filter second) {
        if (Filter.EXCLUDE.equals(first) || Filter.EXCLUDE.equals(second))
            return Filter.EXCLUDE;

        if (first == null || Filter.INCLUDE.equals(first))
            return second;

        if (second == null || Filter.INCLUDE.equals(second))
            return first;

        FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
        return ff.and(first, second);
    }

    /**
     * Summarizes, when possible, the rule filters into one.
     *
     * @param rules
     * @param originalFiter
     * @return
     */
    private static Filter summarizeRuleFilters(List[] rules) {
        if (rules[RULES].size() == 0 || rules[ELSE_RULES].size() > 0)
            return Filter.INCLUDE;

        List filters = new ArrayList();
        for (Iterator it = rules[RULES].iterator(); it.hasNext();) {
            Rule rule = (Rule) it.next();
            // if there is a single rule asking for all filters, we have to
            // return everything that the original filter returned already
            if (rule.getFilter() == null
                    || Filter.INCLUDE.equals(rule.getFilter()))
                return Filter.INCLUDE;
            else
                filters.add(rule.getFilter());
        }

        FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
        return ff.or(filters);
    }
}
TOP

Related Classes of org.vfny.geoserver.wms.responses.map.kml.KMLUtils

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.