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