001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/tools/raster/RasterTreeUpdater.java $
002: /*---------------- FILE HEADER ------------------------------------------
003:
004: This file is part of deegree.
005: Copyright (C) 2001-2008 by:
006: EXSE, Department of Geography, University of Bonn
007: http://www.giub.uni-bonn.de/deegree/
008: lat/lon GmbH
009: http://www.lat-lon.de
010:
011: This library is free software; you can redistribute it and/or
012: modify it under the terms of the GNU Lesser General Public
013: License as published by the Free Software Foundation; either
014: version 2.1 of the License, or (at your option) any later version.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: Contact:
026:
027: Andreas Poth
028: lat/lon GmbH
029: Aennchenstr. 19
030: 53115 Bonn
031: Germany
032: E-Mail: poth@lat-lon.de
033:
034: Prof. Dr. Klaus Greve
035: Department of Geography
036: University of Bonn
037: Meckenheimer Allee 166
038: 53115 Bonn
039: Germany
040: E-Mail: greve@giub.uni-bonn.de
041:
042:
043: ---------------------------------------------------------------------------*/
044: package org.deegree.tools.raster;
045:
046: import java.awt.image.BufferedImage;
047: import java.io.File;
048: import java.io.IOException;
049: import java.net.MalformedURLException;
050: import java.net.URL;
051: import java.util.ArrayList;
052: import java.util.List;
053: import java.util.Properties;
054: import java.util.SortedMap;
055: import java.util.TreeMap;
056:
057: import javax.media.jai.Interpolation;
058: import javax.media.jai.JAI;
059: import javax.media.jai.RenderedOp;
060: import javax.media.jai.TiledImage;
061:
062: import net.sf.ehcache.Cache;
063: import net.sf.ehcache.CacheException;
064: import net.sf.ehcache.CacheManager;
065: import net.sf.ehcache.Element;
066: import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
067:
068: import org.deegree.framework.log.ILogger;
069: import org.deegree.framework.log.LoggerFactory;
070: import org.deegree.framework.util.ImageUtils;
071: import org.deegree.framework.util.StringTools;
072: import org.deegree.io.dbaseapi.DBaseException;
073: import org.deegree.io.shpapi.HasNoDBaseFileException;
074: import org.deegree.io.shpapi.ShapeFile;
075: import org.deegree.model.coverage.grid.WorldFile;
076: import org.deegree.model.crs.UnknownCRSException;
077: import org.deegree.model.feature.Feature;
078: import org.deegree.model.feature.FeatureProperty;
079: import org.deegree.model.spatialschema.Envelope;
080: import org.deegree.model.spatialschema.Geometry;
081: import org.deegree.ogcwebservices.wcs.configuration.Resolution;
082: import org.deegree.ogcwebservices.wcs.configuration.ShapeResolution;
083: import org.deegree.ogcwebservices.wcs.describecoverage.CoverageDescriptionDocument;
084: import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering;
085: import org.deegree.ogcwebservices.wcs.describecoverage.InvalidCoverageDescriptionExcpetion;
086: import org.xml.sax.SAXException;
087:
088: import com.sun.media.jai.codec.FileSeekableStream;
089:
090: /**
091: * The <code>RasterTreeUpdater</code> is a command line utility that can be used in addition to
092: * the <code>RasterTreeBuilder</code> to update a previously generated raster tree.
093: *
094: * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
095: * @author last edited by: $Author: apoth $
096: *
097: * @version 2.0, $Revision: 9346 $, $Date: 2007-12-27 08:39:07 -0800 (Thu, 27 Dec 2007) $
098: *
099: * @since 2.0
100: */
101: public class RasterTreeUpdater {
102:
103: private static final ILogger LOG = LoggerFactory
104: .getLogger(RasterTreeUpdater.class);
105:
106: private RTUConfiguration config;
107:
108: private SortedMap<Double, ShapeResolution> shapeFiles;
109:
110: private Cache imgCache;
111:
112: // is determined automatically off one of the output filenames
113: private String format;
114:
115: /**
116: * Creates a new <code>RasterTreeUpdater</code> configured through the options contained in
117: * the passed configuration
118: *
119: * @param config
120: * @throws IllegalStateException
121: * @throws CacheException
122: * @throws IOException
123: */
124: public RasterTreeUpdater(RTUConfiguration config)
125: throws IllegalStateException, CacheException, IOException {
126: this .config = config;
127:
128: // a lot of lines just for a simple cache, but what the heck...
129: CacheManager singletonManager = CacheManager.create();
130: if (singletonManager.getCache("imgCache") == null) {
131: Cache cache = new Cache("imgCache", 10,
132: MemoryStoreEvictionPolicy.LFU, false, ".", false,
133: 3600, 3600, false, 240, null);
134: singletonManager.addCache(cache);
135: imgCache = singletonManager.getCache("imgCache");
136: } else {
137: imgCache = singletonManager.getCache("imgCache");
138: imgCache.removeAll();
139: }
140: }
141:
142: /**
143: * loads an image
144: * @param imageSource
145: * @return
146: * @throws IOException
147: */
148: private TiledImage loadImage(String imageSource) throws IOException {
149:
150: TiledImage ti = null;
151: Element elem = imgCache.get(imageSource);
152: if (elem != null) {
153: ti = (TiledImage) elem.getObjectValue();
154: }
155:
156: if (ti == null) {
157: if (config.verbose) {
158: LOG.logInfo("Cache size: " + imgCache.getSize());
159: LOG.logInfo("Reading image: " + imageSource);
160: }
161:
162: FileSeekableStream fss = new FileSeekableStream(imageSource);
163: RenderedOp rop = JAI.create("stream", fss);
164: BufferedImage bi = rop.getAsBufferedImage();
165: fss.close();
166: ti = new TiledImage(bi, 500, 500);
167: imgCache.put(new Element(imageSource, ti));
168: }
169:
170: return ti;
171: }
172:
173: /**
174: * Initializes the instance.
175: *
176: * @throws IOException
177: * @throws SAXException
178: * @throws InvalidCoverageDescriptionExcpetion
179: * @throws UnknownCRSException
180: */
181: public void init() throws IOException, SAXException,
182: InvalidCoverageDescriptionExcpetion, UnknownCRSException {
183: CoverageDescriptionDocument doc = new CoverageDescriptionDocument();
184: doc.load(config.wcsConfiguration);
185:
186: CoverageOffering offering = null;
187: if (config.coverageName == null) {
188: offering = doc.getCoverageOfferings()[0];
189: } else {
190: for (CoverageOffering of : doc.getCoverageOfferings()) {
191: if (of.getName().equals(config.coverageName)) {
192: offering = of;
193: }
194: }
195: }
196:
197: Resolution[] rs = offering.getExtension().getResolutions();
198: shapeFiles = new TreeMap<Double, ShapeResolution>();
199: for (Resolution r : rs) {
200: shapeFiles.put(new Double(r.getMinScale()),
201: (ShapeResolution) r);
202: }
203:
204: }
205:
206: /**
207: * extracts the envelopes that correspond to the filenames of getfilenames
208: * @param shapeName
209: * @return
210: * @throws IOException
211: */
212: private ArrayList<Envelope> getEnvelopes(String shapeName)
213: throws IOException {
214: ShapeFile file = new ShapeFile(shapeName);
215: ArrayList<Envelope> envs = new ArrayList<Envelope>(file
216: .getRecordNum());
217:
218: for (int i = 0; i < file.getRecordNum(); ++i) {
219: Geometry geom = file.getGeometryByRecNo(i + 1);
220: envs.add(geom.getEnvelope());
221: if (config.verbose) {
222: LOG.logInfo(StringTools.concat(200,
223: "Envelope of tile is ", geom.getEnvelope()));
224: }
225: }
226: file.close();
227:
228: return envs;
229: }
230:
231: /**
232: * extracts the filenames of the tiles contained within the shape file dbf
233: * @param shapeName
234: * @return
235: * @throws IOException
236: * @throws HasNoDBaseFileException
237: * @throws DBaseException
238: */
239: private ArrayList<String> getTilenames(String shapeName)
240: throws IOException, HasNoDBaseFileException, DBaseException {
241: ShapeFile file = new ShapeFile(shapeName);
242: String dirName = new File(shapeName).getParent();
243: if (dirName == null) {
244: dirName = "./";
245: }
246:
247: ArrayList<String> tileNames = new ArrayList<String>(file
248: .getRecordNum());
249: for (int i = 0; i < file.getRecordNum(); ++i) {
250: Feature f = file.getFeatureByRecNo(i + 1);
251: FeatureProperty[] p = f.getProperties();
252: StringBuffer name = new StringBuffer(200);
253: name.append(dirName).append("/");
254: name.append((p[1].getValue() == null) ? "" : p[1]
255: .getValue());
256: name.append("/").append(p[0].getValue());
257: tileNames.add(name.toString());
258: if (config.verbose) {
259: LOG.logInfo(StringTools
260: .concat(200, "Found tile ", name));
261: }
262: }
263: file.close();
264:
265: return tileNames;
266: }
267:
268: /**
269: * returns the envelopes of the files to be updated
270: * @return
271: * @throws IOException
272: */
273: private ArrayList<Envelope> getUpdatedEnvelopes()
274: throws IOException {
275: ArrayList<Envelope> updatedEnvelopes = new ArrayList<Envelope>(
276: config.updatedFiles.size());
277:
278: for (String filename : config.updatedFiles) {
279: WorldFile wf = WorldFile.readWorldFile(filename,
280: config.worldfileType);
281: updatedEnvelopes.add(wf.getEnvelope());
282: if (config.verbose) {
283: LOG.logInfo(StringTools.concat(200,
284: "Updating from file ", filename,
285: " with envelope ", wf.getEnvelope()));
286: }
287: if (format == null) {
288: format = filename
289: .substring(filename.lastIndexOf('.') + 1);
290: }
291: }
292:
293: return updatedEnvelopes;
294: }
295:
296: /**
297: * updates the tiles with the image file
298: * @param filename
299: * @param envelope
300: * @param tileNames
301: * @param tileEnvelopes
302: * @param res
303: * @throws IOException
304: */
305: private void updateFile(String filename, Envelope envelope,
306: List<String> tileNames, List<Envelope> tileEnvelopes,
307: double res) throws IOException {
308:
309: for (int i = 0; i < tileNames.size(); ++i) {
310: Envelope env = tileEnvelopes.get(i);
311: if (!envelope.intersects(env)) {
312: continue;
313: }
314: String tile = tileNames.get(i);
315:
316: // paint the new image on top of the existing one
317: if (config.verbose) {
318: LOG.logInfo(StringTools.concat(200, "Updating tile ",
319: tile, " with image ", filename));
320: }
321:
322: TiledImage tileImage = loadImage(tile);
323: WorldFile wf = WorldFile.readWorldFile(filename,
324: config.worldfileType);
325: TiledImage inputImage = loadImage(filename);
326: Tile t = new Tile(WorldFile.readWorldFile(tile,
327: config.worldfileType).getEnvelope(), null);
328: BufferedImage out = tileImage.getAsBufferedImage();
329: float[][] data = null;
330: if (out.getColorModel().getPixelSize() == 16) {
331: // do not use image api if target bitDepth = 16
332: data = new float[out.getHeight()][out.getWidth()];
333: }
334: RasterTreeBuilder.drawImage(out, data, inputImage, t, wf,
335: res, config.interpolation, null, format,
336: config.bitDepth, 0, 1);
337:
338: String frm = format;
339: if ("raw".equals(frm)) {
340: frm = "tif";
341: }
342:
343: File file = new File(tile).getAbsoluteFile();
344:
345: ImageUtils.saveImage(out, file, config.quality);
346:
347: }
348:
349: }
350:
351: /**
352: * a hack to determine the minimum resolution
353: * @param shapeName
354: * @return
355: * @throws IOException
356: * @throws HasNoDBaseFileException
357: * @throws DBaseException
358: */
359: private double getLevel(String shapeName) throws IOException,
360: HasNoDBaseFileException, DBaseException {
361: ShapeFile file = new ShapeFile(shapeName);
362: Feature f = file.getFeatureByRecNo(1);
363: FeatureProperty[] p = f.getProperties();
364: file.close();
365: return Double.parseDouble(p[1].getValue().toString());
366: }
367:
368: /**
369: * Updates the images.
370: *
371: * @throws IOException
372: * @throws DBaseException
373: * @throws HasNoDBaseFileException
374: */
375: public void update() throws IOException, HasNoDBaseFileException,
376: DBaseException {
377: SortedMap<Double, ShapeResolution> shapes = new TreeMap<Double, ShapeResolution>();
378: shapes.putAll(shapeFiles);
379:
380: // stores the envelopes of the files that are being updated
381: ArrayList<Envelope> updatedEnvelopes = getUpdatedEnvelopes();
382:
383: while (!shapes.isEmpty()) {
384: ShapeResolution shape = shapes.remove(shapes.firstKey());
385: String shapeName = shape.getShape().getRootFileName();
386: double res = getLevel(shapeName);
387:
388: LOG.logInfo(StringTools.concat(200,
389: "Processing shape file ", shapeName, "..."));
390:
391: // these store the image filenames of the existing tiles and their envelopes
392: ArrayList<String> tileNames = getTilenames(shapeName);
393: ArrayList<Envelope> envelopes = getEnvelopes(shapeName);
394:
395: for (int i = 0; i < config.updatedFiles.size(); ++i) {
396: String filename = config.updatedFiles.get(i);
397: Envelope envelope = updatedEnvelopes.get(i);
398:
399: updateFile(filename, envelope, tileNames, envelopes,
400: res);
401: }
402: }
403: }
404:
405: /**
406: * Prints out usage information and the message, then <code>System.exit</code>s.
407: *
408: * @param message
409: * can be null
410: */
411: private static void printUsage(String message) {
412: if (message != null) {
413: System.out.println(message);
414: System.out.println();
415: }
416:
417: System.out.println("Usage:");
418: System.out.println();
419: System.out.println("<classpath> <rtu> <options>");
420: System.out.println(" where");
421: System.out.println(" <rtu>:");
422: System.out
423: .println(" java <classpath> org.deegree.tools.raster.RasterTreeUpdater");
424: System.out.println(" <classpath>:");
425: System.out
426: .println(" -cp <the classpath containing the deegree.jar and ");
427: System.out
428: .println(" additional required libraries>");
429: System.out.println(" <option>:");
430: System.out.println(" as follows:");
431: System.out.println();
432: System.out.println(" -wcs <URL/filename>:");
433: System.out
434: .println(" The URL or a filename of the WCS configuration that was");
435: System.out
436: .println(" generated by the RasterTreeBuilder. Mandatory.");
437: System.out.println(" -name <name>:");
438: System.out
439: .println(" The name of the coverage to update. Optional.");
440: System.out.println(" -verbose:");
441: System.out
442: .println(" Print out more informational messages.");
443: System.out.println(" -interpolation <name>: ");
444: System.out
445: .println(" The name of the interpolation to be used, as specified in the");
446: System.out
447: .println(" RasterTreeBuilder. Optional. Default is Nearest Neighbor.");
448: System.out.println(" -depth <n>:");
449: System.out
450: .println(" The bit depth of the output images. Optional. Default is 16.");
451: System.out.println(" -quality <n>:");
452: System.out
453: .println(" The desired output quality, between 0 and 1. Optional. Default is 0.95.");
454: System.out.println(" -mapFiles <file1,file2...fileN>:");
455: System.out
456: .println(" comma seperated list of image files to update. These files");
457: System.out
458: .println(" need to have a corresponding worldfile, as usual.");
459: System.out.println(" -worldFileType <type>:");
460: System.out
461: .println(" How to treat worldfiles that are read. Possible values are outer and");
462: System.out.println(" center. Center is the default.");
463: }
464:
465: /**
466: * @param args
467: */
468: public static void main(String[] args) {
469: try {
470: RTUConfiguration config = new RTUConfiguration(args);
471: RasterTreeUpdater updater = new RasterTreeUpdater(config);
472: updater.init();
473: updater.update();
474: } catch (MalformedURLException e) {
475: e.printStackTrace();
476: printUsage("An URL is malformed.");
477: } catch (ClassCastException e) {
478: e.printStackTrace();
479: printUsage("Data is not defined in shapefiles.");
480: } catch (IOException e) {
481: e.printStackTrace();
482: printUsage("The coverage offering document can not be read:");
483: } catch (SAXException e) {
484: e.printStackTrace();
485: printUsage("The coverage offering document is not in XML format:");
486: } catch (InvalidCoverageDescriptionExcpetion e) {
487: e.printStackTrace();
488: printUsage("The coverage offering document is not valid:");
489: } catch (UnknownCRSException e) {
490: e.printStackTrace();
491: printUsage("The coverage offering document is not sound:");
492: } catch (HasNoDBaseFileException e) {
493: e.printStackTrace();
494: printUsage("A shapefile has no associated .dbf.");
495: } catch (DBaseException e) {
496: e.printStackTrace();
497: printUsage("A shapefile database is in the wrong format or has errors.");
498: }
499:
500: }
501:
502: /**
503: * <code>RTUConfiguration</code> is a class containing configuration options for the
504: * <code>RasterTreeUpdater</code>.
505: *
506: * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
507: * @author last edited by: $Author: apoth $
508: *
509: * @version 2.0, $Revision: 9346 $, $Date: 2007-12-27 08:39:07 -0800 (Thu, 27 Dec 2007) $
510: *
511: * @since 2.0
512: */
513: public static class RTUConfiguration {
514:
515: /**
516: * The location of the WCS configuration document.
517: */
518: URL wcsConfiguration;
519:
520: /**
521: * The list of image files being updated.
522: */
523: List<String> updatedFiles;
524:
525: /**
526: * The coverage name to update.
527: */
528: String coverageName;
529:
530: /**
531: * Whether to be verbose in logging.
532: */
533: boolean verbose;
534:
535: /**
536: * The interpolation method to be used.
537: */
538: Interpolation interpolation;
539:
540: /**
541: * The bit depth for the output images.
542: */
543: int bitDepth;
544:
545: /**
546: * Desired output image quality.
547: */
548: float quality;
549:
550: /**
551: * Worldfile type used for reading.
552: */
553: WorldFile.TYPE worldfileType;
554:
555: /**
556: *
557: * @param wcsConfiguration
558: * @param updatedFiles
559: * @param coverageName
560: * @param verbose
561: * @param interpolation
562: * @param bitDepth
563: * @param quality
564: * @param worldfileType
565: */
566: public RTUConfiguration(URL wcsConfiguration,
567: List<String> updatedFiles, String coverageName,
568: boolean verbose, Interpolation interpolation,
569: int bitDepth, float quality,
570: WorldFile.TYPE worldfileType) {
571: this .wcsConfiguration = wcsConfiguration;
572: this .updatedFiles = updatedFiles;
573: this .coverageName = coverageName;
574: this .verbose = verbose;
575: this .interpolation = interpolation;
576: this .bitDepth = bitDepth;
577: this .quality = quality;
578: this .worldfileType = worldfileType;
579: }
580:
581: /**
582: * Constructs a new instance through command line arguments.
583: *
584: * @param args
585: * the command line arguments
586: * @throws MalformedURLException
587: */
588: public RTUConfiguration(String[] args)
589: throws MalformedURLException {
590:
591: Properties map = new Properties();
592: int i = 0;
593: while (i < args.length) {
594: if (args[i].equals("-verbose")) {
595: map.put(args[i++], "-");
596: } else {
597: map.put(args[i++], args[i++]);
598: }
599: }
600:
601: try {
602: wcsConfiguration = new URL(map.getProperty("-wcs"));
603: } catch (MalformedURLException e) {
604: wcsConfiguration = new File(map.getProperty("-wcs"))
605: .toURI().toURL();
606: }
607:
608: coverageName = map.getProperty("-name");
609:
610: verbose = map.getProperty("-verbose") != null;
611:
612: if (map.getProperty("-interpolation") != null) {
613: String t = map.getProperty("-interpolation");
614: interpolation = RasterTreeBuilder
615: .createInterpolation(t);
616: } else {
617: interpolation = RasterTreeBuilder
618: .createInterpolation("Nearest Neighbor");
619: }
620:
621: bitDepth = 32;
622: if (map.getProperty("-depth") != null) {
623: bitDepth = Integer.parseInt(map.getProperty("-depth"));
624: }
625:
626: quality = 0.95f;
627: if (map.getProperty("-quality") != null) {
628: quality = Float.parseFloat(map.getProperty("-quality"));
629: }
630:
631: worldfileType = WorldFile.TYPE.CENTER;
632: if (map.getProperty("-worldFileType") != null) {
633: if (map.getProperty("-worldFileType").equalsIgnoreCase(
634: "outer")) {
635: worldfileType = WorldFile.TYPE.OUTER;
636: }
637: }
638:
639: updatedFiles = StringTools.toList(map
640: .getProperty("-mapFiles"), ",;", true);
641: }
642:
643: /**
644: * @return true, if the configuration values are sound
645: */
646: public boolean isValidConfiguration() {
647: return updatedFiles.size() > 0 && wcsConfiguration != null;
648: }
649:
650: }
651:
652: }
|