Package org.geoserver.geosearch

Source Code of org.geoserver.geosearch.LayerSiteMapRestlet

package org.geoserver.geosearch;

import static org.geoserver.ows.util.ResponseUtils.*;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.config.GeoServerInfo;
import org.geoserver.ows.URLMangler.URLType;
import org.geoserver.ows.util.RequestUtils;
import org.geoserver.wms.MapLayerInfo;
import org.geotools.data.DefaultQuery;
import org.geotools.data.jdbc.JDBCUtils;
import org.geotools.feature.FeatureCollection;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.util.logging.Logging;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.StringRepresentation;
import org.springframework.jdbc.support.incrementer.H2SequenceMaxValueIncrementer;

import com.vividsolutions.jts.geom.Envelope;

public class LayerSiteMapRestlet extends GeoServerProxyAwareRestlet{

    private static Logger LOGGER = Logging.getLogger("org.geoserver.geosearch");

    private Catalog myCatalog;
    private String GEOSERVER_URL;
   
    private Namespace SITEMAP = Namespace.getNamespace("http://www.sitemaps.org/schemas/sitemap/0.9");
    private static Namespace GEOSITEMAP = Namespace.getNamespace("geo","http://www.google.com/geo/schemas/sitemap/1.0");

    static final CoordinateReferenceSystem WGS84;

    static final ReferencedEnvelope WORLD_BOUNDS;

    static final double MAX_TILE_WIDTH;

    static {
        try {
            // Common geographic info
            WGS84 = CRS.decode("EPSG:4326");
            WORLD_BOUNDS = new ReferencedEnvelope(
                    new Envelope(180.0, -180.0,90.0, -90.0), WGS84);
            MAX_TILE_WIDTH = WORLD_BOUNDS.getWidth() / 2.0;

            // Make sure that H2 is around
            Class.forName("org.h2.Driver");
        } catch (Exception e) {
            throw new RuntimeException(
                    "Could not initialize the class constants", e);
        }
    }
   
    public void setCatalog(Catalog c){
        myCatalog = c;
    }

    public Catalog getCatalog(){
        return myCatalog;
    }

    public LayerSiteMapRestlet() {
    }

    public void handle(Request request, Response response){
        GEOSERVER_URL = getBaseURL(request);
       
        if (request.getMethod().equals(Method.GET)){
            doGet(request, response);
        } else {
            response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
        }
    }

    /**
     * Performs basic checks, making sure that this layer
     * does indeed support regionating and is supposed to be searchable.
     *
     * Then kicks off the process that creates a document, filling
     * in the details from the H2 database.
     *
     * @param request
     * @param response
     */
    public void doGet(Request request, Response response){
        String layerName = (String)request.getAttributes().get("layer");
        String page = request.getAttributes().containsKey("page")
            ? (String) request.getAttributes().get("page")
            : null;

        // Check that layer exists, and that we allow people to index it
        FeatureTypeInfo fti = getCatalog().getFeatureTypeByName(layerName);
        if(fti == null || ! (Boolean)fti.getMetadata().get("indexingEnabled")) {
            response.setStatus(Status.CLIENT_ERROR_FORBIDDEN);
            LOGGER.log(Level.FINE, "not allowed to publish layername: " + layerName);
            //TODO nice error message
            return;
        }

//        // Do we have a regionating strategy ?
//        if(fti.getRegionateAttribute() == null
//                || fti.getRegionateAttribute().length() == 0) {
//            response.setStatus(Status.CLIENT_ERROR_NOT_FOUND);
//            //TODO nice error message
//            return;
//        }

        if (page != null) {
            try {
                Integer i = Integer.valueOf(page);
                Document d = buildPagedSitemap(layerName, fti, i);
                response.setEntity(new JDOMRepresentation(d));
            } catch (NumberFormatException e) {
                response.setEntity(
                        new StringRepresentation(e.toString(),
                            MediaType.TEXT_PLAIN
                            )
                        );
                response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            }
        } else {
            // All good, we're finally here:
            Document d = buildSitemap(layerName, fti);
            response.setEntity(new JDOMRepresentation(d));
        }
    }
   
    /**
     * Wrapper function that constructs document, which is then passed
     * on to the H2 database which goes and looks for tiles to link to.
     *
     * @param layerName
     * @param fti
     * @return
     */
    private Document buildSitemap(String layerName, FeatureTypeInfo fti) {
        final Document d = new Document();
       
        Element sitemapindex = new Element("sitemapindex", SiteMapIndexRestlet.SITEMAP);
        d.setRootElement(sitemapindex);
       
        try {
            int featurecount = fti.getFeatureSource(null, null).getFeatures().size();
            int pagecount = featurecount / 50000// 50000 features per page
            pagecount += featurecount % 50000 == 0 ? 0 : 1;
           
            for (int i = 1; i <= pagecount; i++) {
                SiteMapIndexRestlet.addSitemap(
                        sitemapindex,
                        buildURL(GEOSERVER_URL,  "rest/layers/" + layerName + "/sitemap-" + i + ".xml", null, URLType.SERVICE)
                       );
            }
        } catch (IOException ioe) {
            //TODO log
        }
       
        return d;
    }

    private Document buildPagedSitemap(String layername, FeatureTypeInfo fti, int page){
        final Document d = new Document();
        Element urlSet = new Element("urlset", SITEMAP);
        urlSet.addNamespaceDeclaration(GEOSITEMAP);
        d.setRootElement(urlSet);

        try {
            DefaultQuery q = new DefaultQuery();
            if (fti.getFeatureSource(null, null).getQueryCapabilities().isOffsetSupported()){
                // surely no one would use a shapefile for more than 50000 features, right?
                q.setStartIndex(1 + 50000 * (page - 1));
                q.setMaxFeatures(50000);
            }

            FeatureCollection col = fti.getFeatureSource(null, null).getFeatures(q);
           
            Iterator fi = col.iterator();

            while (fi.hasNext()){
                try {
                    SimpleFeature f = (SimpleFeature)fi.next();
                    encodeFeatureLink(urlSet, layername, f);
                } catch (Exception e) {
                    e.printStackTrace();
                    // TODO: Log
                }
            }

            col.close(fi);
        } catch (IOException ioe) {
            ioe.printStackTrace();
            // TODO log
        }

        return d;
    }

    private void encodeFeatureLink(Element urlSet, String layername, SimpleFeature f){
        Element urlElement = new Element("url", SITEMAP);
        Element loc = new Element("loc", SITEMAP);
        loc.setText(buildURL(GEOSERVER_URL, "rest/layers/" + layername + "/" + f.getID() + ".kml", null, URLType.SERVICE));
        urlElement.addContent(loc);
        Element geo = new Element("geo", GEOSITEMAP);
        Element geoformat = new Element("format", GEOSITEMAP);
        geoformat.setText("kml");
        geo.addContent(geoformat);
        urlElement.addContent(geo);
        urlSet.addContent(urlElement);
    }
   
    /**
     * Just adds one URL to the sitemap, with geo tags
     *
     * @param urlSet
     * @param url
     */
    private void addTile(Element urlSet, String url) {
        Element urlElement = new Element("url", SITEMAP);
        Element loc = new Element("loc", SITEMAP);
        loc.setText(url);
        urlElement.addContent(loc);


        Element geo = new Element("geo",GEOSITEMAP);
        Element geoformat = new Element("format",GEOSITEMAP);
        geoformat.setText("kml");   
        geo.addContent(geoformat);
        urlElement.addContent(geo);
       
        urlSet.addContent(urlElement);
    }
   
    /**
     * This method has a bit too much content at the moment, but the trouble
     * is really elsewhere. We need a nice way to build the entire hierarchy.
     *
     * This code extracts all the tiles from the H2 database.
     *
     * If it finds none, it will link to the topmost tile.
     *
     * Afterwards, it links to all the tiles one zoomlevel below. The crawler
     * will then add them to the H2 database, thereby expanding the index.
     *
     * @param urlSet
     * @param fti
     * @throws IOException
     */
    private void getTilesFromDatababase(Element urlSet, FeatureTypeInfo fti)
    throws IOException {       
        String dataDir = getCatalog().getResourceLoader()
            .findOrCreateDirectory("geosearch")
            .getCanonicalPath();

        String tableName = fti.getFeatureType().getName() + "_"
            + fti.getMetadata().get("kml.regionateAttribute");

        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
       
        long[] maxCoords = {0,0,0,0,-1};
       
        try {
            conn = DriverManager.getConnection("jdbc:h2:file:" + dataDir
                            + "/h2cache_" + tableName, "geoserver", "geopass");
            st = conn.createStatement();
            rs = st.executeQuery("SELECT x,y,z FROM TILECACHE WHERE FID IS NOT NULL GROUP BY x,y,z ORDER BY z ASC");
           
            while(rs.next()) {
                long[] coords = new long[3];
                coords[0] = rs.getLong(1);
                coords[1] = rs.getLong(2);
                coords[2] = rs.getLong(3);
               
                updateMaxCoords(maxCoords, coords);
                addTile(urlSet, makeUrl(coords, fti));
            }
           
            rs.close();
           
        } catch (SQLException se) {
            LOGGER.severe(se.getMessage());
            se.printStackTrace();
        } finally {
            JDBCUtils.close(st);
            JDBCUtils.close(conn, null, null);
           
            // Check that we got something ?
            if(maxCoords[4] < 0) {
                // Nope. We start from the top.
                long[][] coordss = zoomedOut(fti);
                for(int i=0; i<coordss.length; i++) {
                    addTile(urlSet, makeUrl(coordss[i], fti));
                    updateMaxCoords(maxCoords, coordss[i]);
                }
            }
           
            expandHierarchy(urlSet, fti, maxCoords);
        }
    }
   
   
    private long[][] zoomedOut(FeatureTypeInfo fti) {
        try {
            Envelope env = fti.getLatLonBoundingBox();
            //double[] coords = {env.getMinX(), env.getMinY(), env.getMaxX(), env.getMaxY()};
           
            // World wide case
            if(env.getMinX() < 0.0 && env.getMaxX() > 0.0) {
                long[][] ret = {{0,0,0},{1,0,0}};
                return ret;
            }
           
            long[] nextQuad = new long[3];
            if(env.getMinX() < 0.0) {
                nextQuad[0] = 0; nextQuad[1] = 0; nextQuad[2] = 0;
            } else {
                nextQuad[0] = 1; nextQuad[1] = 0; nextQuad[2] = 0;
            }
           
            long[] prevQuad = null;

            while(nextQuad != null) {               
                // Try each of the quadrants
                long[][] quads = {
                        { nextQuad[0] * 2,  nextQuad[1] * 2,  nextQuad[2] + 1},
                        { nextQuad[0] * 2 + 1,  nextQuad[1] * 2,  nextQuad[2] + 1},
                        { nextQuad[0] * 2,  nextQuad[1] * 2 + 1 ,  nextQuad[2] + 1},
                        { nextQuad[0] * 2 + 1,  nextQuad[1] * 2 + 1,  nextQuad[2] + 1} };
               
                prevQuad = nextQuad;
                nextQuad = null;
               
                for(int i=0; i<quads.length; i++) {
                    ReferencedEnvelope testEnv = envelope(quads[i][0], quads[i][1], quads[i][2]);
                    if(testEnv.contains(env)) {
                        nextQuad = quads[i];
                    }
                }
            }
           
            long[][] ret = {prevQuad};
            return ret;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * The maxCoords describe as far as the hierarchy is built.
     *
     * This expands one level further, which may lead to linking to
     * some 204s, but unless the data is distributed perfectly evenly
     * it will only be a few tiles.
     *
     * @param urlSet
     * @param maxCoords
     */
    private void expandHierarchy(Element urlSet, FeatureTypeInfo fti, long[] maxCoords) {
        long z = maxCoords[4] + 1;
        for(long x=maxCoords[0]; x<=maxCoords[2]; x++) {
            for(long y=maxCoords[1]; y<=maxCoords[3]; y++) {
                long[] bl = {x * 2, y * 2, z};
                long[] br = {x * 2 + 1, y * 2, z};
                long[] tl = {x * 2, y * 2 + 1 , z};
                long[] tr = {x * 2 + 1, y * 2 + 1, z};
               
                addTile(urlSet, makeUrl(bl, fti));
                addTile(urlSet, makeUrl(br, fti));
                addTile(urlSet, makeUrl(tl, fti));
                addTile(urlSet, makeUrl(tr, fti));
            }
        }
    }

    /**
     * Converts x,y,z into an envelope, to be used in the WMS URL 
     *
     * @param x
     * @param y
     * @param z
     * @return
     */
    private ReferencedEnvelope envelope(long x, long y, long z) {
        double tileSize = MAX_TILE_WIDTH / Math.pow(2, z);
        double xMin = x * tileSize + WORLD_BOUNDS.getMinX();
        double yMin = y * tileSize + WORLD_BOUNDS.getMinY();
        return new ReferencedEnvelope(xMin, xMin + tileSize, yMin, yMin
                + tileSize, WGS84);
    }

    /**
     * This keeps track of the bounds and resets every time
     * the zoomlevel changes
     *
     * @param maxCoords
     * @param coords
     */
    private void updateMaxCoords(long[] maxCoords, long[] coords) {
        if(coords[2] > maxCoords[4]) {
            maxCoords[0] = Long.MAX_VALUE;
            maxCoords[1] = Long.MAX_VALUE;
            maxCoords[2] = Long.MIN_VALUE;
            maxCoords[3] = Long.MIN_VALUE;
            maxCoords[4] = coords[2];
        }
       
        if(maxCoords[0] > coords[0]) {
            maxCoords[0] = coords[0];
        }
        if(maxCoords[1] > coords[1]) {
            maxCoords[1] = coords[1];
        }
        if(maxCoords[2] < coords[0]) {
            maxCoords[2] = coords[0];
        }
        if(maxCoords[3] < coords[1]) {
            maxCoords[3] = coords[1];
        }
    }
   
    /**
     * Constructs a WMS URL for the given coordinates
     *
     * @param coords
     * @param fti
     * @return
     */
    private String makeUrl(long[] coords, FeatureTypeInfo fti) {
        // Ok we have the coordinates, now we turn that into a bbox for a WMS query
        ReferencedEnvelope env = envelope(coords[0],coords[1],coords[2]);
       
        Map<String, String> params = params("service", "wms",
                "version", "1.1.0",
                "request", "GetMap",
                "format", "application/vnd.google-earth.kml+xml",
                "exceptions", "application/vnd.ogc.se_inimage",
                "bbox", env.getMinX() + "," + env.getMinY()
                + "," + env.getMaxX() + "," + env.getMaxY(),
                "srs", "EPSG:4326",
                "layers", fti.getName(),
                "width", "256",
                "height", "256");
       
        return buildURL(GEOSERVER_URL, "wms", params, URLType.SERVICE);
    }
}
TOP

Related Classes of org.geoserver.geosearch.LayerSiteMapRestlet

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.