001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.vfny.geoserver.wms.responses.map.metatile;
006:
007: import java.awt.RenderingHints;
008: import java.awt.image.RenderedImage;
009: import java.io.IOException;
010: import java.io.OutputStream;
011: import java.util.logging.Level;
012: import java.util.logging.Logger;
013:
014: import javax.media.jai.JAI;
015: import javax.media.jai.operator.CropDescriptor;
016:
017: import org.geoserver.platform.GeoServerExtensions;
018: import org.geotools.geometry.jts.ReferencedEnvelope;
019: import org.vfny.geoserver.ServiceException;
020: import org.vfny.geoserver.wms.GetMapProducer;
021: import org.vfny.geoserver.wms.RasterMapProducer;
022: import org.vfny.geoserver.wms.WMSMapContext;
023: import org.vfny.geoserver.wms.WmsException;
024: import org.vfny.geoserver.wms.requests.GetMapRequest;
025: import org.vfny.geoserver.wms.responses.AbstractGetMapProducer;
026: import org.vfny.geoserver.wms.responses.map.metatile.QuickTileCache.MetaTileKey;
027:
028: /**
029: * Wrapping map producer that performs on the fly meta tiling wrapping another
030: * map producer. It will first peek inside a tile cache to see if the requested
031: * tile has already been computed, if so, it'll encode and return that one,
032: * otherwise it'll build a meta tile, split it, and finally encode just the
033: * requested tile, putting the others in the tile cache.
034: *
035: * @author Andrea Aime - TOPP
036: * @author Simone Giannecchini - GeoSolutions
037: */
038: public final class MetatileMapProducer extends AbstractGetMapProducer
039: implements GetMapProducer {
040: /** A logger for this class. */
041: private static final Logger LOGGER = org.geotools.util.logging.Logging
042: .getLogger("org.vfny.geoserver.responses.wms.map.metatile");
043:
044: /** Small number for double equality comparison */
045: public static final double EPS = 1E-6;
046:
047: private GetMapRequest request;
048:
049: private RasterMapProducer delegate;
050:
051: private RenderedImage tile;
052:
053: private static QuickTileCache tileCache;
054:
055: /**
056: * True if the request has the tiled hint, is 256x256 image, and the raw
057: * delegate is a raster one
058: *
059: * @param request
060: * @param delegate
061: * @return
062: */
063: public static boolean isRequestTiled(GetMapRequest request,
064: GetMapProducer delegate) {
065: if (!(request.isTiled() && (request.getTilesOrigin() != null)
066: && (request.getWidth() == 256)
067: && (request.getHeight() == 256) && delegate instanceof RasterMapProducer)) {
068: return false;
069: }
070:
071: return true;
072: }
073:
074: public MetatileMapProducer(GetMapRequest request,
075: RasterMapProducer delegate) {
076: if (tileCache == null) {
077: tileCache = (QuickTileCache) GeoServerExtensions
078: .bean("metaTileCache");
079: }
080: this .request = request;
081: this .delegate = delegate;
082: }
083:
084: public void produceMap() throws WmsException {
085: // get the key that identifies the meta tile. The cache will make sure
086: // two threads asking
087: // for the same tile will get the same key, and thus will synchronize
088: // with each other
089: // (the first eventually builds the meta-tile, the second finds it ready
090: // to be used)
091: QuickTileCache.MetaTileKey key = tileCache
092: .getMetaTileKey(request);
093:
094: synchronized (key) {
095: tile = tileCache.getTile(key, request);
096: if (LOGGER.isLoggable(Level.FINER)) {
097: LOGGER.finer("Looked for meta tile "
098: + key.metaTileCoords.x + ", "
099: + key.metaTileCoords.y + "in cache: "
100: + ((tile != null) ? "hit!" : "miss"));
101: }
102:
103: if (tile == null) {
104: // compute the meta-tile
105: if (LOGGER.isLoggable(Level.FINER)) {
106: LOGGER.finer("Building meta tile "
107: + key.metaTileCoords.x + ", "
108: + key.metaTileCoords.y);
109: }
110:
111: // alter the map definition so that we build a meta-tile instead
112: // of just the tile
113: ReferencedEnvelope origEnv = mapContext
114: .getAreaOfInterest();
115: mapContext.setAreaOfInterest(new ReferencedEnvelope(key
116: .getMetaTileEnvelope(), origEnv
117: .getCoordinateReferenceSystem()));
118: mapContext.setMapWidth(key.getTileSize()
119: * key.getMetaFactor());
120: mapContext.setMapHeight(key.getTileSize()
121: * key.getMetaFactor());
122:
123: // generate, split and cache
124: delegate.setMapContext(mapContext);
125: delegate.produceMap();
126:
127: RenderedImage metaTile = delegate.getImage();
128: RenderedImage[] tiles = split(key, metaTile, mapContext);
129: tileCache.storeTiles(key, tiles);
130: tile = tileCache.getTile(key, request, tiles);
131: }
132: }
133: }
134:
135: // /**
136: // * Splits the tile into a set of tiles, numbered from lower right and
137: // going up so
138: // * that first row is 0,1,2,...,metaTileFactor, and so on.
139: // * In the case of a 3x3 meta-tile, the layout is as follows:
140: // * <pre>
141: // * 6 7 8
142: // * 3 4 5
143: // * 0 1 2
144: // * </pre>
145: // * @param key
146: // * @param metaTile
147: // * @param map
148: // * @return
149: // */
150: // private BufferedImage[] split(MetaTileKey key, BufferedImage metaTile,
151: // WMSMapContext map) {
152: // int metaFactor = key.getMetaFactor();
153: // BufferedImage[] tiles = new BufferedImage[key.getMetaFactor() *
154: // key.getMetaFactor()];
155: // int tileSize = key.getTileSize();
156: //
157: // for (int i = 0; i < metaFactor; i++) {
158: // for (int j = 0; j < metaFactor; j++) {
159: // // TODO: create child writable rasters instead of cloning the images
160: // using
161: // // graphics2d. Should be quite a bit faster and save some memory. Or
162: // else,
163: // // store meta-tiles in the cache directly, and extract children tiles
164: // // on demand (even simpler)
165: // BufferedImage tile;
166: //
167: // // keep the palette if necessary
168: // if (metaTile.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
169: // tile = new BufferedImage(tileSize, tileSize,
170: // BufferedImage.TYPE_BYTE_INDEXED,
171: // (IndexColorModel) metaTile.getColorModel());
172: // } else if (metaTile.getType() == BufferedImage.TYPE_CUSTOM) {
173: // throw new RuntimeException("We don't support custom buffered image
174: // tiling");
175: // } else {
176: // tile = new BufferedImage(tileSize, tileSize, metaTile.getType());
177: // }
178: //
179: // Graphics2D g2d = (Graphics2D) tile.getGraphics();
180: // AffineTransform at = AffineTransform.getTranslateInstance(-j * tileSize,
181: // (-tileSize * (metaFactor - 1)) + (i * tileSize));
182: // setupBackground(g2d, map);
183: // g2d.drawRenderedImage(metaTile, at);
184: // g2d.dispose();
185: // tiles[(i * key.getMetaFactor()) + j] = tile;
186: // }
187: // }
188: //
189: // return tiles;
190: // }
191:
192: /**
193: * Splits the tile into a set of tiles, numbered from lower right and going
194: * up so that first row is 0,1,2,...,metaTileFactor, and so on. In the case
195: * of a 3x3 meta-tile, the layout is as follows:
196: *
197: * <pre>
198: * 6 7 8
199: * 3 4 5
200: * 0 1 2
201: * </pre>
202: *
203: * @param key
204: * @param metaTile
205: * @param map
206: * @return
207: */
208: private RenderedImage[] split(MetaTileKey key,
209: RenderedImage metaTile, WMSMapContext map) {
210: final int metaFactor = key.getMetaFactor();
211: final RenderedImage[] tiles = new RenderedImage[key
212: .getMetaFactor()
213: * key.getMetaFactor()];
214: final int tileSize = key.getTileSize();
215: final RenderingHints no_cache = new RenderingHints(
216: JAI.KEY_TILE_CACHE, null);
217:
218: for (int i = 0; i < metaFactor; i++) {
219: for (int j = 0; j < metaFactor; j++) {
220: int x = j * tileSize;
221: int y = (tileSize * (metaFactor - 1)) - (i * tileSize);
222:
223: tile = CropDescriptor.create(metaTile, new Float(x),
224: new Float(y), new Float(tileSize), new Float(
225: tileSize), no_cache);
226: tiles[(i * key.getMetaFactor()) + j] = tile;
227: }
228: }
229:
230: return tiles;
231: }
232:
233: /**
234: * Have the delegate encode the tile
235: */
236: public void writeTo(OutputStream out) throws ServiceException,
237: IOException {
238: delegate.formatImageOutputStream(tile, out);
239: }
240:
241: public void abort() {
242: delegate.abort();
243: }
244:
245: public String getContentDisposition() {
246: return delegate.getContentDisposition();
247: }
248:
249: public String getContentType() throws IllegalStateException {
250: return delegate.getContentType();
251: }
252: }
|