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