Package org.geoserver.kml.builder

Source Code of org.geoserver.kml.builder.SuperOverlayNetworkLinkBuilder

package org.geoserver.kml.builder;

import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;

import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.kml.KMLMapOutputFormat;
import org.geoserver.kml.KmlEncodingContext;
import org.geoserver.kml.NetworkLinkMapOutputFormat;
import org.geoserver.kml.decorator.LookAtDecoratorFactory;
import org.geoserver.kml.regionate.Tile;
import org.geoserver.kml.utils.KMLFeatureAccessor;
import org.geoserver.kml.utils.LookAtOptions;
import org.geoserver.ows.HttpErrorCodeException;
import org.geoserver.ows.URLMangler.URLType;
import org.geoserver.ows.util.CaseInsensitiveMap;
import org.geoserver.ows.util.ResponseUtils;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.MapLayerInfo;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.WMSRequests;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.FeatureLayer;
import org.geotools.map.Layer;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.styling.Style;
import org.opengis.filter.Filter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.vividsolutions.jts.geom.Envelope;

import de.micromata.opengis.kml.v_2_2_0.AbstractLatLonBox;
import de.micromata.opengis.kml.v_2_2_0.Document;
import de.micromata.opengis.kml.v_2_2_0.Feature;
import de.micromata.opengis.kml.v_2_2_0.Folder;
import de.micromata.opengis.kml.v_2_2_0.GroundOverlay;
import de.micromata.opengis.kml.v_2_2_0.Icon;
import de.micromata.opengis.kml.v_2_2_0.LatLonAltBox;
import de.micromata.opengis.kml.v_2_2_0.LatLonBox;
import de.micromata.opengis.kml.v_2_2_0.Link;
import de.micromata.opengis.kml.v_2_2_0.Lod;
import de.micromata.opengis.kml.v_2_2_0.LookAt;
import de.micromata.opengis.kml.v_2_2_0.NetworkLink;
import de.micromata.opengis.kml.v_2_2_0.Region;
import de.micromata.opengis.kml.v_2_2_0.ViewRefreshMode;

/**
* Builds a KML document with a superoverlay hierarchy for each layer
*
* @author Andrea Aime - GeoSolutions
*
*/
public class SuperOverlayNetworkLinkBuilder extends AbstractNetworkLinkBuilder {

    private GetMapRequest request;

    private WMSMapContent mapContent;

    private WMS wms;

    public SuperOverlayNetworkLinkBuilder(KmlEncodingContext context) {
        super(context);
        this.request = context.getRequest();
        this.mapContent = context.getMapContent();
        this.wms = context.getWms();
    }

    @Override
    void encodeDocumentContents(Document container) {
        boolean cachedMode = "cached".equals(context.getSuperOverlayMode());

        // normalize the requested bounds to match a WGS84 hierarchy tile
        Tile tile = new Tile(new ReferencedEnvelope(request.getBbox(), Tile.WGS84));
        while(tile.getZ() > 0 && !tile.getEnvelope().contains(request.getBbox())) {
            tile = tile.getParent();
        }
        Envelope normalizedEnvelope = null;
        if (tile.getZ() >= 0 && tile.getEnvelope().contains(request.getBbox())) {
            normalizedEnvelope = tile.getEnvelope();
        } else {
            normalizedEnvelope = KmlEncodingContext.WORLD_BOUNDS_WGS84;
        }
        int zoomLevel = (int) tile.getZ();
        // encode top level region, which is always visible
        addRegion(container, normalizedEnvelope, Integer.MAX_VALUE, -1);

        List<MapLayerInfo> layers = request.getLayers();
        for (int i = 0; i < layers.size(); i++) {
            MapLayerInfo layer = layers.get(i);
            if (cachedMode && isRequestGWCCompatible(request, i, wms)) {
                encodeGWCLink(container, request, layer);
            } else {
                encodeLayerSuperOverlay(container, i, normalizedEnvelope, zoomLevel);
            }
        }
    }

    public void encodeGWCLink(Document container, GetMapRequest request, MapLayerInfo layer) {
        NetworkLink nl = container.createAndAddNetworkLink();
        String prefixedName = layer.getResource().prefixedName();
        nl.setName("GWC-" + prefixedName);
        Link link = nl.createAndSetLink();
        String type = layer.getType() == MapLayerInfo.TYPE_RASTER ? "png" : "kml";
        String url = ResponseUtils.buildURL(request.getBaseUrl(), "gwc/service/kml/" + prefixedName
                + "." + type + ".kml", null, URLType.SERVICE);
        link.setHref(url);
        link.setViewRefreshMode(ViewRefreshMode.NEVER);
    }

    private void encodeLayerSuperOverlay(Document container, int layerIndex, Envelope bounds,
            int zoomLevel) {
        Layer layer = mapContent.layers().get(layerIndex);
        Folder folder = container.createAndAddFolder();
        folder.setName(layer.getTitle());

        LookAtOptions lookAtOptions = new LookAtOptions(request.getFormatOptions());
        if (bounds != null) {
            LookAtDecoratorFactory lookAtFactory = new LookAtDecoratorFactory();
            ReferencedEnvelope layerBounds = layer.getBounds();
            CoordinateReferenceSystem layerCRS = layerBounds.getCoordinateReferenceSystem();
            if(layerCRS != null && !CRS.equalsIgnoreMetadata(layerCRS, DefaultGeographicCRS.WGS84)) {
                try {
                    layerBounds = layerBounds.transform(DefaultGeographicCRS.WGS84, true);
                } catch(Exception e) {
                    throw new ServiceException("Failed to transform the layer bounds for "
                            + layer.getTitle() + " to WGS84", e);
                }
            }
            LookAt la = lookAtFactory.buildLookAt(layerBounds, lookAtOptions, false);
            folder.setAbstractView(la);
        }

        encodeNetworkLinks(folder, layer, bounds, zoomLevel);
    }

    /**
     * Encode the network links for the specified envelope and zoom level
     *
     * @param layer
     * @param top
     * @param zoomLevel
     */
    void encodeNetworkLinks(Folder folder, Layer layer, Envelope top, int zoomLevel) {
        // encode the network links
        if (top != KmlEncodingContext.WORLD_BOUNDS_WGS84) {
            // top left
            Envelope e00 = new Envelope(top.getMinX(), top.getMinX() + (top.getWidth() / 2d),
                    top.getMaxY() - (top.getHeight() / 2d), top.getMaxY());

            // top right
            Envelope e01 = new Envelope(e00.getMaxX(), top.getMaxX(), e00.getMinY(), e00.getMaxY());

            // bottom left
            Envelope e10 = new Envelope(e00.getMinX(), e00.getMaxX(), top.getMinY(), e00.getMinY());

            // bottom right
            Envelope e11 = new Envelope(e01.getMinX(), e01.getMaxX(), e10.getMinY(), e10.getMaxY());

            addNetworkLink(folder, e00, "00", layer);
            addNetworkLink(folder, e01, "01", layer);
            addNetworkLink(folder, e10, "10", layer);
            addNetworkLink(folder, e11, "11", layer);
        } else {
            // divide up horizontally by two
            Envelope e0 = new Envelope(top.getMinX(), top.getMinX() + (top.getWidth() / 2d),
                    top.getMinY(), top.getMaxY());
            Envelope e1 = new Envelope(e0.getMaxX(), top.getMaxX(), top.getMinY(), top.getMaxY());

            addNetworkLink(folder, e0, "0", layer);
            addNetworkLink(folder, e1, "1", layer);
        }

        // encode the ground overlay(s)
        if (top == KmlEncodingContext.WORLD_BOUNDS_WGS84) {
            // special case for top since it does not line up as a proper
            // tile -> split it in two
            encodeTileContents(folder, layer, "contents-0", zoomLevel, new Envelope(-180, 0, -90,
                    90));
            encodeTileContents(folder, layer, "contents-1", zoomLevel,
                    new Envelope(0, 180, -90, 90));
        } else {
            // encode straight up
            encodeTileContents(folder, layer, "contents", zoomLevel, top);
        }
    }

    void addRegion(Feature container, Envelope box, int minLodPixels, int maxLodPixels) {
        Region region = container.createAndSetRegion();
        Lod lod = region.createAndSetLod();
        lod.setMinLodPixels(minLodPixels);
        lod.setMaxLodPixels(maxLodPixels);
        LatLonAltBox llaBox = region.createAndSetLatLonAltBox();
        setEnvelope(box, llaBox);
    }

    private void setEnvelope(Envelope box, AbstractLatLonBox llBox) {
        llBox.setNorth(box.getMaxY());
        llBox.setSouth(box.getMinY());
        llBox.setEast(box.getMaxX());
        llBox.setWest(box.getMinX());
    }

    void addNetworkLink(Folder container, Envelope box, String name, Layer layer) {
        // check if we are going to get any feature from this layer
        String overlayMode = context.getSuperOverlayMode();
        if (!"raster".equals(overlayMode) && layer instanceof FeatureLayer && !shouldDrawVectorLayer(layer, box)) {
            return;
        }

        NetworkLink nl = container.createAndAddNetworkLink();
        nl.setName(name);
        addRegion(nl, box, 128, -1);
        Link link = nl.createAndSetLink();
        String getMap = WMSRequests.getGetMapUrl(request, layer, 0, box, new String[] { "format",
                KMLMapOutputFormat.MIME_TYPE, "width", "256", "height", "256", "format", NetworkLinkMapOutputFormat.KML_MIME_TYPE  });
        link.setHref(getMap);
        LOGGER.fine("Network link " + name + ":" + getMap);
        link.setViewRefreshMode(ViewRefreshMode.ON_REGION);
    }

    void encodeTileContents(Folder container, Layer layer, String name, int drawOrder, Envelope box) {
        try {
            if (shouldDrawVectorLayer(layer, box))
                encodeKMLLink(container, layer, name, drawOrder, box);
            if (shouldDrawWMSOverlay(layer, box))
                encodeGroundOverlay(container, layer, drawOrder, box);
        } catch (HttpErrorCodeException e) {
            // no contents, ok
        }
    }

    private boolean shouldDrawVectorLayer(Layer layer, Envelope box) {
        try {
            // should draw as vector if the layer is a vector layer, and based on mode
            // full: yes, if any regionated vectors are present at this zoom level
            // hybrid: yes, if any regionated vectors are present at this zoom level
            // overview: is the non-regionated feature count for this tile below the cutoff?
            // raster: no
            if (!isVectorLayer(layer))
                return false;

            String overlayMode = context.getSuperOverlayMode();
            if ("raster".equals(overlayMode))
                return false;

            if ("overview".equals(overlayMode)) {
                int featureCount = featuresInTile(layer, box, false);
                return featureCount <= getRegionateFeatureLimit(getFeatureTypeInfo(layer));
            }

            int featureCount = featuresInTile(layer, box, true);
            return featureCount > 0;
        } catch (HttpErrorCodeException e) {
            // fine, it means there was no data.... sigh...
            return false;
        }
    }

    private int getRegionateFeatureLimit(FeatureTypeInfo ft) {
        Integer regionateFeatureLimit = ft.getMetadata().get("kml.regionateFeatureLimit",
                Integer.class);
        return regionateFeatureLimit != null ? regionateFeatureLimit : -1;
    }

    private boolean shouldDrawWMSOverlay(Layer layer, Envelope box) {
        // should draw based on the mode:
        // full: no
        // hybrid: yes
        // overview: is the non-regionated feature count for this tile above the cutoff?
        if (!isVectorLayer(layer))
            return true;

        String overlayMode = context.getSuperOverlayMode();
        if ("hybrid".equals(overlayMode) || "raster".equals(overlayMode))
            return true;
        if ("overview".equals(overlayMode))
            return featuresInTile(layer, box, false) > getRegionateFeatureLimit(getFeatureTypeInfo(layer));

        return false;
    }

    void encodeKMLLink(Folder container, Layer layer, String name, int drawOrder, Envelope box) {
        // copy the format options
        CaseInsensitiveMap fo = new CaseInsensitiveMap(new HashMap());
        fo.putAll(mapContent.getRequest().getFormatOptions());

        // we want to pass through format options except for superoverlay, we need to
        // turn it off so we get actual placemarks back, and not more links
        fo.remove("superoverlay");

        // get the regionate mode
        String overlayMode = (String) fo.get("overlayMode");

        if ("overview".equalsIgnoreCase(overlayMode)) {
            // overview mode, turn off regionation
            fo.remove("regionateBy");
        } else {
            // specify regionateBy=auto if not specified
            if (!fo.containsKey("regionateBy")) {
                fo.put("regionateBy", "auto");
            }

        }
        String foEncoded = WMSRequests.encodeFormatOptions(fo);

        // encode the link
        NetworkLink nl = container.createAndAddNetworkLink();
        nl.setName(name);
        addRegion(nl, box, 128, -1);
        nl.setVisibility(true);
        Link link = nl.createAndSetLink();
        String url = WMSRequests.getGetMapUrl(request, layer, 0, box, new String[] { "width",
                "256", "height", "256", "format_options", foEncoded, "superoverlay", "true"});
        link.setHref(url);
    }

    boolean isVectorLayer(Layer layer) {
        int index = mapContent.layers().indexOf(layer);
        MapLayerInfo info = mapContent.getRequest().getLayers().get(index);
        return (info.getType() == MapLayerInfo.TYPE_VECTOR || info.getType() == MapLayerInfo.TYPE_REMOTE_VECTOR);
    }

    private FeatureTypeInfo getFeatureTypeInfo(Layer layer) {
        for (MapLayerInfo info : mapContent.getRequest().getLayers())
            if (info.getName().equals(layer.getTitle()))
                return info.getFeature();
        return null;
    }

    private int featuresInTile(Layer layer, Envelope bounds, boolean regionate) {
        if (!isVectorLayer(layer))
            return 1; // for coverages, we want raster tiles everywhere
        Envelope originalBounds = mapContent.getRequest().getBbox();
        mapContent.getRequest().setBbox(bounds);
        mapContent.getViewport().setBounds(
                new ReferencedEnvelope(bounds, DefaultGeographicCRS.WGS84));

        String originalRegionateBy = null;
        if (regionate) {
            originalRegionateBy = (String) mapContent.getRequest().getFormatOptions()
                    .get("regionateby");
            if (originalRegionateBy == null)
                mapContent.getRequest().getFormatOptions().put("regionateby", "auto");
        }

        int numFeatures = 0;

        try {
            numFeatures = new KMLFeatureAccessor().getFeatureCount(layer, mapContent, wms, -1);
        } catch (ServiceException e) {
            LOGGER.severe("Caught the WmsException!");
            numFeatures = -1;
        } catch (HttpErrorCodeException e) {
            if (e.getErrorCode() == 204) {
                throw e;
            } else {
                LOGGER.log(Level.WARNING, "Failure while checking whether a regionated child tile "
                        + "contained features!", e);
            }
        } catch (Exception e) {
            // Probably just trying to regionate a raster layer...
            LOGGER.log(Level.WARNING,
                    "Failure while checking whether a regionated child tile contained features!", e);
        }

        mapContent.getRequest().setBbox(originalBounds);
        mapContent.getViewport().setBounds(
                new ReferencedEnvelope(originalBounds, DefaultGeographicCRS.WGS84));
        if (regionate && originalRegionateBy == null) {
            mapContent.getRequest().getFormatOptions().remove("regionateby");
        }

        return numFeatures;
    }

    void encodeGroundOverlay(Folder container, Layer layer, int drawOrder, Envelope box) {
        GroundOverlay go = container.createAndAddGroundOverlay();
        go.setDrawOrder(drawOrder);
        Icon icon = go.createAndSetIcon();
        String href = WMSRequests.getGetMapUrl(request, layer, 0, box, new String[] { "width",
                "256", "height", "256", "format", "image/png", "transparent", "true" });
        icon.setHref(href);
        LOGGER.fine(href);

        // make sure the ground overlay disappears as the lower tiles activate
        addRegion(go, box, 128, 512);

        LatLonBox llBox = go.createAndSetLatLonBox();
        setEnvelope(box, llBox);
    }

    /**
     * Returns true if the request is GWC compatible
     *
     * @param mapContent
     * @return
     */
    private boolean isRequestGWCCompatible(GetMapRequest request, int layerIndex, WMS wms) {
        // check the kml params are the same as the defaults (GWC uses always the defaults)
        boolean requestKmAttr = context.isDescriptionEnabled();
        if (requestKmAttr != wms.getKmlKmAttr()) {
            return false;
        }

        boolean requestKmplacemark = context.isPlacemarkForced();
        if (requestKmplacemark != wms.getKmlPlacemark()) {
            return false;
        }

        int requestKmscore = context.getKmScore();
        if (requestKmscore != wms.getKmScore()) {
            return false;
        }

        // check the layer is local
        if (request.getLayers().get(layerIndex).getType() == MapLayerInfo.TYPE_REMOTE_VECTOR) {
            return false;
        }

        // check the layer is using the default style
        Style requestedStyle = request.getStyles().get(layerIndex);
        Style defaultStyle = request.getLayers().get(layerIndex).getDefaultStyle();
        if (!defaultStyle.equals(requestedStyle)) {
            return false;
        }

        // check there is no extra filtering applied to the layer
        List<Filter> filters = request.getFilter();
        if (filters != null && filters.size() > 0 && filters.get(layerIndex) != Filter.INCLUDE) {
            return false;
        }

        // no fiddling with antialiasing settings
        String antialias = (String) request.getFormatOptions().get("antialias");
        if (antialias != null && !"FULL".equalsIgnoreCase(antialias)) {
            return false;
        }

        // no custom palette
        if (request.getPalette() != null) {
            return false;
        }

        // no custom start index
        if (request.getStartIndex() != null && request.getStartIndex() != 0) {
            return false;
        }

        // no custom max features
        if (request.getMaxFeatures() != null) {
            return false;
        }

        // no sql view params
        if (request.getViewParams() != null && request.getViewParams().size() > 0) {
            return false;
        }

        // ok, it seems everything is the same as GWC cached it
        return true;
    }

}
TOP

Related Classes of org.geoserver.kml.builder.SuperOverlayNetworkLinkBuilder

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.