org.geogig.geoserver.gwc.TruncateHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.geogig.geoserver.gwc.TruncateHelper.java

Source

/* (c) 2016 Open Source Geospatial Foundation - all rights reserved
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geogig.geoserver.gwc;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.gwc.layer.GeoServerTileLayer;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geowebcache.GeoWebCacheException;
import org.geowebcache.filter.parameters.ParameterFilter;
import org.geowebcache.grid.Grid;
import org.geowebcache.grid.GridSubset;
import org.geowebcache.grid.SRS;
import org.geowebcache.layer.TileLayer;
import org.geowebcache.mime.MimeType;
import org.geowebcache.seed.GWCTask;
import org.geowebcache.seed.GWCTask.TYPE;
import org.geowebcache.seed.TileBreeder;
import org.geowebcache.storage.DiscontinuousTileRange;
import org.geowebcache.storage.TileRange;
import org.geowebcache.storage.TileRangeMask;
import org.locationtech.geogig.model.ObjectId;
import org.locationtech.geogig.model.Ref;
import org.locationtech.geogig.repository.Context;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.google.common.base.Stopwatch;
import com.google.common.base.Throwables;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.WKTWriter;
import com.vividsolutions.jts.operation.buffer.BufferOp;
import com.vividsolutions.jts.operation.buffer.BufferParameters;
import com.vividsolutions.jts.simplify.TopologyPreservingSimplifier;

class TruncateHelper {

    private static final Logger LOGGER = LoggerFactory.getLogger(TruncateHelper.class);

    public static void issueTruncateTasks(Context context, Optional<Ref> oldRef, Optional<Ref> newRef,
            GeoServerTileLayer tileLayer, TileBreeder breeder) {

        final ObjectId oldCommit = oldRef.isPresent() ? oldRef.get().getObjectId() : ObjectId.NULL;
        final ObjectId newCommit = newRef.isPresent() ? newRef.get().getObjectId() : ObjectId.NULL;

        final String tileLayerName = tileLayer.getName();
        final String layerTreeName = tileLayer.getLayerInfo().getResource().getNativeName();

        LOGGER.debug(
                String.format("Computing minimal bounds geometry on layer '%s' (tree '%s') for change %s...%s ",
                        tileLayerName, layerTreeName, oldCommit, newCommit));
        final Geometry minimalBounds;
        Stopwatch sw = Stopwatch.createStarted();
        try {
            MinimalDiffBounds geomBuildCommand = context.command(MinimalDiffBounds.class)
                    .setOldVersion(oldCommit.toString()).setNewVersion(newCommit.toString());

            geomBuildCommand.setTreeNameFilter(layerTreeName);

            minimalBounds = geomBuildCommand.call();
            sw.stop();
            if (minimalBounds.isEmpty()) {
                LOGGER.debug(String.format("Feature tree '%s' not affected by change %s...%s (took %s)",
                        layerTreeName, oldCommit, newCommit, sw));
                return;
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(String.format("Minimal bounds on layer '%s' computed in %s: %s", tileLayerName, sw,
                        formattedWKT(minimalBounds)));
            }
        } catch (Exception e) {
            sw.stop();
            LOGGER.error(String.format("Error computing minimal bounds for %s...%s on layer '%s' after %s",
                    oldCommit, newCommit, tileLayerName, sw));
            throw Throwables.propagate(e);
        }
        final Set<String> gridSubsets = tileLayer.getGridSubsets();

        LayerInfo layerInfo = tileLayer.getLayerInfo();
        ResourceInfo resource = layerInfo.getResource();
        final CoordinateReferenceSystem sourceCrs;
        {
            CoordinateReferenceSystem nativeCrs = resource.getNativeCRS();
            if (nativeCrs == null) {
                // no native CRS specified, layer must have been configured with an overriding one
                sourceCrs = resource.getCRS();
            } else {
                sourceCrs = nativeCrs;
            }
        }
        for (String gridsetId : gridSubsets) {
            GridSubset gridSubset = tileLayer.getGridSubset(gridsetId);
            final CoordinateReferenceSystem gridSetCrs = getGridsetCrs(gridSubset);

            LOGGER.debug("Reprojecting geometry mask to gridset {}", gridsetId);
            Geometry geomInGridsetCrs = transformToGridsetCrs(minimalBounds, sourceCrs, gridSetCrs);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("geometry mask reprojected to gridset {}: {}", gridsetId,
                        formattedWKT(geomInGridsetCrs));
            }
            geomInGridsetCrs = bufferAndSimplifyBySizeOfSmallerTile(geomInGridsetCrs, gridSetCrs, gridSubset);
            try {
                truncate(tileLayer, gridsetId, geomInGridsetCrs, breeder);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static Geometry bufferAndSimplifyBySizeOfSmallerTile(Geometry geomInGridsetCrs,
            CoordinateReferenceSystem gridSetCrs, GridSubset gridSubset) {

        double bufferRatio;
        // try {
        // Unit<?> axisUnit = gridSetCrs.getCoordinateSystem().getAxis(0).getUnit();
        // bufferRatio = toMeters(axisUnit, 1);
        // } catch (RuntimeException e) {
        // }
        //
        Integer zoomStop = gridSubset.getMaxCachedZoom();
        if (zoomStop == null) {
            zoomStop = gridSubset.getGridSet().getNumLevels() - 1;
        }
        // we know some degenerate gridsets have like 30+ zoom levels and nobody could really seed
        // them to that level where the resolution is sub-millimetric. 18 is usually a safe option
        zoomStop = Math.min(18, zoomStop);

        Grid grid = gridSubset.getGridSet().getGrid(zoomStop);
        double width = grid.getResolution() * gridSubset.getTileWidth();
        double height = grid.getResolution() * gridSubset.getTileHeight();

        // buffer by the length of two tiles at the finest zoom level
        bufferRatio = 2 * Math.max(width, height);

        // create a buffer with no rounded joins
        BufferParameters bp = new BufferParameters();
        bp.setEndCapStyle(BufferParameters.CAP_SQUARE);
        bp.setJoinStyle(BufferParameters.JOIN_MITRE);
        BufferOp bufferOp = new BufferOp(geomInGridsetCrs, bp);
        Geometry geometry = bufferOp.getResultGeometry(bufferRatio);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Geometry buffered by the size of a tile at zoom level %s (%s units): %s",
                    zoomStop, bufferRatio, formattedWKT(geometry)));
        }
        TopologyPreservingSimplifier simplifier = new TopologyPreservingSimplifier(geometry);
        simplifier.setDistanceTolerance(bufferRatio / 2);
        geometry = simplifier.getResultGeometry();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Simplified geometry: {}", formattedWKT(geometry));
        }
        return geometry;
    }

    /**
     * A one-line WKT may be too large, this function returns a multiline formatted one
     */
    private static Object formattedWKT(Geometry geometry) {
        WKTWriter w = new WKTWriter();
        w.setFormatted(true);
        w.setMaxCoordinatesPerLine(100);
        return w.write(geometry);
    }

    private static void truncate(final GeoServerTileLayer tileLayer, final String gridsetId,
            final Geometry geomInGridsetCrs, final TileBreeder breeder) {

        final List<MimeType> mimeTypes = tileLayer.getMimeTypes();
        final Set<String> cachedStyles = getCachedStyles(tileLayer);

        final String defaultStyle = tileLayer.getStyles();

        GridSubset gridSubset = tileLayer.getGridSubset(gridsetId);

        for (String style : cachedStyles) {
            Map<String, String> parameters;
            if (style.isEmpty() || style.equals(defaultStyle)) {
                parameters = null;
            } else {
                parameters = Collections.singletonMap("STYLES", style);
            }

            for (MimeType mime : mimeTypes) {
                truncate(breeder, tileLayer, gridSubset, mime, parameters, geomInGridsetCrs);
            }
        }
    }

    private static void truncate(TileBreeder breeder, GeoServerTileLayer tileLayer, GridSubset gridSubset,
            MimeType mimeType, Map<String, String> parameters, Geometry geomInGridsetCrs) {

        Integer zoomStart = gridSubset.getMinCachedZoom();
        Integer zoomStop = gridSubset.getMaxCachedZoom();
        if (zoomStart == null) {
            zoomStart = 0;
        }
        if (zoomStop == null) {
            zoomStop = gridSubset.getGridSet().getNumLevels() - 1;
        }

        TileRangeMask rasterMask = GeometryTileRangeMask.build(tileLayer, gridSubset, geomInGridsetCrs);

        String layerName = tileLayer.getName();
        String gridSetId = gridSubset.getName();

        TileRange tileRange = new DiscontinuousTileRange(layerName, gridSetId, zoomStart, zoomStop, rasterMask,
                mimeType, parameters);

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("Truncating layer %s#%s#%s with geom mask %s", layerName, gridSetId,
                    mimeType.getFormat(), formattedWKT(geomInGridsetCrs)));
        }
        try {
            GWCTask[] tasks = breeder.createTasks(tileRange, TYPE.TRUNCATE, 1, false);
            breeder.dispatchTasks(tasks);
        } catch (GeoWebCacheException e) {
            throw Throwables.propagate(e);
        }

    }

    private static Geometry transformToGridsetCrs(Geometry minimalBounds, CoordinateReferenceSystem defaultCrs,
            CoordinateReferenceSystem gridSetCrs) {

        Geometry geomInGridsetCrs;
        try {
            MathTransform transform = CRS.findMathTransform(defaultCrs, gridSetCrs);
            geomInGridsetCrs = JTS.transform(minimalBounds, transform);
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }

        return geomInGridsetCrs;
    }

    private static CoordinateReferenceSystem getGridsetCrs(GridSubset gridSubset) {
        final CoordinateReferenceSystem gridSetCrs;

        SRS srs = gridSubset.getGridSet().getSrs();
        try {
            int epsgCode = srs.getNumber();
            String epsgId = "EPSG:" + epsgCode;
            boolean longitudeFirst = true;// as used by geoserver
            gridSetCrs = CRS.decode(epsgId, longitudeFirst);
        } catch (Exception e) {
            throw new RuntimeException("Can't decode SRS  ESPG:" + srs.getNumber());
        }
        return gridSetCrs;
    }

    private static Set<String> getCachedStyles(final TileLayer l) {
        Set<String> cachedStyles = new HashSet<String>();
        String defaultStyle = l.getStyles();
        if (defaultStyle != null) {
            cachedStyles.add(defaultStyle);
        }
        List<ParameterFilter> parameterFilters = l.getParameterFilters();
        if (parameterFilters != null) {
            for (ParameterFilter pf : parameterFilters) {
                if (!"STYLES".equalsIgnoreCase(pf.getKey())) {
                    continue;
                }
                cachedStyles.add(pf.getDefaultValue());
                cachedStyles.addAll(pf.getLegalValues());
                break;
            }
        }
        if (cachedStyles.isEmpty()) {
            cachedStyles.add("");
        }
        return cachedStyles;
    }
}