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