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