org.geowebcache.diskquota.storage.PagePyramid.java Source code

Java tutorial

Introduction

Here is the source code for org.geowebcache.diskquota.storage.PagePyramid.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 Gabriel Roldan (OpenGeo) 2010
 *  
 */
package org.geowebcache.diskquota.storage;

import java.math.BigInteger;
import java.text.NumberFormat;
import java.util.Locale;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.grid.GridSubset;
import org.geowebcache.storage.TileRange;
import org.springframework.util.Assert;

/**
 * Pyramid of tile pages for a given {@link GridSubset}
 * <p>
 * This is a support class for {@link TilePageCalculator}, hence package visible.
 * </p>
 * 
 * @author groldan
 */
class PagePyramid {

    private static final Log log = LogFactory.getLog(PagePyramid.class);

    /**
     * {@code [level][numTilesPerPageX, numTilesPerPageY, numPagesX, numPagesY]}
     */
    private PageLevelInfo[] pageInfo;

    private long[][] gridSubsetCoverages;

    private final int zoomStart;

    private final int zoomStop;

    public static final class PageLevelInfo {

        public final int pagesX;

        public final int pagesY;

        public final int tilesPerPageX;

        public final int tilesPerPageY;

        public final BigInteger tilesPerPage;

        public final long coverageMinX;

        public final long coverageMinY;

        public final long coverageMaxX;

        public final long coverageMaxY;

        public PageLevelInfo(int pagesX, int pagesY, int tilesPerPageX, int tilesPerPageY, long coverageMinX,
                long coverageMinY, long coverageMaxX, long coverageMaxY) {
            this.pagesX = pagesX;
            this.pagesY = pagesY;
            this.tilesPerPageX = tilesPerPageX;
            this.tilesPerPageY = tilesPerPageY;
            this.tilesPerPage = BigInteger.valueOf(tilesPerPageX).multiply(BigInteger.valueOf(tilesPerPageY));
            this.coverageMinX = coverageMinX;
            this.coverageMinY = coverageMinY;
            this.coverageMaxX = coverageMaxX;
            this.coverageMaxY = coverageMaxY;
        }

        @Override
        public String toString() {
            NumberFormat nf = NumberFormat.getInstance(new Locale("es"));
            nf.setGroupingUsed(true);

            return "Pages: " + pagesX + " x " + pagesY + " (" + nf.format(pagesX * pagesY) + "), " + "tiles:"
                    + tilesPerPageX + " x " + tilesPerPageY + " (" + nf.format(tilesPerPageX * (long) tilesPerPageY)
                    + ")";
        }
    }

    /**
     * @param gridSubsetCoverages
     *            grid subset coverage per level, as per {@link GridSubset#getCoverages()}
     * @param zoomStop
     * @param zoomStart
     */
    public PagePyramid(final long[][] gridSubsetCoverages, int zoomStart, int zoomStop) {
        this.gridSubsetCoverages = gridSubsetCoverages;
        this.zoomStart = zoomStart;
        this.zoomStop = zoomStop;
        this.pageInfo = new PageLevelInfo[gridSubsetCoverages.length];
    }

    public int getZoomStart() {
        return zoomStart;
    }

    public int getZoomStop() {
        return zoomStop;
    }

    public PageLevelInfo getPageInfo(final int zoomLevel) {
        Assert.isTrue(zoomLevel >= zoomStart);
        Assert.isTrue(zoomLevel <= zoomStop);

        PageLevelInfo levelInfo = pageInfo[zoomLevel];
        if (levelInfo == null) {
            long[] coverage = this.gridSubsetCoverages[zoomLevel];
            levelInfo = calculatePageInfo(coverage);
            pageInfo[zoomLevel] = levelInfo;
        }

        return levelInfo;
    }

    /**
     * 
     * @param coverage
     *            {@code [minx, miny, maxx, maxy, zoomlevel]} gridsubset coverage for a given zoom
     *            level
     * @return {@code [numTilesPerPageX, numTilesPerPageY, numPagesX, numPagesY]} number of pages in
     *         both directions for the given coverage
     */
    public PageLevelInfo calculatePageInfo(final long[] coverage) {

        final int level = (int) coverage[4];
        final long coverageMinX = coverage[0];
        final long coverageMaxX = coverage[2];
        final long coverageMinY = coverage[1];
        final long coverageMaxY = coverage[3];

        final long coverageTilesWide = 1 + coverageMaxX - coverageMinX;
        final long coverageTilesHigh = 1 + coverageMaxY - coverageMinY;

        final int tilesPerPageX = calculateNumTilesPerPage(coverageTilesWide);
        final int tilesPerPageY = calculateNumTilesPerPage(coverageTilesHigh);
        final int numPagesX = (int) Math.ceil((double) coverageTilesWide / tilesPerPageX);
        final int numPagesY = (int) Math.ceil((double) coverageTilesHigh / tilesPerPageY);

        PageLevelInfo pli = new PageLevelInfo(numPagesX, numPagesY, tilesPerPageX, tilesPerPageY, coverageMinX,
                coverageMinY, coverageMaxX, coverageMaxY);

        // if (log.isDebugEnabled()) {
        // log.debug("Coverage: " + Arrays.toString(coverage) + " (" + coverageTilesWide + "x"
        // + coverageTilesHigh + ") tiles. Tiles perpage: " + tilesPerPageX + " x "
        // + tilesPerPageY + " for a total of " + numPagesX + " x " + numPagesY
        // + " pages and " + (tilesPerPageX * (long) tilesPerPageY) + " tiles per page");
        // }
        return pli;
    }

    /**
     * Calculates the number of tiles per page for a gridset coverage over one of its axes (this
     * method doesn't care which of them, either X or Y), client code knows which zoom level it
     * belongs too.
     * <p>
     * The number of pages for each zoom level is different, and currently calculated on a
     * logarithmic basis.
     * </p>
     * 
     * @param numTilesInAxis
     *            number of tiles in either the x or y axis for the {@link GridSubset} coverage at
     *            one of its zoom levels
     * @return the number of pages corresponding to {@code numTilesInAxis}
     */
    private int calculateNumTilesPerPage(long numTilesInAxis) {
        /*
         * Found log base 1.3 gives a pretty decent progression of number of pages for zoom level
         */
        final double logBase = 1.1;
        // Calculate log base <logBase>
        final double log = (Math.log(numTilesInAxis) / Math.log(logBase));

        // log(1) == 0, so be careful
        final int numTilesPerPage = numTilesInAxis == 1 ? 1 : (int) Math.ceil((numTilesInAxis / log));
        return numTilesPerPage;
    }

    public int[] pageIndexForTile(long x, long y, int level, int[] pageIndexTarget) {
        Assert.notNull(pageIndexTarget);
        Assert.isTrue(pageIndexTarget.length >= 3);

        PageLevelInfo levelInfo = getPageInfo(level);
        final int tilePageX = (int) ((x - levelInfo.coverageMinX) / levelInfo.tilesPerPageX);
        final int tilePageY = (int) ((y - levelInfo.coverageMinY) / levelInfo.tilesPerPageY);

        pageIndexTarget[0] = tilePageX;
        pageIndexTarget[1] = tilePageY;
        pageIndexTarget[2] = level;

        return pageIndexTarget;
    }

    /**
     * Returns a grid subset coverage range suitable for {@link TileRange}
     * 
     * @param page
     * @return {@code [minTileX, minTileY, maxTileX, maxTileY, zoomlevel]}
     */
    public long[][] toGridCoverage(int pageX, int pageY, int level) {

        final PageLevelInfo pageLevelInfo = getPageInfo(level);

        final long coverageMinX = pageLevelInfo.coverageMinX;
        final long coverageMinY = pageLevelInfo.coverageMinY;
        final int numTilesPerPageX = pageLevelInfo.tilesPerPageX;
        final int numTilesPerPageY = pageLevelInfo.tilesPerPageY;

        long minTileX = coverageMinX + (long) pageX * numTilesPerPageX;
        long minTileY = coverageMinY + (long) pageY * numTilesPerPageY;
        long maxTileX = minTileX + numTilesPerPageX - 1;// these are indexes, so rest one
        long maxTileY = minTileY + numTilesPerPageY - 1;// same thing

        long[] pageCoverage = { minTileX, minTileY, maxTileX, maxTileY, level };

        final int numLevels = pageInfo.length;
        long[][] allLevelsCoverage = new long[numLevels][];
        allLevelsCoverage[level] = pageCoverage;
        return allLevelsCoverage;
    }

    public int getTilesPerPageX(int level) {
        return getPageInfo(level).tilesPerPageX;
    }

    public int getTilesPerPageY(int level) {
        return getPageInfo(level).tilesPerPageY;
    }

    public int getPagesPerLevelX(int level) {
        return getPageInfo(level).pagesX;
    }

    public int getPagesPerLevelY(int level) {
        return getPageInfo(level).pagesY;
    }
}