/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012, Geomatys
*
* This library 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;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.display2d.service;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.image.*;
import java.util.Deque;
import java.util.EventListener;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.logging.Level;
import javax.media.jai.RasterFactory;
import javax.swing.event.EventListenerList;
import org.geotoolkit.storage.coverage.CoverageUtilities;
import org.geotoolkit.display.PortrayalException;
import org.geotoolkit.display2d.GO2Hints;
import org.geotoolkit.display2d.canvas.J2DCanvasBuffered;
import org.apache.sis.geometry.GeneralEnvelope;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.util.logging.Logging;
/**
* Implementation of {@link RenderedImage} that is computed on the fly using portrayal rendering.
* This implementation is mostly used to tile a portrayal context in a @{org.geotoolkit.coverage.GridMosaic}.
*
* @author Johann Sorel (Geomatys)
* @see org.geotoolkit.display2d.process.pyramid.MapcontextPyramidProcess
*/
public class PortrayalRenderedImage implements RenderedImage{
/** store pregenerated tiles */
// private final Map<Integer,Raster> tileCache = new HashMap<Integer, Raster>();
private final ColorModel colorModel;
private final SampleModel sampleModel;
private final Dimension gridSize;
private final Dimension tileSize;
private final double scale;
/**
* UpperLeft 2D from ViewDef Envelope.
*/
private final Point2D upperleft;
// private final J2DCanvasBuffered canvas;
private final int nbtileonwidth;
private final int nbtileonheight;
/**
* Index of first geographical axis
*/
private final int minOrdi0;
/**
* Index of first geographical axis
*/
private final int minOrdi1;
/**
* ViewDef envelope.
* Used to fill non geographic range in CanvasDef request Envelope.
*/
private final Envelope viewEnvelope;
/**
* ViewDef envelope CRS.
*/
private final CoordinateReferenceSystem crs;
/**
* Portrayal CanvasDef.
*/
private final CanvasDef canvasDef;
/**
* Portrayal SceneDef that contain Layers.
*/
private final SceneDef sceneDef;
/**
* Portrayal ViewDef with requested envelope.
*/
private final ViewDef viewDef;
/** listener support */
private final EventListenerList listeners = new EventListenerList();
private final Deque<J2DCanvasBuffered> canvas = new ConcurrentLinkedDeque<J2DCanvasBuffered>();
/**
*
* @param canvasDef : canvas size will be ignored.
* @param sceneDef
* @param viewDef
* @param gridSize
* @param tileSize
*/
public PortrayalRenderedImage(final CanvasDef canvasDef, final SceneDef sceneDef, final ViewDef viewDef,
final Dimension gridSize, final Dimension tileSize, final double scale) throws PortrayalException{
this.gridSize = gridSize;
this.tileSize = tileSize;
this.scale = scale;
this.colorModel = ColorModel.getRGBdefault();
this.sampleModel = colorModel.createCompatibleSampleModel(1, 1);
this.canvasDef = canvasDef;
this.sceneDef = sceneDef;
this.viewDef = viewDef;
this.viewEnvelope = viewDef.getEnvelope();
crs = viewEnvelope.getCoordinateReferenceSystem();
this.upperleft = new Point2D.Double(
viewEnvelope.getMinimum(0),
viewEnvelope.getMaximum(1));
this.minOrdi0 = CoverageUtilities.getMinOrdinate(crs);
this.minOrdi1 = minOrdi0 + 1;
nbtileonheight = 1;
nbtileonwidth = 1;
}
/**
* Tiles are generated on the fly, so we have information on their generation
* process but we don't have the tiles themselves.
*
* @return empty vector
*/
@Override
public Vector<RenderedImage> getSources() {
return new Vector<RenderedImage>();
}
/**
* A PortrayalRenderedImage does not have any properties
*
* @param name
* @return always Image.UndefinedProperty
*/
@Override
public Object getProperty(String name) {
return Image.UndefinedProperty;
}
/**
* A PortrayalRenderedImage does not have any properties
*
* @return always null
*/
@Override
public String[] getPropertyNames() {
return null;
}
/**
* Fallback on the mosaic definition.
*
* @return mosaic grid size width * mosaic tile size width.
*/
@Override
public int getWidth() {
return gridSize.width * tileSize.height;
}
/**
* Fallback on the mosaic definition.
*
* @return mosaic grid size width * mosaic tile size width.
*/
@Override
public int getHeight() {
return gridSize.height * tileSize.width;
}
/**
* Generated tiles start at zero.
*
* @return 0
*/
@Override
public int getMinX() {
return 0;
}
/**
* Generated tiles start at zero.
*
* @return 0
*/
@Override
public int getMinY() {
return 0;
}
/**
* Fallback on the mosaic definition.
*
* @return mosaic grid size width.
*/
@Override
public int getNumXTiles() {
return gridSize.width;
}
/**
* Fallback on the mosaic definition.
*
* @return mosaic grid size height.
*/
@Override
public int getNumYTiles() {
return gridSize.height;
}
/**
* Generated tiles start at zero.
*
* @return 0
*/
@Override
public int getMinTileX() {
return 0;
}
/**
* Generated tiles start at zero.
*
* @return 0
*/
@Override
public int getMinTileY() {
return 0;
}
/**
* Fallback on the mosaic definition.
*
* @return mosaic tile size width.
*/
@Override
public int getTileWidth() {
return tileSize.width;
}
/**
* Fallback on the mosaic definition.
*
* @return mosaic tile size height.
*/
@Override
public int getTileHeight() {
return tileSize.height;
}
/**
* Generated tiles start at zero.
*
* @return 0
*/
@Override
public int getTileGridXOffset() {
return 0;
}
/**
* Generated tiles start at zero.
*
* @return 0
*/
@Override
public int getTileGridYOffset() {
return 0;
}
/**
* Returns the image's bounds as a <code>Rectangle</code>.
*
* <p> The image's bounds are defined by the values returned by
* <code>getMinX()</code>, <code>getMinY()</code>,
* <code>getWidth()</code>, and <code>getHeight()</code>.
* A <code>Rectangle</code> is created based on these four methods.
*
* @return Rectangle
*/
public Rectangle getBounds() {
return new Rectangle(getMinX(), getMinY(), getWidth(), getHeight());
}
@Override
public ColorModel getColorModel() {
return colorModel;
}
@Override
public SampleModel getSampleModel() {
return sampleModel;
}
@Override
public Raster getTile(int col, int row) {
final double tilespanX = scale*tileSize.width;
final double tilespanY = scale*tileSize.height;
final GeneralEnvelope canvasEnv = new GeneralEnvelope(crs);
int nbDim = canvasEnv.getDimension();
for (int d = 0; d < nbDim; d++) {
if (d == minOrdi0) {
double minX = upperleft.getX() + (col) * tilespanX;
double maxX = upperleft.getX() + (col+nbtileonwidth) * tilespanX;
canvasEnv.setRange(d, minX, maxX);
} else if (d == minOrdi1) {
double minY = upperleft.getY() - (row+nbtileonheight) * tilespanY;
double maxY = upperleft.getY() - (row) * tilespanY;
canvasEnv.setRange(d, minY, maxY);
} else {
//other dimensions
canvasEnv.setRange(d, viewEnvelope.getMinimum(d), viewEnvelope.getMaximum(d));
}
}
J2DCanvasBuffered cvs = canvas.poll();
try {
if(cvs == null){
cvs = new J2DCanvasBuffered(
crs, new Dimension(tileSize.width,tileSize.height));
cvs.setRenderingHint(GO2Hints.KEY_COLOR_MODEL, colorModel);
DefaultPortrayalService.prepareCanvas(cvs, canvasDef, sceneDef, viewDef);
}
cvs.setVisibleArea(canvasEnv);
} catch (NoninvertibleTransformException | TransformException | PortrayalException ex) {
Logging.getLogger("org.geotoolkit.display2d.service").log(Level.SEVERE, null, ex);
}
//cut the canvas buffer in pieces
cvs.repaint();
final BufferedImage canvasBuffer = cvs.getSnapShot();
final Raster data = canvasBuffer.getData(); // make a copy since we will reuse canvas
fireTileCreated(col,row);
canvas.push(cvs);
return data;
}
@Override
public Raster getData() {
return getData(null);
}
@Override
public WritableRaster getData(Rectangle region) {
return copyData(region, null);
}
@Override
public WritableRaster copyData(WritableRaster raster) {
final Rectangle bounds = (raster!=null) ? raster.getBounds() : null;
return copyData(bounds, raster);
}
public WritableRaster copyData(Rectangle region, WritableRaster dstRaster) {
final Rectangle bounds = getBounds(); // image's bounds
if (region == null) {
region = bounds;
} else if (!region.intersects(bounds)) {
throw new IllegalArgumentException("Rectangle does not intersect datas.");
}
// Get the intersection of the region and the image bounds.
final Rectangle xsect = (region == bounds) ? region : region.intersection(bounds);
//create a raster of this size
if(dstRaster == null){
SampleModel sampleModel = getSampleModel();
sampleModel = sampleModel.createCompatibleSampleModel(xsect.width, xsect.height);
dstRaster = RasterFactory.createWritableRaster(sampleModel, new Point(0, 0));
}
//calculate the first and last tiles index we will need
final int startTileX = xsect.x / getTileWidth();
final int startTileY = xsect.y / getTileHeight();
final int endTileX = (xsect.x+xsect.width) / getTileWidth();
final int endTileY = (xsect.y+xsect.height) / getTileHeight();
//loop on each tile
for (int j = startTileY; j <= endTileY; j++) {
for (int i = startTileX; i <= endTileX; i++) {
final Raster tile = getTile(i, j);
dstRaster.setRect(
i*getTileWidth(),
j*getTileHeight(),
tile);
}
}
return dstRaster;
}
/**
* @return unique index for this tile coordinate
*/
private int getTileIndex(int col, int row){
return row*getNumXTiles() + col;
}
protected void fireTileCreated(int x, int y){
for(ProgressListener l : listeners.getListeners(ProgressListener.class)){
l.tileCreated(x, y);
}
}
public void addProgressListener(ProgressListener listener){
listeners.add(ProgressListener.class, listener);
}
public void removeProgressListener(ProgressListener listener){
listeners.remove(ProgressListener.class, listener);
}
public static interface ProgressListener extends EventListener{
void tileCreated(int x, int y);
}
}