Package org.geoserver.kml.sequence

Source Code of org.geoserver.kml.sequence.PlainFolderSequenceFactory

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

import java.io.IOException;
import java.util.List;

import org.geoserver.kml.KmlEncodingContext;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.WMSRequests;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
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 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.LatLonBox;
import de.micromata.opengis.kml.v_2_2_0.ViewRefreshMode;

/**
* Creates a sequence of folders mapping the layers in the map content, using either kml dumps
* or ground overlays (the classic approach, that is)
*
* @author Andrea Aime - GeoSolutions
*/
public class PlainFolderSequenceFactory extends AbstractFolderSequenceFactory {

    public PlainFolderSequenceFactory(KmlEncodingContext context) {
        super(context);
    }

    @Override
    public Sequence<Feature> newSequence() {
        return new PlainFolderGenerator();
    }

    public class PlainFolderGenerator extends AbstractFolderGenerator {

        protected void encodeFolderContents(Layer layer, Folder folder) {
            // now encode the contents (dynamic bit, it may use the Sequence construct)
            if (layer instanceof FeatureLayer) {
                // do we use a KML placemark dump, or a ground overlay?
                if (useVectorOutput(context)) {
                    List<Feature> features = new SequenceList<Feature>(
                            new FeatureSequenceFactory(context, (FeatureLayer) layer));
                    context.addFeatures(folder, features);
                } else {
                    addGroundOverlay(folder, layer);
                    // in case of ground overlays we might still want to output placemarks
                    // for the
                    if(context.isPlacemarkForced()) {
                        addFeatureCentroids(layer, folder);
                    }
                }
            } else {
                addGroundOverlay(folder, layer);
            }
        }

        /**
         * Adds the feature centroids to the output features, without actually adding the full
         * geometry (used when doing raster overlays of vector data with a desire to retain the
         * popups)
         * @param layer
         * @param folder
         */
        private void addFeatureCentroids(Layer layer, Folder folder) {
            try {
                SimpleFeatureSource source = (SimpleFeatureSource) ((FeatureLayer) layer).getFeatureSource();
                SimpleFeatureCollection original = source.getFeatures();
                SimpleFeatureCollection centroids = new KMLCentroidFeatureCollection(original);
                context.setCurrentFeatureCollection(centroids);
                FeatureLayer centroidsLayer = new FeatureLayer(centroids, layer.getStyle(), layer.getTitle());
                List<Feature> features = new SequenceList<Feature>(
                        new FeatureSequenceFactory(context, centroidsLayer));
                context.addFeatures(folder, features);
            } catch(IOException e) {
                throw new ServiceException(
                        "Failed to load vector data during KML generation", e);
            }
        }

        /**
         * Encodes the ground overlay for the specified layer
         *
         * @param folder
         * @param layer
         */
        private void addGroundOverlay(Folder folder, Layer layer) {
            int mapLayerOrder = context.getMapContent().layers().indexOf(layer);

            GroundOverlay go = folder.createAndAddGroundOverlay();
            go.setName(layer.getTitle());
            go.setDrawOrder(mapLayerOrder);
            Icon icon = go.createAndSetIcon();
            icon.setHref(getGroundOverlayHRef(layer));
            icon.setViewRefreshMode(ViewRefreshMode.NEVER);
            icon.setViewBoundScale(0.75);

            ReferencedEnvelope box = new ReferencedEnvelope(context.getMapContent()
                    .getRenderingArea());
            boolean reprojectBBox = (box.getCoordinateReferenceSystem() != null)
                    && !CRS.equalsIgnoreMetadata(box.getCoordinateReferenceSystem(),
                            DefaultGeographicCRS.WGS84);
            if (reprojectBBox) {
                try {
                    box = box.transform(DefaultGeographicCRS.WGS84, true);
                } catch (Exception e) {
                    throw new ServiceException("Could not transform bbox to WGS84", e,
                            "ReprojectionError", "");
                }
            }

            LatLonBox gobox = go.createAndSetLatLonBox();
            gobox.setEast(box.getMinX());
            gobox.setWest(box.getMaxX());
            gobox.setNorth(box.getMaxY());
            gobox.setSouth(box.getMinY());
        }

        String getGroundOverlayHRef(Layer layer) {
            WMSMapContent mapContent = context.getMapContent();
            if (context.isKmz()) {
                // embed the ground overlay in the kmz archive
                int mapLayerOrder = mapContent.layers().indexOf(layer);
                String href = "images/layers_" + mapLayerOrder + ".png";
                context.addKmzGroundOverlay(href, layer);
                return href;
            } else {
                // refer to a GetMap request
                return WMSRequests.getGetMapUrl(mapContent.getRequest(), layer, 0,
                        mapContent.getRenderingArea(), new String[] { "format", "image/png",
                                "transparent", "true" });
            }
        }

        /**
         * Determines whether to return a vector (KML) result of the data or to return an image
         * instead. If the kmscore is 100, then the output should always be vector. If the kmscore
         * is 0, it should always be raster. In between, the number of features is weighed against
         * the kmscore value. kmscore determines whether to return the features as vectors, or as
         * one raster image. It is the point, determined by the user, where X number of features is
         * "too many" and the result should be returned as an image instead.
         *
         * kmscore is logarithmic. The higher the value, the more features it takes to make the
         * algorithm return an image. The lower the kmscore, the fewer features it takes to force an
         * image to be returned. (in use, the formula is exponential: as you increase the KMScore
         * value, the number of features required increases exponentially).
         *
         * @param kmscore the score, between 0 and 100, use to determine what output to use
         * @param numFeatures how many features are being rendered
         * @return true: use just kml vectors, false: use raster result
         */
        boolean useVectorOutput(KmlEncodingContext context) {
            // are we in download mode?
            String mode = context.getMode();
            if("refresh".equalsIgnoreCase(mode)) {
                // calculate kmscore to determine if we should write as vectors
                // or pre-render
                int kmscore = context.getKmScore();
   
                if (kmscore == 100) {
                    return true; // vector KML
                }
   
                if (kmscore == 0) {
                    return false; // raster KMZ
                }
   
                // For numbers in between, determine exponentionally based on kmscore value:
                // 10^(kmscore/15)
                // This results in exponential growth.
                // The lowest bound is 1 feature and the highest bound is 3.98 million features
                // The most useful kmscore values are between 20 and 70 (21 and 46000 features
                // respectively)
                // A good default kmscore value is around 40 (464 features)
                double magic = Math.pow(10, kmscore / 15);
   
                int currentSize = context.getCurrentFeatureCollection().size();
                if (currentSize > magic) {
                    return false; // return raster
                } else {
                    return true; // return vector
                }
            } else {
                // download or superoverlay
                return true;
            }
        }

    }

}
TOP

Related Classes of org.geoserver.kml.sequence.PlainFolderSequenceFactory

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.