    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }