001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/tools/raster/RasterTreeBuilder.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2007 by:
006 EXSE, Department of Geography, University of Bonn
007 http://www.giub.uni-bonn.de/deegree/
008 lat/lon GmbH
009 http://www.lat-lon.de
010
011 This library is free software; you can redistribute it and/or
012 modify it under the terms of the GNU Lesser General Public
013 License as published by the Free Software Foundation; either
014 version 2.1 of the License, or (at your option) any later version.
015
016 This library is distributed in the hope that it will be useful,
017 but WITHOUT ANY WARRANTY; without even the implied warranty of
018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 Lesser General Public License for more details.
020
021 You should have received a copy of the GNU Lesser General Public
022 License along with this library; if not, write to the Free Software
023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024
025 Contact:
026
027 Andreas Poth
028 lat/lon GmbH
029 Aennchenstraße 19
030 53177 Bonn
031 Germany
032 E-Mail: poth@lat-lon.de
033
034 Prof. Dr. Klaus Greve
035 Department of Geography
036 University of Bonn
037 Meckenheimer Allee 166
038 53115 Bonn
039 Germany
040 E-Mail: greve@giub.uni-bonn.de
041
042 ---------------------------------------------------------------------------*/
043
044 package org.deegree.tools.raster;
045
046 import java.awt.Color;
047 import java.awt.Graphics;
048 import java.awt.color.ColorSpace;
049 import java.awt.image.BufferedImage;
050 import java.awt.image.ColorModel;
051 import java.awt.image.ComponentColorModel;
052 import java.awt.image.DataBuffer;
053 import java.awt.image.Raster;
054 import java.awt.image.WritableRaster;
055 import java.awt.image.renderable.ParameterBlock;
056 import java.io.File;
057 import java.io.FileOutputStream;
058 import java.io.FileWriter;
059 import java.io.FilenameFilter;
060 import java.io.IOException;
061 import java.io.InputStreamReader;
062 import java.io.PrintWriter;
063 import java.io.Reader;
064 import java.net.URI;
065 import java.net.URL;
066 import java.util.ArrayList;
067 import java.util.Arrays;
068 import java.util.Comparator;
069 import java.util.HashMap;
070 import java.util.Hashtable;
071 import java.util.Iterator;
072 import java.util.List;
073 import java.util.Map;
074 import java.util.Properties;
075
076 import javax.media.jai.Interpolation;
077 import javax.media.jai.InterpolationBicubic;
078 import javax.media.jai.InterpolationBicubic2;
079 import javax.media.jai.InterpolationBilinear;
080 import javax.media.jai.InterpolationNearest;
081 import javax.media.jai.JAI;
082 import javax.media.jai.RenderedOp;
083 import javax.media.jai.TiledImage;
084
085 import net.sf.ehcache.Cache;
086 import net.sf.ehcache.CacheManager;
087 import net.sf.ehcache.Element;
088 import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
089
090 import org.deegree.datatypes.QualifiedName;
091 import org.deegree.datatypes.Types;
092 import org.deegree.framework.log.ILogger;
093 import org.deegree.framework.log.LoggerFactory;
094 import org.deegree.framework.util.ImageUtils;
095 import org.deegree.framework.util.StringTools;
096 import org.deegree.framework.xml.XMLFragment;
097 import org.deegree.framework.xml.XSLTDocument;
098 import org.deegree.graphics.transformation.GeoTransform;
099 import org.deegree.graphics.transformation.WorldToScreenTransform;
100 import org.deegree.io.dbaseapi.DBaseFile;
101 import org.deegree.io.shpapi.ShapeFile;
102 import org.deegree.model.coverage.grid.GridCoverageExchange;
103 import org.deegree.model.coverage.grid.WorldFile;
104 import org.deegree.model.crs.GeoTransformer;
105 import org.deegree.model.crs.IGeoTransformer;
106 import org.deegree.model.feature.Feature;
107 import org.deegree.model.feature.FeatureCollection;
108 import org.deegree.model.feature.FeatureFactory;
109 import org.deegree.model.feature.FeatureProperty;
110 import org.deegree.model.feature.schema.FeatureType;
111 import org.deegree.model.feature.schema.PropertyType;
112 import org.deegree.model.spatialschema.Envelope;
113 import org.deegree.model.spatialschema.Geometry;
114 import org.deegree.model.spatialschema.GeometryFactory;
115 import org.deegree.ogcbase.CommonNamespaces;
116 import org.deegree.processing.raster.converter.Image2RawData;
117 import org.deegree.processing.raster.converter.RawData2Image;
118
119 import com.sun.media.jai.codec.FileSeekableStream;
120
121 /**
122 * This class represents a <code>RasterTreeBuilder</code> object.<br>
123 * It wcan be used to create a resolution pyramid from one or more already existing raster dataset
124 * (image). The resulting pyramid will be described by a set of shapes (containing the image tiles
125 * bounding boxes) and a XML coverage description document that can be used with the deegree WCS.
126 * The RTB supports real images like png, tif, jpeg, bmp and gif as well as raw data image like
127 * 16Bit and 32Bit tif-images without color model. <br>
128 * because of the large amount of data that may be process by the RTB it makes use of a caching
129 * mechnism. For this the ehcache project is used. One can configure the cache behavior by placing a
130 * file named ehcache.xml defining a cache named 'imgCache' within the class root when starting the
131 * RTB. (For details please see the ehcache documentation). If no ehcache.xml is available default
132 * cache configuration will be used which is set to:
133 * <ul>
134 * <li>maxElementsInMemory = 10
135 * <li>memoryStoreEvictionPolicy = LFU
136 * <li>overflowToDisk = false (notice that overflow to disk is not supported because cached objects
137 * are not serializable)
138 * <li>eternal = false
139 * <li>timeToLiveSeconds = 3600
140 * <li>timeToIdleSeconds = 3600
141 * </ul>
142 *
143 *
144 * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a>
145 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
146 * @author last edited by: $Author: apoth $
147 *
148 * @version 2.0, $Revision: 7590 $, $Date: 2007-06-19 11:30:24 +0200 (Di, 19 Jun 2007) $
149 *
150 * @since 2.0
151 */
152 public class RasterTreeBuilder {
153
154 private static final ILogger LOG = LoggerFactory.getLogger( RasterTreeBuilder.class );
155
156 private static final URI DEEGREEAPP = CommonNamespaces.buildNSURI( "http://www.deegree.org/app" );
157
158 private static final String APP_PREFIX = "app";
159
160 // templates and transformation scripts
161 private URL configURL = RasterTreeBuilder.class.getResource( "template_wcs_configuration.xml" );
162
163 private URL configXSL = RasterTreeBuilder.class.getResource( "updateConfig.xsl" );
164
165 private URL inputXSL = RasterTreeBuilder.class.getResource( "updateCapabilities.xsl" );
166
167 private int bitDepth = 16;
168
169 // input for new MergeRaste object
170 private List<String> imageFiles;
171
172 private List<WorldFile> imageFilesEnvs;
173
174 private Map<String, String> imageFilesErrors;
175
176 private String outputDir;
177
178 private String baseName;
179
180 private String outputFormat;
181
182 private double maxTileSize;
183
184 private String srs = null;
185
186 private Interpolation interpolation = null;
187
188 private WorldFile.TYPE worldFileType = null;
189
190 private float quality = 0;
191
192 private String bgColor = null;
193
194 private float offset = 0;
195
196 private float scaleFactor = 1;
197
198 // minimum resolution of input images
199 private double minimumRes;
200
201 // combining image bounding box
202 private Envelope combiningEnvelope;
203
204 // size of virtual bounding box in px
205 private long pxWidthVirtualBBox;
206
207 private long pxHeightVirtualBBox;
208
209 // size of every tile in virtual bounding box in px
210 private long pxWidthTile;
211
212 private long pxHeightTile;
213
214 // number of tiles in virtual bounding box
215 private int tileRows;
216
217 private int tileCols;
218
219 private FeatureType ftype = null;
220
221 private FeatureCollection fc = null;
222
223 private Cache imgCache;
224
225 /**
226 *
227 * @param imageFiles
228 * @param outputDir
229 * @param baseName
230 * @param outputFormat
231 * @param maxTileSize
232 * @param srs
233 * @param interpolation
234 * @param worldFileType
235 * @param quality
236 * @param bgColor
237 * @param depth
238 * @param resolution
239 * @param offset
240 * @param scaleFactor
241 */
242 public RasterTreeBuilder( List<String> imageFiles, String outputDir, String baseName, String outputFormat,
243 double maxTileSize, String srs, String interpolation, WorldFile.TYPE worldFileType,
244 float quality, String bgColor, int depth, double resolution, float offset,
245 float scaleFactor ) {
246
247 this.imageFiles = imageFiles;
248 this.imageFilesErrors = new HashMap<String, String>( imageFiles.size() );
249 this.imageFilesEnvs = new ArrayList<WorldFile>( imageFiles.size() );
250 for ( int i = 0; i < imageFiles.size(); i++ ) {
251 this.imageFilesEnvs.add( null );
252 }
253 this.outputDir = outputDir;
254 File dir = new File( outputDir ).getAbsoluteFile();
255 if ( !dir.exists() ) {
256 dir.mkdir();
257 }
258 this.baseName = baseName;
259 this.outputFormat = outputFormat.toLowerCase();
260 this.maxTileSize = maxTileSize;
261 this.srs = srs;
262 this.interpolation = createInterpolation( interpolation );
263 this.worldFileType = worldFileType;
264 this.quality = quality;
265 this.bgColor = bgColor;
266 if ( depth != 0 ) {
267 this.bitDepth = depth;
268 }
269 this.minimumRes = resolution;
270 this.offset = offset;
271 this.scaleFactor = scaleFactor;
272
273 CacheManager singletonManager = CacheManager.create();
274 if ( singletonManager.getCache( "imgCache" ) == null ) {
275 Cache cache = new Cache( "imgCache", 10, MemoryStoreEvictionPolicy.LFU, false, ".", false, 3600, 3600,
276 false, 240, null );
277 singletonManager.addCache( cache );
278 imgCache = singletonManager.getCache( "imgCache" );
279 } else {
280 imgCache = singletonManager.getCache( "imgCache" );
281 try {
282 imgCache.removeAll();
283 } catch ( IOException e ) {
284 e.printStackTrace();
285 }
286 }
287
288 PropertyType[] ftp = new PropertyType[3];
289 ftp[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, false );
290 ftp[1] = FeatureFactory.createSimplePropertyType(
291 new QualifiedName( GridCoverageExchange.SHAPE_IMAGE_FILENAME ),
292 Types.VARCHAR, false );
293 ftp[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( GridCoverageExchange.SHAPE_DIR_NAME ),
294 Types.VARCHAR, false );
295 ftype = FeatureFactory.createFeatureType( new QualifiedName( "tiles" ), false, ftp );
296 }
297
298 /**
299 * @throws IOException
300 */
301 public void logCollectedErrors()
302 throws IOException {
303 FileOutputStream fos = new FileOutputStream( "RasterTreeBuilder" + minimumRes + ".log" );
304 PrintWriter pw = new PrintWriter( fos );
305 pw.println( "processing the following files caused an error" );
306 Iterator<String> iter = imageFilesErrors.keySet().iterator();
307 while ( iter.hasNext() ) {
308 String key = iter.next();
309 String value = imageFilesErrors.get( key );
310 pw.print( key );
311 pw.print( ": " );
312 pw.println( value );
313 }
314 pw.close();
315 LOG.logInfo( "LOG file RasterTreeBuilder.log has been written" );
316 }
317
318 /**
319 * starts creating of a raster tile level using the current bbox and resolution
320 *
321 * @throws Exception
322 */
323 public void start()
324 throws Exception {
325 System.gc();
326 fc = FeatureFactory.createFeatureCollection( Double.toString( minimumRes ), tileRows * tileCols );
327 createTiles( tileRows, tileCols );
328
329 LOG.logInfo( "creating shape for georeferencing ... " );
330 ShapeFile sf = new ShapeFile( outputDir + "/sh" + minimumRes, "rw" );
331 sf.writeShape( fc );
332 sf.close();
333
334 }
335
336 /**
337 * @param env
338 * @param resolution
339 */
340 public void init( Envelope env, double resolution ) {
341
342 // set target envelope
343 setEnvelope( env );
344 setResolution( resolution );
345 determineVirtualBBox();
346 determineTileSize();
347 }
348
349 /**
350 * sets the resolution level to be used for tiling
351 *
352 * @param resolution
353 */
354 public void setResolution( double resolution ) {
355 minimumRes = resolution;
356 }
357
358 /**
359 * sets the bounding box used for tiling
360 *
361 * @param bbox
362 */
363 public void setEnvelope( Envelope bbox ) {
364 combiningEnvelope = bbox;
365 }
366
367 /**
368 * TODO this is a copy from org.deegree.tools.raster#AutoTiler
369 *
370 * loads the base image
371 *
372 * @throws IOException
373 */
374 private TiledImage loadImage( String imageSource )
375 throws IOException {
376
377 TiledImage ti = null;
378 Element elem = imgCache.get( imageSource );
379 if ( elem != null ) {
380 ti = (TiledImage) elem.getObjectValue();
381 }
382
383 if ( ti == null ) {
384 System.out.println( "cache size: " + imgCache.getSize() );
385 System.out.println( "read image: " + imageSource );
386
387 FileSeekableStream fss = new FileSeekableStream( imageSource );
388 RenderedOp rop = JAI.create( "stream", fss );
389 BufferedImage bi = rop.getAsBufferedImage();
390 try {
391 fss.close();
392 } catch ( IOException e ) {
393 // should never happen
394 }
395 ti = new TiledImage( bi, 500, 500 );
396 imgCache.put( new Element( imageSource, ti ) );
397 }
398
399 return ti;
400 }
401
402 /**
403 * Determins the necessary size of a bounding box, which is large enough to hold all input image
404 * files. The result is stored in the combining <code>Envelope</code>.
405 *
406 * @throws Exception
407 */
408 private WorldFile determineCombiningBBox()
409 throws Exception {
410
411 System.out.println( "calculating overall bounding box ..." );
412
413 if ( imageFiles == null || imageFiles.isEmpty() ) {
414 throw new Exception( "No combining BoundingBox to be determined: "
415 + "The list of image files is null or empty." );
416 }
417
418 WorldFile wf1 = null;
419 if ( combiningEnvelope == null ) {
420
421 // upper left corner of combining bounding box
422 double minX = Double.MAX_VALUE;
423 double maxY = Double.MIN_VALUE;
424 // lower right corner of combining bounding box
425 double maxX = Double.MIN_VALUE;
426 double minY = Double.MAX_VALUE;
427 // minimum resolution within combining bounding box
428 double minResX = Double.MAX_VALUE;
429 double minResY = Double.MAX_VALUE;
430
431 for ( int i = 0; i < imageFiles.size(); i++ ) {
432
433 File file = new File( imageFiles.get( i ) );
434 if ( file.exists() && !file.isDirectory() ) {
435 System.out.println( imageFiles.get( i ) );
436 FileSeekableStream fss = new FileSeekableStream( imageFiles.get( i ) );
437 RenderedOp rop = JAI.create( "stream", fss );
438 int iw = ( (Integer) rop.getProperty( "image_width" ) ).intValue();
439 int ih = ( (Integer) rop.getProperty( "image_height" ) ).intValue();
440 fss.close();
441
442 WorldFile wf = null;
443
444 try {
445 wf = WorldFile.readWorldFile( imageFiles.get( i ), worldFileType, iw, ih );
446 } catch ( Exception e ) {
447 LOG.logError( e.getMessage() );
448 continue;
449 }
450 imageFilesEnvs.set( i, wf );
451 // now the values of resx, resy, envelope of the current image
452 // (read from the world file) file are available
453
454 // find min for x and y
455 minX = Math.min( minX, wf.getEnvelope().getMin().getX() );
456 minY = Math.min( minY, wf.getEnvelope().getMin().getY() );
457 // find max for x and y
458 maxX = Math.max( maxX, wf.getEnvelope().getMax().getX() );
459 maxY = Math.max( maxY, wf.getEnvelope().getMax().getY() );
460
461 // find min for resolution of x and y
462 minResX = Math.min( minResX, wf.getResx() );
463 minResY = Math.min( minResY, wf.getResy() );
464 } else {
465 System.out.println( "File: " + imageFiles.get( i ) + " does not exist!" );
466 System.out.println( "Image will be ignored" );
467 }
468 if ( i % 10 == 0 ) {
469 System.gc();
470 }
471
472 }
473 // store minimum resolution
474 if ( minimumRes <= 0 ) {
475 minimumRes = Math.min( minResX, minResY );
476 }
477 combiningEnvelope = GeometryFactory.createEnvelope( minX, minY, maxX, maxY, null );
478 LOG.logInfo( "determined envelope: ", combiningEnvelope );
479 }
480 wf1 = new WorldFile( minimumRes, minimumRes, 0, 0, combiningEnvelope );
481 return wf1;
482 }
483
484 /**
485 * Determins a usefull size for the virtual bounding box. It is somewhat larger than the
486 * combining bounding box. The result is stored in the virtual <code>Envelope</code>.
487 *
488 */
489 private Envelope determineVirtualBBox() {
490
491 double width = combiningEnvelope.getWidth();
492 double height = combiningEnvelope.getHeight();
493
494 // set width and height to next higher even-numbered thousand
495 double pxWidth = ( width / minimumRes ) + 1;
496 double pxHeight = ( height / minimumRes ) + 1;
497
498 pxWidthVirtualBBox = Math.round( pxWidth );
499 pxHeightVirtualBBox = Math.round( pxHeight );
500
501 // lower right corner of virtual bounding box
502
503 WorldFile wf = new WorldFile( minimumRes, minimumRes, 0, 0, combiningEnvelope );
504 // upper left corner of virtual bounding box
505 double minX = combiningEnvelope.getMin().getX();
506 double maxY = combiningEnvelope.getMax().getY();
507
508 double maxX = minX + ( ( pxWidth - 1 ) * wf.getResx() );
509 double minY = maxY - ( ( pxHeight - 1 ) * wf.getResx() );
510
511 return GeometryFactory.createEnvelope( minX, minY, maxX, maxY, null );
512
513 // return combiningEnvelope;
514 }
515
516 /**
517 * This method determins and sets the size of the tiles in pixel both horizontally (pxWidthTile)
518 * and vertically (pxHeightTile). It also sets the necessary number of <code>tileCols</code>
519 * (depending on the tileWidth) and <code>tileRows</code> (depending on the tileHeight).
520 *
521 * By default, all tiles have a size of close to but less than 6000 pixel either way.
522 */
523 private void determineTileSize() {
524 /*
525 * The size of the virtual bbox gets divided by maxTileSize to find an approximat number of
526 * tiles (a).
527 *
528 * If the virtual bbox is in any direction (horizontally or vertically) smaler than
529 * maxTileSize px, then it has only 1 tile in that direction. In this case, the size of the
530 * tile equals the size of the virtual bbox.
531 *
532 * Otherwise, divide the size of the pixel size of virtual bbox by the pixel tile size
533 *
534 */
535 // determin width of tile
536 double a = ( pxWidthVirtualBBox / maxTileSize );
537 int tileCols = (int) Math.ceil( a );
538 if ( a <= 1.0 ) {
539 pxWidthTile = pxWidthVirtualBBox;
540 } else {
541 tileCols = (int) ( pxWidthVirtualBBox / ( maxTileSize - 1 ) ) + 1;
542 pxWidthTile = (int) maxTileSize;
543 }
544
545 // determin height of tile
546 a = ( pxHeightVirtualBBox / maxTileSize );
547 int tileRows = (int) Math.ceil( a );
548 if ( a <= 1.0 ) {
549 pxHeightTile = pxHeightVirtualBBox;
550 } else {
551 tileRows = (int) ( pxHeightVirtualBBox / ( maxTileSize - 1 ) ) + 1;
552 pxHeightTile = (int) maxTileSize;
553 }
554
555 this.tileCols = tileCols;
556 this.tileRows = tileRows;
557
558 LOG.logInfo( "minimum resolution: " + minimumRes );
559 LOG.logInfo( "width = " + pxWidthVirtualBBox + " *** height = " + pxHeightVirtualBBox );
560 LOG.logInfo( "pxWidthTile = " + pxWidthTile + " *** pxHeightTile = " + pxHeightTile );
561 LOG.logInfo( "number of tiles: horizontally = " + tileCols + ", vertically = " + tileRows );
562 }
563
564 /**
565 * Creates one <code>Tile</code> object after the other, with the number of tiles being
566 * specified by the given number of <code>rows</code> and <code>cols</code>.
567 *
568 * Each Tile gets written to the FileOutputStream by the internal call to #paintImagesOnTile.
569 *
570 * @param rows
571 * @param cols
572 * @throws IOException
573 * @throws Exception
574 */
575 private void createTiles( int rows, int cols )
576 throws IOException {
577
578 System.out.println( "creating merged image ..." );
579
580 Envelope virtualEnv = determineVirtualBBox();
581
582 double tileWidth = minimumRes * ( pxWidthTile - 1 );
583 double tileHeight = minimumRes * ( pxHeightTile - 1 );
584
585 double upperY = virtualEnv.getMax().getY();
586
587 File file = new File( outputDir + '/' + Double.toString( minimumRes ) ).getAbsoluteFile();
588 file.mkdir();
589
590 for ( int i = 0; i < rows; i++ ) {
591 System.out.println( "processing row " + i );
592 double leftX = virtualEnv.getMin().getX();
593 double lowerY = upperY - tileHeight;
594 for ( int j = 0; j < cols; j++ ) {
595
596 System.out.println( "processing tile: " + i + " - " + j );
597 double rightX = leftX + tileWidth;
598 Envelope env = GeometryFactory.createEnvelope( leftX, lowerY, rightX, upperY, null );
599 leftX = rightX;
600 String postfix = "_" + i + "_" + j;
601 Tile tile = new Tile( env, postfix );
602
603 paintImagesOnTile( tile );
604 System.gc();
605 }
606 upperY = lowerY;
607 }
608 System.gc();
609
610 }
611
612 /**
613 * Paints all image files that intersect with the passed <code>tile</code> onto that tile and
614 * creates an output file in the <code>outputDir</code>. If no image file intersects with the
615 * given tile, then an empty output file is created. The name of the output file is defined by
616 * the <code>baseName</code> and the tile's index of row and column.
617 *
618 * @param tile
619 * The tile on which to paint the image.
620 * @throws IOException
621 */
622 private void paintImagesOnTile( Tile tile )
623 throws IOException {
624
625 Envelope tileEnv = tile.getTileEnvelope();
626 String postfix = tile.getPostfix();
627
628 BufferedImage out = createOutputImage();
629 float[][] data = null;
630 if ( bitDepth == 16 && "raw".equals( outputFormat ) ) {
631 // do not use image api if target bitDepth = 16
632 data = new float[(int) pxHeightTile][(int) pxWidthTile];
633 }
634
635 if ( bgColor != null ) {
636 Graphics g = out.getGraphics();
637 g.setColor( Color.decode( bgColor ) );
638 g.fillRect( 0, 0, out.getWidth(), out.getHeight() );
639 g.dispose();
640 }
641 boolean paint = false;
642 int gcc = 0;
643
644 for ( int i = 0; i < imageFiles.size(); i++ ) {
645
646 File file = new File( imageFiles.get( i ) );
647 if ( imageFilesErrors.get( imageFiles.get( i ) ) == null && file.exists() && !file.isDirectory() ) {
648
649 WorldFile wf = imageFilesEnvs.get( i );
650 if ( wf == null ) {
651 System.out.println( "read world file" );
652 // just read image if bbox has not been already read
653 FileSeekableStream fss = new FileSeekableStream( imageFiles.get( i ) );
654 RenderedOp rop = JAI.create( "stream", fss );
655 int iw = ( (Integer) rop.getProperty( "image_width" ) ).intValue();
656 int ih = ( (Integer) rop.getProperty( "image_height" ) ).intValue();
657 fss.close();
658 try {
659 wf = WorldFile.readWorldFile( imageFiles.get( i ), worldFileType, iw, ih );
660 } catch ( Exception e ) {
661 imageFilesErrors.put( imageFiles.get( i ), e.getMessage() );
662 continue;
663 }
664 // cache bounding boxes
665 imageFilesEnvs.set( i, wf );
666 gcc++;
667 if ( gcc % 10 == 0 ) {
668 System.out.println( "garbage collecting" );
669 System.gc();
670 }
671 }
672 // now the values of resx, resy, envelope of the current image file are available
673 if ( wf.getEnvelope().intersects( tileEnv ) ) {
674 TiledImage bi = loadImage( imageFiles.get( i ) );
675 gcc++;
676 try {
677 System.out.println( "drawImage" );
678 drawImage( out, data, bi, tile, wf, minimumRes, interpolation, imageFilesErrors, outputFormat,
679 bitDepth, offset, scaleFactor );
680 paint = true;
681 } catch ( Exception e ) {
682 e.printStackTrace();
683 imageFilesErrors.put( imageFiles.get( i ), e.getMessage() );
684 }
685 if ( gcc % 5 == 0 ) {
686 System.out.println( "garbage collecting" );
687 System.gc();
688 }
689 }
690 } else {
691 imageFilesErrors.put( imageFiles.get( i ), "image does not exist!" );
692 }
693 }
694 if ( paint ) {
695 if ( !isTransparent( out ) ) {
696 // just write files if something has been painted
697 if ( bitDepth == 16 && "raw".equals( outputFormat ) ) {
698 out = RawData2Image.rawData2Image( data, false, scaleFactor, offset );
699 }
700 storeTileImageToFileSystem( postfix, out );
701 createWorldFile( tile );
702 String frm = outputFormat;
703 if ( "raw".equals( outputFormat ) ) {
704 frm = "tif";
705 }
706 storeEnvelope( Double.toString( minimumRes ), baseName + postfix + '.' + frm, tileEnv );
707 }
708 }
709 }
710
711 /**
712 * creates an instance of a BufferedImage depending on requested target format
713 *
714 * @return the new image
715 */
716 private BufferedImage createOutputImage() {
717
718 BufferedImage out = null;
719 if ( "jpg".equals( outputFormat ) || "jpeg".equals( outputFormat ) || "bmp".equals( outputFormat ) ) {
720 // for bmp, jpg, jpeg use 3 byte:
721 out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_RGB );
722 } else if ( "tif".equals( outputFormat ) || "tiff".equals( outputFormat ) || "png".equals( outputFormat )
723 || "gif".equals( outputFormat ) ) {
724 // for tif, tiff and png use 4 byte:
725 out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_ARGB );
726 } else {
727 ColorModel ccm;
728
729 if ( bitDepth == 16 ) {
730 ccm = new ComponentColorModel( ColorSpace.getInstance( ColorSpace.CS_GRAY ), null, false, false,
731 BufferedImage.OPAQUE, DataBuffer.TYPE_USHORT );
732 WritableRaster wr = ccm.createCompatibleWritableRaster( (int) pxWidthTile, (int) pxHeightTile );
733
734 out = new BufferedImage( ccm, wr, false, new Hashtable<Object, Object>() );
735 } else {
736 out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_ARGB );
737 }
738 }
739
740 return out;
741
742 }
743
744 /**
745 *
746 * @param postfix
747 * tile name postfix ( -> tile index $x_$y )
748 * @param out
749 * tile image to save
750 */
751 private void storeTileImageToFileSystem( String postfix, BufferedImage out ) {
752 try {
753 String frm = outputFormat;
754 if ( "raw".equals( frm ) ) {
755 frm = "tif";
756 }
757 String imageFile = outputDir + '/' + Double.toString( minimumRes ) + '/' + baseName + postfix + '.' + frm;
758 File file = new File( imageFile ).getAbsoluteFile();
759
760 ImageUtils.saveImage( out, file, quality );
761
762 } catch ( IOException e ) {
763 e.printStackTrace();
764 }
765 }
766
767 /**
768 * Draws an image map to the target tile considering defined interpolation method for rescaling.
769 * This method is static so it can be used easily from the <code>RasterTreeUpdater</code>.
770 *
771 * @param out
772 * target image tile
773 * @param image
774 * source image map
775 * @param tile
776 * tile description, must contain the envelope of the target image
777 * @param wf
778 * must contain the envelope of the TiledImage of the source image
779 * @param minimumRes
780 * the minimum resolution of input images
781 * @param interpolation
782 * the interpolation method
783 * @param imageFilesErrors
784 * a mapping between image files and errors
785 * @param outputFormat
786 * the output format
787 * @param bitDepth
788 * the output bit depth
789 * @param offset
790 * offset used if bitDepth = 16 and outputFormat = raw
791 * @param scaleFactor
792 * scale factor used if bitDepth = 16 and outputFormat = raw
793 */
794 public static void drawImage( BufferedImage out, float[][] data, final TiledImage image, Tile tile, WorldFile wf,
795 double minimumRes, Interpolation interpolation, Map<String, String> imageFilesErrors,
796 String outputFormat, int bitDepth, float offset, float scaleFactor ) {
797
798 Envelope tileEnv = tile.getTileEnvelope();
799 Envelope mapEnv = wf.getEnvelope();
800
801 GeoTransform gt2 = new WorldToScreenTransform( mapEnv.getMin().getX(), mapEnv.getMin().getY(),
802 mapEnv.getMax().getX(), mapEnv.getMax().getY(), 0, 0,
803 image.getWidth() - 1, image.getHeight() - 1 );
804
805 Envelope inter = mapEnv.createIntersection( tileEnv );
806 if ( inter == null )
807 return;
808 int x1 = (int) Math.round( gt2.getDestX( inter.getMin().getX() ) );
809 int y1 = (int) Math.round( gt2.getDestY( inter.getMax().getY() ) );
810 int x2 = (int) Math.round( gt2.getDestX( inter.getMax().getX() ) );
811 int y2 = (int) Math.round( gt2.getDestY( inter.getMin().getY() ) );
812
813 if ( x2 - x1 >= 0 && y2 - y1 >= 0 && x2 - x1 < image.getWidth() && y2 - y1 < image.getHeight() && x1 >= 0
814 && y1 >= 0 ) {
815
816 BufferedImage newImg = null;
817 BufferedImage img = image.getSubImage( x1, y1, x2 - x1 + 1, y2 - y1 + 1 ).getAsBufferedImage();
818
819 if ( !isTransparent( img ) ) {
820
821 // copy source image to a 4 Byte BufferedImage because there are
822 // problems with handling 8 Bit palette images
823 if ( img.getColorModel().getPixelSize() == 18 ) {
824 LOG.logInfo( "copy 8Bit image to 32Bit image" );
825 BufferedImage bi = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB );
826
827 Graphics g = bi.getGraphics();
828 try {
829 g.drawImage( img, 0, 0, null );
830 } catch ( Exception e ) {
831 System.out.println( e.getMessage() );
832 }
833 g.dispose();
834 img = bi;
835 }
836 if ( ( wf.getResx() / minimumRes < 0.9999 ) || ( wf.getResx() / minimumRes > 1.0001 )
837 || ( wf.getResy() / minimumRes < 0.9999 ) || ( wf.getResy() / minimumRes > 1.0001 ) ) {
838 newImg = scale( img, interpolation, (float) ( wf.getResx() / minimumRes ),
839 (float) ( wf.getResy() / minimumRes ) );
840 } else {
841 newImg = img;
842 }
843 GeoTransform gt = new WorldToScreenTransform( tileEnv.getMin().getX(), tileEnv.getMin().getY(),
844 tileEnv.getMax().getX(), tileEnv.getMax().getY(), 0, 0,
845 out.getWidth() - 1, out.getHeight() - 1 );
846
847 x1 = (int) Math.round( gt.getDestX( inter.getMin().getX() ) );
848 y1 = (int) Math.round( gt.getDestY( inter.getMax().getY() ) );
849 x2 = (int) Math.round( gt.getDestX( inter.getMax().getX() ) );
850 y2 = (int) Math.round( gt.getDestY( inter.getMin().getY() ) );
851
852 if ( x2 - x1 > 0 && y2 - y1 > 0 ) {
853 // ensure that there is something to draw
854 try {
855 if ( "raw".equals( outputFormat ) ) {
856 DataBuffer outBuffer = out.getData().getDataBuffer();
857 DataBuffer newImgBuffer = newImg.getData().getDataBuffer();
858 int ps = newImg.getColorModel().getPixelSize();
859 float[][] newData = null;
860 if ( bitDepth == 16 && ps == 16 ) {
861 Image2RawData i2r = new Image2RawData( newImg, 1f / scaleFactor, -1 * offset );
862 // do not use image api if target bitDepth = 16
863 newData = i2r.parse();
864 }
865 for ( int i = 0; i < newImg.getWidth(); i++ ) {
866 for ( int j = 0; j < newImg.getHeight(); j++ ) {
867 if ( x1 + i < out.getWidth() && y1 + j < out.getHeight() ) {
868 int newImgPos = newImg.getWidth() * j + i;
869 int outPos = out.getWidth() * ( y1 + j ) + ( x1 + i );
870 if ( bitDepth == 16 && ps == 16 ) {
871 // int v = newImgBuffer.getElem( newImgPos );
872 // outBuffer.setElem( outPos, v );
873 data[y1 + j][x1 + i] = newData[j][i];
874 } else if ( bitDepth == 16 && ps == 32 ) {
875 int v = newImg.getRGB( i, j );
876 float f = Float.intBitsToFloat( v ) * 10f;
877 outBuffer.setElem( outPos, Math.round( f ) );
878 // TODO
879 // data[y1 + j][x1 + i] = f;
880 } else if ( bitDepth == 32 && ps == 16 ) {
881 float f = newImgBuffer.getElem( newImgPos ) / 10f;
882 outBuffer.setElem( outPos, Float.floatToIntBits( f ) );
883 } else {
884 out.setRGB( x1 + i, y1 + j, newImg.getRGB( i, j ) );
885 }
886 }
887 }
888 }
889 if ( ( bitDepth == 16 && ps == 16 ) || ( bitDepth == 16 && ps == 32 )
890 || ( bitDepth == 32 && ps == 16 ) ) {
891 out.setData( Raster.createRaster( out.getSampleModel(), outBuffer, null ) );
892 }
893 } else {
894 Graphics g = out.getGraphics();
895 g.drawImage( newImg, x1, y1, newImg.getWidth(), newImg.getHeight(), null );
896 g.dispose();
897 }
898 } catch ( Exception e ) {
899 LOG.logError( "Could not draw upon the image: " );
900 LOG.logError( "New image is of size " + newImg.getWidth() + ", " + newImg.getHeight() );
901 LOG.logError( "Position/width tried is (" + x1 + ", " + y1 + ", " + newImg.getWidth() + ", "
902 + newImg.getHeight() + ")" );
903 if ( imageFilesErrors != null ) {
904 imageFilesErrors.put( tile.getPostfix(), StringTools.stackTraceToString( e ) );
905 }
906 }
907 }
908 }
909 }
910 }
911
912 /**
913 *
914 * @param img
915 * @param interpolation
916 * @param scaleX
917 * @param scaleY
918 * @return the scaled image
919 */
920 private static BufferedImage scale( BufferedImage img, Interpolation interpolation, float scaleX, float scaleY ) {
921
922 LOG.logDebug( "Scale image: by factors: " + scaleX + ' ' + scaleY );
923 ParameterBlock pb = new ParameterBlock();
924 pb.addSource( img );
925 pb.add( scaleX ); // The xScale
926 pb.add( scaleY ); // The yScale
927 pb.add( 0.0F ); // The x translation
928 pb.add( 0.0F ); // The y translation
929 pb.add( interpolation ); // The interpolation
930 // Create the scale operation
931 RenderedOp ro = JAI.create( "scale", pb, null );
932 try {
933 img = ro.getAsBufferedImage();
934 } catch ( Exception e ) {
935 e.printStackTrace();
936 }
937 return img;
938 }
939
940 private static boolean isTransparent( BufferedImage bi ) {
941 /*
942 * TODO determine if the passed image is completly transparent for ( int i = 0; i <
943 * bi.getHeight(); i++ ) { for ( int j = 0; j < bi.getWidth(); j++ ) { if ( bi.getRGB( i, j ) !=
944 * 0 && bi.getRGB( i, j ) != -256 ) { return false; } } } return true;
945 */
946 return false;
947 }
948
949 /**
950 * @return an interpolation object from a well known name
951 * @param interpolation
952 */
953 public static Interpolation createInterpolation( String interpolation ) {
954 Interpolation interpol = null;
955
956 if ( interpolation.equalsIgnoreCase( "Nearest Neighbor" ) ) {
957 interpol = new InterpolationNearest();
958 } else if ( interpolation.equalsIgnoreCase( "Bicubic" ) ) {
959 interpol = new InterpolationBicubic( 5 );
960 } else if ( interpolation.equalsIgnoreCase( "Bicubic2" ) ) {
961 interpol = new InterpolationBicubic2( 5 );
962 } else if ( interpolation.equalsIgnoreCase( "Bilinear" ) ) {
963 interpol = new InterpolationBilinear();
964 } else {
965 throw new RuntimeException( "invalid interpolation method: " + interpolation );
966 }
967
968 return interpol;
969 }
970
971 /**
972 * Creates a world file for the corresponding tile in the <code>outputDir</code>. The name of
973 * the output file is defined by the <code>baseName</code> and the tile's index of row and
974 * column.
975 *
976 * @param tile
977 * The tile for which to create a world file.
978 * @throws IOException
979 */
980 private void createWorldFile( Tile tile )
981 throws IOException {
982
983 Envelope env = tile.getTileEnvelope();
984 String postfix = tile.getPostfix();
985
986 StringBuffer sb = new StringBuffer( 1000 );
987
988 sb.append( minimumRes ).append( "\n" ).append( 0.0 ).append( "\n" ).append( 0.0 );
989 sb.append( "\n" ).append( ( -1 ) * minimumRes ).append( "\n" );
990 sb.append( env.getMin().getX() ).append( "\n" ).append( env.getMax().getY() );
991 sb.append( "\n" );
992
993 File f = new File( outputDir + '/' + Double.toString( minimumRes ) + '/' + baseName + postfix + ".wld" );
994
995 FileWriter fw = new FileWriter( f );
996 PrintWriter pw = new PrintWriter( fw );
997
998 pw.print( sb.toString() );
999
1000 pw.close();
1001 fw.close();
1002 }
1003
1004 /**
1005 * stores an envelope and the assigend image file information into a feature/featureCollection
1006 *
1007 * @param dir
1008 * directory where the image file is stored
1009 * @param file
1010 * name of the image file
1011 * @param env
1012 * bbox of the image file
1013 */
1014 private void storeEnvelope( String dir, String file, Envelope env ) {
1015 try {
1016 Geometry geom = GeometryFactory.createSurface( env, null );
1017 FeatureProperty[] props = new FeatureProperty[3];
1018 props[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "GEOM" ), geom );
1019 props[1] = FeatureFactory.createFeatureProperty(
1020 new QualifiedName(
1021 GridCoverageExchange.SHAPE_IMAGE_FILENAME ),
1022 file );
1023 props[2] = FeatureFactory.createFeatureProperty( new QualifiedName( GridCoverageExchange.SHAPE_DIR_NAME ),
1024 dir );
1025 Feature feat = FeatureFactory.createFeature( "file", ftype, props );
1026 fc.add( feat );
1027 } catch ( Exception e ) {
1028 e.printStackTrace();
1029 }
1030 }
1031
1032 /**
1033 * creates a configuration file (extended CoverageDescriotion) for a WCS coverage considering
1034 * the passed resolution levels
1035 *
1036 * @param targetResolutions
1037 */
1038 private void createConfigurationFile( double[] targetResolutions ) {
1039
1040 // copy this file to the target directory
1041 String resolutions = "";
1042 java.util.Arrays.sort( targetResolutions );
1043 int length = targetResolutions.length;
1044
1045 for ( int i = 0; i < length; i++ ) {
1046 resolutions += String.valueOf( targetResolutions[length - 1 - i] );
1047 if ( i < ( length - 1 ) )
1048 resolutions += ',';
1049 }
1050
1051 try {
1052 Map<String, String> param = new HashMap<String, String>( 20 );
1053 Envelope llEnv = getLatLonEnvelope( combiningEnvelope );
1054 param.put( "upperleftll", String.valueOf( llEnv.getMin().getX() ) + ','
1055 + String.valueOf( llEnv.getMin().getY() ) );
1056 param.put( "lowerrightll", String.valueOf( llEnv.getMax().getX() ) + ','
1057 + String.valueOf( llEnv.getMax().getY() ) );
1058 param.put( "upperleft", String.valueOf( combiningEnvelope.getMin().getX() ) + ','
1059 + String.valueOf( combiningEnvelope.getMin().getY() ) );
1060 param.put( "lowerright", String.valueOf( combiningEnvelope.getMax().getX() ) + ','
1061 + combiningEnvelope.getMax().getY() );
1062 File dir = new File( outputDir );
1063 if ( dir.isAbsolute() ) {
1064 // param.put( "dataDir", outputDir + '/' );
1065 param.put( "dataDir", "" );
1066 } else {
1067 param.put( "dataDir", "" );
1068 }
1069 param.put( "label", baseName );
1070 param.put( "name", baseName );
1071 param.put( "description", "" );
1072 param.put( "keywords", "" );
1073 param.put( "resolutions", resolutions );
1074 String frm = outputFormat;
1075 if ( "raw".equals( outputFormat ) && bitDepth == 32 ) {
1076 frm = "tif";
1077 } else if ( "raw".equals( outputFormat ) && bitDepth == 16 ) {
1078 frm = "GeoTiff";
1079 }
1080 param.put( "mimeType", frm );
1081 int p = srs.lastIndexOf( ':' );
1082 param.put( "srs", srs.substring( p + 1, srs.length() ) );
1083 param.put( "srsPre", srs.substring( 0, p + 1 ) );
1084
1085 Reader reader = new InputStreamReader( configURL.openStream() );
1086
1087 XSLTDocument xslt = new XSLTDocument();
1088 xslt.load( configXSL );
1089 XMLFragment xml = xslt.transform( reader, XMLFragment.DEFAULT_URL, null, param );
1090 reader.close();
1091
1092 // write the result
1093 String dstFilename = "wcs_" + baseName + "_configuration.xml";
1094 File dstFile = new File( outputDir, dstFilename );
1095 String configurationFilename = dstFile.getAbsolutePath().toString();
1096 FileOutputStream fos = new FileOutputStream( configurationFilename );
1097 xml.write( fos );
1098 fos.close();
1099
1100 } catch ( Exception e1 ) {
1101 e1.printStackTrace();
1102 }
1103
1104 }
1105
1106 private Envelope getLatLonEnvelope( Envelope env )
1107 throws Exception {
1108 IGeoTransformer gt = new GeoTransformer( "EPSG:4326" );
1109 return gt.transform( env, srs );
1110 }
1111
1112 /**
1113 *
1114 */
1115 private void updateCapabilitiesFile( File capabilitiesFile ) {
1116
1117 try {
1118 XSLTDocument xslt = new XSLTDocument();
1119 xslt.load( inputXSL );
1120 Map<String, String> param = new HashMap<String, String>();
1121
1122 param.put( "dataDirectory", outputDir );
1123 String url = new File( "wcs_" + baseName + "_configuration.xml" ).toURL().toString();
1124 param.put( "configFile", url );
1125 Envelope llEnv = getLatLonEnvelope( combiningEnvelope );
1126 param.put( "upperleftll", String.valueOf( llEnv.getMin().getX() ) + ','
1127 + String.valueOf( llEnv.getMin().getY() ) );
1128 param.put( "lowerrightll", String.valueOf( llEnv.getMax().getX() ) + ','
1129 + String.valueOf( llEnv.getMax().getY() ) );
1130
1131 param.put( "name", baseName );
1132 param.put( "label", baseName );
1133
1134 param.put( "description", "" );
1135 param.put( "keywords", "" );
1136
1137 XMLFragment xml = new XMLFragment();
1138 xml.load( capabilitiesFile.toURL() );
1139
1140 xml = xslt.transform( xml, capabilitiesFile.toURL().toExternalForm(), null, param );
1141
1142 // write the result
1143 FileOutputStream fos = new FileOutputStream( capabilitiesFile );
1144 xml.write( fos );
1145 fos.close();
1146 } catch ( Exception e ) {
1147 e.printStackTrace();
1148 }
1149 }
1150
1151 /**
1152 * Validates the content of <code>map</code>, to see, if necessary arguments were passed when
1153 * calling this class.
1154 *
1155 * @param map
1156 * @throws Exception
1157 */
1158 private static void validate( Properties map )
1159 throws Exception {
1160
1161 if ( map.get( "-outDir" ) == null ) {
1162 throw new Exception( "-outDir must be set" );
1163 }
1164 String s = (String) map.get( "-outDir" );
1165 if ( s.endsWith( "/" ) || s.endsWith( "\\" ) ) {
1166 s = s.substring( 0, s.length() - 1 );
1167 }
1168
1169 if ( map.get( "-baseName" ) == null ) {
1170 throw new Exception( "-baseName must be set" );
1171 }
1172 if ( map.get( "-outputFormat" ) == null ) {
1173 map.put( "-outputFormat", "png" );
1174 } else {
1175 String format = ( (String) map.get( "-outputFormat" ) ).toLowerCase();
1176 if ( !"bmp".equals( format ) && !"png".equals( format ) && !"jpg".equals( format )
1177 && !"jpeg".equals( format ) && !"tif".equals( format ) && !"tiff".equals( format )
1178 && !"gif".equals( format ) && !( "raw" ).equals( format ) ) {
1179
1180 throw new Exception( "-outputFormat must be one of the following: "
1181 + "'bmp', 'jpeg', 'jpg', 'png', 'tif', 'tiff', 'raw'." );
1182 }
1183 }
1184 if ( map.get( "-maxTileSize" ) == null ) {
1185 map.put( "-maxTileSize", "500" );
1186 }
1187 if ( map.get( "-srs" ) == null ) {
1188 map.put( "-srs", "EPSG:4326" );
1189 }
1190 if ( map.get( "-interpolation" ) == null ) {
1191 map.put( "-interpolation", "Nearest Neighbor" );
1192 }
1193 if ( map.get( "-noOfLevel" ) == null ) {
1194 map.put( "-noOfLevel", "1" );
1195 }
1196 if ( map.get( "-worldFileType" ) == null ) {
1197 map.put( "-worldFileType", "center" );
1198 }
1199 if ( map.get( "-quality" ) == null ) {
1200 map.put( "-quality", "0.95" );
1201 }
1202 if ( map.get( "-bbox" ) != null ) {
1203 double[] d = StringTools.toArrayDouble( (String) map.get( "-bbox" ), "," );
1204 Envelope env = GeometryFactory.createEnvelope( d[0], d[1], d[2], d[3], null );
1205 map.put( "-bbox", env );
1206 if ( map.get( "-resolution" ) == null ) {
1207 throw new Exception( "-resolution must be set if -bbox is set" );
1208 }
1209 map.put( "-resolution", new Double( (String) map.get( "-resolution" ) ) );
1210 } else {
1211 if ( map.get( "-resolution" ) == null ) {
1212 map.put( "-resolution", new Double( -1 ) );
1213 } else {
1214 map.put( "-resolution", new Double( (String) map.get( "-resolution" ) ) );
1215 }
1216 }
1217 }
1218
1219 /**
1220 * @return the list of image map files to consider read from -mapFiles parameter
1221 *
1222 * @param mapFiles
1223 */
1224 private static List<String> getFileList( String[] mapFiles ) {
1225 List<String> imageFiles = new ArrayList<String>();
1226 for ( int i = 0; i < mapFiles.length; i++ ) {
1227 imageFiles.add( mapFiles[i] );
1228 }
1229 return imageFiles;
1230 }
1231
1232 /**
1233 * @return the list of image map files to consider read from a defined root directory.
1234 *
1235 * @param rootDir
1236 * root directory where to read image map files
1237 * @param subdirs
1238 * true if subdirectories of the root directory shall be parsed for image maps too
1239 */
1240 private static List<String> getFileList( String rootDir, boolean subdirs ) {
1241 List<String> list = new ArrayList<String>( 10000 );
1242 File file = new File( rootDir );
1243 String[] entries = file.list( new DFileFilter() );
1244 if ( entries != null ) {
1245 for ( int i = 0; i < entries.length; i++ ) {
1246 File entry = new File( rootDir + '/' + entries[i] );
1247 if ( entry.isDirectory() && subdirs ) {
1248 list = readSubDirs( entry, list );
1249 } else {
1250 list.add( rootDir + '/' + entries[i] );
1251 }
1252 }
1253 }
1254 return list;
1255 }
1256
1257 /**
1258 *
1259 * @param file
1260 * @param list
1261 * @return the sub directories
1262 */
1263 private static List<String> readSubDirs( File file, List<String> list ) {
1264
1265 String[] entries = file.list( new DFileFilter() );
1266 if ( entries != null ) {
1267 for ( int i = 0; i < entries.length; i++ ) {
1268 File entry = new File( file.getAbsolutePath() + '/' + entries[i] );
1269 if ( entry.isDirectory() ) {
1270 list = readSubDirs( entry, list );
1271 } else {
1272 list.add( file.getAbsolutePath() + '/' + entries[i] );
1273 }
1274 }
1275 }
1276 return list;
1277 }
1278
1279 /**
1280 * @return the list of image map files to consider read from a dbase file defined by the dbase
1281 * parameter
1282 *
1283 * @param dbaseFile
1284 * name of the dbase file
1285 * @param fileColumn
1286 * name of the column containing the image map files names
1287 * @param baseDir
1288 * name of the directory where the image map files are stored if this parameter is
1289 * <code>null</code> it is assumed that the image map files are full referenced
1290 * within the dbase
1291 * @param sort
1292 * true if map image file names shall be sorted
1293 * @param sortColum
1294 * name of the column that shall be used for sorting
1295 */
1296 private static List<String> getFileList( String dBaseFile, String fileColumn, String baseDir, boolean sort,
1297 String sortColum, String sortDirection )
1298 throws Exception {
1299
1300 // handle dbase file extension and file location/reading problems
1301 if ( dBaseFile.endsWith( ".dbf" ) ) {
1302 dBaseFile = dBaseFile.substring( 0, dBaseFile.lastIndexOf( "." ) );
1303 }
1304 DBaseFile dbf = new DBaseFile( dBaseFile );
1305
1306 // sort dbase file contents chronologicaly (oldest first)
1307 int cnt = dbf.getRecordNum();
1308
1309 Object[][] mapItems = new Object[cnt][2];
1310 QualifiedName fileC = new QualifiedName( APP_PREFIX, fileColumn.toUpperCase(), DEEGREEAPP );
1311 QualifiedName sortC = null;
1312 if ( sort ) {
1313 sortC = new QualifiedName( APP_PREFIX, sortColum.toUpperCase(), DEEGREEAPP );
1314 }
1315 for ( int i = 0; i < cnt; i++ ) {
1316 if ( sort ) {
1317 mapItems[i][0] = dbf.getFRow( i + 1 ).getDefaultProperty( sortC ).getValue();
1318 } else {
1319 mapItems[i][0] = new Integer( 1 );
1320 }
1321 // name of map file
1322 mapItems[i][1] = dbf.getFRow( i + 1 ).getDefaultProperty( fileC ).getValue();
1323 }
1324 Arrays.sort( mapItems, new MapAgeComparator( sortDirection ) );
1325
1326 // extract names of image files from dBase file and attach them to rootDir
1327 if ( baseDir == null ) {
1328 baseDir = "";
1329 } else if ( !baseDir.endsWith( "/" ) && !baseDir.endsWith( "\\" ) ) {
1330 baseDir = baseDir + "/";
1331 }
1332 List<String> imageFiles = new ArrayList<String>( mapItems.length );
1333 for ( int i = 0; i < mapItems.length; i++ ) {
1334 if ( mapItems[i][0] != null ) {
1335 LOG.logDebug( "" + mapItems[i][0] );
1336 imageFiles.add( baseDir + mapItems[i][1] );
1337 }
1338 }
1339
1340 return imageFiles;
1341 }
1342
1343 private static void printHelp() {
1344
1345 System.out.println( "-outDir directory where resulting tiles and describing shape(s) will be stored (mandatory)\r\n"
1346 + "-baseName base name used for creating names of the raster tile files. It also will be the name of the created coverage. (mandatory)\r\n"
1347 + "-outputFormat name of the image format used for created tiles (png|jpg|jpeg|bmp|tif|tiff|gif|raw default png)\r\n"
1348 + "-maxTileSize maximum size of created raster tiles in pixel (default 500)\r\n"
1349 + "-srs name of the spatial reference system used for the coverage (default EPSG:4326)\r\n"
1350 + "-interpolation interpolation method used for rescaling raster images (Nearest Neighbor|Bicubic|Bicubic2|Bilinear default Nearest Neighbor)\r\n"
1351 + " be careful using Bicubic and Bicubic2 interpolation; there seems to be a problem with JAI\r\n"
1352 + " If you use the proogram with images (tif) containing raw data like DEMs just use \r\n"
1353 + " Nearest Neighbor interpolation. All other interpolation methods will cause artefacts."
1354 + "-bbox boundingbox of the the resulting coverage. If not set the bbox will be determined by analysing the input map files. (optional)\r\n"
1355 + "-resolution spatial resolution of the resulting coverage. If not set the resolution will determined by analysing the input map files. This parameter is conditional; if -bbox is defined -resolution must be defined too.\r\n"
1356 + "-noOfLevel number of tree levels created (optional default = 1)\r\n"
1357 + "-capabilitiesFile name of a deegree WCS capabilities/configuration file. If defined the program will add the created rastertree as a new coverage to the WCS configuration.\r\n"
1358 + "-h or -? print this help\r\n"
1359 + "\r\n"
1360 + "Input files\r\n"
1361 + "there are three alternative ways/parameters to define which input files shall be used for creating a raster tree:\r\n"
1362 + "1)\r\n"
1363 + "-mapFiles defines a list of image file names (including full path information) seperated by \',\', \';\' or \'|\'\r\n"
1364 + "\r\n"
1365 + "2)\r\n"
1366 + "-rootDir defines a directory that shall be parsed for files in a known image format. Each file found will be used as input.\r\n"
1367 + "-subDirs conditional parameter used with -rootDir. It defines if all sub directories of -rootDir shall be parsed too (true|false default false)\r\n"
1368 + "\r\n"
1369 + "3)\r\n"
1370 + "-dbaseFile name a dBase file that contains a column listing all files to be considered by the program\r\n"
1371 + "-fileColumn name of the column containing the file names (mandatory if -dbaseFile is defined)\r\n"
1372 + "-baseDir name of the directory where the files are stored. If this parameter will not be set the program assumes the -fileColumn contains completely referenced file names (optional)\r\n"
1373 + "-sortColumn If -dbaseFile is defined one can define a column that shall be used for sorting the files referenced by the -fileColumn (optional)\r\n"
1374 + "-sortDirection If -sortColumn is defined this parameter will be used for definition of sorting direction (UP|DOWN default UP)\r\n"
1375 + "-worldFileType two types of are common: \r\n "
1376 + " a) the boundingbox is defined on the center of the corner pixels; \r\n "
1377 + " b) the boundingbox is defined on the outer corner of the corner pixels; \r\n "
1378 + " first is default and will be used if this parameter is not set; second will be use if '-worldFileType outer' is defined.\r\n"
1379 + "-quality image quality if jpeg is used as output format; valid range is from 0..1 (default 0.95) \r\n"
1380 + "-bitDepth image bit depth; valid values are 32 and 16, default is 16 \r\n"
1381 + "-bgColor defines the background color of the created tiles for those region no data are available (e.g. -bgColor 0xFFFFF defines background as white) \r\n"
1382 + " If no -bgColor is defined, transparent background will be used for image formats that are transparency enabled (e.g. png) and black is used for all other formats (e.g. bmp) \r\n"
1383 + "-offset defines the offset added to raster values if -outputFormat = raw and -bitDepth (default 0) \r\n"
1384 + "-scaleFactor defines the factor by which raster values are multiplied if -outputFormat = raw and -bitDepth (default 1) \r\n"
1385 + "\r\n"
1386 + "Common to all option defining the input files is that each referenced file must be in a known image format (png, tif, jpeg, bmp, gif) and if must be geo-referenced by a world file or must be a GeoTIFF." );
1387 System.out.println();
1388 System.out.println( "caching:" );
1389 System.out.println( "To use default caching mechanism you just have to start RTB as before; to define " );
1390 System.out.println( "your own caching behavior you have to place a file named ehcache.xml within the " );
1391 System.out.println( "root of your classpath. The content of this file is described by the ehcache documentation " );
1392 System.out.println( "(http://ehcache.sourceforge.net/documentation); at least it must provide a cache named 'imgCache'." );
1393 System.out.println( " When defining your own cache please consider that just 'inMemory' caching is supported because " );
1394 System.out.println( "the objects cached by RTB are not serializable." );
1395 System.out.println();
1396 System.out.println( "Example invoking RTB (windows):" );
1397 System.out.println( "java -Xms300m -Xmx1000m -classpath .;.\\lib\\deegree2.jar;.\\lib\\acme.jar;"
1398 + ".\\lib\\batik-awt-util.jar;.\\lib\\commons-beanutils-1.5.jar;"
1399 + ".\\lib\\commons-codec-1.3.jar;.\\lib\\commons-collections-3.1.jar;"
1400 + ".\\lib\\commons-digester-1.7.jar;.\\lib\\commons-discovery-0.2.jar;"
1401 + ".\\lib\\commons-logging.jar;.\\lib\\jai_codec.jar;.\\lib\\jai_core.jar;"
1402 + ".\\lib\\mlibwrapper_jai.jar;.\\lib\\j3dcore.jar;.\\lib\\j3dutils.jar;"
1403 + ".\\lib\\vecmath.jar;.\\lib\\jts-1.6.jar;.\\lib\\log4j-1.2.9.jar;"
1404 + ".\\lib\\axis.jar;.\\lib\\jaxen-1.1-beta-7.jar;.\\lib\\ehcache-1.2.0_03.jar "
1405 + "org.deegree.tools.raster.RasterTreeBuilder "
1406 + "-dbaseFile D:/lgv/resources/data/dbase/dip.dbf -outDir D:/lgv/output/ "
1407 + "-baseName out -outputFormat jpg -maxTileSize 500 -noOfLevel 4 -interpolation "
1408 + "Bilinear -bbox 3542428,5918168,3593354,5957043 -resolution 0.2 -sortColumn "
1409 + "PLANJAHR -fileColumn NAME_PNG -sortDirection UP -quality 0.91 -baseDir "
1410 + "D:/lgv/resources/data/images/ " );
1411 }
1412
1413 /**
1414 *
1415 * @param args
1416 * Example arguments to pass when calling are:
1417 * <ul>
1418 * <li>-mapFiles D:/temp/europe_DK.jpg,D:/temp/europe_BeNeLux.jpg</li>
1419 * <li>-outDir D:/temp/out/</li>
1420 * <li>-baseName pretty</li>
1421 * <li>-outputFormat png</li>
1422 * <li>-maxTileSize 600</li>
1423 * </ul>
1424 *
1425 * @throws Exception
1426 */
1427 public static void main( String[] args )
1428 throws Exception {
1429
1430 Properties map = new Properties();
1431 for ( int i = 0; i < args.length; i += 2 ) {
1432 map.put( args[i], args[i + 1] );
1433 }
1434
1435 if ( map.get( "-?" ) != null || map.get( "-h" ) != null ) {
1436 printHelp();
1437 return;
1438 }
1439
1440 try {
1441 validate( map );
1442 } catch ( Exception e ) {
1443 LOG.logInfo( map.toString() );
1444 System.out.println( e.getMessage() );
1445 System.out.println();
1446 printHelp();
1447 return;
1448 }
1449
1450 // read input parameters
1451 String outDir = map.getProperty( "-outDir" );
1452 String baseName = map.getProperty( "-baseName" );
1453 String outputFormat = map.getProperty( "-outputFormat" );
1454 String srs = map.getProperty( "-srs" );
1455 String interpolation = map.getProperty( "-interpolation" );
1456 Envelope env = (Envelope) map.get( "-bbox" );
1457 double resolution = ( (Double) map.get( "-resolution" ) ).doubleValue();
1458 int level = Integer.parseInt( map.getProperty( "-noOfLevel" ) );
1459 double maxTileSize = ( Double.valueOf( map.getProperty( "-maxTileSize" ) ) ).doubleValue();
1460 WorldFile.TYPE worldFileType = WorldFile.TYPE.CENTER;
1461 if ( "outer".equals( map.getProperty( "-worldFileType" ) ) ) {
1462 worldFileType = WorldFile.TYPE.OUTER;
1463 }
1464 float quality = Float.parseFloat( map.getProperty( "-quality" ) );
1465 String backgroundColor = map.getProperty( "-bgColor" );
1466
1467 int depth = 0;
1468
1469 if ( map.get( "-bitDepth" ) != null ) {
1470 depth = Integer.parseInt( map.getProperty( "-bitDepth" ) );
1471 }
1472
1473 float offset = 0;
1474 if ( map.get( "-offset" ) != null ) {
1475 offset = Float.parseFloat( map.getProperty( "-offset" ) );
1476 }
1477
1478 float scaleFactor = 1;
1479 if ( map.get( "-scaleFactor" ) != null ) {
1480 scaleFactor = Float.parseFloat( map.getProperty( "-scaleFactor" ) );
1481 }
1482
1483 List<String> imageFiles = null;
1484 if ( map.get( "-mapFiles" ) != null ) {
1485 String[] mapFiles = StringTools.toArray( map.getProperty( "-mapFiles" ), ",;|", true );
1486 imageFiles = getFileList( mapFiles );
1487 } else if ( map.get( "-dbaseFile" ) != null ) {
1488 String dBaseFile = map.getProperty( "-dbaseFile" );
1489 String fileColum = map.getProperty( "-fileColumn" );
1490 String baseDir = map.getProperty( "-baseDir" );
1491 if ( baseDir == null ) {
1492 baseDir = map.getProperty( "-rootDir" );
1493 }
1494 boolean sort = map.get( "-sortColumn" ) != null;
1495 String sortColumn = map.getProperty( "-sortColumn" );
1496 if ( map.get( "-sortDirection" ) == null ) {
1497 map.put( "-sortDirection", "UP" );
1498 }
1499 String sortDirection = map.getProperty( "-sortDirection" );
1500 imageFiles = getFileList( dBaseFile, fileColum, baseDir, sort, sortColumn, sortDirection );
1501 } else if ( map.get( "-rootDir" ) != null ) {
1502 String rootDir = map.getProperty( "-rootDir" );
1503 boolean subDirs = "true".equals( map.get( "-subDirs" ) );
1504 imageFiles = getFileList( rootDir, subDirs );
1505 } else {
1506 LOG.logInfo( map.toString() );
1507 System.out.println( "-mapFiles, -rootDir or -dbaseFile parameter must be defined" );
1508 printHelp();
1509 return;
1510 }
1511
1512 LOG.logDebug( imageFiles.toString() );
1513 LOG.logInfo( map.toString() );
1514
1515 // initialize RasterTreeBuilder
1516 RasterTreeBuilder rtb = new RasterTreeBuilder( imageFiles, outDir, baseName, outputFormat, maxTileSize, srs,
1517 interpolation, worldFileType, quality, backgroundColor, depth,
1518 resolution, offset, scaleFactor );
1519
1520 // calculate bbox and resolution from input images if parameters are not set
1521 if ( env == null ) {
1522 WorldFile wf = rtb.determineCombiningBBox();
1523 env = wf.getEnvelope();
1524 resolution = wf.getResx();
1525 }
1526
1527 // Calculate necessary number of levels to get not more than 4
1528 // tiles in highest resolution
1529 if ( level == -1 ) {
1530 rtb.init( env, resolution );
1531 level = 0;
1532 int numTilesMax = Math.min( rtb.tileCols, rtb.tileRows );
1533 int numTiles = 4;
1534 while ( numTiles < numTilesMax ) {
1535 level += 1;
1536 numTiles *= 2;
1537 }
1538 }
1539 if ( level == 0 ) {
1540 level = 1;
1541 }
1542 System.out.println( "Number of Levels: " + level );
1543
1544 // create tree where for each loop resolution will be halfed
1545 double[] re = new double[level];
1546 for ( int i = 0; i < level; i++ ) {
1547 rtb.init( env, resolution );
1548 rtb.start();
1549 rtb.logCollectedErrors();
1550 re[i] = resolution;
1551 if ( i < level - 1 ) {
1552 String dir = outDir + '/' + Double.toString( resolution );
1553 imageFiles = getFileList( dir, false );
1554 rtb = new RasterTreeBuilder( imageFiles, outDir, baseName, outputFormat, maxTileSize, srs,
1555 interpolation, WorldFile.TYPE.CENTER, quality, backgroundColor, depth,
1556 resolution, offset, scaleFactor );
1557 }
1558 resolution = resolution * 2;
1559 }
1560
1561 LOG.logInfo( "create configuration files ..." );
1562 rtb.createConfigurationFile( re );
1563
1564 if ( map.get( "-capabilitiesFile" ) != null ) {
1565 LOG.logInfo( "adjust capabilities ..." );
1566 File file = new File( map.getProperty( "-capabilitiesFile" ) );
1567 rtb.updateCapabilitiesFile( file );
1568 }
1569
1570 rtb.logCollectedErrors();
1571 }
1572
1573 /**
1574 * class: official version of a FilenameFilter
1575 */
1576 static class DFileFilter implements FilenameFilter {
1577
1578 private List<String> extensions = null;
1579
1580 /**
1581 *
1582 */
1583 public DFileFilter() {
1584 extensions = new ArrayList<String>();
1585 extensions.add( "JPEG" );
1586 extensions.add( "JPG" );
1587 extensions.add( "BMP" );
1588 extensions.add( "PNG" );
1589 extensions.add( "GIF" );
1590 extensions.add( "TIF" );
1591 extensions.add( "TIFF" );
1592 extensions.add( "GEOTIFF" );
1593 }
1594
1595 /**
1596 * @return "*.*"
1597 */
1598 public String getDescription() {
1599 return "*.*";
1600 }
1601
1602 /*
1603 * (non-Javadoc)
1604 *
1605 * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
1606 */
1607 public boolean accept( java.io.File file, String name ) {
1608 int pos = name.lastIndexOf( "." );
1609 String ext = name.substring( pos + 1 ).toUpperCase();
1610 if ( file.isDirectory() ) {
1611 String s = file.getAbsolutePath() + '/' + name;
1612 File tmp = new File( s );
1613 if ( tmp.isDirectory() ) {
1614 return true;
1615 }
1616 }
1617 return extensions.contains( ext );
1618 }
1619 }
1620
1621 /**
1622 *
1623 * This class enables sorting of dBaseFile objects in chronological order (lowest first, highest
1624 * last).
1625 *
1626 * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a>
1627 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
1628 * @author last edited by: $Author: apoth $
1629 *
1630 * @version 2.0, $Revision: 7590 $, $Date: 2007-06-19 11:30:24 +0200 (Di, 19 Jun 2007) $
1631 *
1632 * @since 2.0
1633 */
1634 private static class MapAgeComparator implements Comparator<Object> {
1635
1636 private String direction = null;
1637
1638 /**
1639 * @param direction
1640 */
1641 public MapAgeComparator( String direction ) {
1642 this.direction = direction.toUpperCase();
1643 }
1644
1645 public int compare( Object o1, Object o2 ) {
1646 Object[] o1a = (Object[]) o1;
1647 Object[] o2a = (Object[]) o2;
1648
1649 if ( o1a[0] == null || o2a[0] == null ) {
1650 return 0;
1651 }
1652 if ( direction.equals( "UP" ) ) {
1653 return o1a[0].toString().compareTo( o2a[0].toString() );
1654 }
1655 return o2a[0].toString().compareTo( o1a[0].toString() );
1656 }
1657 }
1658
1659 }