org.geowebcache.grid.GridSubset.java Source code

Java tutorial

Introduction

Here is the source code for org.geowebcache.grid.GridSubset.java

Source

/**
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @author Arne Kepp, OpenGeo, Copyright 2009
 */
package org.geowebcache.grid;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.geowebcache.GeoWebCacheException;
import org.geowebcache.util.ServletUtils;

/**
 * A GridSubSet is a GridSet + a coverage area
 */
public class GridSubset {

    private final GridSet gridSet;

    // {level}{minx,miny,maxx,maxy,z}
    private final Map<Integer, GridCoverage> gridCoverageLevels;

    private final boolean fullGridSetCoverage;

    private final BoundingBox subSetExtent;

    private final Integer minCachedZoom;

    private final Integer maxCachedZoom;

    protected GridSubset(GridSet gridSet, Map<Integer, GridCoverage> coverages, BoundingBox originalExtent,
            boolean fullCoverage) {
        this(gridSet, coverages, originalExtent, fullCoverage, null, null);
    }

    public GridSubset(GridSet gridSet, Map<Integer, GridCoverage> coverages, BoundingBox originalExtent,
            boolean fullCoverage, Integer minCachedZoom, Integer maxCachedZoom) {
        this.gridSet = gridSet;
        this.gridCoverageLevels = coverages;
        this.subSetExtent = originalExtent;
        this.fullGridSetCoverage = fullCoverage;
        this.minCachedZoom = minCachedZoom;
        this.maxCachedZoom = maxCachedZoom;
    }

    public BoundingBox boundsFromIndex(long[] tileIndex) {
        return gridSet.boundsFromIndex(tileIndex);
    }

    /**
     * Finds the spatial bounding box of a rectangular group of tiles.
     * @param rectangleExtent the rectangle of tiles.  {minx, miny, maxx, maxy} in tile coordinates
     * @return the spatial bounding box in the coordinates of the SRS used by the GridSet
     */
    public BoundingBox boundsFromRectangle(long[] rectangleExtent) {
        return gridSet.boundsFromRectangle(rectangleExtent);
    }

    public long[] closestIndex(BoundingBox tileBounds) throws GridMismatchException {
        return gridSet.closestIndex(tileBounds);
    }

    public long[] closestRectangle(BoundingBox rectangleBounds) {
        return gridSet.closestRectangle(rectangleBounds);
    }

    /**
     * Indicates whether this gridsubset coverage contains the given tile
     * 
     * @param index
     *            the tile index to check for coverage inclusion
     * @return {@code true} if {@code index} is inside this grid subset's coverage, {@code false}
     *         otherwise
     */
    public boolean covers(long[] index) {
        final int level = (int) index[2];
        final long[] coverage = getCoverage(level);
        if (coverage == null) {
            return false;
        }

        if (index[0] >= coverage[0] && index[0] <= coverage[2] && index[1] >= coverage[1]
                && index[1] <= coverage[3]) {
            return true;
        }

        return false;
    }

    public void checkCoverage(long[] index) throws OutsideCoverageException {
        if (covers(index)) {
            return;
        }

        if (index[2] < getZoomStart() || index[2] > getZoomStop()) {
            throw new OutsideCoverageException(index, getZoomStart(), getZoomStop());
        }

        long[] coverage = getCoverage((int) index[2]);
        throw new OutsideCoverageException(index, coverage);
    }

    public void checkTileDimensions(int width, int height) throws TileDimensionsMismatchException {

        if (width != gridSet.getTileWidth() || height != gridSet.getTileHeight()) {
            throw new TileDimensionsMismatchException(width, height, gridSet.getTileWidth(),
                    gridSet.getTileWidth());
        }
    }

    public long[][] expandToMetaFactors(final long[][] coverages, final int[] metaFactors) {
        long[][] ret = ServletUtils.arrayDeepCopy(coverages);

        for (long[] cov : ret) {
            final int z = (int) cov[4];
            final Grid grid = this.gridSet.getGrid(z);
            final long numTilesWide = grid.getNumTilesWide();
            final long numTilesHigh = grid.getNumTilesHigh();

            cov[0] = cov[0] - (cov[0] % metaFactors[0]);
            cov[1] = cov[1] - (cov[1] % metaFactors[1]);

            cov[2] = cov[2] - (cov[2] % metaFactors[0]) + (metaFactors[0] - 1);
            if (cov[2] > numTilesWide) {
                cov[2] = numTilesWide;
            }

            cov[3] = cov[3] - (cov[3] % metaFactors[1]) + (metaFactors[1] - 1);
            if (cov[3] > numTilesHigh) {
                cov[3] = numTilesHigh;
            }
        }

        return ret;
    }

    public long[] getCoverage(int level) {
        GridCoverage gridCoverage = gridCoverageLevels.get(Integer.valueOf(level));
        if (gridCoverage == null) {
            return null;
        }
        long[] coverage = gridCoverage.coverage.clone();
        return coverage;
    }

    public long[][] getCoverages() {
        long[][] ret = new long[gridCoverageLevels.size()][5];

        final int zoomStart = getZoomStart();
        final int zoomStop = getZoomStop();

        for (int level = zoomStart, i = 0; level <= zoomStop; level++, i++) {
            long[] cov = getCoverage(level);
            ret[i] = cov;
        }

        return ret;
    }

    /**
     * Convert pixel size to dots per inch
     * 
     * @return
     */
    public double getDotsPerInch() {
        return (0.0254 / this.gridSet.getPixelSize());
    }

    public BoundingBox getCoverageBounds(int level) {
        long[] coverage = getCoverage(level);
        return gridSet.boundsFromRectangle(coverage);
    }

    // Returns the tightest rectangle that covers the data
    public long[] getCoverageBestFit() {
        int level;
        long[] cov = null;

        final int zoomStart = getZoomStart();
        final int zoomStop = getZoomStop();

        for (level = zoomStop; level > zoomStart; level--) {
            cov = getCoverage(level);

            if (cov[0] == cov[2] && cov[1] == cov[3]) {
                break;
            }
        }

        cov = getCoverage(level);

        return cov;
    }

    public BoundingBox getCoverageBestFitBounds() {
        return boundsFromRectangle(getCoverageBestFit());
    }

    public long[] getCoverageIntersection(long[] reqRectangle) {
        final int level = (int) reqRectangle[4];
        GridCoverage gridCov = gridCoverageLevels.get(Integer.valueOf(level));
        return gridCov.getIntersection(reqRectangle);
    }

    public long[][] getCoverageIntersections(BoundingBox reqBounds) {
        final int zoomStart = getZoomStart();
        final int zoomStop = getZoomStop();

        long[][] ret = new long[1 + zoomStop - zoomStart][5];

        for (int level = zoomStart; level <= zoomStop; level++) {
            ret[level - zoomStart] = getCoverageIntersection(level, reqBounds);
        }
        return ret;
    }

    /**
     * Find the area that covers the given rectangle with tiles from the subset.
     * @param level integer zoom level at which to consider the tiles
     * @param reqBounds BoundingBox to try to cover.
     * @return Array of long, the rectangle in tile coordinates, {minx, miny, maxx, maxy}
     */
    public long[] getCoverageIntersection(int level, BoundingBox reqBounds) {
        long[] reqRectangle = gridSet.closestRectangle(level, reqBounds);
        GridCoverage gridCoverage = gridCoverageLevels.get(Integer.valueOf(level));
        return gridCoverage.getIntersection(reqRectangle);
    }

    public long getGridIndex(String gridId) {

        final int zoomStart = getZoomStart();
        final int zoomStop = getZoomStop();

        for (int index = zoomStart; index <= zoomStop; index++) {
            if (gridSet.getGrid(index).getName().equals(gridId)) {
                return index;
            }
        }

        return -1L;
    }

    public String[] getGridNames() {
        List<String> ret = new ArrayList<String>(gridCoverageLevels.size());

        final int zoomStart = getZoomStart();
        final int zoomStop = getZoomStop();
        for (int i = zoomStart; i <= zoomStop; i++) {
            ret.add(gridSet.getGrid(i).getName());
        }

        return ret.toArray(new String[ret.size()]);
    }

    public GridSet getGridSet() {
        return gridSet;
    }

    public BoundingBox getGridSetBounds() {
        return gridSet.getBounds();
    }

    public long getNumTilesWide(int zoomLevel) {
        return gridSet.getGridLevels()[zoomLevel].getNumTilesWide();
    }

    public long getNumTilesHigh(int zoomLevel) {
        return gridSet.getGridLevels()[zoomLevel].getNumTilesHigh();
    }

    public String getName() {
        return gridSet.getName();
    }

    public BoundingBox getOriginalExtent() {
        if (this.subSetExtent == null) {
            return gridSet.getOriginalExtent();
        }
        return this.subSetExtent;
    }

    public double[] getResolutions() {
        double[] ret = new double[gridCoverageLevels.size()];

        final int zoomStart = getZoomStart();
        final int zoomStop = getZoomStop();

        final Grid[] gridLevels = gridSet.getGridLevels();
        for (int z = zoomStart, i = 0; z <= zoomStop; z++, i++) {
            Grid grid = gridLevels[z];
            ret[i] = grid.getResolution();
        }

        return ret;
    }

    // TODO: this is specific to KML service, move it somewhere on the kml module
    public long[][] getSubGrid(long[] gridLoc) throws GeoWebCacheException {
        final int firstLevel = getZoomStart();
        final int zoomStop = getZoomStop();
        int idx = (int) gridLoc[2];

        long[][] ret = { { -1, -1, -1 }, { -1, -1, -1 }, { -1, -1, -1 }, { -1, -1, -1 } };

        if ((idx - firstLevel + 1) <= zoomStop) {
            // Check whether this grid is doubling
            double resolutionCheck = gridSet.getGridLevels()[idx].getResolution() / 2
                    - gridSet.getGridLevels()[idx + 1].getResolution();

            if (Math.abs(resolutionCheck) > gridSet.getGridLevels()[idx + 1].getResolution() * 0.025) {
                throw new GeoWebCacheException(
                        "The resolution is not decreasing by a factor of two for " + this.getName());
            } else {
                long[] coverage = getCoverage(idx + 1);

                long baseX = gridLoc[0] * 2;
                long baseY = gridLoc[1] * 2;
                long baseZ = idx + 1;

                long[] xOffset = { 0, 1, 0, 1 };
                long[] yOffset = { 0, 0, 1, 1 };

                for (int i = 0; i < 4; i++) {
                    if (baseX + xOffset[i] >= coverage[0] && baseX + xOffset[i] <= coverage[2]
                            && baseY + yOffset[i] >= coverage[1] && baseY + yOffset[i] <= coverage[3]) {

                        ret[i][0] = baseX + xOffset[i];
                        ret[i][1] = baseY + yOffset[i];
                        ret[i][2] = baseZ;
                    }
                }
            }
        }

        return ret;
    }

    /**
     * @return whether the scale is based on CRS84, even though it may not be
     */
    public boolean getScaleWarning() {
        return gridSet.isScaleWarning();
    }

    public SRS getSRS() {
        return gridSet.getSrs();
    }

    public int getTileHeight() {
        return gridSet.getTileHeight();
    }

    public int getTileWidth() {
        return gridSet.getTileWidth();
    }

    /**
     * WMTS is indexed from top left hand corner. We will still return {minx,miny,maxx,maxy}, but
     * note that the y positions have been reversed
     * 
     * @return
     */
    // TODO: this is specific to WMTS, move it somewhere on the wmts module
    // TODO: Does this need to be public?
    public long[][] getWMTSCoverages() {
        long[][] ret = new long[gridCoverageLevels.size()][4];

        final int zoomStop = getZoomStop();
        int zoomStart = getZoomStart();
        for (int i = zoomStart; i <= zoomStop; i++) {
            Grid grid = gridSet.getGrid(i);
            long[] coverage = getCoverage(i);

            /*
             * Both internal and WMTS coordinates start at 0 and run to 1 less than the number of
             * tiles.  In internal coordinates, row 0 is the bottommost, and in WMTS it's the
             * topmost.  So subtract the row number from 1 less than the height to convert.
             */
            long bottomRow = grid.getNumTilesHigh() - 1; // The WMST row number for the bottom row

            long[] cur = { coverage[0], // minX
                    bottomRow - coverage[3], // minY
                    coverage[2], // maxX
                    bottomRow - coverage[1] // maxY
            };

            ret[i - zoomStart] = cur;
        }

        return ret;
    }

    public int getZoomStart() {
        Integer firstLevel = Collections.min(gridCoverageLevels.keySet());
        return firstLevel.intValue();
    }

    public int getZoomStop() {
        Integer maxLevel = Collections.max(gridCoverageLevels.keySet());
        return maxLevel.intValue();
    }

    public Integer getMinCachedZoom() {
        return minCachedZoom;
    }

    public Integer getMaxCachedZoom() {
        return maxCachedZoom;
    }

    /**
     * Whether the Grid Subset equals or exceeds the extent of the Grid Set
     * 
     * @return
     */
    public boolean fullGridSetCoverage() {
        return fullGridSetCoverage;
    }

    public boolean shouldCacheAtZoom(long zoom) {
        boolean shouldCache = true;
        if (minCachedZoom != null) {
            shouldCache = zoom >= minCachedZoom;
        }
        if (shouldCache && maxCachedZoom != null) {
            shouldCache = zoom <= maxCachedZoom;
        }
        return shouldCache;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
}