/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2010, Open Source Geospatial Foundation (OSGeo)
*
* 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.geotools.geopkg.mosaic;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.factory.Hints;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.geopkg.GeoPackage;
import org.geotools.geopkg.Tile;
import org.geotools.geopkg.TileEntry;
import org.geotools.geopkg.TileMatrix;
import org.geotools.geopkg.TileReader;
import org.geotools.referencing.CRS;
import org.geotools.util.Utilities;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterValue;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* GeoPackage Grid Reader (supports the GP mosaic datastore).
*
* @author Justin Deoliveira
* @author Niels Charlier
*/
public class GeoPackageReader extends AbstractGridCoverage2DReader {
/** The {@link Logger} for this {@link GeoPackageReader}. */
private final static Logger LOGGER = Logging.getLogger("org.geotools.geopkg.mosaic");
protected final static int DEFAULT_TILE_SIZE = 256;
protected final static int ZOOM_LEVEL_BASE = 2;
protected GridCoverageFactory coverageFactory;
protected File sourceFile;
protected Map<String, TileEntry> tiles = new HashMap<String, TileEntry>();
public GeoPackageReader(Object source, Hints hints) throws IOException {
coverageFactory = CoverageFactoryFinder.getGridCoverageFactory(this.hints);
sourceFile = GeoPackageFormat.getFileFromSource(source);
GeoPackage file = new GeoPackage(sourceFile);
try {
for (TileEntry tile : file.tiles()){
tiles.put(tile.getTableName(), tile);
}
} finally {
file.close();
}
}
@Override
public Format getFormat() {
return new GeoPackageFormat();
}
@Override
protected boolean checkName(String coverageName) {
Utilities.ensureNonNull("coverageName", coverageName);
return tiles.keySet().contains(coverageName);
}
@Override
public GeneralEnvelope getOriginalEnvelope(String coverageName){
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
return new GeneralEnvelope(tiles.get(coverageName).getBounds());
}
@Override
protected double[] getHighestRes(String coverageName){
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
List<TileMatrix> matrices = tiles.get(coverageName).getTileMatricies();
TileMatrix matrix = matrices.get(matrices.size()-1);
return new double[] {matrix.getXPixelSize(), matrix.getYPixelSize()};
}
@Override
public GridEnvelope getOriginalGridRange(String coverageName){
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
List<TileMatrix> matrices = tiles.get(coverageName).getTileMatricies();
TileMatrix matrix = matrices.get(matrices.size()-1);
return new GridEnvelope2D(new Rectangle(matrix.getMatrixWidth() * matrix.getTileWidth(), matrix.getMatrixHeight() * matrix.getTileHeight()));
}
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem(String coverageName) {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
try {
return CRS.decode("EPSG:" + tiles.get(coverageName).getSrid(), true);
} catch (Exception e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
return null;
}
}
@Override
public String[] getGridCoverageNames() {
return tiles.keySet().toArray(new String[tiles.size()]);
}
@Override
public int getGridCoverageCount() {
return tiles.size();
}
@Override
public GridCoverage2D read(String coverageName, GeneralParameterValue[] parameters) throws IllegalArgumentException, IOException {
TileEntry entry = tiles.get(coverageName);
BufferedImage image = null;
ReferencedEnvelope resultEnvelope = null;
GeoPackage file = new GeoPackage(sourceFile);
try {
CoordinateReferenceSystem crs = getCoordinateReferenceSystem(coverageName);
ReferencedEnvelope requestedEnvelope = null;
Rectangle dim = null;
if (parameters != null) {
for (int i = 0; i < parameters.length; i++) {
final ParameterValue param = (ParameterValue) parameters[i];
final ReferenceIdentifier name = param.getDescriptor().getName();
if (name.equals(AbstractGridFormat.READ_GRIDGEOMETRY2D.getName())) {
final GridGeometry2D gg = (GridGeometry2D) param.getValue();
try {
requestedEnvelope = ReferencedEnvelope.create(gg.getEnvelope(), gg.getCoordinateReferenceSystem()).transform(crs, true);;
} catch (Exception e) {
requestedEnvelope = null;
}
dim = gg.getGridRange2D().getBounds();
continue;
}
}
}
int leftTile, bottomTile, rightTile, topTile;
//find the closest zoom based on horizontal resolution
TileMatrix bestMatrix = null;
if (requestedEnvelope != null && dim != null) {
//requested res
double horRes = requestedEnvelope.getSpan(0) / dim.getWidth(); //proportion of total width that is being requested
double worldSpan = crs.getCoordinateSystem().getAxis(0).getMaximumValue() - crs.getCoordinateSystem().getAxis(0).getMinimumValue();
//loop over matrices
double difference = Double.MAX_VALUE;
for (TileMatrix matrix : entry.getTileMatricies()) {
double newRes = worldSpan / (matrix.getMatrixWidth() * matrix.getTileWidth());
double newDifference = Math.abs(horRes - newRes);
if (newDifference < difference) {
difference = newDifference;
bestMatrix = matrix;
}
}
}
if (bestMatrix == null) {
bestMatrix = entry.getTileMatricies().get(0);
}
//take available tiles from database
leftTile = file.getTileBound(entry, bestMatrix.getZoomLevel(), false, false);
rightTile = file.getTileBound(entry, bestMatrix.getZoomLevel(), true, false);
topTile = file.getTileBound(entry, bestMatrix.getZoomLevel(), false, true);
bottomTile = file.getTileBound(entry, bestMatrix.getZoomLevel(), true, true);
double resX = (crs.getCoordinateSystem().getAxis(0).getMaximumValue() - crs.getCoordinateSystem().getAxis(0).getMinimumValue()) / bestMatrix.getMatrixWidth();
double resY = (crs.getCoordinateSystem().getAxis(1).getMaximumValue() - crs.getCoordinateSystem().getAxis(1).getMinimumValue()) / bestMatrix.getMatrixHeight();
double offsetX = crs.getCoordinateSystem().getAxis(0).getMinimumValue();
double offsetY = crs.getCoordinateSystem().getAxis(1).getMinimumValue();
if (requestedEnvelope != null) { //crop tiles to requested envelope
leftTile = Math.max(leftTile, (int) Math.round(Math.floor((requestedEnvelope.getMinimum(0) - offsetX) / resX )));
topTile = Math.max(topTile, (int) Math.round(Math.floor((requestedEnvelope.getMinimum(1) - offsetY) / resY )));
rightTile = Math.max(leftTile, (int) Math.min(rightTile, Math.round(Math.floor((requestedEnvelope.getMaximum(0) - offsetX) / resX ))));
bottomTile = Math.max(topTile, (int) Math.min(bottomTile, Math.round(Math.floor((requestedEnvelope.getMaximum(1) - offsetY) / resY ))));
}
int width = (int) (rightTile - leftTile + 1) * DEFAULT_TILE_SIZE;
int height = (int) (bottomTile - topTile + 1) * DEFAULT_TILE_SIZE;
//recalculate the envelope we are actually returning
resultEnvelope = new ReferencedEnvelope(offsetX + leftTile * resX, offsetX + (rightTile+1) * resX, offsetY + topTile * resY, offsetY + (bottomTile+1) * resY, crs);
TileReader it;
it = file.reader(entry, bestMatrix.getZoomLevel(), bestMatrix.getZoomLevel(), leftTile, rightTile, topTile, bottomTile);
while (it.hasNext()) {
Tile tile = it.next();
BufferedImage tileImage = readImage(tile.getData());
if (image == null) {
image = getStartImage(tileImage, width, height);
}
//coordinates
int posx = (int) (tile.getColumn() - leftTile) * DEFAULT_TILE_SIZE;
int posy = (int) (tile.getRow() - topTile) * DEFAULT_TILE_SIZE;
image.getRaster().setRect(posx, posy, tileImage.getData() );
}
it.close();
if (image == null){ // no tiles ??
image = getStartImage(width, height);
}
}
finally {
file.close();
}
return coverageFactory.create(entry.getTableName(), image, resultEnvelope);
}
protected static BufferedImage readImage(byte[] data) throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
Object source = bis;
ImageInputStream iis = ImageIO.createImageInputStream(source);
Iterator<?> readers = ImageIO.getImageReaders(iis);
ImageReader reader = (ImageReader) readers.next();
reader.setInput(iis, true);
ImageReadParam param = reader.getDefaultReadParam();
return reader.read(0, param);
}
protected BufferedImage getStartImage(BufferedImage copyFrom, int width, int height) {
Map<String, Object> properties = null;
if (copyFrom.getPropertyNames() != null) {
properties = new HashMap<String, Object>();
for (String name : copyFrom.getPropertyNames()) {
properties.put(name, copyFrom.getProperty(name));
}
}
SampleModel sm = copyFrom.getSampleModel().createCompatibleSampleModel(width, height);
WritableRaster raster = Raster.createWritableRaster(sm, null);
BufferedImage image = new BufferedImage(copyFrom.getColorModel(), raster,
copyFrom.isAlphaPremultiplied(), (Hashtable<?, ?>) properties);
//white background
Graphics2D g2D = (Graphics2D) image.getGraphics();
Color save = g2D.getColor();
g2D.setColor(Color.WHITE);
g2D.fillRect(0, 0, image.getWidth(), image.getHeight());
g2D.setColor(save);
return image;
}
protected BufferedImage getStartImage(int imageType, int width, int height) {
if (imageType == BufferedImage.TYPE_CUSTOM)
imageType = BufferedImage.TYPE_3BYTE_BGR;
BufferedImage image = new BufferedImage(width, height, imageType);
//white background
Graphics2D g2D = (Graphics2D) image.getGraphics();
Color save = g2D.getColor();
g2D.setColor(Color.WHITE);
g2D.fillRect(0, 0, image.getWidth(), image.getHeight());
g2D.setColor(save);
return image;
}
protected BufferedImage getStartImage(int width, int height) {
return getStartImage(BufferedImage.TYPE_CUSTOM, width, height);
}
@Override
public GridCoverage2D read(GeneralParameterValue[] parameters) throws IllegalArgumentException,
IOException {
throw new IllegalArgumentException("No layer specified!");
}
}