/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.gwc.layer;
import static com.google.common.base.Preconditions.checkNotNull;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.io.OutputStream;
import javax.media.jai.PlanarImage;
import org.geoserver.gwc.GWC;
import org.geoserver.ows.Response;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.WebMap;
import org.geoserver.wms.map.RawMap;
import org.geoserver.wms.map.RenderedImageMap;
import org.geoserver.wms.map.RenderedImageMapResponse;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.image.ImageWorker;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geowebcache.grid.BoundingBox;
import org.geowebcache.grid.GridSubset;
import org.geowebcache.io.Resource;
import org.geowebcache.layer.MetaTile;
import org.geowebcache.mime.FormatModifier;
import org.geowebcache.mime.MimeType;
import it.geosolutions.jaiext.BufferedImageAdapter;
public class GeoServerMetaTile extends MetaTile {
private WebMap metaTileMap;
public GeoServerMetaTile(GridSubset gridSubset, MimeType responseFormat,
FormatModifier formatModifier, long[] tileGridPosition, int metaX, int metaY,
Integer gutter) {
super(gridSubset, responseFormat, formatModifier, tileGridPosition, metaX, metaY, gutter);
}
public void setWebMap(WebMap webMap) {
this.metaTileMap = webMap;
if (webMap instanceof RenderedImageMap) {
setImage(((RenderedImageMap) webMap).getImage());
}
}
/**
* Creates the {@link RenderedImage} corresponding to the tile at index {@code tileIdx} and uses
* a {@link RenderedImageMapResponse} to encode it into the {@link #getResponseFormat() response
* format}.
*
* @see org.geowebcache.layer.MetaTile#writeTileToStream(int, org.geowebcache.io.Resource)
* @see RenderedImageMapResponse#write
*
*/
@Override
public boolean writeTileToStream(final int tileIdx, Resource target) throws IOException {
checkNotNull(metaTileMap, "webMap is not set");
if (metaTileMap instanceof RawMap) {
OutputStream outStream = target.getOutputStream();
try {
((RawMap) metaTileMap).writeTo(outStream);
} finally {
outStream.close();
}
return true;
}
if (!(metaTileMap instanceof RenderedImageMap)) {
throw new IllegalArgumentException("Only RenderedImageMaps are supported so far: "
+ metaTileMap.getClass().getName());
}
final RenderedImageMap metaTileMap = (RenderedImageMap) this.metaTileMap;
final RenderedImageMapResponse mapEncoder;
{
final GWC mediator = GWC.get();
final Response responseEncoder = mediator.getResponseEncoder(responseFormat,
metaTileMap);
mapEncoder = (RenderedImageMapResponse) responseEncoder;
}
RenderedImage tile = metaTileMap.getImage();
WMSMapContent tileContext = metaTileMap.getMapContext();
if (this.tiles.length > 1 || (this.tiles.length == 1 && metaHasGutter())) {
final Rectangle tileDim = this.tiles[tileIdx];
tile = createTile(tileDim.x, tileDim.y, tileDim.width, tileDim.height);
disposeLater(tile);
{
final WMSMapContent metaTileContext = metaTileMap.getMapContext();
// do not create tileContext with metaTileContext.getLayers() as the layer list.
// It is not needed at this stage and the constructor would force a
// MapLayer.getBounds() that might fail
tileContext = new WMSMapContent();
tileContext.setRequest(metaTileContext.getRequest());
tileContext.setBgColor(metaTileContext.getBgColor());
tileContext.setMapWidth(tileDim.width);
tileContext.setMapHeight(tileDim.height);
tileContext.setPalette(metaTileContext.getPalette());
tileContext.setTransparent(tileContext.isTransparent());
long[][] tileIndexes = getTilesGridPositions();
BoundingBox tileBounds = gridSubset.boundsFromIndex(tileIndexes[tileIdx]);
ReferencedEnvelope tilebbox = new ReferencedEnvelope(
metaTileContext.getCoordinateReferenceSystem());
tilebbox.init(tileBounds.getMinX(), tileBounds.getMaxX(), tileBounds.getMinY(),
tileBounds.getMaxY());
tileContext.getViewport().setBounds(tilebbox);
}
}
OutputStream outStream = target.getOutputStream();
try {
// call formatImageOuputStream instead of write to avoid disposition of rendered images
// when processing a tile from a metatile and instead defer it to this class' dispose()
// method
mapEncoder.formatImageOutputStream(tile, outStream, tileContext);
return true;
} finally {
outStream.close();
}
}
/**
* Checks if this meta tile has a gutter, or not
*
*/
private boolean metaHasGutter() {
if(this.gutter == null) {
return false;
}
for (int element : gutter) {
if(element > 0) {
return true;
}
}
return false;
}
/**
* Overrides to use the same method to slice the tiles than {@code MetatileMapOutputFormat} so
* the GeoServer settings such as use native accel are leveraged in the same way when calling
* {@link RenderedImageMapResponse#formatImageOutputStream},
*
* @see org.geowebcache.layer.MetaTile#createTile(int, int, int, int)
*/
@Override
public RenderedImage createTile(final int x, final int y, final int tileWidth,
final int tileHeight) {
// check image type
final int type;
if (metaTileImage instanceof PlanarImage) {
type = 1;
} else if (metaTileImage instanceof BufferedImage) {
type = 2;
} else {
type = 0;
}
// now do the splitting
RenderedImage tile;
switch (type) {
case 0:
// do a crop, and then turn it into a buffered image so that we can release
// the image chain
ImageWorker w = new ImageWorker(metaTileImage);
w.crop(Float.valueOf(x), Float.valueOf(y), Float.valueOf(tileWidth), Float.valueOf(tileHeight));
tile = w.getBufferedImage();
disposeLater(w.getRenderedImage());
break;
case 1:
final PlanarImage pImage = (PlanarImage) metaTileImage;
final WritableRaster wTile = WritableRaster.createWritableRaster(pImage
.getSampleModel().createCompatibleSampleModel(tileWidth, tileHeight),
new Point(x, y));
Rectangle sourceArea = new Rectangle(x, y, tileWidth, tileHeight);
sourceArea = sourceArea.intersection(pImage.getBounds());
// copying the data to ensure we don't have side effects when we clean the cache
pImage.copyData(wTile);
if (wTile.getMinX() != 0 || wTile.getMinY() != 0) {
tile = new BufferedImage(pImage.getColorModel(),
(WritableRaster) wTile.createTranslatedChild(0, 0), pImage.getColorModel()
.isAlphaPremultiplied(), null);
} else {
tile = new BufferedImage(pImage.getColorModel(), wTile, pImage.getColorModel()
.isAlphaPremultiplied(), null);
}
break;
case 2:
final BufferedImage image = (BufferedImage) metaTileImage;
final BufferedImage subimage = image.getSubimage(x, y, tileWidth, tileHeight);
tile = new BufferedImageAdapter(subimage);
break;
default:
throw new IllegalStateException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
"metaTile class", metaTileImage.getClass().toString()));
}
return tile;
}
@Override
public void dispose() {
if (metaTileMap != null) {
metaTileMap.dispose();
metaTileMap = null;
}
super.dispose();
}
}