//$HeadURL$
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2008 by:
Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
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; either
version 2.1 of the License, or (at your option) any later version.
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.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstr. 19
53177 Bonn
Germany
E-Mail: poth@lat-lon.de
Prof. Dr. Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: greve@giub.uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.igeo.dataadapter;
import java.awt.image.BufferedImage;
import java.awt.image.renderable.ParameterBlock;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Locale;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationBilinear;
import javax.media.jai.JAI;
import javax.media.jai.RenderedOp;
import javax.media.jai.TiledImage;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.framework.util.HttpUtils;
import org.deegree.graphics.transformation.GeoTransform;
import org.deegree.graphics.transformation.WorldToScreenTransform;
import org.deegree.igeo.config.TargetDeviceType;
import org.deegree.igeo.i18n.Messages;
import org.deegree.igeo.io.FileSystemAccess;
import org.deegree.igeo.io.FileSystemAccessFactory;
import org.deegree.igeo.io.RemoteFSAccess;
import org.deegree.igeo.mapmodel.Datasource;
import org.deegree.igeo.mapmodel.FileDatasource;
import org.deegree.igeo.mapmodel.Layer;
import org.deegree.igeo.mapmodel.MapModel;
import org.deegree.igeo.views.DialogFactory;
import org.deegree.igeo.views.swing.util.GenericFileChooser.FILECHOOSERTYPE;
import org.deegree.model.coverage.grid.GridCoverage;
import org.deegree.model.coverage.grid.ImageGridCoverage;
import org.deegree.model.coverage.grid.WorldFile;
import org.deegree.model.coverage.grid.WorldFile.TYPE;
import org.deegree.model.crs.CRSTransformationException;
import org.deegree.model.crs.GeoTransformer;
import org.deegree.model.spatialschema.Envelope;
import org.deegree.model.spatialschema.GeometryFactory;
import com.sun.media.jai.codec.FileSeekableStream;
import com.sun.media.jai.codec.MemoryCacheSeekableStream;
import com.sun.media.jai.codec.SeekableStream;
/**
* concrete {@link GridCoverageAdapter} for read grid coverages from files
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
public class FileGridCoverageAdapter extends GridCoverageAdapter {
private static ILogger LOG = LoggerFactory.getLogger( FileGridCoverageAdapter.class );
private boolean isLazyLoading;
private Envelope currentEnv;
private BufferedImage image;
private TiledImage tiledImage;
private int oldWith;
private int oldHeight;
private Envelope oldEnv;
private Envelope bbox;
/**
*
* @param module
* @param datasource
* @param layer
* @param mapModel
* @param isLazyLoading
*/
FileGridCoverageAdapter( Datasource datasource, Layer layer, MapModel mapModel, boolean isLazyLoading ) {
super( datasource, layer, mapModel );
this.isLazyLoading = isLazyLoading;
oldEnv = GeometryFactory.createEnvelope( -9E9, -9E9, 9E9, 9E9, null );
File file = ( (FileDatasource) datasource ).getFile();
file = getAbsoluteFilePath( file );
URL url = null;
SeekableStream fss = null;
FileSystemAccessFactory fsaf = FileSystemAccessFactory.getInstance( mapModel.getApplicationContainer() );
try {
FileSystemAccess fsa = fsaf.getFileSystemAccess( FILECHOOSERTYPE.geoDataFile );
url = fsa.getFileURL( file.getAbsolutePath() );
fss = new MemoryCacheSeekableStream( url.openStream() );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10070",
file.getAbsolutePath(), e.getMessage() ) );
}
BufferedImage bi;
try {
RenderedOp rop = JAI.create( "stream", fss );
int iw = ( (Integer) rop.getProperty( "image_width" ) ).intValue();
int ih = ( (Integer) rop.getProperty( "image_height" ) ).intValue();
LOG.logInfo( "size of image: " + url + " -> " + iw + "x" + ih );
if ( iw * ih > 50000000 ) {
boolean ok = DialogFactory.openConfirmDialogYESNO( mapModel.getApplicationContainer().getViewPlatform(),
null, Messages.get( "$MD11585" ),
Messages.get( "$MD11586" ) );
if ( !ok ) {
throw new DataAccessException( "Will not load image: " + url + " because it is too large" );
}
}
bi = rop.getAsBufferedImage();
fss.close();
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( e.getMessage() );
}
String fname = findWorldFileName( fsaf, file.getAbsolutePath() );
WorldFile worldFile = null;
try {
FileSystemAccess fsa = fsaf.getFileSystemAccess( FILECHOOSERTYPE.geoDataFile );
url = fsa.getFileURL( fname );
LOG.logDebug( "world file URL: ", url );
worldFile = WorldFile.readWorldFile( url.openStream(), TYPE.CENTER, bi.getWidth(), bi.getHeight() );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10070",
file.getAbsolutePath(), e.getMessage() ) );
}
Envelope env = worldFile.getEnvelope();
LOG.logDebug( "envelope read from world file: ", env );
env = GeometryFactory.createEnvelope( env.getMin(), env.getMax(), datasource.getNativeCoordinateSystem() );
datasource.setExtent( env );
if ( !datasource.getNativeCoordinateSystem().equals( mapModel.getCoordinateSystem() ) ) {
GeoTransformer transformer = new GeoTransformer( mapModel.getCoordinateSystem() );
try {
bbox = transformer.transform( env, datasource.getNativeCoordinateSystem(), true );
// BufferedImage bi = ( (PlanarImage) ri ).getAsBufferedImage();
bi = transformer.transform( bi, env, bbox, mapModel.getTargetDevice().getPixelWidth(),
mapModel.getTargetDevice().getPixelHeight(), 16, 3, null );
currentEnv = bbox;
} catch ( CRSTransformationException e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( e.getMessage() );
}
} else {
bbox = env;
currentEnv = bbox;
}
tiledImage = new TiledImage( bi, 500, 500 );
refresh();
}
private File getAbsoluteFilePath( File file ) {
if ( !file.isAbsolute() ) {
try {
URL url = mapModel.getApplicationContainer().resolve( file.getPath() );
file = new File( url.toExternalForm().substring( 5 ) );
} catch ( MalformedURLException e ) {
// should never happen
LOG.logError( e );
}
}
return file;
}
/**
* @param absolutePath
* @return
*/
private static String findWorldFileName( FileSystemAccessFactory fsaf, String fname ) {
int pos = fname.lastIndexOf( "." );
URL url = null;
FileSystemAccess fsa;
try {
fsa = fsaf.getFileSystemAccess( FILECHOOSERTYPE.geoDataFile );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10070", fname, e.getMessage() ) );
}
if ( fsa instanceof RemoteFSAccess ) {
// if a raster file is access via RemoteFSAccess its world file
// must have extension .wld
return fname.substring( 0, pos ) + ".wld";
}
String[] ext = new String[] { ".tfw", ".wld", ".jgw", ".gfw", ".gifw", ".pgw", ".pngw" };
for ( String extension : ext ) {
String tmp = fname.substring( 0, pos ) + extension;
try {
url = fsa.getFileURL( tmp );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10070", fname,
e.getMessage() ) );
}
try {
HttpUtils.validateURL( url.toExternalForm() );
// worldfile with current extension exist
return tmp;
} catch ( Exception e ) {
// don't do nothing
LOG.logWarning( "", e );
}
}
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10102", fname ) );
}
/**
*
* @return adapted coverage
*/
@Override
public GridCoverage getCoverage() {
TargetDeviceType td = mapModel.getTargetDevice();
if ( isLazyLoading ) {
if ( !mapModel.getEnvelope().equals( oldEnv ) || td.getPixelWidth() != oldWith
|| td.getPixelHeight() != oldHeight ) {
double min = datasource.getMinScaleDenominator();
double max = datasource.getMaxScaleDenominator();
if ( mapModel.getScaleDenominator() >= min && mapModel.getScaleDenominator() < max ) {
// raster data just has to loaded if bounding box of the map model or size of
// target device has been changed
loadRasterSubset();
}
}
} else if ( coverage == null ) {
loadFullRaster();
}
if ( isLazyLoading
&& ( !mapModel.getEnvelope().equals( oldEnv ) || td.getPixelWidth() != oldWith || td.getPixelHeight() != oldHeight ) ) {
double min = datasource.getMinScaleDenominator();
double max = datasource.getMaxScaleDenominator();
// raster data may have to be rescaled if bounding box of the map model or size of
// target device has been changed
if ( ( mapModel.getScaleDenominator() >= min && mapModel.getScaleDenominator() < max )
&& ( td.getPixelWidth() != image.getWidth() || td.getPixelHeight() != image.getHeight() ) ) {
// pixel size of target device is different from image size, the image must
// be rescaled to avoid strange optical effects and increasing rendering performance
float scaleX = ( (float) td.getPixelWidth() ) / (float) image.getWidth();
float scaleY = ( (float) td.getPixelHeight() ) / (float) image.getHeight();
image = scale( image, scaleX, scaleY );
}
}
// store current relevant map model parameters
oldHeight = td.getPixelHeight();
oldWith = td.getPixelWidth();
oldEnv = mapModel.getEnvelope();
this.coverage = new ImageGridCoverage( null, currentEnv, image );
return this.coverage;
}
/**
*
* @param img
* @param interpolation
* @param scaleX
* @param scaleY
* @return the scaled image
*/
private static BufferedImage scale( BufferedImage img, float scaleX, float scaleY ) {
Interpolation interpolation = new InterpolationBilinear();
LOG.logDebug( "Scale image: by factors: " + scaleX + ' ' + scaleY );
ParameterBlock pb = new ParameterBlock();
pb.addSource( img );
pb.add( scaleX ); // The xScale
pb.add( scaleY ); // The yScale
pb.add( 0.0F ); // The x translation
pb.add( 0.0F ); // The y translation
pb.add( interpolation ); // The interpolation
// Create the scale operation
RenderedOp ro = JAI.create( "scale", pb, null );
try {
img = ro.getAsBufferedImage();
} catch ( Exception e ) {
e.printStackTrace();
}
return img;
}
/**
* loads entire raster source (image) and sets current envelope for the adapted grid coverage to the envelope read
* from assigned world file
*
*/
private void loadFullRaster() {
image = tiledImage.getAsBufferedImage();
}
/**
* loads subset matching the current map models boundingbox
*
*/
private void loadRasterSubset() {
Envelope modelBbox = mapModel.getEnvelope();
if ( modelBbox.contains( bbox ) ) {
// if current bounding box of the map model contains the complete source image
// no processing is required. So image will be read and current envelope will
// be set to envelope read from the images world file
image = tiledImage.getAsBufferedImage();
currentEnv = bbox;
} else if ( modelBbox.intersects( bbox ) ) {
Envelope intersection = modelBbox.createIntersection( bbox );
Envelope full = bbox;
SeekableStream fss = null;
try {
fss = new FileSeekableStream( ( (FileDatasource) datasource ).getFile().getAbsoluteFile() );
} catch ( IOException e ) {
// never happens
LOG.logWarning( "", e );
}
RenderedOp rop = JAI.create( "stream", fss );
int iw = ( (Integer) rop.getProperty( "image_width" ) ).intValue();
int ih = ( (Integer) rop.getProperty( "image_height" ) ).intValue();
GeoTransform gt = new WorldToScreenTransform( full.getMin().getX(), full.getMin().getY(),
full.getMax().getX(), full.getMax().getY(), 0, 0, iw, ih );
int x1 = (int) Math.round( gt.getDestX( intersection.getMin().getX() ) );
int y1 = (int) Math.round( gt.getDestY( intersection.getMax().getY() ) );
int x2 = (int) Math.round( gt.getDestX( intersection.getMax().getX() ) );
int y2 = (int) Math.round( gt.getDestY( intersection.getMin().getY() ) );
image = tiledImage.getSubImage( x1, y1, x2 - x1, y2 - y1 ).getAsBufferedImage();
} else {
image = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB );
}
this.coverage = new ImageGridCoverage( null, modelBbox, image );
}
@Override
public void refresh() {
if ( !this.isLazyLoading ) {
refreshRaster();
}
}
private void refreshRaster() {
loadFullRaster();
layer.setDataRefreshed( this );
}
@Override
public void refresh( boolean forceReload ) {
if ( forceReload ) {
refreshRaster();
} else {
refresh();
}
}
@Override
public void commitChanges()
throws IOException {
// TODO Auto-generated method stub
}
@Override
public void invalidate() {
this.tiledImage = null;
this.image = null;
super.invalidate();
}
}