001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/coverage/grid/GridCoverageExchange.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree
005 Copyright (C) 2001-2006 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 Klaus Greve
035 Department of Geography
036 University of Bonn
037 Meckenheimer Allee 166
038 53115 Bonn
039 Germany
040 E-Mail: klaus.greve@giub.uni-bonn.de
041
042 ---------------------------------------------------------------------------*/
043 package org.deegree.model.coverage.grid;
044
045 import java.io.FilenameFilter;
046 import java.io.IOException;
047 import java.io.InputStream;
048 import java.lang.reflect.Constructor;
049 import java.net.URI;
050 import java.util.ArrayList;
051 import java.util.HashMap;
052 import java.util.List;
053 import java.util.Map;
054
055 import org.deegree.datatypes.CodeList;
056 import org.deegree.datatypes.QualifiedName;
057 import org.deegree.framework.log.ILogger;
058 import org.deegree.framework.log.LoggerFactory;
059 import org.deegree.framework.util.StringTools;
060 import org.deegree.io.oraclegeoraster.GeoRasterDescription;
061 import org.deegree.io.shpapi.ShapeFile;
062 import org.deegree.model.crs.CRSFactory;
063 import org.deegree.model.crs.CoordinateSystem;
064 import org.deegree.model.crs.UnknownCRSException;
065 import org.deegree.model.feature.Feature;
066 import org.deegree.model.spatialschema.Envelope;
067 import org.deegree.model.spatialschema.Geometry;
068 import org.deegree.model.spatialschema.GeometryFactory;
069 import org.deegree.ogcbase.CommonNamespaces;
070 import org.deegree.ogcwebservices.InvalidParameterValueException;
071 import org.deegree.ogcwebservices.wcs.configuration.Directory;
072 import org.deegree.ogcwebservices.wcs.configuration.Extension;
073 import org.deegree.ogcwebservices.wcs.configuration.File;
074 import org.deegree.ogcwebservices.wcs.configuration.GridDirectory;
075 import org.deegree.ogcwebservices.wcs.configuration.Shape;
076 import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering;
077
078 /**
079 * Support for creation of grid coverages from persistent formats as well as exporting a grid
080 * coverage to a persistent formats. For example, it allows for creation of grid coverages from the
081 * GeoTIFF Well-known binary format and exporting to the GeoTIFF file format. Basic implementations
082 * only require creation of grid coverages from a file format or resource. More sophesticated
083 * implementations may extract the grid coverages from a database. In such case, a
084 * <code>GridCoverageExchange</code> instance will hold a connection to a specific database and
085 * the {@link #dispose} method will need to be invoked in order to close this connection.
086 * <p>
087 *
088 * @author Andreas Poth
089 * @version 1.0
090 * @since 2.0
091 */
092 public class GridCoverageExchange {
093
094 private static final ILogger LOG = LoggerFactory.getLogger( GridCoverageExchange.class );
095
096 private static final URI DEEGREEAPP = CommonNamespaces.buildNSURI( "http://www.deegree.org/app" );
097
098 private static final String APP_PREFIX = "app";
099
100 public static final String SHAPE_IMAGE_FILENAME = "FILENAME";
101
102 public static final String SHAPE_DIR_NAME = "FOLDER";
103
104 private CoverageOffering coverageOffering;
105
106 /**
107 * @param formats
108 */
109 public GridCoverageExchange( CoverageOffering coverageOffering ) {
110 this.coverageOffering = coverageOffering;
111 }
112
113 /**
114 * Returns a grid coverage reader that can manage the specified source
115 *
116 * @param source
117 * An object that specifies somehow the data source. Can be a
118 * {@link java.lang.String}, an {@link java.io.InputStream}, a
119 * {@link java.nio.channels.FileChannel}, whatever. It's up to the associated grid
120 * coverage reader to make meaningful use of it.
121 * @return The grid coverage reader.
122 * @throws IOException
123 * if an error occurs during reading.
124 *
125 * @revisit We need a mechanism to allow the right GridCoverageReader Something like an SPI.
126 * What if we can't find a GridCoverageReader? Do we return null or throw an Exception?
127 */
128 public GridCoverageReader getReader( Object source )
129 throws IOException {
130 if ( !( source instanceof InputStream ) ) {
131 throw new IOException( "source parameter must be an instance of InputStream" );
132 }
133 return null;
134 }
135
136 /**
137 * This method is a deegree specific enhancement of the <tt>GridCoverageExchange</tt>
138 * class/interface as defined by GeoAPI. Returns a grid coverage reader that can manage the
139 * specified source
140 *
141 * @param source
142 * An object that specifies somehow the data source.
143 * @param description
144 * an object describing the grid coverage and the access to avaiable metadata
145 * @param envelope
146 * @param format
147 * @return The grid coverage reader.
148 * @throws IOException
149 * if an error occurs during reading.
150 *
151 * @revisit We need a mechanism to allow the right GridCoverageReader Something like an SPI.
152 * What if we can't find a GridCoverageReader? Do we return null or throw an Exception?
153 */
154 public GridCoverageReader getReader( InputStream source, CoverageOffering description, Envelope envelope,
155 Format format )
156 throws IOException {
157 GridCoverageReader gcr = null;
158 Extension ext = description.getExtension();
159 String type = ext.getType();
160 if ( type.equals( Extension.FILEBASED ) ) {
161 if ( format.getName().toUpperCase().indexOf( "GEOTIFF" ) > -1 ) {
162 gcr = new GeoTIFFGridCoverageReader( source, description, envelope, format );
163 } else if ( isImageFormat( format ) ) {
164 gcr = new ImageGridCoverageReader( source, description, envelope, format );
165 } else {
166 throw new IOException( "not supported file format: " + format.getName() );
167 }
168 } else {
169 throw new IOException( "coverage storage type: " + type
170 + " is not supported with method: getReader(InputStream, "
171 + "CoverageOffering, Envelope, Format )" );
172 }
173 return gcr;
174 }
175
176 /**
177 * This method is a deegree specific enhancement of the <tt>GridCoverageExchange</tt>
178 * class/interface as defined by GeoAPI. Returns a grid coverage reader that can manage the
179 * specified source
180 *
181 * @param resource
182 * a string that specifies somehow the data source (e.g. a file).
183 * @param description
184 * an object describing the grid coverage and the access to avaiable metadata
185 * @param envelope
186 * @param format
187 *
188 * @return The grid coverage reader.
189 * @throws IOException
190 * if an error occurs during reading.
191 *
192 * @revisit We need a mechanism to allow the right GridCoverageReader Something like an SPI.
193 * What if we can't find a GridCoverageReader? Do we return null or throw an Exception?
194 */
195 public GridCoverageReader getReader( Object resource, CoverageOffering description, Envelope envelope, Format format )
196 throws IOException, InvalidParameterValueException {
197 GridCoverageReader gcr = null;
198 Extension ext = description.getExtension();
199 String type = ext.getType();
200 if ( type.equals( Extension.FILEBASED ) ) {
201 File file = new File( null, (String) resource, envelope );
202 if ( format.getName().toUpperCase().indexOf( "GEOTIFF" ) > -1 ) {
203 LOG.logDebug( "creating GeoTIFFGridCoverageReader" );
204 gcr = new GeoTIFFGridCoverageReader( file, description, envelope, format );
205 } else if ( isImageFormat( format ) ) {
206 LOG.logDebug( "creating ImageGridCoverageReader" );
207 gcr = new ImageGridCoverageReader( file, description, envelope, format );
208 } else {
209 throw new IOException( "not supported file format: " + format.getName() );
210 }
211 } else if ( type.equals( Extension.NAMEINDEXED ) ) {
212 LOG.logDebug( "creating nameIndexed CompoundGridCoverageReader" );
213 Directory[] dirs = new Directory[] { (Directory) resource };
214 gcr = getReader( dirs, description, envelope, format );
215 } else if ( type.equals( Extension.SHAPEINDEXED ) ) {
216 LOG.logDebug( "creating shapeIndexed CompoundGridCoverageReader" );
217 File[] files = null;
218 try {
219 files = getFilesFromShape( (Shape) resource, envelope, description );
220 } catch ( UnknownCRSException e ) {
221 throw new InvalidParameterValueException( e );
222 }
223 gcr = getReader( files, description, envelope, format );
224 } else if ( type.equals( Extension.ORACLEGEORASTER ) ) {
225 LOG.logDebug( "creating OracleGeoRasterGridCoverageReader" );
226 gcr = createOracleGeoRasterGridCoverageReader( (GeoRasterDescription) resource, description, envelope,
227 format );
228 } else {
229 throw new IOException( "coverage storage type: " + type + " is not supported" );
230 }
231 return gcr;
232 }
233
234 /**
235 * This method is a deegree specific enhancement of the <tt>GridCoverageExchange</tt>
236 * class/interface as defined by GeoAPI. Returns a grid coverage reader that can manage the
237 * specified source
238 *
239 * @param resources
240 * an array strings that specifies somehow the data sources (e.g. some files).
241 * @param description
242 * an object describing the grid coverage and the access to avaiable metadata
243 * @param envelope
244 * @return The grid coverage reader.
245 * @throws IOException
246 * if an error occurs during reading.
247 *
248 * @revisit We need a mechanism to allow the right GridCoverageReader Something like an SPI.
249 * What if we can't find a GridCoverageReader? Do we return null or throw an Exception?
250 */
251 public GridCoverageReader getReader( Object[] resources, CoverageOffering description, Envelope envelope,
252 Format format )
253 throws IOException, InvalidParameterValueException {
254
255 // CS_CoordinateSystem crs = createNativeCRS( description );
256 GridCoverageReader gcr = null;
257 Extension ext = description.getExtension();
258 String type = ext.getType();
259 File[] files = null;
260 if ( type.equals( Extension.FILEBASED ) ) {
261 LOG.logDebug( "creating filebased CompoundGridCoverageReader" );
262 files = (File[]) resources;
263 gcr = new CompoundGridCoverageReader( files, description, envelope, format );
264 } else if ( type.equals( Extension.NAMEINDEXED ) ) {
265 LOG.logDebug( "creating nameIndexed CompoundGridCoverageReader" );
266 try {
267 files = getFilesFromDirectories( (Directory[]) resources, envelope, description );
268 } catch ( UnknownCRSException e ) {
269 throw new InvalidParameterValueException( e );
270 }
271 gcr = new CompoundGridCoverageReader( files, description, envelope, format );
272 } else if ( type.equals( Extension.SHAPEINDEXED ) ) {
273 LOG.logDebug( "creating shapeIndexed CompoundGridCoverageReader" );
274 files = (File[]) resources;
275 gcr = new CompoundGridCoverageReader( files, description, envelope, format );
276 } else if ( type.equals( Extension.ORACLEGEORASTER ) ) {
277 LOG.logDebug( "creating OracleGeoRasterGridCoverageReader" );
278 gcr = createOracleGeoRasterGridCoverageReader( (GeoRasterDescription) resources[0], description, envelope,
279 format );
280 } else {
281 throw new IOException( "coverage storage type: " + type + " is not supported" );
282 }
283
284 return gcr;
285 }
286
287 /**
288 * Creates a OracleGeoRasterGridCoverageReader instance from the given parameters.
289 *
290 * @param grDesc
291 * @param description
292 * @param envelope
293 * @param format
294 * @return
295 * @throws IOException
296 */
297 private GridCoverageReader createOracleGeoRasterGridCoverageReader( GeoRasterDescription grDesc,
298 CoverageOffering description,
299 Envelope envelope, Format format )
300 throws IOException {
301 GridCoverageReader gcr = null;
302 // gcr = new OracleGeoRasterGridCoverageReader( (GeoRasterDescription) resource,
303 // description, envelope, format );
304 try {
305 Class gridCoverageReaderClass = Class.forName( "org.deegree.model.coverage.grid.OracleGeoRasterGridCoverageReader" );
306
307 // get constructor
308 Class[] parameterTypes = new Class[] { GeoRasterDescription.class, CoverageOffering.class, Envelope.class,
309 Format.class };
310 Constructor constructor = gridCoverageReaderClass.getConstructor( parameterTypes );
311
312 // call constructor
313 Object arglist[] = new Object[] { grDesc, description, envelope, format };
314 gcr = (GridCoverageReader) constructor.newInstance( arglist );
315 } catch ( ClassNotFoundException e ) {
316 throw new IOException( "Cannot find Oracle raster library: " + e.getMessage() );
317 } catch ( Exception e ) {
318 throw new IOException( e.getMessage() );
319 }
320 return gcr;
321 }
322
323 /**
324 * returns true if the passed format is an image format
325 *
326 * @param format
327 * @return <code>true</code> if the passed format is an image format
328 */
329 private boolean isImageFormat( Format format ) {
330 String frmt = format.getName().toUpperCase();
331 return frmt.equalsIgnoreCase( "png" ) || frmt.equalsIgnoreCase( "bmp" ) || frmt.equalsIgnoreCase( "tif" )
332 || frmt.equalsIgnoreCase( "tiff" ) || frmt.equalsIgnoreCase( "gif" ) || frmt.equalsIgnoreCase( "jpg" )
333 || frmt.equalsIgnoreCase( "jpeg" ) || frmt.indexOf( "ECW" ) > -1;
334 }
335
336 /**
337 * reads the names of the grid coverage files intersecting the requested region from the passed
338 * shape (name).
339 *
340 * @param shape
341 * @param envelope
342 * requested envelope
343 * @param description
344 * description (metadata) of the source coverage
345 * @return
346 * @throws IOException
347 * @throws UnknownCRSException
348 */
349 private File[] getFilesFromShape( Shape shape, Envelope envelope, CoverageOffering description )
350 throws IOException, UnknownCRSException {
351
352 CoordinateSystem crs = createNativeCRS( description );
353
354 String shapeBaseName = StringTools.replace( shape.getRootFileName(), "\\", "/", true );
355 String shapeDir = shapeBaseName.substring( 0, shapeBaseName.lastIndexOf( "/" ) + 1 );
356
357 ShapeFile shp = new ShapeFile( shapeBaseName );
358 File[] files = null;
359 int[] idx = shp.getGeoNumbersByRect( envelope );
360 if ( idx != null ) {
361 files = new File[idx.length];
362 try {
363 for ( int i = 0; i < files.length; i++ ) {
364 Feature feature = shp.getFeatureByRecNo( idx[i] );
365 QualifiedName qn = new QualifiedName( APP_PREFIX, SHAPE_IMAGE_FILENAME, DEEGREEAPP );
366 String img = (String) feature.getDefaultProperty( qn ).getValue();
367 qn = new QualifiedName( APP_PREFIX, SHAPE_DIR_NAME, DEEGREEAPP );
368 String dir = (String) feature.getDefaultProperty( qn ).getValue();
369 if ( !( new java.io.File( dir ).isAbsolute() ) ) {
370 // solve relative path; it is assumed that the tile directories
371 // are located in the same directory as the shape file
372 dir = shapeDir + dir;
373 }
374 Geometry geom = feature.getGeometryPropertyValues()[0];
375 Envelope env = geom.getEnvelope();
376 env = GeometryFactory.createEnvelope( env.getMin(), env.getMax(), crs );
377 files[i] = new File( crs, dir.concat( "/".concat( img ) ), env );
378 }
379 } catch ( Exception e ) {
380 shp.close();
381 LOG.logError( e.getMessage(), e );
382 throw new IOException( e.getMessage() );
383 }
384 } else {
385 files = new File[0];
386 }
387 shp.close();
388
389 return files;
390
391 }
392
393 /**
394 * reads the names of the grid coverage files intersecting the requested region from raster data
395 * files contained in the passed directories
396 *
397 * @param directories
398 * list of directories searched for matching raster files
399 * @param envelope
400 * requested envelope
401 * @param description
402 * description (metadata) of the source coverage
403 * @return list of files intersecting the requested envelope
404 * @throws UnknownCRSException
405 * @throws IOException
406 */
407 private File[] getFilesFromDirectories( Directory[] directories, Envelope envelope, CoverageOffering description )
408 throws UnknownCRSException {
409
410 CoordinateSystem crs = createNativeCRS( description );
411
412 List<File> list = new ArrayList<File>( 1000 );
413
414 for ( int i = 0; i < directories.length; i++ ) {
415
416 double widthCRS = ( (GridDirectory) directories[i] ).getTileWidth();
417 double heightCRS = ( (GridDirectory) directories[i] ).getTileHeight();
418 String[] extensions = directories[i].getFileExtensions();
419 String dirName = directories[i].getName();
420
421 DFileFilter fileFilter = new DFileFilter( extensions );
422 java.io.File iofile = new java.io.File( dirName );
423 String[] tiles = iofile.list( fileFilter );
424 for ( int j = 0; j < tiles.length; j++ ) {
425 int pos1 = tiles[j].indexOf( '_' );
426 int pos2 = tiles[j].lastIndexOf( '.' );
427 String tmp = tiles[j].substring( 0, pos1 );
428 double x1 = Double.parseDouble( tmp ) / 1000d;
429 tmp = tiles[j].substring( pos1 + 1, pos2 );
430 double y1 = Double.parseDouble( tmp ) / 1000d;
431 Envelope env = GeometryFactory.createEnvelope( x1, y1, x1 + widthCRS, y1 + heightCRS, crs );
432 if ( env.intersects( envelope ) ) {
433 File file = new File( crs, dirName + '/' + tiles[j], env );
434 list.add( file );
435 }
436 }
437
438 }
439
440 File[] files = list.toArray( new File[list.size()] );
441
442 return files;
443 }
444
445 /**
446 * creates an instance of <tt>CS_CoordinateSystem</tt> from the name of the native CRS of the
447 * grid coverage
448 *
449 * @param description
450 * @return
451 * @throws UnknownCRSException
452 */
453 private CoordinateSystem createNativeCRS( CoverageOffering description )
454 throws UnknownCRSException {
455 String srs = description.getSupportedCRSs().getNativeSRSs()[0].getCodes()[0];
456
457 return CRSFactory.create( srs );
458 }
459
460 /**
461 * Returns a GridCoverageWriter that can write the specified format. The file format name is
462 * determined from the {@link Format} interface. Sample file formats include:
463 *
464 * <blockquote><table>
465 * <tr>
466 * <td>"GeoTIFF"</td>
467 * <td> - GeoTIFF</td>
468 * </tr>
469 * <tr>
470 * <td>"PIX"</td>
471 * <td> - PCI Geomatics PIX</td>
472 * </tr>
473 * <tr>
474 * <td>"HDF-EOS"</td>
475 * <td> - NASA HDF-EOS</td>
476 * </tr>
477 * <tr>
478 * <td>"NITF"</td>
479 * <td> - National Image Transfer Format</td>
480 * </tr>
481 * <tr>
482 * <td>"STDS-DEM"</td>
483 * <td> - Standard Transfer Data Standard</td>
484 * </tr>
485 * </table></blockquote>
486 *
487 * @param destination
488 * An object that specifies somehow the data destination. Can be a
489 * {@link java.lang.String}, an {@link java.io.OutputStream}, a
490 * {@link java.nio.channels.FileChannel}, whatever. It's up to the associated grid
491 * coverage writer to make meaningful use of it.
492 * @param format
493 * the output format.
494 * @return The grid coverage writer.
495 * @throws IOException
496 * if an error occurs during reading.
497 */
498 public GridCoverageWriter getWriter( Object destination, Format format )
499 throws IOException {
500
501 LOG.logDebug( "requested format: ", format.getName() );
502
503 GridCoverageWriter gcw = null;
504
505 if ( !isKnownFormat( format ) ) {
506 throw new IOException( "not supported Format: " + format );
507 }
508
509 Map<String, Object> metadata = new HashMap<String, Object>();
510 metadata.put( "offset", coverageOffering.getExtension().getOffset() );
511 metadata.put( "scaleFactor", coverageOffering.getExtension().getScaleFactor() );
512 if ( format.getName().equalsIgnoreCase( "GEOTIFF" ) ) {
513 gcw = new GeoTIFFGridCoverageWriter( destination, metadata, null, null, format );
514 } else if ( isImageFormat( format ) ) {
515 gcw = new ImageGridCoverageWriter( destination, metadata, null, null, format );
516 } else if ( format.getName().equalsIgnoreCase( "GML" ) || format.getName().equalsIgnoreCase( "GML2" )
517 || format.getName().equalsIgnoreCase( "GML3" ) ) {
518 gcw = new GMLGridCoverageWriter( destination, metadata, null, null, format );
519 } else if ( format.getName().equalsIgnoreCase( "XYZ" ) ) {
520 gcw = new XYZGridCoverageWriter( destination, metadata, null, null, format );
521 } else {
522 throw new IOException( "not supported Format: " + format );
523 }
524
525 return gcw;
526 }
527
528 /**
529 * validates if a passed format is known to an instance of <tt>GridCoverageExchange</tt>
530 *
531 * @param format
532 * @return <code>true</code> if the format is known, <code>false</code> otherwise.
533 */
534 private boolean isKnownFormat( Format format ) {
535 CodeList[] codeList = coverageOffering.getSupportedFormats().getFormats();
536 for ( int i = 0; i < codeList.length; i++ ) {
537 String[] codes = codeList[i].getCodes();
538 for ( int j = 0; j < codes.length; j++ ) {
539 if ( format.getName().equalsIgnoreCase( codes[j] ) ) {
540 return true;
541 }
542 }
543 }
544 LOG.logDebug( format.getName() + " not supported" );
545 return false;
546 }
547
548 /**
549 * class: official version of a FilenameFilter
550 */
551 class DFileFilter implements FilenameFilter {
552
553 private Map<String, String> extensions = null;
554
555 public DFileFilter( String[] extensions ) {
556 this.extensions = new HashMap<String, String>();
557 for ( int i = 0; i < extensions.length; i++ ) {
558 this.extensions.put( extensions[i].toUpperCase(), extensions[i] );
559 }
560 }
561
562 public String getDescription() {
563 return "*.*";
564 }
565
566 /*
567 * (non-Javadoc)
568 *
569 * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
570 */
571 public boolean accept( java.io.File arg0, String name ) {
572 int pos = name.lastIndexOf( "." );
573 String ext = name.substring( pos + 1 ).toUpperCase();
574 return extensions.get( ext ) != null;
575 }
576 }
577
578 }