/**
* 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, Copyright 2010
* @author Arne Kepp, OpenGeo, Copyright 2010
*/
package org.geowebcache.storage;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import org.geowebcache.grid.GridSubset;
public class RasterMask implements TileRangeMask {
/**
* By zoom level bitmasked images where every pixel represents a tile in the level's
* {@link GridSubset#getCoverages() grid coverage}.
*/
private final BufferedImage[] byLevelMasks;
private final long[][] coveredBounds;
private final int maxMaskLevel;
private final int noDataValue;
private long[][] fullCoverage;
/**
* Creates a RasterMask from the given parameters with default {@code noDataValue == 0}
*
* @param byLevelMasks
* @param gridCoverages
* @see #RasterMask(BufferedImage[], long[][], int)
*/
public RasterMask(BufferedImage[] byLevelMasks, long[][] fullCoverage,
final long[][] coveredBounds) {
this(byLevelMasks, fullCoverage, coveredBounds, 0);
}
/**
* Creates a RasterMask based on a set of bitmask images and covered tile grid bounds;
* <p>
* The number of zoom levels is determined by the length of the {@code gridCoverages} array. The
* length of the {@code byLevelMasks} might be lower than the actual zoom levels, meaning the
* values for any zoom level for which a masking image is not provided will be interpolated from
* the higher resolution available one.
* </p>
* <p>
* Also, note each bounding box in {@code gridCoverages} may represent a smaller area than its
* bitmasked image, which represents the whole tile range for the layer at a specific zoom
* level.
* </p>
*
* @param byLevelMasks
* @param fullCoverage
* the full grid subsets coverage, needed to compute downsampled pixel locations
* @param coveredBounds
* by level bounds enclosing the area that has pixels set in the masks
* @param noDatavalue
* raster sample value to be considered as no-data (eg, tile is not set at the pixel
* location)
*/
public RasterMask(BufferedImage[] byLevelMasks, long[][] fullCoverage,
final long[][] coveredBounds, final int noDataValue) {
this.byLevelMasks = byLevelMasks;
this.fullCoverage = fullCoverage;
this.coveredBounds = coveredBounds;
this.maxMaskLevel = byLevelMasks.length - 1;
this.noDataValue = noDataValue;
}
public long[][] getGridCoverages() {
return coveredBounds;
}
public boolean lookup(long[] idx) {
return lookup(idx[0], idx[1], (int) idx[2]);
}
public boolean lookup(final long x, final long y, final int z) {
long tileX = x;
long tileY = y;
int level = z;
long[] coverage = getGridCoverages()[level];
if (tileX < coverage[0] || tileX > coverage[2] || tileY < coverage[1]
|| tileY > coverage[3]) {
return false;
}
if (level > maxMaskLevel) {
// downsample
long[] requestedCoverage = fullCoverage[level];
long[] lastMaskedCoverage = fullCoverage[maxMaskLevel];
double requestedW = requestedCoverage[2] - requestedCoverage[0];
double requestedH = requestedCoverage[3] - requestedCoverage[1];
double availableW = lastMaskedCoverage[2] - lastMaskedCoverage[0];
double availableH = lastMaskedCoverage[3] - lastMaskedCoverage[1];
tileX = Math.round(tileX * (availableW / requestedW));
tileY = Math.round(tileY * (availableH / requestedH));
// tileX = Math.max(Math.min(tileX, lastMaskedCoverage[2]), lastMaskedCoverage[0]);
// tileY = Math.max(Math.min(tileY, lastMaskedCoverage[3]), lastMaskedCoverage[1]);
level = maxMaskLevel;
}
return isTileSet(tileX, tileY, level);
}
private boolean isTileSet(long tileX, long tileY, int level) {
long[] coverage = getGridCoverages()[level];
if (tileX < coverage[0] || tileX > coverage[2] || tileY < coverage[1]
|| tileY > coverage[3]) {
return false;
}
/*
* Use getRaster instead of getData(), getData() returns a copy
*/
final Raster raster = byLevelMasks[level].getRaster();
// Changing index to top left hand origin
long rasx = tileX;
long rasy = (raster.getHeight() - 1) - tileY;
if (rasx < 0 || rasy < 0 || rasx >= raster.getWidth() || rasy >= raster.getHeight()) {
// coverage might include meta tiling factors but raster doesn't!
return false;
}
int sample = raster.getSample((int) rasx, (int) rasy, 0);
return sample != noDataValue;
}
}