001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/shpapi/shape_new/ShapeFile.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003     This file is part of deegree.
004     Copyright (C) 2001-2008 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.ByteArrayInputStream;
045    import java.io.IOException;
046    import java.util.ArrayList;
047    import java.util.Arrays;
048    import java.util.LinkedList;
049    import java.util.List;
050    
051    import org.deegree.datatypes.Types;
052    import org.deegree.framework.log.ILogger;
053    import org.deegree.framework.log.LoggerFactory;
054    import org.deegree.io.dbaseapi.DBaseException;
055    import org.deegree.io.dbaseapi.DBaseFile;
056    import org.deegree.io.dbaseapi.FieldDescriptor;
057    import org.deegree.model.feature.Feature;
058    import org.deegree.model.feature.FeatureCollection;
059    import org.deegree.model.feature.FeatureFactory;
060    import org.deegree.model.feature.FeatureProperty;
061    import org.deegree.model.feature.schema.FeatureType;
062    import org.deegree.model.feature.schema.GeometryPropertyType;
063    import org.deegree.model.feature.schema.PropertyType;
064    import org.deegree.model.spatialschema.Curve;
065    import org.deegree.model.spatialschema.CurveSegment;
066    import org.deegree.model.spatialschema.Geometry;
067    import org.deegree.model.spatialschema.GeometryException;
068    import org.deegree.model.spatialschema.GeometryFactory;
069    import org.deegree.model.spatialschema.MultiCurve;
070    import org.deegree.model.spatialschema.MultiPoint;
071    import org.deegree.model.spatialschema.MultiSurface;
072    import org.deegree.model.spatialschema.Point;
073    import org.deegree.model.spatialschema.Ring;
074    import org.deegree.model.spatialschema.Surface;
075    
076    /**
077     * <code>ShapeFile</code> encapsulates and provides access to data and properties of a shapefile.
078     * Please note that writing will probably fail if the data was read by shapefile.
079     * 
080     * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
081     * @author last edited by: $Author: apoth $
082     * 
083     * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 2007) $
084     */
085    public class ShapeFile {
086    
087        /**
088         * The file type number.
089         */
090        public static final int FILETYPE = 9994;
091    
092        /**
093         * The shape file version.
094         */
095        public static final int VERSION = 1000;
096    
097        /**
098         * The NULL shape.
099         */
100        public static final int NULL = 0;
101    
102        /**
103         * The normal point.
104         */
105        public static final int POINT = 1;
106    
107        /**
108         * The normal polyline.
109         */
110        public static final int POLYLINE = 3;
111    
112        /**
113         * The normal polygon.
114         */
115        public static final int POLYGON = 5;
116    
117        /**
118         * The normal multipoint.
119         */
120        public static final int MULTIPOINT = 8;
121    
122        /**
123         * The point with z coordinates.
124         */
125        public static final int POINTZ = 11;
126    
127        /**
128         * The polyline with z coordinates.
129         */
130        public static final int POLYLINEZ = 13;
131    
132        /**
133         * The polygon with z coordinates.
134         */
135        public static final int POLYGONZ = 15;
136    
137        /**
138         * The multipoint with z coordinates.
139         */
140        public static final int MULTIPOINTZ = 18;
141    
142        /**
143         * The point with measure.
144         */
145        public static final int POINTM = 21;
146    
147        /**
148         * The polyline with measures.
149         */
150        public static final int POLYLINEM = 23;
151    
152        /**
153         * The polygon with measures.
154         */
155        public static final int POLYGONM = 25;
156    
157        /**
158         * The multipoint with measures.
159         */
160        public static final int MULTIPOINTM = 28;
161    
162        /**
163         * The multipatch shape.
164         */
165        public static final int MULTIPATCH = 31;
166    
167        private static final ILogger LOG = LoggerFactory.getLogger( ShapeFile.class );
168    
169        private LinkedList<Shape> shapes;
170    
171        private ShapeEnvelope envelope;
172    
173        private List<FieldDescriptor> descriptors;
174    
175        private DBaseFile dbf;
176    
177        private String baseName;
178    
179        /**
180         * @param shapes
181         *            the shapes that this shapefile consists of
182         * @param envelope
183         *            the envelope of all the shapes
184         * @param dbf
185         *            the associated DBase file
186         * @param baseName
187         *            the base name
188         */
189        public ShapeFile( LinkedList<Shape> shapes, ShapeEnvelope envelope, DBaseFile dbf,
190                          String baseName ) {
191            this.shapes = shapes;
192            this.envelope = envelope;
193            this.dbf = dbf;
194            this.baseName = baseName;
195        }
196    
197        /**
198         * Creates shapefile datastructures from the feature collection.
199         * 
200         * @param fc
201         * @param baseName
202         *            necessary for DBF creation, base filename without .dbf extension
203         * @throws DBaseException
204         * @throws GeometryException
205         */
206        public ShapeFile( FeatureCollection fc, String baseName ) throws DBaseException,
207                                GeometryException {
208            this.baseName = baseName;
209            shapes = new LinkedList<Shape>();
210    
211            // get all shapes
212            for ( int i = 0; i < fc.size(); ++i ) {
213                Feature f = fc.getFeature( i );
214                Shape s = extractShape( f );
215                shapes.add( s );
216                updateEnvelope( s );
217            }
218    
219            createDBF( fc );
220        }
221    
222        // this adds the metadata to the dbf
223        private void createDBF( FeatureCollection fc )
224                                throws DBaseException {
225            extractDescriptors( fc );
226            dbf = new DBaseFile( baseName,
227                                 descriptors.toArray( new FieldDescriptor[descriptors.size()] ) );
228    
229            for ( int i = 0; i < fc.size(); ++i ) {
230    
231                PropertyType[] ftp = fc.getFeature( 0 ).getFeatureType().getProperties();
232                ArrayList<Object> list = new ArrayList<Object>( ftp.length );
233                for ( int j = 0; j < ftp.length; j++ ) {
234                    if ( ftp[j].getType() == Types.GEOMETRY ) {
235                        continue;
236                    }
237                    FeatureProperty fp = fc.getFeature( i ).getDefaultProperty( ftp[j].getName() );
238                    Object obj = null;
239                    if ( fp != null ) {
240                        obj = fp.getValue();
241                    }
242    
243                    if ( obj instanceof Object[] ) {
244                        obj = ( (Object[]) obj )[0];
245                    }
246    
247                    if ( ( ftp[j].getType() == Types.INTEGER ) || ( ftp[j].getType() == Types.BIGINT )
248                         || ( ftp[j].getType() == Types.SMALLINT ) || ( ftp[j].getType() == Types.CHAR )
249                         || ( ftp[j].getType() == Types.FLOAT ) || ( ftp[j].getType() == Types.DOUBLE )
250                         || ( ftp[j].getType() == Types.NUMERIC )
251                         || ( ftp[j].getType() == Types.VARCHAR ) || ( ftp[j].getType() == Types.DATE ) ) {
252                        list.add( obj );
253                    }
254    
255                }
256    
257                dbf.setRecord( list );
258            }
259    
260        }
261    
262        // updates the envelope upon adding a new shape
263        private void updateEnvelope( Shape s ) {
264            if ( s.getEnvelope() != null ) {
265                if ( envelope == null ) {
266                    envelope = new ShapeEnvelope( s.getEnvelope() );
267                } else {
268                    envelope.fit( s.getEnvelope() );
269                }
270            } else {
271                if ( s instanceof ShapePoint ) {
272                    ShapePoint p = (ShapePoint) s;
273                    // to avoid envelope extension to (0,0,0):
274                    if ( envelope == null ) {
275                        envelope = new ShapeEnvelope( true, false );
276                        envelope.xmin = p.x;
277                        envelope.ymin = p.y;
278                        envelope.zmin = p.z;
279                        envelope.xmax = p.x;
280                        envelope.ymax = p.y;
281                        envelope.zmax = p.z;
282                    } else {
283                        envelope.fit( p.x, p.y, p.z );
284                    }
285                }
286            }
287        }
288    
289        private ArrayList<Curve> getAsCurves( Surface s )
290                                throws GeometryException {
291            ArrayList<Curve> curves = new ArrayList<Curve>( 10 );
292    
293            addAllCurves( s, curves );
294    
295            return curves;
296        }
297    
298        private void addAllCurves( Surface s, List<Curve> curves )
299                                throws GeometryException {
300            // add exterior ring first
301            CurveSegment cs = s.getSurfaceBoundary().getExteriorRing().getAsCurveSegment();
302            curves.add( GeometryFactory.createCurve( cs ) );
303    
304            // then, add inner rings
305            Ring[] innerRings = s.getSurfaceBoundary().getInteriorRings();
306    
307            if ( innerRings != null ) {
308                for ( Ring r : innerRings ) {
309                    cs = r.getAsCurveSegment();
310                    curves.add( GeometryFactory.createCurve( cs ) );
311                }
312            }
313        }
314    
315        // currently just the first geometry is extracted, the others are ignored
316        private Shape extractShape( Feature f )
317                                throws GeometryException {
318            Geometry g = f.getDefaultGeometryPropertyValue();
319    
320            if ( f.getGeometryPropertyValues().length > 1 ) {
321                LOG.logWarning( "Warning, a Feature had more than one Geometries, only the first one is used. Geometry classes:" );
322                for ( Geometry g1 : f.getGeometryPropertyValues() ) {
323                    LOG.logWarning( g1.getClass().getName() );
324                }
325            }
326    
327            if ( g instanceof Point ) {
328                return new ShapePoint( (Point) g );
329            }
330    
331            if ( g instanceof Curve ) {
332                return new ShapePolyline( (Curve) g );
333            }
334    
335            if ( g instanceof Surface ) {
336                return new ShapePolygon( getAsCurves( (Surface) g ) );
337            }
338    
339            if ( g instanceof MultiPoint ) {
340                return new ShapeMultiPoint( (MultiPoint) g );
341            }
342    
343            if ( g instanceof MultiCurve ) {
344                List<Curve> cs = Arrays.asList( ( (MultiCurve) g ).getAllCurves() );
345                return new ShapePolyline( cs );
346            }
347    
348            if ( g instanceof MultiSurface ) {
349                return new ShapeMultiPatch( (MultiSurface) g );
350            }
351    
352            return null;
353        }
354    
355        private void extractDescriptors( FeatureCollection fc )
356                                throws DBaseException {
357            // get feature properties
358            FeatureProperty[] pairs = getFeatureProperties( fc, 0 );
359    
360            // count regular fields
361            int cnt = 0;
362            FeatureType featT = fc.getFeature( 0 ).getFeatureType();
363            PropertyType[] ftp = featT.getProperties();
364            for ( int i = 0; i < pairs.length; i++ ) {
365                Object obj = pairs[i].getValue();
366    
367                if ( obj instanceof Object[] ) {
368                    obj = ( (Object[]) obj )[0];
369                }
370                if ( !( obj instanceof ByteArrayInputStream ) && !( obj instanceof Geometry ) ) {
371                    cnt++;
372                }
373            }
374    
375            // allocate memory for fielddescriptors
376            descriptors = new ArrayList<FieldDescriptor>( cnt );
377    
378            // get properties names and types and create a FieldDescriptor
379            // for each properties except the geometry-property
380            cnt = 0;
381    
382            for ( int i = 0; i < ftp.length; i++ ) {
383                int pos = ftp[i].getName().getLocalName().lastIndexOf( '.' );
384                if ( pos < 0 ) {
385                    pos = -1;
386                }
387                String s = ftp[i].getName().getLocalName().substring( pos + 1 );
388                if ( ftp[i].getType() == Types.INTEGER ) {
389                    descriptors.add( new FieldDescriptor( s, "N", (byte) 20, (byte) 0 ) );
390                } else if ( ftp[i].getType() == Types.BIGINT ) {
391                    descriptors.add( new FieldDescriptor( s, "N", (byte) 30, (byte) 0 ) );
392                } else if ( ftp[i].getType() == Types.SMALLINT ) {
393                    descriptors.add( new FieldDescriptor( s, "N", (byte) 4, (byte) 0 ) );
394                } else if ( ftp[i].getType() == Types.CHAR ) {
395                    descriptors.add( new FieldDescriptor( s, "C", (byte) 1, (byte) 0 ) );
396                } else if ( ftp[i].getType() == Types.FLOAT ) {
397                    descriptors.add( new FieldDescriptor( s, "N", (byte) 30, (byte) 10 ) );
398                } else if ( ftp[i].getType() == Types.DOUBLE || ftp[i].getType() == Types.NUMERIC ) {
399                    descriptors.add( new FieldDescriptor( s, "N", (byte) 30, (byte) 10 ) );
400                } else if ( ftp[i].getType() == Types.VARCHAR ) {
401                    descriptors.add( new FieldDescriptor( s, "C", (byte) 127, (byte) 0 ) );
402                } else if ( ftp[i].getType() == Types.DATE ) {
403                    descriptors.add( new FieldDescriptor( s, "D", (byte) 12, (byte) 0 ) );
404                }
405            }
406    
407        }
408    
409        private FeatureProperty[] getFeatureProperties( FeatureCollection fc, int n ) {
410            Feature feature = null;
411    
412            feature = fc.getFeature( n );
413    
414            PropertyType[] ftp = feature.getFeatureType().getProperties();
415            FeatureProperty[] fp = new FeatureProperty[ftp.length];
416            FeatureProperty[] fp_ = feature.getProperties();
417            for ( int i = 0; i < ftp.length; i++ ) {
418                FeatureProperty[] tfp = feature.getProperties( ftp[i].getName() );
419                if ( tfp != null && tfp.length > 0 ) {                
420                    fp[i] = FeatureFactory.createFeatureProperty( ftp[i].getName(), fp_[i].getValue() );
421                } else {
422                    fp[i] = FeatureFactory.createFeatureProperty( ftp[i].getName(), "" );
423                }
424            }
425    
426            return fp;
427        }
428    
429        /**
430         * @return the list of shapes contained within this shape file
431         */
432        public List<Shape> getShapes() {
433            return shapes;
434        }
435    
436        /**
437         * @return just the type of the first shape
438         */
439        public int getShapeType() {
440            return shapes.get( 0 ).getType();
441        }
442    
443        /**
444         * @return the sum of all shape sizes plus record header lengths, in bytes
445         */
446        public int getSize() {
447            int len = 0;
448            for ( Shape s : shapes ) {
449                len += s.getByteLength() + 8;
450            }
451            return len;
452        }
453    
454        /**
455         * @return the envelope of the shapes.
456         */
457        public ShapeEnvelope getEnvelope() {
458            return envelope;
459        }
460    
461        /**
462         * This writes the DBF file.
463         * 
464         * @throws IOException
465         * @throws DBaseException
466         */
467        public void writeDBF()
468                                throws IOException, DBaseException {
469            dbf.writeAllToFile();
470        }
471    
472        /**
473         * This method destroys the internal list of shapes and the associated .dbf structure!
474         * 
475         * @return a feature collection with all shapes
476         * @throws DBaseException
477         */
478        public FeatureCollection getFeatureCollection()
479                                throws DBaseException {
480            FeatureCollection fc = FeatureFactory.createFeatureCollection( baseName, shapes.size() );
481    
482            LinkedList<Feature> features = new LinkedList<Feature>();
483            for ( int i = 0; i < shapes.size(); ++i ) {
484                features.add( dbf.getFRow( i + 1 ) );
485            }
486    
487            dbf = null;
488    
489            int i = 0;
490            while ( shapes.size() > 0 ) {
491                Shape s = shapes.poll();
492                Feature feature = features.poll();
493                if ( i % 10000 == 0 ) {
494                    System.out.print( i + " shapes processed.\r" );
495                }
496    
497                Geometry geo = s.getGeometry();
498    
499                GeometryPropertyType[] geoPTs = feature.getFeatureType().getGeometryProperties();
500                for ( GeometryPropertyType pt : geoPTs ) {
501                    FeatureProperty[] geoProp = feature.getProperties( pt.getName() );
502                    for ( int j = 0; j < geoProp.length; j++ ) {
503                        geoProp[j].setValue( geo );
504                    }
505                }
506    
507                fc.add( feature );
508                ++i;
509            }
510            
511            LOG.logInfo( i + " shapes processed in total." );
512            
513            return fc;
514        }
515    
516    }