001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/shpapi/shape_new/ShapeFileReader.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003     This file is part of deegree.
004     Copyright (C) 2001-2006 by:
005     Department of Geography, University of Bonn
006     http://www.giub.uni-bonn.de/deegree/
007     lat/lon GmbH
008     http://www.lat-lon.de
009    
010     This library is free software; you can redistribute it and/or
011     modify it under the terms of the GNU Lesser General Public
012     License as published by the Free Software Foundation; either
013     version 2.1 of the License, or (at your option) any later version.
014    
015     This library is distributed in the hope that it will be useful,
016     but WITHOUT ANY WARRANTY; without even the implied warranty of
017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
018     Lesser General Public License for more details.
019    
020     You should have received a copy of the GNU Lesser General Public
021     License along with this library; if not, write to the Free Software
022     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
023    
024     Contact:
025    
026     Andreas Poth
027     lat/lon GmbH
028     Aennchenstr. 19
029     53177 Bonn
030     Germany
031     E-Mail: poth@lat-lon.de
032    
033     Prof. Dr. Klaus Greve
034     Department of Geography
035     University of Bonn
036     Meckenheimer Allee 166
037     53115 Bonn
038     Germany
039     E-Mail: greve@giub.uni-bonn.de
040    
041     ---------------------------------------------------------------------------*/
042    package org.deegree.io.shpapi.shape_new;
043    
044    import java.io.BufferedInputStream;
045    import java.io.File;
046    import java.io.FileInputStream;
047    import java.io.IOException;
048    import java.io.InputStream;
049    import java.util.LinkedList;
050    
051    import org.deegree.framework.log.ILogger;
052    import org.deegree.framework.log.LoggerFactory;
053    import org.deegree.io.dbaseapi.DBaseFile;
054    import org.deegree.model.spatialschema.ByteUtils;
055    
056    /**
057     * <code>ShapeFileReader</code> is a class to read shapefiles.
058     * 
059     * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
060     * @author last edited by: $Author: rbezema $
061     * 
062     * @version $Revision: 7668 $, $Date: 2007-06-27 19:07:07 +0200 (Mi, 27 Jun 2007) $
063     */
064    public class ShapeFileReader {
065    
066        private static final ILogger LOG = LoggerFactory.getLogger( ShapeFileReader.class );
067    
068        private String baseName;
069    
070        private int shapeType;
071    
072        private ShapeEnvelope envelope;
073    
074        private int length;
075    
076        /**
077         * Does not read it yet - just initializes the object.
078         * 
079         * @param baseName
080         */
081        public ShapeFileReader( String baseName ) {
082    
083            if( baseName.endsWith( ".shp" ) ){
084                baseName = baseName.substring( 0, baseName.length() - 4 );
085                System.out.println( baseName );
086            }
087            this.baseName = baseName;
088        }
089    
090        private void readHeader( InputStream in )
091                                throws IOException {
092            byte[] header = new byte[100];
093    
094            if ( in.read( header ) != 100 ) {
095                LOG.logError( "Header is too small, unexpected things might happen!" );
096                return;
097            }
098    
099            int fileType = ByteUtils.readBEInt( header, 0 );
100            if ( fileType != ShapeFile.FILETYPE ) {
101                LOG.logWarning( "File type is wrong, unexpected things might happen, continuing anyway..." );
102            }
103    
104            length = ByteUtils.readBEInt( header, 24 ) * 2; // 16 bit words...
105    
106            int version = ByteUtils.readLEInt( header, 28 );
107            if ( version != ShapeFile.VERSION ) {
108                LOG.logWarning( "File version is wrong, continuing in the hope of compatibility..." );
109            }
110    
111            shapeType = ByteUtils.readLEInt( header, 32 );
112    
113            envelope = new ShapeEnvelope( false, false );
114            envelope.read( header, 36 );
115    
116            // it shouldn't hurt to write these values as doubles default to 0.0 anyway
117            double zmin = ByteUtils.readLEDouble( header, 68 );
118            double zmax = ByteUtils.readLEDouble( header, 76 );
119            double mmin = ByteUtils.readLEDouble( header, 84 );
120            double mmax = ByteUtils.readLEDouble( header, 92 );
121    
122            switch ( shapeType ) {
123            case ShapeFile.NULL:
124            case ShapeFile.POINT:
125            case ShapeFile.POLYLINE:
126            case ShapeFile.POLYGON:
127            case ShapeFile.MULTIPOINT:
128                break;
129    
130            case ShapeFile.POINTM:
131            case ShapeFile.POLYLINEM:
132            case ShapeFile.POLYGONM:
133            case ShapeFile.MULTIPOINTM:
134                envelope.extend( mmin, mmax );
135                break;
136    
137            case ShapeFile.POINTZ:
138            case ShapeFile.POLYLINEZ:
139            case ShapeFile.POLYGONZ:
140            case ShapeFile.MULTIPOINTZ:
141            case ShapeFile.MULTIPATCH:
142                envelope.extend( zmin, zmax, mmin, mmax );
143    
144            }
145        }
146    
147        private LinkedList<Shape> readShapes( InputStream in, boolean strict )
148                                throws IOException {
149            LinkedList<Shape> shapes = new LinkedList<Shape>();
150    
151            int offset = 0;
152            byte[] bytes = new byte[length - 100];
153            // read the whole file at once, this makes the "parsing" pretty fast
154            in.read( bytes );
155    
156            while ( offset < bytes.length ) {
157                if ( shapes.size() % 10000 == 0 ) {
158                    System.out.print( shapes.size() + " shapes read.\r" );
159                }
160    
161                // ByteUtils.readBEInt( hdr, 0 ); // just ignore the record number
162                int len = ByteUtils.readBEInt( bytes, offset + 4 ) * 2;
163                offset += 8;
164    
165                if ( strict ) {
166                    Shape s = null;
167                    switch ( shapeType ) {
168                    case ShapeFile.NULL:
169                        break;
170                    case ShapeFile.POINT:
171                        s = new ShapePoint( false, false );
172                        break;
173                    case ShapeFile.POINTM:
174                        s = new ShapePoint( false, true );
175                        break;
176                    case ShapeFile.POINTZ:
177                        s = new ShapePoint( true, false );
178                        break;
179                    case ShapeFile.POLYLINE:
180                        s = new ShapePolyline( false, false );
181                        break;
182                    case ShapeFile.POLYLINEM:
183                        s = new ShapePolyline( false, true );
184                        break;
185                    case ShapeFile.POLYLINEZ:
186                        s = new ShapePolyline( true, false );
187                        break;
188                    case ShapeFile.POLYGON:
189                        s = new ShapePolygon( false, false );
190                        break;
191                    case ShapeFile.POLYGONM:
192                        s = new ShapePolygon( false, true );
193                        break;
194                    case ShapeFile.POLYGONZ:
195                        s = new ShapePolygon( true, false );
196                        break;
197                    case ShapeFile.MULTIPOINT:
198                        s = new ShapeMultiPoint( false, false );
199                        break;
200                    case ShapeFile.MULTIPOINTM:
201                        s = new ShapeMultiPoint( false, true );
202                        break;
203                    case ShapeFile.MULTIPOINTZ:
204                        s = new ShapeMultiPoint( true, false );
205                        break;
206                    case ShapeFile.MULTIPATCH:
207                        s = new ShapeMultiPatch( len );
208                        break;
209                    }
210    
211                    LOG.logDebug( "Reading shape type " + s.getClass().getSimpleName() );
212    
213                    int alen = s.read( bytes, offset ) - offset;
214    
215                    if ( len != alen ) {
216                        LOG.logWarning( "Length is supposedly " + len + ", actual read length was "
217                                        + alen );
218                        // broken files that omit the M-section and that add the record length to the
219                        // length header:
220                        offset += len - 8;
221                    } else {
222                        offset += len;
223                    }
224    
225                    shapes.add( s );
226    
227                } else {
228                    // TODO
229                }
230            }
231    
232            LOG.logInfo( "Read " + shapes.size() + " shapes in total." );
233    
234            return shapes;
235        }
236    
237        /**
238         * @return the shape file contents.
239         * @throws IOException
240         */
241        public ShapeFile read()
242                                throws IOException {
243            File mainFile = new File( baseName + ".shp" );
244            BufferedInputStream mainIn = new BufferedInputStream( new FileInputStream( mainFile ) );
245            readHeader( mainIn );
246    
247            LinkedList<Shape> shapes = readShapes( mainIn, true );
248    
249            DBaseFile dbf = new DBaseFile( baseName );
250    
251            return new ShapeFile( shapes, envelope, dbf, baseName );
252        }
253    
254    }
255    
256    /***************************************************************************************************
257     * <code>
258     Changes to this class. What the people have been up to:
259    
260     $Log$
261     Revision 1.4  2007/03/05 09:26:07  schmitz
262     Added support for broken files with incorrect length record headers that omit the M-section.
263    
264     Revision 1.3  2007/02/27 13:03:40  schmitz
265     Fixed handling of Multipatch type.
266    
267     Revision 1.2  2007/02/27 09:10:24  schmitz
268     Reading ignores length attribute in record header now.
269    
270     Revision 1.1  2007/02/26 14:26:49  schmitz
271     Added a new implementation of the ShapeFile API that implements the Z and M variants of the datatypes as well.
272     Also added some basic tests for the API as well as a new version of the GML/Shape converters.
273    
274     </code>
275     **************************************************************************************************/