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>&nbsp;&nbsp;- GeoTIFF</td>
468         * </tr>
469         * <tr>
470         * <td>"PIX"</td>
471         * <td>&nbsp;&nbsp;- PCI Geomatics PIX</td>
472         * </tr>
473         * <tr>
474         * <td>"HDF-EOS"</td>
475         * <td>&nbsp;&nbsp;- NASA HDF-EOS</td>
476         * </tr>
477         * <tr>
478         * <td>"NITF"</td>
479         * <td>&nbsp;&nbsp;- National Image Transfer Format</td>
480         * </tr>
481         * <tr>
482         * <td>"STDS-DEM"</td>
483         * <td>&nbsp;&nbsp;- 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    }