0001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/tools/raster/RasterTreeBuilder.java $
0002: /*---------------- FILE HEADER ------------------------------------------
0003:
0004: This file is part of deegree.
0005: Copyright (C) 2001-2008 by:
0006: EXSE, Department of Geography, University of Bonn
0007: http://www.giub.uni-bonn.de/deegree/
0008: lat/lon GmbH
0009: http://www.lat-lon.de
0010:
0011: This library is free software; you can redistribute it and/or
0012: modify it under the terms of the GNU Lesser General Public
0013: License as published by the Free Software Foundation; either
0014: version 2.1 of the License, or (at your option) any later version.
0015:
0016: This library is distributed in the hope that it will be useful,
0017: but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: Lesser General Public License for more details.
0020:
0021: You should have received a copy of the GNU Lesser General Public
0022: License along with this library; if not, write to the Free Software
0023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0024:
0025: Contact:
0026:
0027: Andreas Poth
0028: lat/lon GmbH
0029: Aennchenstraße 19
0030: 53177 Bonn
0031: Germany
0032: E-Mail: poth@lat-lon.de
0033:
0034: Prof. Dr. Klaus Greve
0035: Department of Geography
0036: University of Bonn
0037: Meckenheimer Allee 166
0038: 53115 Bonn
0039: Germany
0040: E-Mail: greve@giub.uni-bonn.de
0041:
0042: ---------------------------------------------------------------------------*/
0043:
0044: package org.deegree.tools.raster;
0045:
0046: import static java.io.File.separator;
0047: import static java.util.Arrays.sort;
0048:
0049: import java.awt.Color;
0050: import java.awt.Graphics;
0051: import java.awt.color.ColorSpace;
0052: import java.awt.image.BufferedImage;
0053: import java.awt.image.ColorModel;
0054: import java.awt.image.ComponentColorModel;
0055: import java.awt.image.DataBuffer;
0056: import java.awt.image.Raster;
0057: import java.awt.image.WritableRaster;
0058: import java.awt.image.renderable.ParameterBlock;
0059: import java.io.File;
0060: import java.io.FileOutputStream;
0061: import java.io.FileWriter;
0062: import java.io.FilenameFilter;
0063: import java.io.IOException;
0064: import java.io.InputStreamReader;
0065: import java.io.PrintStream;
0066: import java.io.PrintWriter;
0067: import java.io.Reader;
0068: import java.net.URI;
0069: import java.net.URL;
0070: import java.util.ArrayList;
0071: import java.util.Arrays;
0072: import java.util.Comparator;
0073: import java.util.HashMap;
0074: import java.util.Hashtable;
0075: import java.util.Iterator;
0076: import java.util.List;
0077: import java.util.Map;
0078: import java.util.Properties;
0079:
0080: import javax.media.jai.Interpolation;
0081: import javax.media.jai.InterpolationBicubic;
0082: import javax.media.jai.InterpolationBicubic2;
0083: import javax.media.jai.InterpolationBilinear;
0084: import javax.media.jai.InterpolationNearest;
0085: import javax.media.jai.JAI;
0086: import javax.media.jai.RenderedOp;
0087: import javax.media.jai.TiledImage;
0088:
0089: import net.sf.ehcache.Cache;
0090: import net.sf.ehcache.CacheManager;
0091: import net.sf.ehcache.Element;
0092: import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
0093:
0094: import org.deegree.datatypes.QualifiedName;
0095: import org.deegree.datatypes.Types;
0096: import org.deegree.framework.log.ILogger;
0097: import org.deegree.framework.log.LoggerFactory;
0098: import org.deegree.framework.util.ImageUtils;
0099: import org.deegree.framework.util.StringTools;
0100: import org.deegree.framework.xml.XMLFragment;
0101: import org.deegree.framework.xml.XSLTDocument;
0102: import org.deegree.graphics.transformation.GeoTransform;
0103: import org.deegree.graphics.transformation.WorldToScreenTransform;
0104: import org.deegree.io.dbaseapi.DBaseFile;
0105: import org.deegree.io.shpapi.ShapeFile;
0106: import org.deegree.model.coverage.grid.GridCoverageExchange;
0107: import org.deegree.model.coverage.grid.WorldFile;
0108: import org.deegree.model.crs.GeoTransformer;
0109: import org.deegree.model.feature.Feature;
0110: import org.deegree.model.feature.FeatureCollection;
0111: import org.deegree.model.feature.FeatureFactory;
0112: import org.deegree.model.feature.FeatureProperty;
0113: import org.deegree.model.feature.schema.FeatureType;
0114: import org.deegree.model.feature.schema.PropertyType;
0115: import org.deegree.model.spatialschema.Envelope;
0116: import org.deegree.model.spatialschema.Geometry;
0117: import org.deegree.model.spatialschema.GeometryFactory;
0118: import org.deegree.ogcbase.CommonNamespaces;
0119: import org.deegree.processing.raster.converter.Image2RawData;
0120: import org.deegree.processing.raster.converter.RawData2Image;
0121:
0122: import com.sun.media.jai.codec.FileSeekableStream;
0123:
0124: /**
0125: * This class represents a <code>RasterTreeBuilder</code> object.<br>
0126: * It wcan be used to create a resolution pyramid from one or more already existing raster dataset
0127: * (image). The resulting pyramid will be described by a set of shapes (containing the image tiles
0128: * bounding boxes) and a XML coverage description document that can be used with the deegree WCS.
0129: * The RTB supports real images like png, tif, jpeg, bmp and gif as well as raw data image like
0130: * 16Bit and 32Bit tif-images without color model. <br>
0131: * because of the large amount of data that may be process by the RTB it makes use of a caching
0132: * mechnism. For this the ehcache project is used. One can configure the cache behavior by placing a
0133: * file named ehcache.xml defining a cache named 'imgCache' within the class root when starting the
0134: * RTB. (For details please see the ehcache documentation). If no ehcache.xml is available default
0135: * cache configuration will be used which is set to:
0136: * <ul>
0137: * <li>maxElementsInMemory = 10
0138: * <li>memoryStoreEvictionPolicy = LFU
0139: * <li>overflowToDisk = false (notice that overflow to disk is not supported because cached objects
0140: * are not serializable)
0141: * <li>eternal = false
0142: * <li>timeToLiveSeconds = 3600
0143: * <li>timeToIdleSeconds = 3600
0144: * </ul>
0145: *
0146: *
0147: * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a>
0148: * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
0149: * @author last edited by: $Author: apoth $
0150: *
0151: * @version 2.0, $Revision: 9815 $, $Date: 2008-01-29 12:41:59 -0800 (Tue, 29 Jan 2008) $
0152: *
0153: * @since 2.0
0154: */
0155: public class RasterTreeBuilder {
0156:
0157: private static final ILogger LOG = LoggerFactory
0158: .getLogger(RasterTreeBuilder.class);
0159:
0160: private static final URI DEEGREEAPP = CommonNamespaces
0161: .buildNSURI("http://www.deegree.org/app");
0162:
0163: private static final String APP_PREFIX = "app";
0164:
0165: // templates and transformation scripts
0166: private URL configURL = RasterTreeBuilder.class
0167: .getResource("template_wcs_configuration.xml");
0168:
0169: private URL configXSL = RasterTreeBuilder.class
0170: .getResource("updateConfig.xsl");
0171:
0172: private URL inputXSL = RasterTreeBuilder.class
0173: .getResource("updateCapabilities.xsl");
0174:
0175: private int bitDepth = 16;
0176:
0177: // input for new MergeRaste object
0178: private List<String> imageFiles;
0179:
0180: private List<WorldFile> imageFilesEnvs;
0181:
0182: private Map<String, String> imageFilesErrors;
0183:
0184: private String outputDir;
0185:
0186: private String baseName;
0187:
0188: private String outputFormat;
0189:
0190: private double maxTileSize;
0191:
0192: private String srs = null;
0193:
0194: private Interpolation interpolation = null;
0195:
0196: private WorldFile.TYPE worldFileType = null;
0197:
0198: private float quality = 0;
0199:
0200: private String bgColor = null;
0201:
0202: private float offset = 0;
0203:
0204: private float scaleFactor = 1;
0205:
0206: // minimum resolution of input images
0207: private double minimumRes;
0208:
0209: // combining image bounding box
0210: private Envelope combiningEnvelope;
0211:
0212: // size of virtual bounding box in px
0213: private long pxWidthVirtualBBox;
0214:
0215: private long pxHeightVirtualBBox;
0216:
0217: // size of every tile in virtual bounding box in px
0218: private long pxWidthTile;
0219:
0220: private long pxHeightTile;
0221:
0222: // number of tiles in virtual bounding box
0223: private int tileRows;
0224:
0225: private int tileCols;
0226:
0227: private FeatureType ftype = null;
0228:
0229: private FeatureCollection fc = null;
0230:
0231: private Cache imgCache;
0232:
0233: /**
0234: *
0235: * @param imageFiles
0236: * @param outputDir
0237: * @param baseName
0238: * @param outputFormat
0239: * @param maxTileSize
0240: * @param srs
0241: * @param interpolation
0242: * @param worldFileType
0243: * @param quality
0244: * @param bgColor
0245: * @param depth
0246: * @param resolution
0247: * @param offset
0248: * @param scaleFactor
0249: */
0250: public RasterTreeBuilder(List<String> imageFiles, String outputDir,
0251: String baseName, String outputFormat, double maxTileSize,
0252: String srs, String interpolation,
0253: WorldFile.TYPE worldFileType, float quality,
0254: String bgColor, int depth, double resolution, float offset,
0255: float scaleFactor) {
0256:
0257: this .imageFiles = imageFiles;
0258: this .imageFilesErrors = new HashMap<String, String>(imageFiles
0259: .size());
0260: this .imageFilesEnvs = new ArrayList<WorldFile>(imageFiles
0261: .size());
0262: for (int i = 0; i < imageFiles.size(); i++) {
0263: this .imageFilesEnvs.add(null);
0264: }
0265: this .outputDir = outputDir;
0266: File dir = new File(outputDir).getAbsoluteFile();
0267: if (!dir.exists()) {
0268: dir.mkdir();
0269: }
0270: this .baseName = baseName;
0271: this .outputFormat = outputFormat.toLowerCase();
0272: this .maxTileSize = maxTileSize;
0273: this .srs = srs;
0274: this .interpolation = createInterpolation(interpolation);
0275: this .worldFileType = worldFileType;
0276: this .quality = quality;
0277: this .bgColor = bgColor;
0278: if (depth != 0) {
0279: this .bitDepth = depth;
0280: }
0281: this .minimumRes = resolution;
0282: this .offset = offset;
0283: this .scaleFactor = scaleFactor;
0284:
0285: CacheManager singletonManager = CacheManager.create();
0286: if (singletonManager.getCache("imgCache") == null) {
0287: Cache cache = new Cache("imgCache", 10,
0288: MemoryStoreEvictionPolicy.LFU, false, ".", false,
0289: 3600, 3600, false, 240, null);
0290: singletonManager.addCache(cache);
0291: imgCache = singletonManager.getCache("imgCache");
0292: } else {
0293: imgCache = singletonManager.getCache("imgCache");
0294: try {
0295: imgCache.removeAll();
0296: } catch (IOException e) {
0297: e.printStackTrace();
0298: }
0299: }
0300:
0301: PropertyType[] ftp = new PropertyType[3];
0302: ftp[0] = FeatureFactory.createSimplePropertyType(
0303: new QualifiedName("GEOM"), Types.GEOMETRY, false);
0304: ftp[1] = FeatureFactory.createSimplePropertyType(
0305: new QualifiedName(
0306: GridCoverageExchange.SHAPE_IMAGE_FILENAME),
0307: Types.VARCHAR, false);
0308: ftp[2] = FeatureFactory.createSimplePropertyType(
0309: new QualifiedName(GridCoverageExchange.SHAPE_DIR_NAME),
0310: Types.VARCHAR, false);
0311: ftype = FeatureFactory.createFeatureType(new QualifiedName(
0312: "tiles"), false, ftp);
0313: }
0314:
0315: /**
0316: * @throws IOException
0317: */
0318: public void logCollectedErrors() throws IOException {
0319: FileOutputStream fos = new FileOutputStream("RasterTreeBuilder"
0320: + minimumRes + ".log");
0321: PrintWriter pw = new PrintWriter(fos);
0322: pw.println("processing the following files caused an error");
0323: Iterator<String> iter = imageFilesErrors.keySet().iterator();
0324: while (iter.hasNext()) {
0325: String key = iter.next();
0326: String value = imageFilesErrors.get(key);
0327: pw.print(key);
0328: pw.print(": ");
0329: pw.println(value);
0330: }
0331: pw.close();
0332: LOG.logInfo("LOG file RasterTreeBuilder.log has been written");
0333: }
0334:
0335: /**
0336: * starts creating of a raster tile level using the current bbox and resolution
0337: *
0338: * @throws Exception
0339: */
0340: public void start() throws Exception {
0341: System.gc();
0342: fc = FeatureFactory.createFeatureCollection(Double
0343: .toString(minimumRes), tileRows * tileCols);
0344: createTiles(tileRows, tileCols);
0345:
0346: LOG.logInfo("creating shape for georeferencing ... ");
0347: ShapeFile sf = new ShapeFile(outputDir + "/sh" + minimumRes,
0348: "rw");
0349: sf.writeShape(fc);
0350: sf.close();
0351:
0352: }
0353:
0354: /**
0355: * @param env
0356: * @param resolution
0357: */
0358: public void init(Envelope env, double resolution) {
0359:
0360: // set target envelope
0361: setEnvelope(env);
0362: setResolution(resolution);
0363: determineVirtualBBox();
0364: determineTileSize();
0365: }
0366:
0367: /**
0368: * sets the resolution level to be used for tiling
0369: *
0370: * @param resolution
0371: */
0372: public void setResolution(double resolution) {
0373: minimumRes = resolution;
0374: }
0375:
0376: /**
0377: * sets the bounding box used for tiling
0378: *
0379: * @param bbox
0380: */
0381: public void setEnvelope(Envelope bbox) {
0382: combiningEnvelope = bbox;
0383: }
0384:
0385: /**
0386: * TODO this is a copy from org.deegree.tools.raster#AutoTiler
0387: *
0388: * loads the base image
0389: *
0390: * @throws IOException
0391: */
0392: private TiledImage loadImage(String imageSource) throws IOException {
0393:
0394: TiledImage ti = null;
0395: Element elem = imgCache.get(imageSource);
0396: if (elem != null) {
0397: ti = (TiledImage) elem.getObjectValue();
0398: }
0399:
0400: if (ti == null) {
0401: System.out.println("cache size: " + imgCache.getSize());
0402: System.out.println("read image: " + imageSource);
0403:
0404: FileSeekableStream fss = new FileSeekableStream(imageSource);
0405: RenderedOp rop = JAI.create("stream", fss);
0406: BufferedImage bi = rop.getAsBufferedImage();
0407: try {
0408: fss.close();
0409: } catch (IOException e) {
0410: // should never happen
0411: }
0412: ti = new TiledImage(bi, 500, 500);
0413: imgCache.put(new Element(imageSource, ti));
0414: }
0415:
0416: return ti;
0417: }
0418:
0419: /**
0420: * Determins the necessary size of a bounding box, which is large enough to hold all input image
0421: * files. The result is stored in the combining <code>Envelope</code>.
0422: *
0423: * @throws Exception
0424: */
0425: private WorldFile determineCombiningBBox() throws Exception {
0426:
0427: System.out.println("calculating overall bounding box ...");
0428:
0429: if (imageFiles == null || imageFiles.isEmpty()) {
0430: throw new Exception(
0431: "No combining BoundingBox to be determined: "
0432: + "The list of image files is null or empty.");
0433: }
0434:
0435: WorldFile wf1 = null;
0436: if (combiningEnvelope == null) {
0437:
0438: // upper left corner of combining bounding box
0439: double minX = Double.MAX_VALUE;
0440: double maxY = Double.MIN_VALUE;
0441: // lower right corner of combining bounding box
0442: double maxX = Double.MIN_VALUE;
0443: double minY = Double.MAX_VALUE;
0444: // minimum resolution within combining bounding box
0445: double minResX = Double.MAX_VALUE;
0446: double minResY = Double.MAX_VALUE;
0447:
0448: for (int i = 0; i < imageFiles.size(); i++) {
0449:
0450: File file = new File(imageFiles.get(i));
0451: if (file.exists() && !file.isDirectory()) {
0452: System.out.println(imageFiles.get(i));
0453: FileSeekableStream fss = new FileSeekableStream(
0454: imageFiles.get(i));
0455: RenderedOp rop = JAI.create("stream", fss);
0456: int iw = ((Integer) rop.getProperty("image_width"))
0457: .intValue();
0458: int ih = ((Integer) rop.getProperty("image_height"))
0459: .intValue();
0460: fss.close();
0461:
0462: WorldFile wf = null;
0463:
0464: try {
0465: wf = WorldFile.readWorldFile(imageFiles.get(i),
0466: worldFileType, iw, ih);
0467: } catch (Exception e) {
0468: LOG.logError(e.getMessage());
0469: continue;
0470: }
0471: imageFilesEnvs.set(i, wf);
0472: // now the values of resx, resy, envelope of the current image
0473: // (read from the world file) file are available
0474:
0475: // find min for x and y
0476: minX = Math.min(minX, wf.getEnvelope().getMin()
0477: .getX());
0478: minY = Math.min(minY, wf.getEnvelope().getMin()
0479: .getY());
0480: // find max for x and y
0481: maxX = Math.max(maxX, wf.getEnvelope().getMax()
0482: .getX());
0483: maxY = Math.max(maxY, wf.getEnvelope().getMax()
0484: .getY());
0485:
0486: // find min for resolution of x and y
0487: minResX = Math.min(minResX, wf.getResx());
0488: minResY = Math.min(minResY, wf.getResy());
0489: } else {
0490: System.out.println("File: " + imageFiles.get(i)
0491: + " does not exist!");
0492: System.out.println("Image will be ignored");
0493: }
0494: if (i % 10 == 0) {
0495: System.gc();
0496: }
0497:
0498: }
0499: // store minimum resolution
0500: if (minimumRes <= 0) {
0501: minimumRes = Math.min(minResX, minResY);
0502: }
0503: combiningEnvelope = GeometryFactory.createEnvelope(minX,
0504: minY, maxX, maxY, null);
0505: LOG.logInfo("determined envelope: ", combiningEnvelope);
0506: }
0507: wf1 = new WorldFile(minimumRes, minimumRes, 0, 0,
0508: combiningEnvelope);
0509: return wf1;
0510: }
0511:
0512: /**
0513: * Determins a usefull size for the virtual bounding box. It is somewhat larger than the
0514: * combining bounding box. The result is stored in the virtual <code>Envelope</code>.
0515: *
0516: */
0517: private Envelope determineVirtualBBox() {
0518:
0519: double width = combiningEnvelope.getWidth();
0520: double height = combiningEnvelope.getHeight();
0521:
0522: // set width and height to next higher even-numbered thousand
0523: // double pxWidth = ( width / minimumRes ) + 1;
0524: // double pxHeight = ( height / minimumRes ) + 1;
0525: // changes by idgis
0526: double pxWidth = (width / minimumRes);
0527: double pxHeight = (height / minimumRes);
0528:
0529: pxWidthVirtualBBox = Math.round(pxWidth);
0530: pxHeightVirtualBBox = Math.round(pxHeight);
0531:
0532: // lower right corner of virtual bounding box
0533:
0534: WorldFile wf = new WorldFile(minimumRes, minimumRes, 0, 0,
0535: combiningEnvelope);
0536: // upper left corner of virtual bounding box
0537: double minX = combiningEnvelope.getMin().getX();
0538: double maxY = combiningEnvelope.getMax().getY();
0539:
0540: // double maxX = minX + ( ( pxWidth - 1 ) * wf.getResx() );
0541: // double minY = maxY - ( ( pxHeight - 1 ) * wf.getResx() );
0542: // changes by idgis
0543: double maxX = minX + ((pxWidth) * wf.getResx());
0544: double minY = maxY - ((pxHeight) * wf.getResx());
0545:
0546: return GeometryFactory.createEnvelope(minX, minY, maxX, maxY,
0547: null);
0548:
0549: // return combiningEnvelope;
0550: }
0551:
0552: /**
0553: * This method determins and sets the size of the tiles in pixel both horizontally (pxWidthTile)
0554: * and vertically (pxHeightTile). It also sets the necessary number of <code>tileCols</code>
0555: * (depending on the tileWidth) and <code>tileRows</code> (depending on the tileHeight).
0556: *
0557: * By default, all tiles have a size of close to but less than 6000 pixel either way.
0558: */
0559: private void determineTileSize() {
0560: /*
0561: * The size of the virtual bbox gets divided by maxTileSize to find an approximat number of
0562: * tiles (a).
0563: *
0564: * If the virtual bbox is in any direction (horizontally or vertically) smaler than
0565: * maxTileSize px, then it has only 1 tile in that direction. In this case, the size of the
0566: * tile equals the size of the virtual bbox.
0567: *
0568: * Otherwise, divide the size of the pixel size of virtual bbox by the pixel tile size
0569: *
0570: */
0571: // determin width of tile
0572: double a = (pxWidthVirtualBBox / maxTileSize);
0573: int tileCols = (int) Math.ceil(a);
0574: if (a <= 1.0) {
0575: pxWidthTile = pxWidthVirtualBBox;
0576: } else {
0577: // tileCols = (int) ( pxWidthVirtualBBox / ( maxTileSize - 1 ) ) + 1;
0578: // changes by idgis
0579: tileCols = (int) (pxWidthVirtualBBox / (maxTileSize - 1));
0580: pxWidthTile = (int) maxTileSize;
0581: }
0582:
0583: // determin height of tile
0584: a = (pxHeightVirtualBBox / maxTileSize);
0585: int tileRows = (int) Math.ceil(a);
0586: if (a <= 1.0) {
0587: pxHeightTile = pxHeightVirtualBBox;
0588: } else {
0589: // tileRows = (int) ( pxHeightVirtualBBox / ( maxTileSize - 1 ) ) + 1;
0590: // changes by idgis
0591: tileRows = (int) (pxHeightVirtualBBox / (maxTileSize - 1));
0592: pxHeightTile = (int) maxTileSize;
0593: }
0594:
0595: this .tileCols = tileCols;
0596: this .tileRows = tileRows;
0597:
0598: LOG.logInfo("minimum resolution: " + minimumRes);
0599: LOG.logInfo("width = " + pxWidthVirtualBBox + " *** height = "
0600: + pxHeightVirtualBBox);
0601: LOG.logInfo("pxWidthTile = " + pxWidthTile
0602: + " *** pxHeightTile = " + pxHeightTile);
0603: LOG.logInfo("number of tiles: horizontally = " + tileCols
0604: + ", vertically = " + tileRows);
0605: }
0606:
0607: /**
0608: * Creates one <code>Tile</code> object after the other, with the number of tiles being
0609: * specified by the given number of <code>rows</code> and <code>cols</code>.
0610: *
0611: * Each Tile gets written to the FileOutputStream by the internal call to #paintImagesOnTile.
0612: *
0613: * @param rows
0614: * @param cols
0615: * @throws IOException
0616: * @throws Exception
0617: */
0618: private void createTiles(int rows, int cols) throws IOException {
0619:
0620: System.out.println("creating merged image ...");
0621:
0622: Envelope virtualEnv = determineVirtualBBox();
0623:
0624: // changes by idgis
0625: // double tileWidth = minimumRes * ( pxWidthTile - 1 );
0626: double tileWidth = minimumRes * (pxWidthTile);
0627: // double tileHeight = minimumRes * ( pxHeightTile - 1 );
0628: double tileHeight = minimumRes * (pxHeightTile);
0629:
0630: double upperY = virtualEnv.getMax().getY();
0631:
0632: File file = new File(outputDir + '/'
0633: + Double.toString(minimumRes)).getAbsoluteFile();
0634: file.mkdir();
0635:
0636: for (int i = 0; i < rows; i++) {
0637: System.out.println("processing row " + i);
0638: double leftX = virtualEnv.getMin().getX();
0639: double lowerY = upperY - tileHeight;
0640: for (int j = 0; j < cols; j++) {
0641:
0642: System.out.println("processing tile: " + i + " - " + j);
0643: double rightX = leftX + tileWidth;
0644: Envelope env = GeometryFactory.createEnvelope(leftX,
0645: lowerY, rightX, upperY, null);
0646: leftX = rightX;
0647: String postfix = "_" + i + "_" + j;
0648: Tile tile = new Tile(env, postfix);
0649:
0650: paintImagesOnTile(tile);
0651: System.gc();
0652: }
0653: upperY = lowerY;
0654: }
0655: System.gc();
0656:
0657: }
0658:
0659: /**
0660: * Paints all image files that intersect with the passed <code>tile</code> onto that tile and
0661: * creates an output file in the <code>outputDir</code>. If no image file intersects with the
0662: * given tile, then an empty output file is created. The name of the output file is defined by
0663: * the <code>baseName</code> and the tile's index of row and column.
0664: *
0665: * @param tile
0666: * The tile on which to paint the image.
0667: * @throws IOException
0668: */
0669: private void paintImagesOnTile(Tile tile) throws IOException {
0670:
0671: Envelope tileEnv = tile.getTileEnvelope();
0672: String postfix = tile.getPostfix();
0673:
0674: BufferedImage out = createOutputImage();
0675: float[][] data = null;
0676: if (bitDepth == 16 && "raw".equals(outputFormat)) {
0677: // do not use image api if target bitDepth = 16
0678: data = new float[(int) pxHeightTile][(int) pxWidthTile];
0679: }
0680:
0681: if (bgColor != null) {
0682: Graphics g = out.getGraphics();
0683: g.setColor(Color.decode(bgColor));
0684: g.fillRect(0, 0, out.getWidth(), out.getHeight());
0685: g.dispose();
0686: }
0687: boolean paint = false;
0688: int gcc = 0;
0689:
0690: for (int i = 0; i < imageFiles.size(); i++) {
0691:
0692: File file = new File(imageFiles.get(i));
0693: if (imageFilesErrors.get(imageFiles.get(i)) == null
0694: && file.exists() && !file.isDirectory()) {
0695:
0696: WorldFile wf = imageFilesEnvs.get(i);
0697: if (wf == null) {
0698: System.out.println("read world file");
0699: // just read image if bbox has not been already read
0700: FileSeekableStream fss = new FileSeekableStream(
0701: imageFiles.get(i));
0702: RenderedOp rop = JAI.create("stream", fss);
0703: int iw = ((Integer) rop.getProperty("image_width"))
0704: .intValue();
0705: int ih = ((Integer) rop.getProperty("image_height"))
0706: .intValue();
0707: fss.close();
0708: try {
0709: wf = WorldFile.readWorldFile(imageFiles.get(i),
0710: worldFileType, iw, ih);
0711: } catch (Exception e) {
0712: imageFilesErrors.put(imageFiles.get(i), e
0713: .getMessage());
0714: continue;
0715: }
0716: // cache bounding boxes
0717: imageFilesEnvs.set(i, wf);
0718: gcc++;
0719: if (gcc % 10 == 0) {
0720: System.out.println("garbage collecting");
0721: System.gc();
0722: }
0723: }
0724: // now the values of resx, resy, envelope of the current image file are available
0725: if (wf.getEnvelope().intersects(tileEnv)) {
0726: TiledImage bi = loadImage(imageFiles.get(i));
0727: gcc++;
0728: try {
0729: System.out.println("drawImage");
0730: drawImage(out, data, bi, tile, wf, minimumRes,
0731: interpolation, imageFilesErrors,
0732: outputFormat, bitDepth, offset,
0733: scaleFactor);
0734: paint = true;
0735: } catch (Exception e) {
0736: e.printStackTrace();
0737: imageFilesErrors.put(imageFiles.get(i), e
0738: .getMessage());
0739: }
0740: if (gcc % 5 == 0) {
0741: System.out.println("garbage collecting");
0742: System.gc();
0743: }
0744: }
0745: } else {
0746: imageFilesErrors.put(imageFiles.get(i),
0747: "image does not exist!");
0748: }
0749: }
0750: if (paint) {
0751: if (!isTransparent(out)) {
0752: // just write files if something has been painted
0753: if (bitDepth == 16 && "raw".equals(outputFormat)) {
0754: out = RawData2Image.rawData2Image(data, false,
0755: scaleFactor, offset);
0756: }
0757: storeTileImageToFileSystem(postfix, out);
0758: createWorldFile(tile);
0759: String frm = outputFormat;
0760: if ("raw".equals(outputFormat)) {
0761: frm = "tif";
0762: }
0763: storeEnvelope(Double.toString(minimumRes), baseName
0764: + postfix + '.' + frm, tileEnv);
0765: }
0766: }
0767: }
0768:
0769: /**
0770: * creates an instance of a BufferedImage depending on requested target format
0771: *
0772: * @return the new image
0773: */
0774: private BufferedImage createOutputImage() {
0775:
0776: BufferedImage out = null;
0777: if ("jpg".equals(outputFormat) || "jpeg".equals(outputFormat)
0778: || "bmp".equals(outputFormat)) {
0779: // for bmp, jpg, jpeg use 3 byte:
0780: out = new BufferedImage((int) pxWidthTile,
0781: (int) pxHeightTile, BufferedImage.TYPE_INT_RGB);
0782: } else if ("tif".equals(outputFormat)
0783: || "tiff".equals(outputFormat)
0784: || "png".equals(outputFormat)
0785: || "gif".equals(outputFormat)) {
0786: // for tif, tiff and png use 4 byte:
0787: out = new BufferedImage((int) pxWidthTile,
0788: (int) pxHeightTile, BufferedImage.TYPE_INT_ARGB);
0789: } else {
0790: ColorModel ccm;
0791:
0792: if (bitDepth == 16) {
0793: ccm = new ComponentColorModel(ColorSpace
0794: .getInstance(ColorSpace.CS_GRAY), null, false,
0795: false, BufferedImage.OPAQUE,
0796: DataBuffer.TYPE_USHORT);
0797: WritableRaster wr = ccm.createCompatibleWritableRaster(
0798: (int) pxWidthTile, (int) pxHeightTile);
0799:
0800: out = new BufferedImage(ccm, wr, false,
0801: new Hashtable<Object, Object>());
0802: } else {
0803: out = new BufferedImage((int) pxWidthTile,
0804: (int) pxHeightTile, BufferedImage.TYPE_INT_ARGB);
0805: }
0806: }
0807:
0808: return out;
0809:
0810: }
0811:
0812: /**
0813: *
0814: * @param postfix
0815: * tile name postfix ( -> tile index $x_$y )
0816: * @param out
0817: * tile image to save
0818: */
0819: private void storeTileImageToFileSystem(String postfix,
0820: BufferedImage out) {
0821: try {
0822: String frm = outputFormat;
0823: if ("raw".equals(frm)) {
0824: frm = "tif";
0825: }
0826: String imageFile = outputDir + '/'
0827: + Double.toString(minimumRes) + '/' + baseName
0828: + postfix + '.' + frm;
0829: File file = new File(imageFile).getAbsoluteFile();
0830:
0831: ImageUtils.saveImage(out, file, quality);
0832:
0833: } catch (IOException e) {
0834: e.printStackTrace();
0835: }
0836: }
0837:
0838: /**
0839: * Draws an image map to the target tile considering defined interpolation method for rescaling.
0840: * This method is static so it can be used easily from the <code>RasterTreeUpdater</code>.
0841: *
0842: * @param out
0843: * target image tile
0844: * @param data
0845: * @param image
0846: * source image map
0847: * @param tile
0848: * tile description, must contain the envelope of the target image
0849: * @param wf
0850: * must contain the envelope of the TiledImage of the source image
0851: * @param minimumRes
0852: * the minimum resolution of input images
0853: * @param interpolation
0854: * the interpolation method
0855: * @param imageFilesErrors
0856: * a mapping between image files and errors
0857: * @param outputFormat
0858: * the output format
0859: * @param bitDepth
0860: * the output bit depth
0861: * @param offset
0862: * offset used if bitDepth = 16 and outputFormat = raw
0863: * @param scaleFactor
0864: * scale factor used if bitDepth = 16 and outputFormat = raw
0865: */
0866: public static void drawImage(BufferedImage out, float[][] data,
0867: final TiledImage image, Tile tile, WorldFile wf,
0868: double minimumRes, Interpolation interpolation,
0869: Map<String, String> imageFilesErrors, String outputFormat,
0870: int bitDepth, float offset, float scaleFactor) {
0871:
0872: Envelope tileEnv = tile.getTileEnvelope();
0873: Envelope mapEnv = wf.getEnvelope();
0874:
0875: GeoTransform gt2 = new WorldToScreenTransform(mapEnv.getMin()
0876: .getX(), mapEnv.getMin().getY(),
0877: mapEnv.getMax().getX(), mapEnv.getMax().getY(), 0, 0,
0878: image.getWidth() - 1, image.getHeight() - 1);
0879:
0880: Envelope inter = mapEnv.createIntersection(tileEnv);
0881: if (inter == null)
0882: return;
0883: int x1 = (int) Math.round(gt2.getDestX(inter.getMin().getX()));
0884: int y1 = (int) Math.round(gt2.getDestY(inter.getMax().getY()));
0885: int x2 = (int) Math.round(gt2.getDestX(inter.getMax().getX()));
0886: int y2 = (int) Math.round(gt2.getDestY(inter.getMin().getY()));
0887:
0888: if (x2 - x1 >= 0 && y2 - y1 >= 0
0889: && x1 + x2 - x1 < image.getWidth()
0890: && y1 + y2 - y1 < image.getHeight() && x1 >= 0
0891: && y1 >= 0) {
0892:
0893: BufferedImage newImg = null;
0894: int w = x2 - x1 + 1;
0895: int h = y2 - y1 + 1;
0896: // System.out.println( x1 + " " + y1 + " " + w + " " + h + " " + image.getWidth() + " "
0897: // + image.getHeight() );
0898: BufferedImage img = image.getSubImage(x1, y1, w, h)
0899: .getAsBufferedImage();
0900:
0901: if (!isTransparent(img)) {
0902:
0903: // copy source image to a 4 Byte BufferedImage because there are
0904: // problems with handling 8 Bit palette images
0905: if (img.getColorModel().getPixelSize() == 18) {
0906: LOG.logInfo("copy 8Bit image to 32Bit image");
0907: BufferedImage bi = new BufferedImage(
0908: img.getWidth(), img.getHeight(),
0909: BufferedImage.TYPE_INT_ARGB);
0910:
0911: Graphics g = bi.getGraphics();
0912: try {
0913: g.drawImage(img, 0, 0, null);
0914: } catch (Exception e) {
0915: System.out.println(e.getMessage());
0916: }
0917: g.dispose();
0918: img = bi;
0919: }
0920: if ((wf.getResx() / minimumRes < 0.9999)
0921: || (wf.getResx() / minimumRes > 1.0001)
0922: || (wf.getResy() / minimumRes < 0.9999)
0923: || (wf.getResy() / minimumRes > 1.0001)) {
0924: newImg = scale(img, interpolation, (float) (wf
0925: .getResx() / minimumRes), (float) (wf
0926: .getResy() / minimumRes));
0927: } else {
0928: newImg = img;
0929: }
0930: GeoTransform gt = new WorldToScreenTransform(tileEnv
0931: .getMin().getX(), tileEnv.getMin().getY(),
0932: tileEnv.getMax().getX(), tileEnv.getMax()
0933: .getY(), 0, 0, out.getWidth() - 1, out
0934: .getHeight() - 1);
0935:
0936: x1 = (int) Math.round(gt
0937: .getDestX(inter.getMin().getX()));
0938: y1 = (int) Math.round(gt
0939: .getDestY(inter.getMax().getY()));
0940: x2 = (int) Math.round(gt
0941: .getDestX(inter.getMax().getX()));
0942: y2 = (int) Math.round(gt
0943: .getDestY(inter.getMin().getY()));
0944:
0945: if (x2 - x1 > 0 && y2 - y1 > 0) {
0946: // ensure that there is something to draw
0947: try {
0948: if ("raw".equals(outputFormat)) {
0949: DataBuffer outBuffer = out.getData()
0950: .getDataBuffer();
0951: DataBuffer newImgBuffer = newImg.getData()
0952: .getDataBuffer();
0953: int ps = newImg.getColorModel()
0954: .getPixelSize();
0955: float[][] newData = null;
0956: if (bitDepth == 16 && ps == 16) {
0957: Image2RawData i2r = new Image2RawData(
0958: newImg, 1f / scaleFactor, -1
0959: * offset);
0960: // do not use image api if target bitDepth = 16
0961: newData = i2r.parse();
0962: }
0963: for (int i = 0; i < newImg.getWidth(); i++) {
0964: for (int j = 0; j < newImg.getHeight(); j++) {
0965: if (x1 + i < out.getWidth()
0966: && y1 + j < out.getHeight()) {
0967: int newImgPos = newImg
0968: .getWidth()
0969: * j + i;
0970: int outPos = out.getWidth()
0971: * (y1 + j) + (x1 + i);
0972: if (bitDepth == 16 && ps == 16) {
0973: // int v = newImgBuffer.getElem( newImgPos );
0974: // outBuffer.setElem( outPos, v );
0975: data[y1 + j][x1 + i] = newData[j][i];
0976: } else if (bitDepth == 16
0977: && ps == 32) {
0978: int v = newImg.getRGB(i, j);
0979: float f = Float
0980: .intBitsToFloat(v) * 10f;
0981: outBuffer.setElem(outPos,
0982: Math.round(f));
0983: // TODO
0984: // data[y1 + j][x1 + i] = f;
0985: } else if (bitDepth == 32
0986: && ps == 16) {
0987: float f = newImgBuffer
0988: .getElem(newImgPos) / 10f;
0989: outBuffer
0990: .setElem(
0991: outPos,
0992: Float
0993: .floatToIntBits(f));
0994: } else {
0995: out
0996: .setRGB(x1 + i, y1
0997: + j, newImg
0998: .getRGB(i,
0999: j));
1000: }
1001: }
1002: }
1003: }
1004: if ((bitDepth == 16 && ps == 16)
1005: || (bitDepth == 16 && ps == 32)
1006: || (bitDepth == 32 && ps == 16)) {
1007: out.setData(Raster.createRaster(out
1008: .getSampleModel(), outBuffer,
1009: null));
1010: }
1011: } else {
1012: Graphics g = out.getGraphics();
1013: g.drawImage(newImg, x1, y1, newImg
1014: .getWidth(), newImg.getHeight(),
1015: null);
1016: g.dispose();
1017: }
1018: } catch (Exception e) {
1019: LOG.logError("Could not draw upon the image: ");
1020: LOG.logError("New image is of size "
1021: + newImg.getWidth() + ", "
1022: + newImg.getHeight());
1023: LOG.logError("Position/width tried is (" + x1
1024: + ", " + y1 + ", " + newImg.getWidth()
1025: + ", " + newImg.getHeight() + ")");
1026: if (imageFilesErrors != null) {
1027: imageFilesErrors.put(tile.getPostfix(),
1028: StringTools.stackTraceToString(e));
1029: }
1030: }
1031: }
1032: }
1033: }
1034: }
1035:
1036: /**
1037: *
1038: * @param img
1039: * @param interpolation
1040: * @param scaleX
1041: * @param scaleY
1042: * @return the scaled image
1043: */
1044: private static BufferedImage scale(BufferedImage img,
1045: Interpolation interpolation, float scaleX, float scaleY) {
1046:
1047: LOG.logDebug("Scale image: by factors: " + scaleX + ' '
1048: + scaleY);
1049: ParameterBlock pb = new ParameterBlock();
1050: pb.addSource(img);
1051: pb.add(scaleX); // The xScale
1052: pb.add(scaleY); // The yScale
1053: pb.add(0.0F); // The x translation
1054: pb.add(0.0F); // The y translation
1055: pb.add(interpolation); // The interpolation
1056: // Create the scale operation
1057: RenderedOp ro = JAI.create("scale", pb, null);
1058: try {
1059: img = ro.getAsBufferedImage();
1060: } catch (Exception e) {
1061: e.printStackTrace();
1062: }
1063: return img;
1064: }
1065:
1066: private static boolean isTransparent(BufferedImage bi) {
1067: /*
1068: * TODO determine if the passed image is completly transparent for ( int i = 0; i <
1069: * bi.getHeight(); i++ ) { for ( int j = 0; j < bi.getWidth(); j++ ) { if ( bi.getRGB( i, j ) !=
1070: * 0 && bi.getRGB( i, j ) != -256 ) { return false; } } } return true;
1071: */
1072: return false;
1073: }
1074:
1075: /**
1076: * @return an interpolation object from a well known name
1077: * @param interpolation
1078: */
1079: public static Interpolation createInterpolation(String interpolation) {
1080: Interpolation interpol = null;
1081:
1082: if (interpolation.equalsIgnoreCase("Nearest Neighbor")) {
1083: interpol = new InterpolationNearest();
1084: } else if (interpolation.equalsIgnoreCase("Bicubic")) {
1085: interpol = new InterpolationBicubic(5);
1086: } else if (interpolation.equalsIgnoreCase("Bicubic2")) {
1087: interpol = new InterpolationBicubic2(5);
1088: } else if (interpolation.equalsIgnoreCase("Bilinear")) {
1089: interpol = new InterpolationBilinear();
1090: } else {
1091: throw new RuntimeException("invalid interpolation method: "
1092: + interpolation);
1093: }
1094:
1095: return interpol;
1096: }
1097:
1098: /**
1099: * Creates a world file for the corresponding tile in the <code>outputDir</code>. The name of
1100: * the output file is defined by the <code>baseName</code> and the tile's index of row and
1101: * column.
1102: *
1103: * @param tile
1104: * The tile for which to create a world file.
1105: * @throws IOException
1106: */
1107: private void createWorldFile(Tile tile) throws IOException {
1108:
1109: Envelope env = tile.getTileEnvelope();
1110: String postfix = tile.getPostfix();
1111:
1112: StringBuffer sb = new StringBuffer(1000);
1113:
1114: /*
1115: * sb.append( minimumRes ).append( "\n" ).append( 0.0 ).append( "\n" ).append( 0.0 );
1116: * sb.append( "\n" ).append( ( -1 ) * minimumRes ).append( "\n" ); sb.append(
1117: * env.getMin().getX() ).append( "\n" ).append( env.getMax().getY() ); sb.append( "\n" );
1118: */
1119:
1120: // changes by idgis
1121: sb.append(minimumRes).append("\n").append(0.0).append("\n")
1122: .append(0.0);
1123: sb.append("\n").append((-1) * minimumRes).append("\n");
1124: sb.append(env.getMin().getX() + (minimumRes / 2)).append("\n");
1125: sb.append(env.getMax().getY() - (minimumRes / 2));
1126: sb.append("\n");
1127:
1128: File f = new File(outputDir + '/' + Double.toString(minimumRes)
1129: + '/' + baseName + postfix + ".wld");
1130:
1131: FileWriter fw = new FileWriter(f);
1132: PrintWriter pw = new PrintWriter(fw);
1133:
1134: pw.print(sb.toString());
1135:
1136: pw.close();
1137: fw.close();
1138: }
1139:
1140: /**
1141: * stores an envelope and the assigend image file information into a feature/featureCollection
1142: *
1143: * @param dir
1144: * directory where the image file is stored
1145: * @param file
1146: * name of the image file
1147: * @param env
1148: * bbox of the image file
1149: */
1150: private void storeEnvelope(String dir, String file, Envelope env) {
1151: try {
1152: Geometry geom = GeometryFactory.createSurface(env, null);
1153: FeatureProperty[] props = new FeatureProperty[3];
1154: props[0] = FeatureFactory.createFeatureProperty(
1155: new QualifiedName("GEOM"), geom);
1156: props[1] = FeatureFactory.createFeatureProperty(
1157: new QualifiedName(
1158: GridCoverageExchange.SHAPE_IMAGE_FILENAME),
1159: file);
1160: props[2] = FeatureFactory.createFeatureProperty(
1161: new QualifiedName(
1162: GridCoverageExchange.SHAPE_DIR_NAME), dir);
1163: Feature feat = FeatureFactory.createFeature("file", ftype,
1164: props);
1165: fc.add(feat);
1166: } catch (Exception e) {
1167: e.printStackTrace();
1168: }
1169: }
1170:
1171: /**
1172: * creates a configuration file (extended CoverageDescriotion) for a WCS coverage considering
1173: * the passed resolution levels
1174: *
1175: * @param targetResolutions
1176: */
1177: private void createConfigurationFile(double[] targetResolutions) {
1178:
1179: // copy this file to the target directory
1180: String resolutions = "";
1181: sort(targetResolutions);
1182: int length = targetResolutions.length;
1183:
1184: for (int i = 0; i < length; i++) {
1185: resolutions += String.valueOf(targetResolutions[length - 1
1186: - i]);
1187: if (i < (length - 1))
1188: resolutions += ',';
1189: }
1190:
1191: try {
1192: Map<String, String> param = new HashMap<String, String>(20);
1193: Envelope llEnv = getLatLonEnvelope(combiningEnvelope);
1194: param.put("upperleftll", String.valueOf(llEnv.getMin()
1195: .getX())
1196: + ',' + String.valueOf(llEnv.getMin().getY()));
1197: param.put("lowerrightll", String.valueOf(llEnv.getMax()
1198: .getX())
1199: + ',' + String.valueOf(llEnv.getMax().getY()));
1200: param
1201: .put("upperleft", String.valueOf(combiningEnvelope
1202: .getMin().getX())
1203: + ','
1204: + String.valueOf(combiningEnvelope.getMin()
1205: .getY()));
1206: param.put("lowerright", String.valueOf(combiningEnvelope
1207: .getMax().getX())
1208: + ',' + combiningEnvelope.getMax().getY());
1209: File dir = new File(outputDir);
1210: if (dir.isAbsolute()) {
1211: // param.put( "dataDir", outputDir + '/' );
1212: param.put("dataDir", "");
1213: } else {
1214: param.put("dataDir", "");
1215: }
1216: param.put("label", baseName);
1217: param.put("name", baseName);
1218: param.put("description", "");
1219: param.put("keywords", "");
1220: param.put("resolutions", resolutions);
1221: String frm = outputFormat;
1222: if ("raw".equals(outputFormat) && bitDepth == 32) {
1223: frm = "tif";
1224: } else if ("raw".equals(outputFormat) && bitDepth == 16) {
1225: frm = "GeoTiff";
1226: }
1227: param.put("mimeType", frm);
1228: int p = srs.lastIndexOf(':');
1229: param.put("srs", srs.substring(p + 1, srs.length()));
1230: param.put("srsPre", srs.substring(0, p + 1));
1231:
1232: Reader reader = new InputStreamReader(configURL
1233: .openStream());
1234:
1235: XSLTDocument xslt = new XSLTDocument();
1236: xslt.load(configXSL);
1237: XMLFragment xml = xslt.transform(reader,
1238: XMLFragment.DEFAULT_URL, null, param);
1239: reader.close();
1240:
1241: // write the result
1242: String dstFilename = "wcs_" + baseName
1243: + "_configuration.xml";
1244: File dstFile = new File(outputDir, dstFilename);
1245: String configurationFilename = dstFile.getAbsolutePath()
1246: .toString();
1247: FileOutputStream fos = new FileOutputStream(
1248: configurationFilename);
1249: xml.write(fos);
1250: fos.close();
1251:
1252: } catch (Exception e1) {
1253: e1.printStackTrace();
1254: }
1255:
1256: }
1257:
1258: private Envelope getLatLonEnvelope(Envelope env) throws Exception {
1259: GeoTransformer gt = new GeoTransformer("EPSG:4326");
1260: return gt.transform(env, srs);
1261: }
1262:
1263: /**
1264: *
1265: */
1266: private void updateCapabilitiesFile(File capabilitiesFile) {
1267:
1268: try {
1269: XSLTDocument xslt = new XSLTDocument();
1270: xslt.load(inputXSL);
1271: Map<String, String> param = new HashMap<String, String>();
1272:
1273: param.put("dataDirectory", outputDir);
1274: String url = new File("wcs_" + baseName
1275: + "_configuration.xml").toURL().toString();
1276: param.put("configFile", url);
1277: Envelope llEnv = getLatLonEnvelope(combiningEnvelope);
1278: param.put("upperleftll", String.valueOf(llEnv.getMin()
1279: .getX())
1280: + ',' + String.valueOf(llEnv.getMin().getY()));
1281: param.put("lowerrightll", String.valueOf(llEnv.getMax()
1282: .getX())
1283: + ',' + String.valueOf(llEnv.getMax().getY()));
1284:
1285: param.put("name", baseName);
1286: param.put("label", baseName);
1287:
1288: param.put("description", "");
1289: param.put("keywords", "");
1290:
1291: XMLFragment xml = new XMLFragment();
1292: xml.load(capabilitiesFile.toURL());
1293:
1294: xml = xslt.transform(xml, capabilitiesFile.toURL()
1295: .toExternalForm(), null, param);
1296:
1297: // write the result
1298: FileOutputStream fos = new FileOutputStream(
1299: capabilitiesFile);
1300: xml.write(fos);
1301: fos.close();
1302: } catch (Exception e) {
1303: e.printStackTrace();
1304: }
1305: }
1306:
1307: /**
1308: * Validates the content of <code>map</code>, to see, if necessary arguments were passed when
1309: * calling this class.
1310: *
1311: * @param map
1312: * @throws Exception
1313: */
1314: private static void validate(Properties map) throws Exception {
1315:
1316: if (map.get("-outDir") == null) {
1317: throw new Exception("-outDir must be set");
1318: }
1319: String s = (String) map.get("-outDir");
1320: if (s.endsWith("/") || s.endsWith("\\")) {
1321: s = s.substring(0, s.length() - 1);
1322: }
1323:
1324: if (map.get("-baseName") == null) {
1325: throw new Exception("-baseName must be set");
1326: }
1327: if (map.get("-outputFormat") == null) {
1328: map.put("-outputFormat", "png");
1329: } else {
1330: String format = ((String) map.get("-outputFormat"))
1331: .toLowerCase();
1332: if (!"bmp".equals(format) && !"png".equals(format)
1333: && !"jpg".equals(format) && !"jpeg".equals(format)
1334: && !"tif".equals(format) && !"tiff".equals(format)
1335: && !"gif".equals(format) && !("raw").equals(format)) {
1336:
1337: throw new Exception(
1338: "-outputFormat must be one of the following: "
1339: + "'bmp', 'jpeg', 'jpg', 'png', 'tif', 'tiff', 'raw'.");
1340: }
1341: }
1342: if (map.get("-maxTileSize") == null) {
1343: map.put("-maxTileSize", "500");
1344: }
1345: if (map.get("-srs") == null) {
1346: map.put("-srs", "EPSG:4326");
1347: }
1348: if (map.get("-interpolation") == null) {
1349: map.put("-interpolation", "Nearest Neighbor");
1350: }
1351: if (map.get("-noOfLevel") == null) {
1352: map.put("-noOfLevel", "1");
1353: }
1354: if (map.get("-worldFileType") == null) {
1355: map.put("-worldFileType", "center");
1356: }
1357: if (map.get("-quality") == null) {
1358: map.put("-quality", "0.95");
1359: }
1360: if (map.get("-bbox") != null) {
1361: double[] d = StringTools.toArrayDouble((String) map
1362: .get("-bbox"), ",");
1363: Envelope env = GeometryFactory.createEnvelope(d[0], d[1],
1364: d[2], d[3], null);
1365: map.put("-bbox", env);
1366: if (map.get("-resolution") == null) {
1367: throw new Exception(
1368: "-resolution must be set if -bbox is set");
1369: }
1370: map.put("-resolution", new Double((String) map
1371: .get("-resolution")));
1372: } else {
1373: if (map.get("-resolution") == null) {
1374: map.put("-resolution", new Double(-1));
1375: } else {
1376: map.put("-resolution", new Double((String) map
1377: .get("-resolution")));
1378: }
1379: }
1380: }
1381:
1382: /**
1383: * @return the list of image map files to consider read from -mapFiles parameter
1384: *
1385: * @param mapFiles
1386: */
1387: private static List<String> getFileList(String[] mapFiles) {
1388: List<String> imageFiles = new ArrayList<String>();
1389: for (int i = 0; i < mapFiles.length; i++) {
1390: imageFiles.add(mapFiles[i]);
1391: }
1392: return imageFiles;
1393: }
1394:
1395: /**
1396: * @return the list of image map files to consider read from a defined root directory.
1397: *
1398: * @param rootDir
1399: * root directory where to read image map files
1400: * @param subdirs
1401: * true if subdirectories of the root directory shall be parsed for image maps too
1402: */
1403: private static List<String> getFileList(String rootDir,
1404: boolean subdirs) {
1405: List<String> list = new ArrayList<String>(10000);
1406: File file = new File(rootDir);
1407: String[] entries = file.list(new DFileFilter());
1408: if (entries != null) {
1409: for (int i = 0; i < entries.length; i++) {
1410: File entry = new File(rootDir + '/' + entries[i]);
1411: if (entry.isDirectory() && subdirs) {
1412: list = readSubDirs(entry, list);
1413: } else {
1414: list.add(rootDir + '/' + entries[i]);
1415: }
1416: }
1417: }
1418: return list;
1419: }
1420:
1421: /**
1422: *
1423: * @param file
1424: * @param list
1425: * @return the sub directories
1426: */
1427: private static List<String> readSubDirs(File file, List<String> list) {
1428:
1429: String[] entries = file.list(new DFileFilter());
1430: if (entries != null) {
1431: for (int i = 0; i < entries.length; i++) {
1432: File entry = new File(file.getAbsolutePath() + '/'
1433: + entries[i]);
1434: if (entry.isDirectory()) {
1435: list = readSubDirs(entry, list);
1436: } else {
1437: list.add(file.getAbsolutePath() + '/' + entries[i]);
1438: }
1439: }
1440: }
1441: return list;
1442: }
1443:
1444: /**
1445: * @return the list of image map files to consider read from a dbase file defined by the dbase
1446: * parameter
1447: *
1448: * @param dbaseFile
1449: * name of the dbase file
1450: * @param fileColumn
1451: * name of the column containing the image map files names
1452: * @param baseDir
1453: * name of the directory where the image map files are stored if this parameter is
1454: * <code>null</code> it is assumed that the image map files are full referenced
1455: * within the dbase
1456: * @param sort
1457: * true if map image file names shall be sorted
1458: * @param sortColum
1459: * name of the column that shall be used for sorting
1460: */
1461: private static List<String> getFileList(String dBaseFile,
1462: String fileColumn, String baseDir, boolean sort,
1463: String sortColum, String sortDirection) throws Exception {
1464:
1465: // handle dbase file extension and file location/reading problems
1466: if (dBaseFile.endsWith(".dbf")) {
1467: dBaseFile = dBaseFile.substring(0, dBaseFile
1468: .lastIndexOf("."));
1469: }
1470: DBaseFile dbf = new DBaseFile(dBaseFile);
1471:
1472: // sort dbase file contents chronologicaly (oldest first)
1473: int cnt = dbf.getRecordNum();
1474:
1475: Object[][] mapItems = new Object[cnt][2];
1476: QualifiedName fileC = new QualifiedName(APP_PREFIX, fileColumn
1477: .toUpperCase(), DEEGREEAPP);
1478: QualifiedName sortC = null;
1479: if (sort) {
1480: sortC = new QualifiedName(APP_PREFIX, sortColum
1481: .toUpperCase(), DEEGREEAPP);
1482: }
1483: for (int i = 0; i < cnt; i++) {
1484: if (sort) {
1485: mapItems[i][0] = dbf.getFRow(i + 1).getDefaultProperty(
1486: sortC).getValue();
1487: } else {
1488: mapItems[i][0] = new Integer(1);
1489: }
1490: // name of map file
1491: mapItems[i][1] = dbf.getFRow(i + 1).getDefaultProperty(
1492: fileC).getValue();
1493: }
1494: Arrays.sort(mapItems, new MapAgeComparator(sortDirection));
1495:
1496: // extract names of image files from dBase file and attach them to rootDir
1497: if (baseDir == null) {
1498: baseDir = "";
1499: } else if (!baseDir.endsWith("/") && !baseDir.endsWith("\\")) {
1500: baseDir = baseDir + "/";
1501: }
1502: List<String> imageFiles = new ArrayList<String>(mapItems.length);
1503: for (int i = 0; i < mapItems.length; i++) {
1504: if (mapItems[i][0] != null) {
1505: LOG.logDebug("" + mapItems[i][0]);
1506: imageFiles.add(baseDir + mapItems[i][1]);
1507: }
1508: }
1509:
1510: return imageFiles;
1511: }
1512:
1513: private static void printHelp() {
1514:
1515: System.out
1516: .println("-outDir directory where resulting tiles and describing shape(s) will be stored (mandatory)\r\n"
1517: + "-redirect whether to redirect the standard output/error streams to a file rtb.log in the output directory. Default is false.\r\n"
1518: + "-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"
1519: + "-outputFormat name of the image format used for created tiles (png|jpg|jpeg|bmp|tif|tiff|gif|raw default png)\r\n"
1520: + "-maxTileSize maximum size of created raster tiles in pixel (default 500)\r\n"
1521: + "-srs name of the spatial reference system used for the coverage (default EPSG:4326)\r\n"
1522: + "-interpolation interpolation method used for rescaling raster images (Nearest Neighbor|Bicubic|Bicubic2|Bilinear default Nearest Neighbor)\r\n"
1523: + " be careful using Bicubic and Bicubic2 interpolation; there seems to be a problem with JAI\r\n"
1524: + " If you use the proogram with images (tif) containing raw data like DEMs just use \r\n"
1525: + " Nearest Neighbor interpolation. All other interpolation methods will cause artefacts."
1526: + "-bbox boundingbox of the the resulting coverage. If not set the bbox will be determined by analysing the input map files. (optional)\r\n"
1527: + "-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"
1528: + "-noOfLevel number of tree levels created (optional default = 1)\r\n"
1529: + "-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"
1530: + "-h or -? print this help\r\n"
1531: + "\r\n"
1532: + "Input files\r\n"
1533: + "there are three alternative ways/parameters to define which input files shall be used for creating a raster tree:\r\n"
1534: + "1)\r\n"
1535: + "-mapFiles defines a list of image file names (including full path information) seperated by \',\', \';\' or \'|\'\r\n"
1536: + "\r\n"
1537: + "2)\r\n"
1538: + "-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"
1539: + "-subDirs conditional parameter used with -rootDir. It defines if all sub directories of -rootDir shall be parsed too (true|false default false)\r\n"
1540: + "\r\n"
1541: + "3)\r\n"
1542: + "-dbaseFile name a dBase file that contains a column listing all files to be considered by the program\r\n"
1543: + "-fileColumn name of the column containing the file names (mandatory if -dbaseFile is defined)\r\n"
1544: + "-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"
1545: + "-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"
1546: + "-sortDirection If -sortColumn is defined this parameter will be used for definition of sorting direction (UP|DOWN default UP)\r\n"
1547: + "-worldFileType two types of are common: \r\n "
1548: + " a) the boundingbox is defined on the center of the corner pixels; \r\n "
1549: + " b) the boundingbox is defined on the outer corner of the corner pixels; \r\n "
1550: + " first is default and will be used if this parameter is not set; second will be use if '-worldFileType outer' is defined.\r\n"
1551: + "-quality image quality if jpeg is used as output format; valid range is from 0..1 (default 0.95) \r\n"
1552: + "-bitDepth image bit depth; valid values are 32 and 16, default is 16 \r\n"
1553: + "-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"
1554: + " 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"
1555: + "-offset defines the offset added to raster values if -outputFormat = raw and -bitDepth (default 0) \r\n"
1556: + "-scaleFactor defines the factor by which raster values are multiplied if -outputFormat = raw and -bitDepth (default 1) \r\n"
1557: + "\r\n"
1558: + "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.");
1559: System.out.println();
1560: System.out.println("caching:");
1561: System.out
1562: .println("To use default caching mechanism you just have to start RTB as before; to define ");
1563: System.out
1564: .println("your own caching behavior you have to place a file named ehcache.xml within the ");
1565: System.out
1566: .println("root of your classpath. The content of this file is described by the ehcache documentation ");
1567: System.out
1568: .println("(http://ehcache.sourceforge.net/documentation); at least it must provide a cache named 'imgCache'.");
1569: System.out
1570: .println(" When defining your own cache please consider that just 'inMemory' caching is supported because ");
1571: System.out
1572: .println("the objects cached by RTB are not serializable.");
1573: System.out.println();
1574: System.out.println("Example invoking RTB (windows):");
1575: System.out
1576: .println("java -Xms300m -Xmx1000m -classpath .;.\\lib\\deegree2.jar;.\\lib\\acme.jar;"
1577: + ".\\lib\\batik-awt-util.jar;.\\lib\\commons-beanutils-1.5.jar;"
1578: + ".\\lib\\commons-codec-1.3.jar;.\\lib\\commons-collections-3.1.jar;"
1579: + ".\\lib\\commons-digester-1.7.jar;.\\lib\\commons-discovery-0.2.jar;"
1580: + ".\\lib\\commons-logging.jar;.\\lib\\jai_codec.jar;.\\lib\\jai_core.jar;"
1581: + ".\\lib\\mlibwrapper_jai.jar;.\\lib\\j3dcore.jar;.\\lib\\j3dutils.jar;"
1582: + ".\\lib\\vecmath.jar;.\\lib\\jts-1.6.jar;.\\lib\\log4j-1.2.9.jar;"
1583: + ".\\lib\\axis.jar;.\\lib\\jaxen-1.1-beta-7.jar;.\\lib\\ehcache-1.2.0_03.jar "
1584: + "org.deegree.tools.raster.RasterTreeBuilder "
1585: + "-dbaseFile D:/lgv/resources/data/dbase/dip.dbf -outDir D:/lgv/output/ "
1586: + "-baseName out -outputFormat jpg -maxTileSize 500 -noOfLevel 4 -interpolation "
1587: + "Bilinear -bbox 3542428,5918168,3593354,5957043 -resolution 0.2 -sortColumn "
1588: + "PLANJAHR -fileColumn NAME_PNG -sortDirection UP -quality 0.91 -baseDir "
1589: + "D:/lgv/resources/data/images/ ");
1590: }
1591:
1592: /**
1593: *
1594: * @param args
1595: * Example arguments to pass when calling are:
1596: * <ul>
1597: * <li>-mapFiles D:/temp/europe_DK.jpg,D:/temp/europe_BeNeLux.jpg</li>
1598: * <li>-outDir D:/temp/out/</li>
1599: * <li>-baseName pretty</li>
1600: * <li>-outputFormat png</li>
1601: * <li>-maxTileSize 600</li>
1602: * </ul>
1603: *
1604: * @throws Exception
1605: */
1606: public static void main(String[] args) throws Exception {
1607:
1608: Properties map = new Properties();
1609: for (int i = 0; i < args.length; i += 2) {
1610: map.put(args[i], args[i + 1]);
1611: }
1612:
1613: if (map.get("-?") != null || map.get("-h") != null) {
1614: printHelp();
1615: return;
1616: }
1617:
1618: try {
1619: validate(map);
1620: } catch (Exception e) {
1621: LOG.logInfo(map.toString());
1622: System.out.println(e.getMessage());
1623: System.out.println();
1624: printHelp();
1625: return;
1626: }
1627:
1628: String outDir = map.getProperty("-outDir");
1629:
1630: // set up stderr/stdout redirection
1631: String redirect = map.getProperty("-redirect");
1632: if (redirect != null && redirect.equals("true")) {
1633: File f = new File(outDir + separator + "rtb.log");
1634: PrintStream out = new PrintStream(new FileOutputStream(f));
1635: System.setOut(out);
1636: System.setErr(out);
1637: }
1638:
1639: // read input parameters
1640: String baseName = map.getProperty("-baseName");
1641: String outputFormat = map.getProperty("-outputFormat");
1642: String srs = map.getProperty("-srs");
1643: if (srs == null) {
1644: srs = "EPSG:4326";
1645: }
1646: String interpolation = map.getProperty("-interpolation");
1647: Envelope env = (Envelope) map.get("-bbox");
1648: double resolution = ((Double) map.get("-resolution"))
1649: .doubleValue();
1650: int level = Integer.parseInt(map.getProperty("-noOfLevel"));
1651: double maxTileSize = (Double.valueOf(map
1652: .getProperty("-maxTileSize"))).doubleValue();
1653: WorldFile.TYPE worldFileType = WorldFile.TYPE.CENTER;
1654: if ("outer".equals(map.getProperty("-worldFileType"))) {
1655: worldFileType = WorldFile.TYPE.OUTER;
1656: }
1657: float quality = Float.parseFloat(map.getProperty("-quality"));
1658: String backgroundColor = map.getProperty("-bgColor");
1659:
1660: int depth = 0;
1661:
1662: if (map.get("-bitDepth") != null) {
1663: depth = Integer.parseInt(map.getProperty("-bitDepth"));
1664: }
1665:
1666: float offset = 0;
1667: if (map.get("-offset") != null) {
1668: offset = Float.parseFloat(map.getProperty("-offset"));
1669: }
1670:
1671: float scaleFactor = 1;
1672: if (map.get("-scaleFactor") != null) {
1673: scaleFactor = Float.parseFloat(map
1674: .getProperty("-scaleFactor"));
1675: }
1676:
1677: List<String> imageFiles = null;
1678: if (map.get("-mapFiles") != null) {
1679: String[] mapFiles = StringTools.toArray(map
1680: .getProperty("-mapFiles"), ",;|", true);
1681: imageFiles = getFileList(mapFiles);
1682: } else if (map.get("-dbaseFile") != null) {
1683: String dBaseFile = map.getProperty("-dbaseFile");
1684: String fileColum = map.getProperty("-fileColumn");
1685: String baseDir = map.getProperty("-baseDir");
1686: if (baseDir == null) {
1687: baseDir = map.getProperty("-rootDir");
1688: }
1689: boolean sort = map.get("-sortColumn") != null;
1690: String sortColumn = map.getProperty("-sortColumn");
1691: if (map.get("-sortDirection") == null) {
1692: map.put("-sortDirection", "UP");
1693: }
1694: String sortDirection = map.getProperty("-sortDirection");
1695: imageFiles = getFileList(dBaseFile, fileColum, baseDir,
1696: sort, sortColumn, sortDirection);
1697: } else if (map.get("-rootDir") != null) {
1698: String rootDir = map.getProperty("-rootDir");
1699: boolean subDirs = "true".equals(map.get("-subDirs"));
1700: imageFiles = getFileList(rootDir, subDirs);
1701: } else {
1702: LOG.logInfo(map.toString());
1703: System.out
1704: .println("-mapFiles, -rootDir or -dbaseFile parameter must be defined");
1705: printHelp();
1706: return;
1707: }
1708:
1709: LOG.logDebug(imageFiles.toString());
1710: LOG.logInfo(map.toString());
1711:
1712: // initialize RasterTreeBuilder
1713: RasterTreeBuilder rtb = new RasterTreeBuilder(imageFiles,
1714: outDir, baseName, outputFormat, maxTileSize, srs,
1715: interpolation, worldFileType, quality, backgroundColor,
1716: depth, resolution, offset, scaleFactor);
1717:
1718: // calculate bbox and resolution from input images if parameters are not set
1719: if (env == null) {
1720: WorldFile wf = rtb.determineCombiningBBox();
1721: env = wf.getEnvelope();
1722: resolution = wf.getResx();
1723: }
1724:
1725: // Calculate necessary number of levels to get not more than 4
1726: // tiles in highest resolution
1727: if (level == -1) {
1728: rtb.init(env, resolution);
1729: level = 0;
1730: int numTilesMax = Math.min(rtb.tileCols, rtb.tileRows);
1731: int numTiles = 4;
1732: while (numTiles < numTilesMax) {
1733: level += 1;
1734: numTiles *= 2;
1735: }
1736: }
1737: if (level == 0) {
1738: level = 1;
1739: }
1740: System.out.println("Number of Levels: " + level);
1741:
1742: // create tree where for each loop resolution will be halfed
1743: double[] re = new double[level];
1744: for (int i = 0; i < level; i++) {
1745: rtb.init(env, resolution);
1746: rtb.start();
1747: rtb.logCollectedErrors();
1748: re[i] = resolution;
1749: if (i < level - 1) {
1750: String dir = outDir + '/' + Double.toString(resolution);
1751: imageFiles = getFileList(dir, false);
1752: rtb = new RasterTreeBuilder(imageFiles, outDir,
1753: baseName, outputFormat, maxTileSize, srs,
1754: interpolation, WorldFile.TYPE.CENTER, quality,
1755: backgroundColor, depth, resolution, offset,
1756: scaleFactor);
1757: }
1758: resolution = resolution * 2;
1759: }
1760:
1761: LOG.logInfo("create configuration files ...");
1762: rtb.createConfigurationFile(re);
1763:
1764: if (map.get("-capabilitiesFile") != null) {
1765: LOG.logInfo("adjust capabilities ...");
1766: File file = new File(map.getProperty("-capabilitiesFile"));
1767: rtb.updateCapabilitiesFile(file);
1768: }
1769:
1770: rtb.logCollectedErrors();
1771: }
1772:
1773: /**
1774: * class: official version of a FilenameFilter
1775: */
1776: static class DFileFilter implements FilenameFilter {
1777:
1778: private List<String> extensions = null;
1779:
1780: /**
1781: *
1782: */
1783: public DFileFilter() {
1784: extensions = new ArrayList<String>();
1785: extensions.add("JPEG");
1786: extensions.add("JPG");
1787: extensions.add("BMP");
1788: extensions.add("PNG");
1789: extensions.add("GIF");
1790: extensions.add("TIF");
1791: extensions.add("TIFF");
1792: extensions.add("GEOTIFF");
1793: }
1794:
1795: /**
1796: * @return "*.*"
1797: */
1798: public String getDescription() {
1799: return "*.*";
1800: }
1801:
1802: /*
1803: * (non-Javadoc)
1804: *
1805: * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
1806: */
1807: public boolean accept(java.io.File file, String name) {
1808: int pos = name.lastIndexOf(".");
1809: String ext = name.substring(pos + 1).toUpperCase();
1810: if (file.isDirectory()) {
1811: String s = file.getAbsolutePath() + '/' + name;
1812: File tmp = new File(s);
1813: if (tmp.isDirectory()) {
1814: return true;
1815: }
1816: }
1817: return extensions.contains(ext);
1818: }
1819: }
1820:
1821: /**
1822: *
1823: * This class enables sorting of dBaseFile objects in chronological order (lowest first, highest
1824: * last).
1825: *
1826: * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a>
1827: * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
1828: * @author last edited by: $Author: apoth $
1829: *
1830: * @version 2.0, $Revision: 9815 $, $Date: 2008-01-29 12:41:59 -0800 (Tue, 29 Jan 2008) $
1831: *
1832: * @since 2.0
1833: */
1834: private static class MapAgeComparator implements Comparator<Object> {
1835:
1836: private String direction = null;
1837:
1838: /**
1839: * @param direction
1840: */
1841: public MapAgeComparator(String direction) {
1842: this .direction = direction.toUpperCase();
1843: }
1844:
1845: public int compare(Object o1, Object o2) {
1846: Object[] o1a = (Object[]) o1;
1847: Object[] o2a = (Object[]) o2;
1848:
1849: if (o1a[0] == null || o2a[0] == null) {
1850: return 0;
1851: }
1852: if (direction.equals("UP")) {
1853: return o1a[0].toString().compareTo(o2a[0].toString());
1854: }
1855: return o2a[0].toString().compareTo(o1a[0].toString());
1856: }
1857: }
1858:
1859: }
|