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