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 }