001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/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 (Tue, 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 }