001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/shpapi/ShapeFile.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005     Department of Geography, University of Bonn
006     and
007     lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035     ----------------------------------------------------------------------------*/
036    package org.deegree.io.shpapi;
037    
038    import java.io.ByteArrayInputStream;
039    import java.io.IOException;
040    import java.util.ArrayList;
041    import java.util.Enumeration;
042    import java.util.HashMap;
043    import java.util.Hashtable;
044    import java.util.Iterator;
045    import java.util.List;
046    import java.util.Map;
047    
048    import org.deegree.datatypes.QualifiedName;
049    import org.deegree.datatypes.Types;
050    import org.deegree.io.dbaseapi.DBaseException;
051    import org.deegree.io.dbaseapi.DBaseFile;
052    import org.deegree.io.dbaseapi.DBaseIndex;
053    import org.deegree.io.dbaseapi.DBaseIndexException;
054    import org.deegree.io.dbaseapi.FieldDescriptor;
055    import org.deegree.io.rtree.HyperBoundingBox;
056    import org.deegree.io.rtree.HyperPoint;
057    import org.deegree.io.rtree.RTree;
058    import org.deegree.io.rtree.RTreeException;
059    import org.deegree.model.feature.Feature;
060    import org.deegree.model.feature.FeatureCollection;
061    import org.deegree.model.feature.FeatureFactory;
062    import org.deegree.model.feature.FeatureProperty;
063    import org.deegree.model.feature.schema.FeatureType;
064    import org.deegree.model.feature.schema.GeometryPropertyType;
065    import org.deegree.model.feature.schema.PropertyType;
066    import org.deegree.model.spatialschema.ByteUtils;
067    import org.deegree.model.spatialschema.Curve;
068    import org.deegree.model.spatialschema.Envelope;
069    import org.deegree.model.spatialschema.Geometry;
070    import org.deegree.model.spatialschema.GeometryException;
071    import org.deegree.model.spatialschema.GeometryFactory;
072    import org.deegree.model.spatialschema.JTSAdapter;
073    import org.deegree.model.spatialschema.MultiCurve;
074    import org.deegree.model.spatialschema.MultiPoint;
075    import org.deegree.model.spatialschema.MultiSurface;
076    import org.deegree.model.spatialschema.Point;
077    import org.deegree.model.spatialschema.Position;
078    import org.deegree.model.spatialschema.Ring;
079    import org.deegree.model.spatialschema.Surface;
080    import org.deegree.model.spatialschema.SurfaceInterpolationImpl;
081    
082    import com.vividsolutions.jts.algorithm.CGAlgorithms;
083    
084    /**
085     * Class representing an ESRI Shape File.
086     * 
087     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
088     * @author last edited by: $Author: apoth $
089     * 
090     * @version $Revision: 25096 $, $Date: 2010-06-29 13:00:08 +0200 (Di, 29 Jun 2010) $
091     */
092    public class ShapeFile {
093    
094        private DBaseFile dbf = null;
095    
096        private SHP2WKS shpwks = new SHP2WKS();
097    
098        /*
099         * contains the dBase indexes
100         */
101        private Hashtable<String, DBaseIndex> dBaseIndexes = new Hashtable<String, DBaseIndex>( 50 );
102    
103        /*
104         * aggregated Instance-variables
105         */
106        private MainFile shp = null;
107    
108        private RTree rti = null;
109    
110        private String fileName = null;
111    
112        /*
113         * indicates if a dBase-file is associated to the shape-file
114         */
115        private boolean hasDBaseFile = true;
116    
117        /*
118         * indicates if an R-tree index is associated to the shape-file
119         */
120        private boolean hasRTreeIndex = true;
121    
122        /**
123         * constructor: <BR>
124         * Construct a ShapeFile from a file name.<BR>
125         * 
126         * @param fileName
127         * @throws IOException
128         */
129        public ShapeFile( String fileName ) throws IOException {
130            this.fileName = fileName;
131            /*
132             * initialize the MainFile
133             */
134            shp = new MainFile( fileName );
135    
136            /*
137             * initialize the DBaseFile
138             */
139            try {
140                dbf = new DBaseFile( fileName );
141            } catch ( IOException e ) {
142                hasDBaseFile = false;
143            }
144    
145            /*
146             * initialize the RTreeIndex
147             */
148            try {
149                rti = new RTree( fileName + ".rti" );
150            } catch ( RTreeException e ) {
151                hasRTreeIndex = false;
152            }
153    
154            if ( hasDBaseFile ) {
155                String[] s = null;
156                try {
157                    s = getProperties();
158                } catch ( Exception e ) {
159                    e.printStackTrace();
160                }
161                for ( int i = 0; i < s.length; i++ ) {
162                    try {
163                        dBaseIndexes.put( s[i], new DBaseIndex( fileName + "$" + s[i] ) );
164                    } catch ( IOException e ) {
165                        // uh, oh
166                    }
167                }
168            }
169        }
170    
171        /**
172         * constructor: <BR>
173         * Construct a ShapeFile from a file name.<BR>
174         * 
175         * @param url
176         * @param rwflag
177         * @throws IOException
178         */
179        public ShapeFile( String url, String rwflag ) throws IOException {
180            this.fileName = url;
181            shp = new MainFile( url, rwflag );
182            // TODO: initialize dbf, rti
183            hasDBaseFile = false;
184            hasRTreeIndex = false;
185        }
186    
187        /**
188         *
189         */
190        public void close() {
191    
192            shp.close();
193            dbf.close();
194    
195            if ( rti != null ) {
196                try {
197                    rti.close();
198                } catch ( Exception e ) {
199                    // should never happen
200                    e.printStackTrace();
201                }
202            }
203    
204            for ( Enumeration<DBaseIndex> e = dBaseIndexes.elements(); e.hasMoreElements(); ) {
205                DBaseIndex index = e.nextElement();
206    
207                try {
208                    index.close();
209                } catch ( Exception ex ) {
210                    // uh, oh
211                }
212            }
213        }
214    
215        /**
216         * Overrides the default feature type (which is generated from all columns in the dbase file) to allow customized
217         * naming and ordering of properties.
218         * 
219         * @param ft
220         * @param ftMapping
221         */
222        public void setFeatureType( FeatureType ft, Map<PropertyType, String> ftMapping ) {
223            dbf.setFeatureType( ft, ftMapping );
224        }
225    
226        /**
227         * returns true if a column is indexed
228         * 
229         * @param column
230         * @return true if a column is indexed
231         */
232        public boolean hasDBaseIndex( String column ) {
233            DBaseIndex index = dBaseIndexes.get( column );
234            return index != null;
235        }
236    
237        /**
238         * returns true if a dBase-file is associated to the shape-file<BR>
239         * 
240         * @return true if a dBase-file is associated to the shape-file<BR>
241         */
242        public boolean hasDBaseFile() {
243            return this.hasDBaseFile;
244        }
245    
246        /**
247         * returns true if an R-tree index is associated to the shape-file<BR>
248         * 
249         * @return true if an R-tree index is associated to the shape-file<BR>
250         */
251        public boolean hasRTreeIndex() {
252            return this.hasRTreeIndex;
253        }
254    
255        /**
256         * returns the number of records within a shape-file<BR>
257         * 
258         * @return the number of records within a shape-file<BR>
259         */
260        public int getRecordNum() {
261            return shp.getRecordNum();
262        }
263    
264        /**
265         * returns the minimum bounding rectangle of all geometries<BR>
266         * within the shape-file
267         * 
268         * @return the minimum bounding rectangle of all geometries<BR>
269         *         within the shape-file
270         */
271        public Envelope getFileMBR() {
272            double xmin = shp.getFileMBR().west;
273            double xmax = shp.getFileMBR().east;
274            double ymin = shp.getFileMBR().south;
275            double ymax = shp.getFileMBR().north;
276    
277            return GeometryFactory.createEnvelope( xmin, ymin, xmax, ymax, null );
278        }
279    
280        /**
281         * returns the minimum bound rectangle of RecNo'th Geometrie<BR>
282         * 
283         * @param recNo
284         * @return the minimum bound rectangle of RecNo'th Geometrie<BR>
285         * @throws IOException
286         */
287        public Envelope getMBRByRecNo( int recNo )
288                                throws IOException {
289            SHPEnvelope shpenv = shp.getRecordMBR( recNo );
290            if ( shpenv != null ) {
291                double xmin = shpenv.west;
292                double xmax = shpenv.east;
293                double ymin = shpenv.south;
294                double ymax = shpenv.north;
295    
296                return GeometryFactory.createEnvelope( xmin, ymin, xmax, ymax, null );
297            } else {
298                return null;
299            }
300        }
301    
302        /**
303         * Returns the given entry of the shape file as a {@link Feature} instance.
304         * <p>
305         * This contains the geometry as well as the attributes stored into the dbase file. The geometry property will use a
306         * default name (app:GEOM).
307         * 
308         * @param RecNo
309         * @return the given entry of the shape file as a Feature instance
310         * @throws IOException
311         * @throws HasNoDBaseFileException
312         * @throws DBaseException
313         */
314        public Feature getFeatureByRecNo( int RecNo )
315                                throws IOException, HasNoDBaseFileException, DBaseException {
316    
317            if ( !hasDBaseFile ) {
318                throw new HasNoDBaseFileException( "Exception: there is no dBase-file " + "associated to this shape-file" );
319            }
320    
321            // get feature (without geometry property) from DBaseFile
322            Feature feature = dbf.getFRow( RecNo );
323    
324            // exchange null geometries with real geometry
325            Geometry geo = getGeometryByRecNo( RecNo );
326            GeometryPropertyType[] geoPTs = feature.getFeatureType().getGeometryProperties();
327            for ( int i = 0; i < geoPTs.length; i++ ) {
328                FeatureProperty[] geoProp = feature.getProperties( geoPTs[i].getName() );
329                for ( int j = 0; j < geoProp.length; j++ ) {
330                    geoProp[j].setValue( geo );
331                }
332            }
333    
334            return feature;
335        }
336    
337        /**
338         * returns RecNo'th Geometrie<BR>
339         * 
340         * @param recNo
341         * @return RecNo'th Geometrie<BR>
342         * @throws IOException
343         */
344        public Geometry getGeometryByRecNo( int recNo )
345                                throws IOException {
346            Geometry geom = null;
347    
348            int shpType = getShapeTypeByRecNo( recNo );
349    
350            if ( shpType == ShapeConst.SHAPE_TYPE_POINT ) {
351                SHPPoint shppoint = (SHPPoint) shp.getByRecNo( recNo );
352                geom = shpwks.transformPoint( null, shppoint );
353            } else if ( shpType == ShapeConst.SHAPE_TYPE_MULTIPOINT ) {
354                SHPMultiPoint shpmultipoint = (SHPMultiPoint) shp.getByRecNo( recNo );
355                Point[] points = shpwks.transformMultiPoint( null, shpmultipoint );
356                if ( points != null ) {
357                    MultiPoint mp = GeometryFactory.createMultiPoint( points );
358                    geom = mp;
359                } else {
360                    geom = null;
361                }
362            } else if ( shpType == ShapeConst.SHAPE_TYPE_POLYLINE ) {
363                SHPPolyLine shppolyline = (SHPPolyLine) shp.getByRecNo( recNo );
364                Curve[] curves = shpwks.transformPolyLine( null, shppolyline );
365                if ( ( curves != null ) && ( curves.length > 1 ) ) {
366                    // create multi curve
367                    MultiCurve mc = GeometryFactory.createMultiCurve( curves );
368                    geom = mc;
369                } else if ( ( curves != null ) && ( curves.length == 1 ) ) {
370                    // single curve
371                    geom = curves[0];
372                } else {
373                    geom = null;
374                }
375            } else if ( shpType == ShapeConst.SHAPE_TYPE_POLYGON ) {
376                SHPPolygon shppoly = (SHPPolygon) shp.getByRecNo( recNo );
377                Surface[] polygons = shpwks.transformPolygon( null, shppoly );
378                if ( ( polygons != null ) && ( polygons.length > 1 ) ) {
379                    // create multi surface
380                    MultiSurface ms = GeometryFactory.createMultiSurface( polygons );
381                    geom = ms;
382                } else if ( ( polygons != null ) && ( polygons.length == 1 ) ) {
383                    geom = polygons[0];
384                } else {
385                    geom = null;
386                }
387            } else if ( shpType == ShapeConst.SHAPE_TYPE_POLYGONZ ) {
388    
389                SHPPolygon3D shppoly = (SHPPolygon3D) shp.getByRecNo( recNo );
390    
391                Surface[] polygons = shpwks.transformPolygon( null, shppoly );
392    
393                if ( ( polygons != null ) && ( polygons.length > 1 ) ) {
394                    // create multi surface
395                    MultiSurface ms = GeometryFactory.createMultiSurface( polygons );
396                    geom = ms;
397                } else if ( ( polygons != null ) && ( polygons.length == 1 ) ) {
398                    geom = polygons[0];
399                } else {
400                    geom = null;
401                }
402            }
403    
404            return geom;
405        }
406    
407        /**
408         * returns the type of the RecNo'th Geometrie<BR>
409         * per definition a shape file contains onlay one shape type<BR>
410         * but null shapes are possible too!<BR>
411         * 
412         * @param RecNo
413         * @return the type of the RecNo'th Geometrie<BR>
414         *         per definition a shape file contains onlay one shape type<BR>
415         *         but null shapes are possible too!<BR>
416         * @throws IOException
417         */
418        public int getShapeTypeByRecNo( int RecNo )
419                                throws IOException {
420            return shp.getShapeTypeByRecNo( RecNo );
421        }
422    
423        /**
424         * returns a int array that containts all the record numbers that matches the search operation
425         * 
426         * @param column
427         * @param value
428         * @return a int array that containts all the record numbers that matches the search operation
429         * @throws IOException
430         * @throws DBaseIndexException
431         */
432        public int[] getGeoNumbersByAttribute( String column, Comparable<?> value )
433                                throws IOException, DBaseIndexException {
434            DBaseIndex index = dBaseIndexes.get( column );
435    
436            if ( index == null ) {
437                return null;
438            }
439    
440            return index.search( value );
441        }
442    
443        /**
444         * returns a ArrayList that contains all geomeries of the shape file<BR>
445         * which mbr's are completly or partly within the rectangle r<BR>
446         * only Points, MultiPoints, PolyLines and Polygons are handled<BR>
447         * 
448         * @param r
449         * @return a ArrayList that contains all geomeries of the shape file<BR>
450         *         which mbr's are completly or partly within the rectangle r<BR>
451         *         only Points, MultiPoints, PolyLines and Polygons are handled<BR>
452         * @throws IOException
453         */
454        public int[] getGeoNumbersByRect( Envelope r )
455                                throws IOException {
456            SHPPoint geom = null;
457            int[] num = null;
458            int numRecs = getRecordNum();
459    
460            Envelope mbr = getFileMBR();
461    
462            if ( !mbr.intersects( r ) ) {
463                return null;
464            }
465    
466            if ( hasRTreeIndex ) {
467                try {
468                    // translate envelope (deegree) to bounding box (rtree)
469                    HyperBoundingBox box = new HyperBoundingBox( new HyperPoint( r.getMin().getAsArray() ),
470                                                                 new HyperPoint( r.getMax().getAsArray() ) );
471                    Object[] iNumbers = rti.intersects( box );
472                    num = new int[iNumbers.length];
473    
474                    for ( int i = 0; i < iNumbers.length; i++ )
475                        num[i] = ( (Integer) iNumbers[i] ).intValue();
476    
477                    return num;
478                } catch ( Exception e ) {
479                    e.printStackTrace();
480                }
481            }
482    
483            // for every geometry (record) within the shape file
484            // check if it's inside the search-rectangle r
485            List<Integer> numbers = new ArrayList<Integer>( 500 );
486            for ( int i = 0; i < numRecs; i++ ) {
487                if ( getShapeTypeByRecNo( i + 1 ) == ShapeConst.SHAPE_TYPE_NULL ) {
488                    // why is nothing done here?
489                } else if ( getShapeTypeByRecNo( i + 1 ) == ShapeConst.SHAPE_TYPE_POINT ) {
490                    geom = (SHPPoint) shp.getByRecNo( i + 1 );
491    
492                    // is the Point within the seach rectangle?
493                    Position pos = GeometryFactory.createPosition( geom.x, geom.y );
494    
495                    if ( r.contains( pos ) == true ) {
496                        numbers.add( new Integer( i + 1 ) );
497                    }
498                } else {
499                    // get minimum bounding rectangle of the i'th record
500                    mbr = getMBRByRecNo( i + 1 );
501    
502                    // is the i'th record a geometrie having a mbr
503                    // (only for PolyLines, Polygons and MultiPoints mbrs are defined)
504                    if ( mbr != null ) {
505                        // if the tested rectangles are not disjunct the number of the
506                        // actual record is added to the ArrayList
507                        if ( mbr.intersects( r ) ) {
508                            numbers.add( new Integer( i + 1 ) );
509                        }
510                    }
511                }
512            }
513    
514            if ( numbers.size() > 0 ) {
515                num = new int[numbers.size()];
516    
517                // put all numbers within numbers to an array
518                for ( int i = 0; i < numbers.size(); i++ ) {
519                    num[i] = numbers.get( i );
520                }
521            }
522    
523            return num;
524        } // end of getGeoNumbersByRect
525    
526        /**
527         * is a property unique?
528         * 
529         * @param property
530         * @return if it is unique
531         */
532        public boolean isUnique( String property ) {
533            DBaseIndex index = dBaseIndexes.get( property );
534    
535            if ( index == null ) {
536                return false;
537            }
538    
539            return index.isUnique();
540        }
541    
542        /**
543         * returns the properties (column headers) of the dBase-file<BR>
544         * associated to the shape-file<BR>
545         * 
546         * @return the properties (column headers) of the dBase-file<BR>
547         *         associated to the shape-file<BR>
548         * @throws HasNoDBaseFileException
549         * @throws DBaseException
550         */
551        public String[] getProperties()
552                                throws HasNoDBaseFileException, DBaseException {
553            if ( !hasDBaseFile ) {
554                throw new HasNoDBaseFileException( "Exception: there is no dBase-file " + "associated to this shape-file" );
555            }
556    
557            return dbf.getProperties();
558        }
559    
560        /**
561         * returns the datatype of each column of the database file<BR>
562         * associated to the shape-file<BR>
563         * 
564         * @return the datatype of each column of the database file<BR>
565         *         associated to the shape-file<BR>
566         * @throws HasNoDBaseFileException
567         * @throws DBaseException
568         */
569        public String[] getDataTypes()
570                                throws HasNoDBaseFileException, DBaseException {
571            if ( !hasDBaseFile ) {
572                throw new HasNoDBaseFileException( "Exception: there is no dBase-file " + "associated to this shape-file" );
573            }
574    
575            return dbf.getDataTypes();
576        }
577    
578        /**
579         * 
580         * 
581         * @return the lengths
582         * 
583         * @throws HasNoDBaseFileException
584         * @throws DBaseException
585         */
586        public int[] getDataLengths()
587                                throws HasNoDBaseFileException, DBaseException {
588            String[] properties = getProperties();
589            int[] retval = new int[properties.length];
590    
591            for ( int i = 0; i < properties.length; i++ ) {
592                retval[i] = dbf.getDataLength( properties[i] );
593            }
594    
595            return retval;
596        }
597    
598        /**
599         * returns the datatype of each column of the dBase associated<BR>
600         * to the shape-file specified by fields<BR>
601         * 
602         * @param fields
603         * @return the datatype of each column of the dBase associated<BR>
604         *         to the shape-file specified by fields<BR>
605         * @throws HasNoDBaseFileException
606         * @throws DBaseException
607         */
608        public String[] getDataTypes( String[] fields )
609                                throws HasNoDBaseFileException, DBaseException {
610            if ( !hasDBaseFile ) {
611                throw new HasNoDBaseFileException( "Exception: there is no dBase-file " + "associated to this shape-file" );
612            }
613    
614            return dbf.getDataTypes( fields );
615        }
616    
617        /**
618         * returns the number of geometries within a feature collection<BR>
619         * 
620         * @param fc
621         *            : featurecollection which is checked for the number geomtries<BR>
622         */
623        private int getGeometryCount( FeatureCollection fc ) {
624            return fc.size();
625        }
626    
627        /**
628         * returns the type of the n'th feature in a featurecollection
629         * 
630         * @param fc
631         *            FeatureCollection. must not be empty.
632         * @param n
633         *            number of the feature which should be examined starts with 0
634         */
635        private int getGeometryType( FeatureCollection fc, int n ) {
636            Feature feature = null;
637    
638            feature = fc.getFeature( n );
639    
640            Geometry[] g = feature.getGeometryPropertyValues();
641    
642            if ( ( g == null ) || ( g.length == 0 ) ) {
643                return -1;
644            }
645    
646            if ( g[0] instanceof Point ) {
647                return 0;
648            }
649    
650            if ( g[0] instanceof Curve ) {
651                return 1;
652            }
653    
654            if ( g[0] instanceof Surface ) {
655                return 2;
656            }
657    
658            if ( g[0] instanceof MultiPoint ) {
659                return 3;
660            }
661    
662            if ( g[0] instanceof MultiCurve ) {
663                return 4;
664            }
665    
666            if ( g[0] instanceof MultiSurface ) {
667                return 5;
668            }
669    
670            return -1;
671        }
672    
673        /**
674         * returns the n'th feature of a featurecollection as a Geometry<BR>
675         * 
676         * @param fc
677         *            : FeatureCollection<BR>
678         * @param n
679         *            : number of the feature which should be returned<BR>
680         */
681        private Geometry getFeatureAsGeometry( FeatureCollection fc, int n ) {
682            Feature feature = null;
683    
684            feature = fc.getFeature( n );
685    
686            return feature.getGeometryPropertyValues()[0];
687        }
688    
689        /**
690         * Copies the feature properties of a feature.
691         * 
692         * @param fc
693         * @param n
694         *            the number of the feature to get the properties for
695         * @return the properties
696         */
697        public FeatureProperty[] getFeatureProperties( FeatureCollection fc, int n ) {
698            Feature feature = fc.getFeature( n );
699    
700            PropertyType[] ftp = feature.getFeatureType().getProperties();
701            FeatureProperty[] fp = new FeatureProperty[ftp.length];
702            
703            for ( int i = 0; i < ftp.length; i++ ) {
704                FeatureProperty fp_ = feature.getDefaultProperty( ftp[i].getName() );
705                Object value = null;
706                if ( fp_ != null ) {
707                    value = fp_.getValue();
708                }
709                fp[i] = FeatureFactory.createFeatureProperty( ftp[i].getName(), value );
710            }
711    
712            return fp;
713        }
714    
715        /**
716         */
717        private void initDBaseFile( FeatureCollection fc )
718                                throws DBaseException {
719            FieldDescriptor[] fieldDesc = null;
720    
721            // get feature properties
722            FeatureProperty[] pairs = getFeatureProperties( fc, 0 );
723    
724            // count regular fields
725            int cnt = 0;
726            FeatureType featT = fc.getFeature( 0 ).getFeatureType();
727            PropertyType[] ftp = featT.getProperties();
728                  
729            for ( int i = 0; i < pairs.length; i++ ) {
730                Object obj = pairs[i].getValue();
731    
732                if ( obj instanceof Object[] ) {
733                    obj = ( (Object[]) obj )[0];
734                }
735                PropertyType pt = featT.getProperty( pairs[i].getName() );
736                if ( !( obj instanceof ByteArrayInputStream ) && !( obj instanceof Geometry ) && pt.getType() != Types.GEOMETRY ) {
737                    cnt++;
738                }
739            }
740    
741            // allocate memory for field descriptors
742            fieldDesc = new FieldDescriptor[cnt];
743    
744            // determine maximum field length for string columns
745            Iterator<Feature> iter = fc.iterator();
746            Map<QualifiedName, Integer> fieldLength = new HashMap<QualifiedName, Integer>();
747            while ( iter.hasNext() ) {
748                Feature feature = (Feature) iter.next();
749                FeatureProperty[] fp = feature.getProperties();
750                for ( FeatureProperty featureProperty : fp ) {
751                    Object o = null;
752                    if ( featureProperty != null && ( o = featureProperty.getValue() ) instanceof String ) {
753                        int k = ( (String) o ).length();
754                        if ( fieldLength.get( featureProperty.getName() ) == null || k > fieldLength.get( featureProperty.getName() ) ) {
755                            fieldLength.put( featureProperty.getName(), new Integer( k + 5) );
756                        }
757                    }
758                }
759    
760            }
761            // get properties names and types and create a FieldDescriptor
762            // for each properties except the geometry-property
763            cnt = 0;
764            for ( int i = 0; i < ftp.length; i++ ) {
765                int pos = ftp[i].getName().getLocalName().lastIndexOf( '.' );
766                if ( pos < 0 ) {
767                    pos = -1;
768                }
769                String s = ftp[i].getName().getLocalName().substring( pos + 1 );
770                if ( ftp[i].getType() == Types.INTEGER ) {
771                    fieldDesc[cnt++] = new FieldDescriptor( s, "N", (byte) 20, (byte) 0 );
772                } else if ( ftp[i].getType() == Types.BIGINT ) {
773                    fieldDesc[cnt++] = new FieldDescriptor( s, "N", (byte) 30, (byte) 0 );
774                } else if ( ftp[i].getType() == Types.SMALLINT ) {
775                    fieldDesc[cnt++] = new FieldDescriptor( s, "N", (byte) 4, (byte) 0 );
776                } else if ( ftp[i].getType() == Types.CHAR ) {
777                    fieldDesc[cnt++] = new FieldDescriptor( s, "C", (byte) 1, (byte) 0 );
778                } else if ( ftp[i].getType() == Types.FLOAT ) {
779                    fieldDesc[cnt++] = new FieldDescriptor( s, "N", (byte) 30, (byte) 10 );
780                } else if ( ftp[i].getType() == Types.DOUBLE || ftp[i].getType() == Types.NUMERIC ) {
781                    fieldDesc[cnt++] = new FieldDescriptor( s, "N", (byte) 30, (byte) 10 );
782                } else if ( ftp[i].getType() == Types.DECIMAL ) {
783                    fieldDesc[cnt++] = new FieldDescriptor( s, "N", (byte) 30, (byte) 10 );
784                } else if ( ftp[i].getType() == Types.VARCHAR ) {
785                    int len = 10;
786                    if ( fieldLength.get( ftp[i].getName() ) != null ) {
787                        len = fieldLength.get( ftp[i].getName() ).intValue()+2;    
788                    }                
789                    if ( len > 127 ) {
790                        len = 127;
791                    }
792                    fieldDesc[cnt++] = new FieldDescriptor( s, "C", (byte)len, (byte) 0 );
793                } else if ( ftp[i].getType() == Types.DATE ) {
794                    fieldDesc[cnt++] = new FieldDescriptor( s, "D", (byte) 12, (byte) 0 );
795                } else {
796                }
797            }
798            // initialize/create DBaseFile
799            try {
800                dbf = new DBaseFile( fileName, fieldDesc );
801            } catch ( DBaseException e ) {
802                hasDBaseFile = false;
803            }
804        }
805    
806        /**
807         * writes a OGC FeatureCollection to a ESRI shape file.<BR>
808         * all features in the collection must have the same properties.<BR>
809         * 
810         * @param fc
811         *            FeatureCollection. must not be null or empty.
812         * @throws Exception
813         */
814        public void writeShape( FeatureCollection fc )
815                                throws Exception {
816    
817            int nbyte = 0;
818            int geotype = -1;
819            byte shptype = -1;
820            int typ_ = getGeometryType( fc, 0 );
821            byte[] bytearray = null;
822            IndexRecord record = null;
823            SHPEnvelope mbr = null;
824            // mbr of the whole shape file
825            SHPEnvelope shpmbr = new SHPEnvelope();
826    
827            // Set the Offset to the end of the fileHeader
828            int offset = ShapeConst.SHAPE_FILE_HEADER_LENGTH;
829    
830            // initialize the dbasefile associated with the shapefile
831            initDBaseFile( fc );
832    
833            // loop throug the Geometries of the feature collection anf write them
834            // to a bytearray
835            for ( int i = 0; i < getGeometryCount( fc ); i++ ) {
836                if ( i % 1000 == 0 ) {
837                    System.gc();
838                }
839                // write i'th features properties to a ArrayList
840                PropertyType[] ftp = fc.getFeature( 0 ).getFeatureType().getProperties();
841                ArrayList<Object> vec = new ArrayList<Object>( ftp.length );
842                for ( int j = 0; j < ftp.length; j++ ) {
843                    if ( ftp[j].getType() == Types.GEOMETRY )
844                        continue;
845                    FeatureProperty fp = fc.getFeature( i ).getDefaultProperty( ftp[j].getName() );
846                    Object obj = null;
847                    if ( fp != null ) {
848                        obj = fp.getValue();
849                    }
850    
851                    if ( obj instanceof Object[] ) {
852                        obj = ( (Object[]) obj )[0];
853                    }
854    
855                    if ( ( ftp[j].getType() == Types.INTEGER ) || ( ftp[j].getType() == Types.BIGINT )
856                         || ( ftp[j].getType() == Types.DECIMAL )
857                         || ( ftp[j].getType() == Types.SMALLINT ) || ( ftp[j].getType() == Types.CHAR )
858                         || ( ftp[j].getType() == Types.FLOAT ) || ( ftp[j].getType() == Types.DOUBLE )
859                         || ( ftp[j].getType() == Types.NUMERIC ) || ( ftp[j].getType() == Types.VARCHAR )
860                         || ( ftp[j].getType() == Types.DATE ) ) {
861                        vec.add( obj );
862                    }
863    
864                }
865    
866                // write the ArrayList (properties) to the dbase file
867                try {
868                    dbf.setRecord( vec );
869                } catch ( DBaseException db ) {
870                    db.printStackTrace();
871                    throw new Exception( db.toString() );
872                }
873    
874                // Get Geometry Type of i'th feature
875                geotype = getGeometryType( fc, i );
876    
877                if ( geotype < 0 ) {
878                    continue;
879                }
880    
881                if ( ( typ_ == 0 ) || ( typ_ == 3 ) ) {
882                    if ( ( geotype != 0 ) && ( geotype != 3 ) ) {
883                        throw new Exception( "Not a homogen FeatureCollection" );
884                    }
885                }
886    
887                if ( ( typ_ == 1 ) || ( typ_ == 4 ) ) {
888                    if ( ( geotype != 1 ) && ( geotype != 4 ) ) {
889                        throw new Exception( "Not a homogen FeatureCollection" );
890                    }
891                }
892    
893                if ( ( typ_ == 2 ) || ( typ_ == 5 ) ) {
894                    if ( ( geotype != 2 ) && ( geotype != 5 ) ) {
895                        throw new Exception( "Not a homogen FeatureCollection" );
896                    }
897                }
898    
899                // get wks geometrie for feature (i) and write it to a file
900                if ( geotype == 0 ) {
901                    // Geometrie Type = Point
902                    Point wks = (Point) getFeatureAsGeometry( fc, i );
903                    SHPPoint shppoint = new SHPPoint( wks.getPosition() );
904                    nbyte = shppoint.size();
905                    bytearray = new byte[nbyte + ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH];
906                    shppoint.writeSHPPoint( bytearray, ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH );
907                    mbr = new SHPEnvelope( shppoint, shppoint );
908    
909                    if ( i == 0 ) {
910                        shpmbr = mbr;
911                    }
912    
913                    shptype = 1;
914                } else if ( geotype == 1 ) {
915                    // Geometrie Type = LineString
916                    Curve[] wks = new Curve[1];
917                    wks[0] = (Curve) getFeatureAsGeometry( fc, i );
918    
919                    SHPPolyLine shppolyline = new SHPPolyLine( wks );
920                    nbyte = shppolyline.size();
921                    bytearray = new byte[nbyte + ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH];
922                    shppolyline.writeSHPPolyLine( bytearray, ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH );
923                    mbr = shppolyline.getEnvelope();
924    
925                    if ( i == 0 ) {
926                        shpmbr = mbr;
927                    }
928    
929                    shptype = 3;
930                } else if ( geotype == 2 ) {
931                    // Geometrie Type = Polygon
932                    Surface[] wks = new Surface[1];
933                    wks[0] = (Surface) getFeatureAsGeometry( fc, i );
934                    validateOrientation( wks );
935    
936                    SHPPolygon shppolygon = new SHPPolygon( wks );
937                    nbyte = shppolygon.size();
938                    bytearray = new byte[nbyte + ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH];
939                    shppolygon.writeSHPPolygon( bytearray, ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH );
940                    mbr = shppolygon.getEnvelope();
941    
942                    if ( i == 0 ) {
943                        shpmbr = mbr;
944                    }
945    
946                    shptype = 5;
947                } else if ( geotype == 3 ) {
948                    // Geometrie Type = MultiPoint
949                    MultiPoint wks = (MultiPoint) getFeatureAsGeometry( fc, i );
950                    SHPMultiPoint shpmultipoint = new SHPMultiPoint( wks );
951                    nbyte = shpmultipoint.size();
952                    bytearray = new byte[nbyte + ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH];
953                    shpmultipoint.writeSHPMultiPoint( bytearray, ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH );
954                    mbr = shpmultipoint.getEnvelope();
955                    shptype = 8;
956                } else if ( geotype == 4 ) {
957                    // Geometrie Type = MultiLineString
958                    MultiCurve wks = (MultiCurve) getFeatureAsGeometry( fc, i );
959                    SHPPolyLine shppolyline = new SHPPolyLine( wks.getAllCurves() );
960                    nbyte = shppolyline.size();
961                    bytearray = new byte[nbyte + ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH];
962                    shppolyline.writeSHPPolyLine( bytearray, ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH );
963                    mbr = shppolyline.getEnvelope();
964    
965                    if ( i == 0 ) {
966                        shpmbr = mbr;
967                    }
968    
969                    shptype = 3;
970                } else if ( geotype == 5 ) {
971                    // Geometrie Type = MultiPolygon
972                    MultiSurface wks = (MultiSurface) getFeatureAsGeometry( fc, i );
973                    SHPPolygon shppolygon = new SHPPolygon( wks.getAllSurfaces() );
974                    nbyte = shppolygon.size();
975                    bytearray = new byte[nbyte + ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH];
976                    shppolygon.writeSHPPolygon( bytearray, ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH );
977                    mbr = shppolygon.getEnvelope();
978    
979                    if ( i == 0 ) {
980                        shpmbr = mbr;
981                    }
982    
983                    shptype = 5;
984                }
985    
986                // write bytearray to the shape file
987                record = new IndexRecord( offset / 2, nbyte / 2 );
988    
989                // write recordheader to the bytearray
990                ByteUtils.writeBEInt( bytearray, 0, i );
991                ByteUtils.writeBEInt( bytearray, 4, nbyte / 2 );
992    
993                // write record (bytearray) including recordheader to the shape file
994                shp.write( bytearray, record, mbr );
995    
996                // actualise shape file minimum boundary rectangle
997                if ( mbr.west < shpmbr.west ) {
998                    shpmbr.west = mbr.west;
999                }
1000    
1001                if ( mbr.east > shpmbr.east ) {
1002                    shpmbr.east = mbr.east;
1003                }
1004    
1005                if ( mbr.south < shpmbr.south ) {
1006                    shpmbr.south = mbr.south;
1007                }
1008    
1009                if ( mbr.north > shpmbr.north ) {
1010                    shpmbr.north = mbr.north;
1011                }
1012    
1013                // icrement offset for pointing at the end of the file
1014                offset += ( nbyte + ShapeConst.SHAPE_FILE_RECORD_HEADER_LENGTH );
1015    
1016                bytearray = null;
1017            }
1018    
1019            dbf.writeAllToFile();
1020    
1021            // write shape header
1022            shp.writeHeader( offset, shptype, shpmbr );
1023    
1024        }
1025    
1026        private void validateOrientation( Surface[] wks )
1027                                throws GeometryException {
1028            com.vividsolutions.jts.geom.Geometry jts = JTSAdapter.export( wks[0] );
1029            CGAlgorithms.isCCW( jts.getCoordinates() );
1030            if ( CGAlgorithms.isCCW( jts.getCoordinates() ) ) {
1031                Position[] pos = wks[0].getSurfaceBoundary().getExteriorRing().getPositions();
1032                Position[] pos2 = new Position[pos.length];
1033                for ( int j = 0; j < pos2.length; j++ ) {
1034                    pos2[j] = pos[pos.length - j - 1];
1035                }
1036                Position[][] iPos = null;
1037                if ( wks[0].getSurfaceBoundary().getInteriorRings() != null ) {
1038                    Ring[] rings = wks[0].getSurfaceBoundary().getInteriorRings();
1039                    iPos = new Position[rings.length][];
1040                    for ( int j = 0; j < rings.length; j++ ) {
1041                        pos = rings[j].getPositions();
1042                        iPos[j] = new Position[pos.length];
1043                        for ( int k = 0; k < pos.length; k++ ) {
1044                            iPos[j][k] = pos[pos.length - k - 1];
1045                        }
1046                    }
1047                }
1048                wks[0] = GeometryFactory.createSurface( pos2, iPos, new SurfaceInterpolationImpl(),
1049                                                        wks[0].getCoordinateSystem() );
1050            }
1051        }
1052    }