Package org.geoserver.wms

Source Code of org.geoserver.wms.DefaultWebMapService

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import net.opengis.wfs.FeatureCollectionType;

import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.ServiceException;
import org.geoserver.sld.GetStyles;
import org.geoserver.sld.GetStylesRequest;
import org.geoserver.wms.animate.Animator;
import org.geoserver.wms.capabilities.Capabilities_1_3_0_Transformer;
import org.geoserver.wms.capabilities.GetCapabilitiesTransformer;
import org.geoserver.wms.describelayer.DescribeLayerModel;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.operation.projection.ProjectionException;
import org.geotools.styling.Style;
import org.geotools.styling.StyledLayerDescriptor;
import org.geotools.xml.transform.TransformerBase;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.vividsolutions.jts.geom.Envelope;

/**
* A default implementation of a {@link WebMapService}
* <p>
* This implementations relies on the code setting up the instance to provide the operation beans
* through the following properties:
* <ul>
* <li>{@link #setDescribeLayer DescribeLayer} for the {@link #describeLayer(DescribeLayerRequest)}
* operation
* <li>{@link #setGetCapabilities GetCapabilities} for the
* {@link #getCapabilities(GetCapabilitiesRequest)} operation
* <li>{@link #setGetFeatureInfo GetFeatureInfo} for the
* {@link #getFeatureInfo(GetFeatureInfoRequest)} operation
* <li>{@link #setGetLegendGraphic GetLegendGraphic} for the
* {@link #getLegendGraphic(GetLegendGraphicRequest)} operation
* <li>{@link #setGetMap GetMap} for the {@link #getMap(GetMapRequest)} operation
* <li>{@link #setGetStyles GetStyles} for the {@link #getStyles(GetStylesRequest)} operation
* </ul>
* If an operation is called for which its corresponding operation bean is not set, the call will
* result in an {@link UnsupportedOperationException}
* </p>
*
* @author Andrea Aime
* @author Justin Deoliveira
* @author Gabriel Roldan
*/
public class DefaultWebMapService implements WebMapService, ApplicationContextAware,
        DisposableBean {
    /**
     * default for 'format' parameter.
     */
    public static String FORMAT = "image/png";

    /**
     * default for 'styles' parameter.
     */
    public static List<Style> STYLES = Collections.emptyList();

    /**
     * longest side for the preview
     */
    public static int MAX_SIDE = 512;

    /**
     * minimum height to have a decent looking OL preview
     */
    public static int MIN_OL_HEIGHT = 330;

    /**
     * default for 'srs' parameter.
     */
    public static String SRS = "EPSG:4326";

    /**
     * default for 'transparent' parameter.
     */
    public static Boolean TRANSPARENT = Boolean.TRUE;

    /**
     * default for 'transparent' parameter.
     */
    public static ExecutorService RENDERING_POOL;

    /**
     * default for 'bbox' paramter
     */
    public static ReferencedEnvelope BBOX = new ReferencedEnvelope(
            new Envelope(-180, 180, -90, 90), DefaultGeographicCRS.WGS84);

    /**
     * wms configuration
     */
    private final WMS wms;

    /**
     * Temporary field that handles the usage of the line width optimization code
     */
    private static Boolean OPTIMIZE_LINE_WIDTH = null;

    /**
     * This variable is used to bypass direct raster rendering.
     */
    private static boolean BYPASS_DIRECT = Boolean.getBoolean("org.geoserver.render.raster.direct.disable");

    /**
     * Max number of rule filters to be used against the data source
     */
    private static Integer MAX_FILTER_RULES = null;
   
    /**
     * Enable continuous map wrapping
     */
    private static Boolean ENABLE_MAP_WRAPPING = null;
   
    /**
     * Use a global rendering pool, or use a new pool each time
     */
    private static Boolean USE_GLOBAL_RENDERING_POOL = null;

    private GetCapabilities getCapabilities;

    private DescribeLayer describeLayer;

    private GetMap getMap;

    private GetFeatureInfo getFeatureInfo;

    private GetStyles getStyles;

    private GetLegendGraphic getLegendGraphic;

    public DefaultWebMapService(WMS wms) {
        this.wms = wms;
    }

    /**
     * @see WebMapService#getServiceInfo()
     */
    public WMSInfo getServiceInfo() {
        return wms.getServiceInfo();
    }

    /**
     * Establishes the operation bean responsible for executing the GetCapabilities requests
     */
    public void setGetCapabilities(GetCapabilities getCapabilities) {
        this.getCapabilities = getCapabilities;
    }

    /**
     * Establishes the operation bean responsible for executing the DescribeLayer requests
     */
    public void setDescribeLayer(DescribeLayer describeLayer) {
        this.describeLayer = describeLayer;
    }

    /**
     * Establishes the operation bean responsible for executing the GetMap requests
     */
    public void setGetMap(GetMap getMap) {
        this.getMap = getMap;
    }

    /**
     * Establishes the operation bean responsible for executing the GetFeatureInfo requests
     */
    public void setGetFeatureInfo(GetFeatureInfo getFeatureInfo) {
        this.getFeatureInfo = getFeatureInfo;
    }

    /**
     * Establishes the operation bean responsible for executing the GetStyles requests
     */
    public void setGetStyles(GetStyles getStyles) {
        this.getStyles = getStyles;
    }

    /**
     * Establishes the operation bean responsible for executing the GetLegendGraphics requests
     */
    public void setGetLegendGraphic(GetLegendGraphic getLegendGraphic) {
        this.getLegendGraphic = getLegendGraphic;
    }

    /**
     * @see ApplicationContextAware#setApplicationContext(ApplicationContext)
     */
    public void setApplicationContext(ApplicationContext context) throws BeansException {

        // first time initialization of line width optimization flag
        if (OPTIMIZE_LINE_WIDTH == null) {
            String enabled = GeoServerExtensions.getProperty("OPTIMIZE_LINE_WIDTH", context);
            // default to true, but allow switching off
            if (enabled == null)
                OPTIMIZE_LINE_WIDTH = false;
            else
                OPTIMIZE_LINE_WIDTH = Boolean.valueOf(enabled);
        }

        // initialization of the renderer choice flag
        if (MAX_FILTER_RULES == null) {
            String rules = GeoServerExtensions.getProperty("MAX_FILTER_RULES", context);
            // default to true, but allow switching off
            if (rules == null)
                MAX_FILTER_RULES = 20;
            else
                MAX_FILTER_RULES = Integer.valueOf(rules);
        }
       
        // enable/disable map wrapping
        if (ENABLE_MAP_WRAPPING == null) {
            String wrapping = GeoServerExtensions.getProperty("ENABLE_MAP_WRAPPING", context);
            // default to true, but allow switching off
            if (wrapping == null)
                ENABLE_MAP_WRAPPING = true;
            else
                ENABLE_MAP_WRAPPING = Boolean.valueOf(wrapping);
        }
       
        // control usage of the global rendering thread pool
        if (USE_GLOBAL_RENDERING_POOL == null) {
            String usePool = GeoServerExtensions.getProperty("USE_GLOBAL_RENDERING_POOL", context);
            // default to true, but allow switching off
            if (usePool == null)
                USE_GLOBAL_RENDERING_POOL = true;
            else
                USE_GLOBAL_RENDERING_POOL = Boolean.valueOf(usePool);
        }
    }

    /**
     * Checks wheter the line width optimization is enabled, or not (defaults to true unless the
     * user sets the OPTIMIZE_LINE_WIDTH property to false)
     *
     * @return
     */
    public static boolean isLineWidthOptimizationEnabled() {
        return OPTIMIZE_LINE_WIDTH;
    }
   
    /**
     * Checks if continuous map wrapping is enabled or not
     *
     * @return
     */
    public static boolean isContinuousMapWrappingEnabled() {
        return ENABLE_MAP_WRAPPING;
    }

    /**
     * If true (default) use the sld rule filters to compose the query to the DB, otherwise don't
     * and get down only with the bbox and eventual definition filter)
     *
     * @return
     */
    public static int getMaxFilterRules() {
        return MAX_FILTER_RULES;
    }
   
    /**
     * If true (default) the direct raster rendering path is enabled
     * @return
     */
    public static boolean isDirectRasterPathEnabled() {
        return !BYPASS_DIRECT;
    }

    /**
     * @see WebMapService#getCapabilities(GetCapabilitiesRequest)
     * @see GetCapabilitiesTransformer
     * @see Capabilities_1_3_0_Transformer
     */
    public TransformerBase getCapabilities(GetCapabilitiesRequest request) {
        if (null == getCapabilities) {
            throw new UnsupportedOperationException(
                    "Operation not properly configured, make sure the operation bean has been set");
        }
        return getCapabilities.run(request);
    }

    /**
     * @see WebMapService#capabilities(GetCapabilitiesRequest)
     */
    public TransformerBase capabilities(GetCapabilitiesRequest request) {
        return getCapabilities(request);
    }

    /**
     * @see WebMapService#describeLayer(DescribeLayerRequest)
     */
    @Override
    public DescribeLayerModel describeLayer(DescribeLayerRequest request) {
        if (null == describeLayer) {
            throw new UnsupportedOperationException(
                    "Operation not properly configured, make sure the operation bean has been set");
        }
        return describeLayer.run(request);
    }

    /**
     * @see WebMapService#getMap(GetMapRequest)
     */
    public WebMap getMap(GetMapRequest request) {
        if (null == getMap) {
            throw new UnsupportedOperationException(
                    "Operation not properly configured, make sure the operation bean has been set");
        }
        return getMap.run(request);
    }

    /**
     * @see WebMapService#map(GetMapRequest)
     */
    public WebMap map(GetMapRequest request) {
        return getMap(request);
    }

    /**
     * @see WebMapService#getFeatureInfo(GetFeatureInfoRequest)
     */
    public FeatureCollectionType getFeatureInfo(final GetFeatureInfoRequest request) {
        if (null == getFeatureInfo) {
            throw new UnsupportedOperationException(
                    "Operation not properly configured, make sure the operation bean has been set");
        }
        return getFeatureInfo.run(request);
    }

    /**
     * @see WebMapService#getLegendGraphic(GetLegendGraphicRequest)
     */
    public Object getLegendGraphic(GetLegendGraphicRequest request) {
        if (null == getLegendGraphic) {
            throw new UnsupportedOperationException(
                    "Operation not properly configured, make sure the operation bean has been set");
        }
        return getLegendGraphic.run(request);
    }

    public WebMap kml(GetMapRequest getMap) {
        throw new ServiceException("kml service is not available, please include a KML module in WEB-INF/lib");
    }

    /**
     * Method for generation of WMS animations.
     *
     * @param getMap GetMapRequest
     * @return the <WebMap> output
     */
    public WebMap animate(GetMapRequest getMap) {
        try {
            return Animator.produce(getMap, this, wms);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
   
    /**
     * @see WebMapService#reflect(GetMapRequest)
     */
    public WebMap reflect(GetMapRequest request) {
        return getMapReflect(request);
    }

    /**
     * @see org.geoserver.wms.WebMapService#getStyles(org.geoserver.sld.GetStylesRequest)
     */
    public StyledLayerDescriptor getStyles(GetStylesRequest request) {
        return (StyledLayerDescriptor) getStyles.run(request);

    }

    /**
     * s
     *
     * @see WebMapService#getMapReflect(GetMapRequest)
     */
    public WebMap getMapReflect(GetMapRequest request) {

        GetMapRequest getMap = autoSetMissingProperties(request);

        return getMap(getMap);
    }

    public static GetMapRequest autoSetMissingProperties(GetMapRequest getMap) {
        // set the defaults
        if (getMap.getFormat() == null) {
            getMap.setFormat(FORMAT);
        }

        if ((getMap.getStyles() == null) || getMap.getStyles().isEmpty()) {
            // set styles to be the defaults for the specified layers
            // TODO: should this be part of core WMS logic? is so lets throw
            // this
            // into the GetMapKvpRequestReader
            if ((getMap.getLayers() != null) && (getMap.getLayers().size() > 0)) {
                ArrayList<Style> styles = new ArrayList<Style>(getMap.getLayers().size());

                for (int i = 0; i < getMap.getLayers().size(); i++) {
                    styles.add(getMap.getLayers().get(i).getDefaultStyle());
                }

                getMap.setStyles(styles);
            } else {
                getMap.setStyles(STYLES);
            }
        }

        // auto-magic missing info configuration
        autoSetBoundsAndSize(getMap);

        return getMap;
    }

    /**
     * This method tries to automatically determine SRS, bounding box and output size based on the
     * layers provided by the user and any other parameters.
     *
     * If bounds are not specified by the user, they are automatically se to the union of the bounds
     * of all layers.
     *
     * The size of the output image defaults to 512 pixels, the height is automatically determined
     * based on the width to height ratio of the requested layers. This is also true if either
     * height or width are specified by the user. If both height and width are specified by the
     * user, the automatically determined bounding box will be adjusted to fit inside these bounds.
     *
     * General idea 1) Figure out whether SRS has been specified, fall back to EPSG:4326 2)
     * Determine whether all requested layers use the same SRS, - if so, try to do bounding box
     * calculations in native coordinates 3) Aggregate the bounding boxes (in EPSG:4326 or native)
     * 4a) If bounding box has been specified, adjust height of image to match 4b) If bounding box
     * has not been specified, but height has, adjust bounding box
     */
    public static void autoSetBoundsAndSize(GetMapRequest getMap) {
        // Get the layers
        List<MapLayerInfo> layers = getMap.getLayers();

        /** 1) Check what SRS has been requested */
        String reqSRS = getMap.getSRS();

        // if none, try to determine which SRS to use
        // and keep track of whether we can use native all the way
        boolean useNativeBounds = true;
        if (reqSRS == null) {
            reqSRS = guessCommonSRS(layers);
            forceSRS(getMap, reqSRS);
        }

        /** 2) Compare requested SRS */
        for (int i = 0; useNativeBounds && i < layers.size(); i++) {
            if (layers.get(i) != null) {
                String layerSRS = layers.get(i).getSRS();
                useNativeBounds = reqSRS.equalsIgnoreCase(layerSRS)
                        && layers.get(i).getResource().getNativeBoundingBox() != null;
            } else {
                useNativeBounds = false;
            }
        }

        CoordinateReferenceSystem reqCRS;
        try {
            reqCRS = CRS.decode(reqSRS);
        } catch (Exception e) {
            throw new ServiceException(e);
        }

        // Ready to determine the bounds based on the layers, if not specified
        Envelope aggregateBbox = getMap.getBbox();
        boolean specifiedBbox = true;

        // If bbox is not specified by request
        if (aggregateBbox == null) {
            specifiedBbox = false;

            // Get the bounding box from the layers
            for (int i = 0; i < layers.size(); i++) {
                MapLayerInfo layerInfo = layers.get(i);
                ReferencedEnvelope curbbox;
                try {
                    curbbox = layerInfo.getLatLongBoundingBox();
                    if (useNativeBounds) {
                        ReferencedEnvelope nativeBbox = layerInfo.getBoundingBox();
                        if (nativeBbox == null) {
                            try {
                                CoordinateReferenceSystem nativeCrs = layerInfo
                                        .getCoordinateReferenceSystem();
                                nativeBbox = curbbox.transform(nativeCrs, true);
                            } catch (Exception e) {
                                throw new ServiceException(
                                        "Best effort native bbox computation failed", e);
                            }
                        }
                        curbbox = nativeBbox;
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                if (aggregateBbox != null) {
                    aggregateBbox.expandToInclude(curbbox);
                } else {
                    aggregateBbox = curbbox;
                }
            }

            ReferencedEnvelope ref = null;
            // Reproject back to requested SRS if we have to
            if (!useNativeBounds && !reqSRS.equalsIgnoreCase(SRS)) {
                try {
                    ref = new ReferencedEnvelope(aggregateBbox, CRS.decode("EPSG:4326"));
                    aggregateBbox = ref.transform(reqCRS, true);
                } catch (ProjectionException pe) {
                    ref.expandBy(-1 * ref.getWidth() / 50, -1 * ref.getHeight() / 50);
                    try {
                        aggregateBbox = ref.transform(reqCRS, true);
                    } catch (FactoryException e) {
                        e.printStackTrace();
                    } catch (TransformException e) {
                        e.printStackTrace();
                    }
                    // And again...
                } catch (NoSuchAuthorityCodeException e) {
                    e.printStackTrace();
                } catch (TransformException e) {
                    e.printStackTrace();
                } catch (FactoryException e) {
                    e.printStackTrace();
                }
            }
        }

        // Just in case
        if (aggregateBbox == null) {
            forceSRS(getMap, DefaultWebMapService.SRS);
            aggregateBbox = DefaultWebMapService.BBOX;
        }

        // Start the processing of adjust either the bounding box
        // or the pixel height / width

        double bbheight = aggregateBbox.getHeight();
        double bbwidth = aggregateBbox.getWidth();
        double bbratio = bbwidth / bbheight;

        double mheight = getMap.getHeight();
        double mwidth = getMap.getWidth();

        if (mheight > 0.5 && mwidth > 0.5 && specifiedBbox) {
            // This person really doesnt want our help,
            // we'll warp it any way they like it...
        } else {
            if (mheight > 0.5 && mwidth > 0.5) {
                // Fully specified, need to adjust bbox
                double mratio = mwidth / mheight;
                // Adjust bounds to be less than ideal to meet spec
                if (bbratio > mratio) {
                    // Too wide, need to increase height of bb
                    double diff = ((bbwidth / mratio) - bbheight) / 2;
                    aggregateBbox.expandBy(0, diff);
                } else {
                    // Too tall, need to increase width of bb
                    double diff = ((bbheight * mratio) - bbwidth) / 2;
                    aggregateBbox.expandBy(diff, 0);
                }

                adjustBounds(reqSRS, aggregateBbox);

            } else if (mheight > 0.5) {
                mwidth = bbratio * mheight;
            } else {
                if (mwidth > 0.5) {
                    mheight = (mwidth / bbratio >= 1) ? mwidth / bbratio : 1;
                } else {
                    if (bbratio > 1) {
                        mwidth = MAX_SIDE;
                        mheight = (mwidth / bbratio >= 1) ? mwidth / bbratio : 1;
                    } else {
                        mheight = MAX_SIDE;
                        mwidth = (mheight * bbratio >= 1) ? mheight * bbratio : 1;
                    }

                    // make sure OL output height is sufficient to show the OL scale bar fully
                    if (mheight < MIN_OL_HEIGHT
                            && ("application/openlayers".equalsIgnoreCase(getMap.getFormat()) || "openlayers"
                                    .equalsIgnoreCase(getMap.getFormat()))) {
                        mheight = MIN_OL_HEIGHT;
                        mwidth = (mheight * bbratio >= 1) ? mheight * bbratio : 1;
                    }

                }

            }

            // Actually set the bounding box and size of image
            getMap.setBbox(aggregateBbox);
            getMap.setWidth((int) mwidth);
            getMap.setHeight((int) mheight);
        }
    }

    private static String guessCommonSRS(List<MapLayerInfo> layers) {
        String SRS = null;
        for (MapLayerInfo layer : layers) {
            String layerSRS = layer.getSRS();
            if (SRS == null) {
                SRS = layerSRS.toUpperCase();
            } else if (!SRS.equals(layerSRS)) {
                // layers with mixed native SRS, let's just use the default
                return DefaultWebMapService.SRS;
            }
        }
        if (SRS == null) {
            return DefaultWebMapService.SRS;
        }
        return SRS;
    }

    private static void forceSRS(GetMapRequest getMap, String srs) {
        getMap.setSRS(srs);

        try {
            getMap.setCrs(CRS.decode(srs));
        } catch (NoSuchAuthorityCodeException e) {
            e.printStackTrace();
        } catch (FactoryException e) {
            e.printStackTrace();
        }
    }

    /**
     * This adjusts the bounds by zooming out 2%, but also ensuring that the maximum bounds do not
     * exceed the world bounding box
     *
     * This only applies if the SRS is EPSG:4326 or EPSG:900913
     *
     * @param reqSRS
     *            the SRS
     * @param bbox
     *            the current bounding box
     * @return the adjusted bounding box
     */
    private static Envelope adjustBounds(String reqSRS, Envelope bbox) {
        if (reqSRS.equalsIgnoreCase("EPSG:4326")) {
            bbox.expandBy(bbox.getWidth() / 100, bbox.getHeight() / 100);
            Envelope maxEnv = new Envelope(-180.0, -90.0, 180.0, 90.0);
            return bbox.intersection(maxEnv);

        } else if (reqSRS.equalsIgnoreCase("EPSG:900913")) {
            bbox.expandBy(bbox.getWidth() / 100, bbox.getHeight() / 100);
            Envelope maxEnv = new Envelope(-20037508.33, -20037508.33, 20037508.33, 20037508.33);
            return bbox.intersection(maxEnv);
        }
        return bbox;
    }

    /**
     * Returns a app wide cached rendering pool that can be used for parallelized rendering
     *
     * @return
     */
    public static ExecutorService getRenderingPool() {
        if(USE_GLOBAL_RENDERING_POOL && RENDERING_POOL == null) {
            synchronized (DefaultWebMapService.class) {
                if(RENDERING_POOL == null) {
                    RENDERING_POOL = Executors.newCachedThreadPool();
                }
            }
        }
       
        return RENDERING_POOL;
    }

    public void destroy() throws Exception {
        if (RENDERING_POOL != null) {
            RENDERING_POOL.shutdown();
            RENDERING_POOL.awaitTermination(10, TimeUnit.SECONDS);
            RENDERING_POOL = null;
        }
    }

}
TOP

Related Classes of org.geoserver.wms.DefaultWebMapService

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.