001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/quadtree/DBQuadtreeManagerWithNumberId.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     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    package org.deegree.io.quadtree;
044    
045    import java.io.IOException;
046    import java.io.StringReader;
047    import java.sql.Connection;
048    import java.sql.Date;
049    import java.sql.PreparedStatement;
050    import java.sql.ResultSet;
051    import java.sql.Statement;
052    
053    import org.deegree.datatypes.Types;
054    import org.deegree.framework.log.ILogger;
055    import org.deegree.framework.log.LoggerFactory;
056    import org.deegree.framework.util.StringTools;
057    import org.deegree.io.DBConnectionPool;
058    import org.deegree.io.JDBCConnection;
059    import org.deegree.io.shpapi.ShapeFile;
060    import org.deegree.model.feature.Feature;
061    import org.deegree.model.feature.schema.FeatureType;
062    import org.deegree.model.feature.schema.PropertyType;
063    import org.deegree.model.spatialschema.Envelope;
064    import org.deegree.model.spatialschema.GMLGeometryAdapter;
065    import org.deegree.model.spatialschema.Geometry;
066    import org.deegree.model.spatialschema.GeometryFactory;
067    import org.deegree.model.spatialschema.Point;
068    
069    /**
070     * Same as
071     * 
072     * @see org.deegree.io.quadtree.DBQuadtreeManager but uses Integer values as IDs instead of UUIDs.
073     * 
074     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
075     * @author last edited by: $Author: bezema $
076     * 
077     * @version $Revision: 6259 $, $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 2007) $
078     */
079    public class DBQuadtreeManagerWithNumberId extends DBQuadtreeManager {
080    
081        private static final ILogger LOG = LoggerFactory.getLogger( DBQuadtreeManagerWithNumberId.class );
082    
083        /**
084         * @param jdbc
085         *            database connection info
086         * @param indexName
087         *            this name will be used to create the table that stores the nodes of a specific
088         *            quadtree
089         * @param table
090         *            name of table the index shall be created for
091         * @param column
092         *            name of column the index shall be created for
093         * @param owner
094         *            owner of the table (optional, database user will be used if set to null )
095         * @param maxDepth
096         *            max depth of the generated quadtree (default = 6 if a value &lt; 2 will be passed)
097         */
098        public DBQuadtreeManagerWithNumberId( JDBCConnection jdbc, String indexName, String table,
099                                              String column, String owner, int maxDepth ) {
100            super( jdbc, indexName, table, column, owner, maxDepth );
101        }
102    
103        /**
104         * 
105         * @param driver
106         *            database connection driver
107         * @param logon
108         *            database connection logon
109         * @param user
110         *            database user
111         * @param password
112         *            database user's password
113         * @param encoding
114         *            character encoding to be used (if possible)
115         * @param indexName
116         *            this name will be used to create the table that stores the nodes of a specific
117         *            quadtree
118         * @param table
119         *            name of table the index shall be created for
120         * @param column
121         *            name of column the index shall be created for
122         * @param owner
123         *            owner of the table (optional, database user will be used if set to null )
124         * @param maxDepth
125         *            max depth of the generated quadtree (default = 6 if a value &lt; 2 will be passed)
126         */
127        public DBQuadtreeManagerWithNumberId( String driver, String logon, String user,
128                                              String password, String encoding, String indexName,
129                                              String table, String column, String owner, int maxDepth ) {
130            super( driver, logon, user, password, encoding, indexName, table, column, owner, maxDepth );
131        }
132    
133        /**
134         * initializes a QuadtreeManager to access an alread existing Quadtree
135         * 
136         * @param jdbc
137         *            database connection info
138         * @param table
139         *            name of table the index shall be created for
140         * @param column
141         *            name of column the index shall be created for
142         * @param owner
143         *            owner of the table (optional, database user will be used if set to null )
144         */
145        public DBQuadtreeManagerWithNumberId( JDBCConnection jdbc, String table, String column,
146                                              String owner ) {
147            super( jdbc, table, column, owner );
148        }
149    
150        /**
151         * initializes a QuadtreeManager to access an alread existing Quadtree
152         * 
153         * @param driver
154         *            database connection driver
155         * @param logon
156         *            database connection logon
157         * @param user
158         *            database user
159         * @param password
160         *            database user's password
161         * @param encoding
162         *            character encoding to be used (if possible)
163         * @param table
164         *            name of table the index shall be created for
165         * @param column
166         *            name of column the index shall be created for
167         * @param owner
168         *            owner of the table (optional, database user will be used if set to null )
169         */
170        public DBQuadtreeManagerWithNumberId( String driver, String logon, String user,
171                                              String password, String encoding, String table,
172                                              String column, String owner ) {
173            super( driver, logon, user, password, encoding, table, column, owner );
174        }
175    
176        /**
177         * stores one feature into the defined table
178         * 
179         * @param feature
180         * @param jdbc
181         * @throws Exception
182         */
183        private void storeFeature( Feature feature, int id, JDBCConnection jdbc )
184                                throws Exception {
185    
186            Connection con = null;
187            DBConnectionPool pool = null;
188    
189            FeatureType ft = feature.getFeatureType();
190            PropertyType[] ftp = ft.getProperties();
191            try {
192                pool = DBConnectionPool.getInstance();
193                con = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(),
194                                              jdbc.getPassword() );
195    
196                StringBuffer sb = new StringBuffer( 100 );
197                sb.append( "INSERT INTO " ).append( table ).append( '(' );
198                sb.append( "FEATURE_ID," );
199                for ( int i = 0; i < ftp.length; i++ ) {
200                    if ( ftp[i].getType() == Types.GEOMETRY ) {
201                        sb.append( column ).append( ' ' );
202                    } else {
203                        sb.append( ftp[i].getName().getLocalName() );
204                    }
205                    if ( i < ftp.length - 1 ) {
206                        sb.append( ", " );
207                    }
208                }
209                sb.append( ") VALUES (?," );
210                for ( int i = 0; i < ftp.length; i++ ) {
211                    sb.append( '?' );
212                    if ( i < ftp.length - 1 ) {
213                        sb.append( ", " );
214                    }
215                }
216                sb.append( ')' );
217                LOG.logDebug( "SQL for inser feature: " + sb );
218    
219                PreparedStatement stmt = con.prepareStatement( sb.toString() );
220                stmt.setInt( 1, id );
221                for ( int i = 0; i < ftp.length; i++ ) {
222                    Object o = null;
223                    if ( feature.getProperties( ftp[i].getName() ) != null ) {
224                        if ( feature.getProperties( ftp[i].getName() ).length > 0 ) {
225                            o = feature.getProperties( ftp[i].getName() )[0].getValue();
226                        }
227                    }
228                    if ( o == null ) {
229                        stmt.setNull( i + 2, ftp[i].getType() );
230                    } else {
231                        switch ( ftp[i].getType() ) {
232                        case Types.CHAR:
233                        case Types.VARCHAR:
234                            stmt.setString( i + 2, o.toString() );
235                            break;
236                        case Types.SMALLINT:
237                        case Types.TINYINT:
238                        case Types.INTEGER:
239                        case Types.BIGINT:
240                            stmt.setInt( i + 2, (int) Double.parseDouble( o.toString() ) );
241                            break;
242                        case Types.DOUBLE:
243                        case Types.FLOAT:
244                        case Types.DECIMAL:
245                        case Types.NUMERIC:
246                            stmt.setFloat( i + 2, Float.parseFloat( o.toString() ) );
247                            break;
248                        case Types.DATE:
249                        case Types.TIME:
250                        case Types.TIMESTAMP:
251                            stmt.setDate( i + 2, (Date) o );
252                            break;
253                        case Types.GEOMETRY: {
254                            StringBuffer gs = GMLGeometryAdapter.export( (Geometry) o );
255                            String s = StringTools.replace(
256                                                            gs.toString(),
257                                                            ">",
258                                                            " xmlns:gml=\"http://www.opengis.net/gml\">",
259                                                            false );
260                            if ( backend.equals( "POSTGRES" ) || backend.equals( "HSQLDB" ) ) {
261                                stmt.setString( i + 2, s );
262                            } else if ( backend.equals( "INGRES" ) ) {
263                                stmt.setObject( i + 2, new StringReader( s ) );
264                            } else {
265                                stmt.setObject( i + 2, s.getBytes() );
266                            }
267                            break;
268                        }
269                        default: {
270                            LOG.logWarning( "unsupported type: " + ftp[i].getType() );
271                        }
272                        }
273                    }
274                }
275    
276                stmt.execute();
277                stmt.close();
278    
279            } catch ( Exception e ) {
280                e.printStackTrace();
281                throw new IndexException( "could not insert feature into database", e );
282            } finally {
283                try {
284                    pool.releaseConnection( con, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(),
285                                            jdbc.getPassword() );
286                } catch ( Exception e1 ) {
287                    e1.printStackTrace();
288                }
289            }
290        }
291    
292        /**
293         * creates table the shape data shall be stored
294         * 
295         * @param fileName
296         * @throws IndexException
297         * @throws IOException
298         */
299        protected void createDataTable( String fileName )
300                                throws IndexException, IOException {
301            ShapeFile sf = new ShapeFile( fileName );
302            FeatureType ft = null;
303            try {
304                ft = sf.getFeatureByRecNo( 1 ).getFeatureType();
305            } catch ( Exception e ) {
306                e.printStackTrace();
307                throw new IndexException( e );
308            }
309            sf.close();
310            StringBuffer sb = new StringBuffer( 1000 );
311            sb.append( "CREATE TABLE " ).append( table ).append( '(' );
312            sb.append( "FEATURE_ID " ).append( getDatabaseType( Types.INTEGER ) ).append( "," );
313            PropertyType[] ftp = ft.getProperties();
314            for ( int i = 0; i < ftp.length; i++ ) {
315                if ( ftp[i].getType() == Types.GEOMETRY ) {
316                    sb.append( column ).append( ' ' );
317                } else {
318                    sb.append( ftp[i].getName().getLocalName() ).append( ' ' );
319                }
320                sb.append( getDatabaseType( ftp[i].getType() ) );
321                if ( i < ftp.length - 1 ) {
322                    sb.append( ", " );
323                }
324            }
325            sb.append( ')' );
326    
327            Connection con = null;
328            DBConnectionPool pool = null;
329            try {
330                pool = DBConnectionPool.getInstance();
331                con = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(),
332                                              jdbc.getPassword() );
333    
334                Statement stmt = con.createStatement();
335                LOG.logDebug( sb.toString() );
336                stmt.execute( sb.toString() );
337                stmt.close();
338            } catch ( Exception e ) {
339                e.printStackTrace();
340                throw new IndexException( "could not create node definition at database", e );
341            } finally {
342                try {
343                    pool.releaseConnection( con, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(),
344                                            jdbc.getPassword() );
345                } catch ( Exception e1 ) {
346                    e1.printStackTrace();
347                }
348            }
349        }
350    
351        /**
352         * imports a shape into the database and builds a quadtree on it
353         * 
354         * @param fileName
355         * @throws Exception
356         * @throws IOException
357         */
358        public void importShape( String fileName )
359                                throws Exception, IOException {
360    
361            createDataTable( fileName );
362    
363            int qtid = initQuadtree( fileName );
364    
365            insertIndexMetadata( qtid );
366    
367            qt = new DBQuadtree( qtid, indexName, jdbc );
368    
369            ShapeFile sf = new ShapeFile( fileName );
370    
371            int b = sf.getRecordNum() / 100;
372            if ( b == 0 )
373                b = 1;
374            int k = 0;
375            Envelope sfEnv = sf.getFileMBR();
376            for ( int i = 0; i < sf.getRecordNum(); i++ ) {
377                Feature feat = sf.getFeatureByRecNo( i + 1 );
378                if ( i % b == 0 ) {
379                    System.out.println( k + "%" );
380                    k++;
381                }
382                if ( i % 200 == 0 ) {
383                    System.gc();
384                }
385                Envelope env = feat.getDefaultGeometryPropertyValue().getEnvelope();
386                LOG.logDebug( i + " --- " + env );
387                if ( env == null ) {
388                    // must be a point geometry
389                    Point point = (Point) feat.getDefaultGeometryPropertyValue();
390                    double w = sfEnv.getWidth() / 1000;
391                    double h = sfEnv.getHeight() / 1000;
392                    env = GeometryFactory.createEnvelope( point.getX() - w / 2d, point.getY() - h / 2d,
393                                                          point.getX() + w / 2d, point.getY() + h / 2d,
394                                                          null );
395                }
396                int id = i;
397                qt.insert( id, env );
398                storeFeature( feat, id, jdbc );
399            }
400            LOG.logInfo( " finished!" );
401            sf.close();
402        }
403    
404        /**
405         * appends the features of a shape to an existing datatable and inserts references into the
406         * assigned quadtree table.
407         * <p>
408         * you have to consider that the quadtree is just valid for a defined area. if the features to
409         * append exceeds this area the quadtree has to be rebuilded.
410         * </p>
411         * 
412         * @param fileName
413         * @throws Exception
414         * @throws IOException
415         */
416        public void appendShape( String fileName )
417                                throws Exception, IOException {
418    
419            ShapeFile sf = new ShapeFile( fileName );
420    
421            int b = sf.getRecordNum() / 100;
422            if ( b == 0 )
423                b = 1;
424            int k = 0;
425            qt = getQuadtree();
426            Envelope sfEnv = sf.getFileMBR();
427    
428            int cnt = getMaxIdValue();
429    
430            for ( int i = 0; i < sf.getRecordNum(); i++ ) {
431                Feature feat = sf.getFeatureByRecNo( i + 1 );
432                if ( i % b == 0 ) {
433                    System.out.println( k + "%" );
434                    k++;
435                }
436                if ( i % 200 == 0 ) {
437                    System.gc();
438                }
439                Envelope env = feat.getDefaultGeometryPropertyValue().getEnvelope();
440                if ( env == null ) {
441                    // must be a point geometry
442                    Point point = (Point) feat.getDefaultGeometryPropertyValue();
443                    double w = sfEnv.getWidth() / 1000;
444                    double h = sfEnv.getHeight() / 1000;
445                    env = GeometryFactory.createEnvelope( point.getX() - w / 2d, point.getY() - h / 2d,
446                                                          point.getX() + w / 2d, point.getY() + h / 2d,
447                                                          null );
448                }
449                int id = cnt + i + 1;
450                qt.insert( id, env );
451                storeFeature( feat, id, jdbc );
452            }
453            LOG.logInfo( " finished!" );
454            sf.close();
455        }
456    
457        /**
458         * returns the maximum ID of the data table
459         * 
460         * @return the maximum ID of the data table
461         * @throws IndexException
462         */
463        private int getMaxIdValue()
464                                throws IndexException {
465            String sql = "SELECT MAX( FEATURE_ID ) FROM " + table;
466    
467            Connection con = null;
468            DBConnectionPool pool = null;
469            Statement stmt = null;
470            int maxId = 0;
471            try {
472                pool = DBConnectionPool.getInstance();
473                con = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(),
474                                              jdbc.getPassword() );
475    
476                stmt = con.createStatement();
477                LOG.logDebug( sql );
478                ResultSet rs = stmt.executeQuery( sql );
479                if ( rs.next() ) {
480                    maxId = rs.getInt( 1 );
481                }
482            } catch ( Exception e ) {
483                e.printStackTrace();
484                throw new IndexException( "could not read max( Faeture_Id ) from table: " + table );
485            } finally {
486                try {
487                    stmt.close();
488                    pool.releaseConnection( con, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(),
489                                            jdbc.getPassword() );
490                } catch ( Exception e1 ) {
491                    e1.printStackTrace();
492                }
493            }
494    
495            return maxId;
496        }
497    
498        /**
499         * creates a table that will store the nodes assigned to a specific quadtree index.
500         * 
501         * @param indexTable
502         */
503        protected void createIndexTable( String indexTable ) {
504            StringBuffer sb = new StringBuffer( 2000 );
505            sb.append( "CREATE TABLE " ).append( indexTable ).append( " ( " );
506            try {
507                sb.append( "ID " ).append( getDatabaseType( Types.VARCHAR ) ).append( " NOT NULL," );
508            } catch ( IndexException neverhappens ) {
509            }
510            sb.append( "minx float NOT NULL," );
511            sb.append( "miny float NOT NULL," );
512            sb.append( "maxx float NOT NULL," );
513            sb.append( "maxy float NOT NULL," );
514            sb.append( "FK_SUBNODE1 varchar(150)," );
515            sb.append( "FK_SUBNODE2 varchar(150)," );
516            sb.append( "FK_SUBNODE3 varchar(150)," );
517            sb.append( "FK_SUBNODE4 varchar(150) )" );
518    
519            StringBuffer sb2 = new StringBuffer( 1000 );
520            sb2.append( "CREATE TABLE " ).append( indexName ).append( "_ITEM ( " );
521            try {
522                sb2.append( "FK_QTNODE " ).append( getDatabaseType( Types.VARCHAR ) ).append(
523                                                                                              " NOT NULL," );
524                sb2.append( "FK_ITEM " ).append( getDatabaseType( Types.INTEGER ) ).append(
525                                                                                            " NOT NULL )" );
526            } catch ( IndexException neverhappens ) {
527            }
528    
529            Connection con = null;
530            DBConnectionPool pool = null;
531            try {
532                pool = DBConnectionPool.getInstance();
533                con = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(),
534                                              jdbc.getPassword() );
535    
536                Statement stmt = con.createStatement();
537                stmt.execute( sb.toString() );
538                stmt.close();
539    
540                stmt = con.createStatement();
541                stmt.execute( sb2.toString() );
542                stmt.close();
543            } catch ( Exception e ) {
544                // throw new IndexException( "could not create node definition at database", e );
545            } finally {
546                try {
547                    pool.releaseConnection( con, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(),
548                                            jdbc.getPassword() );
549                } catch ( Exception e1 ) {
550                    e1.printStackTrace();
551                }
552            }
553        }
554    
555    }
556    
557    /***************************************************************************************************
558     * <code>
559     Changes to this class. What the people have been up to: 
560     
561     $Log$
562     Revision 1.4  2007/03/06 13:11:17  wanhoff
563     Fixed Javadoc (@see, @throws)
564    
565     Revision 1.3  2007/02/26 14:10:20  poth
566     bug fix - missing call for ShapeFile.close operation added
567    
568     Revision 1.2  2007/01/26 14:37:03  wanhoff
569     fixed Javadoc @return tag and footer
570    
571     Revision 1.1  2006/10/20 07:56:00  poth
572     core methods extracted to interfaces
573     
574     Revision 1.2  2006/07/26 12:58:47  poth
575     implementation of appendShape method
576     
577     Revision 1.1  2006/07/26 12:43:12  poth
578     new quadtree manager using integer as datatype for object IDs
579     
580     Revision 1.33  2006/07/20 12:30:01  poth
581     *** empty log message ***
582     
583     Revision 1.32  2006/07/18 14:50:45  poth
584     *** empty log message ***
585     
586     Revision 1.31  2006/07/10 11:44:56  poth
587     log statements inserted
588     
589     Revision 1.30  2006/06/12 10:59:49  schmitz
590     Updated the Quadtree framework to work with INGRES database backends.
591     
592     Revision 1.29  2006/05/18 14:08:54  poth
593     file comments completed
594     
595     Revision 1.28  2006/05/16 09:01:45  poth
596     Ingres identification adapted
597     
598     Revision 1.27  2006/05/15 19:13:39  poth
599     support for Ingres added
600     
601     Revision 1.26  2006/05/12 15:26:05  poth
602     *** empty log message ***
603     
604     Revision 1.25  2006/05/12 06:46:23  poth
605     *** empty log message ***
606     
607     Revision 1.24  2006/05/11 16:37:35  poth
608     *** empty log message ***
609     
610     Revision 1.23  2006/05/11 13:26:31  poth
611     *** empty log message ***
612     
613     Revision 1.22  2006/05/11 08:02:14  poth
614     *** empty log message ***
615     
616     Revision 1.21  2006/04/13 07:49:10  poth
617     *** empty log message ***
618     
619     Revision 1.20  2006/04/06 20:25:31  poth
620     *** empty log message ***
621     
622     Revision 1.19  2006/03/30 21:20:28  poth
623     *** empty log message ***
624     
625     Revision 1.18  2006/01/31 16:23:14  mschneider
626     Changes due to refactoring of org.deegree.model.feature package.
627     
628     Revision 1.17  2006/01/25 10:26:24  poth
629     *** empty log message ***
630     
631     Revision 1.16  2006/01/16 20:36:40  poth
632     *** empty log message ***
633     
634     Revision 1.15  2006/01/08 14:09:35  poth
635     *** empty log message ***
636     
637     Revision 1.14  2005/12/18 19:06:30  poth
638     no message
639     
640     Revision 1.13  2005/12/06 13:45:20  poth
641     System.out.println substituted by logging api
642     
643     Revision 1.12  2005/12/04 19:21:09  poth
644     no message
645     
646     Revision 1.11  2005/11/21 18:42:10  mschneider
647     Refactoring due to changes in Feature class.
648     
649     Revision 1.10  2005/11/18 08:47:35  deshmukh
650     Geometry cast replaced
651     Revision
652     
653     </code>
654     **************************************************************************************************/