/* Copyright (c) 2001 - 2011 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.gwc.layer; 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 javax.media.jai.RenderedOp; import org.geoserver.ows.Response; import org.geoserver.wms.WMSMapContent; import org.geoserver.wms.map.RenderedImageMap; import org.geoserver.wms.map.RenderedImageMapResponse; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.image.crop.GTCropDescriptor; 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 org.springframework.util.Assert; public class GeoServerMetaTile extends MetaTile { private RenderedImageMap metaTileMap; private final String layer; private final CatalogConfiguration mediator; public GeoServerMetaTile(final String layer, GridSubset gridSubset, MimeType responseFormat, FormatModifier formatModifier, long[] tileGridPosition, int metaX, int metaY, Integer gutter, CatalogConfiguration mediator) { super(gridSubset, responseFormat, formatModifier, tileGridPosition, metaX, metaY, gutter); this.layer = layer; this.mediator = mediator; } public void setWebMap(RenderedImageMap 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 { Assert.notNull(metaTileMap, "webMap is not set"); if (!(metaTileMap instanceof RenderedImageMap)) { throw new IllegalArgumentException("Only RenderedImageMaps are supported so far: " + metaTileMap.getClass().getName()); } final RenderedImageMapResponse mapEncoder; { final Response responseEncoder = mediator.getResponseEncoder(responseFormat, metaTileMap); mapEncoder = (RenderedImageMapResponse) responseEncoder; } RenderedImage tile = metaTileMap.getImage(); WMSMapContent tileContext = metaTileMap.getMapContext(); if (this.tiles.length > 1) { 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.setPaletteInverter(metaTileContext.getPaletteInverter()); 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(); } } /** * 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 RenderedOp cropped = GTCropDescriptor .create(metaTileImage, Float.valueOf(x), Float.valueOf(y), Float.valueOf(tileWidth), Float.valueOf(tileHeight), NO_CACHE); tile = cropped.getAsBufferedImage(); disposeLater(cropped); 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; tile = image.getSubimage(x, y, tileWidth, tileHeight); 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(); } }