001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/io/shpapi/shape_new/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.shape_new;
037
038 import java.io.ByteArrayInputStream;
039 import java.io.IOException;
040 import java.util.ArrayList;
041 import java.util.Arrays;
042 import java.util.LinkedList;
043 import java.util.List;
044
045 import org.deegree.datatypes.Types;
046 import org.deegree.framework.log.ILogger;
047 import org.deegree.framework.log.LoggerFactory;
048 import org.deegree.io.dbaseapi.DBaseException;
049 import org.deegree.io.dbaseapi.DBaseFile;
050 import org.deegree.io.dbaseapi.FieldDescriptor;
051 import org.deegree.model.feature.Feature;
052 import org.deegree.model.feature.FeatureCollection;
053 import org.deegree.model.feature.FeatureFactory;
054 import org.deegree.model.feature.FeatureProperty;
055 import org.deegree.model.feature.schema.FeatureType;
056 import org.deegree.model.feature.schema.GeometryPropertyType;
057 import org.deegree.model.feature.schema.PropertyType;
058 import org.deegree.model.spatialschema.Curve;
059 import org.deegree.model.spatialschema.CurveSegment;
060 import org.deegree.model.spatialschema.Geometry;
061 import org.deegree.model.spatialschema.GeometryException;
062 import org.deegree.model.spatialschema.GeometryFactory;
063 import org.deegree.model.spatialschema.MultiCurve;
064 import org.deegree.model.spatialschema.MultiPoint;
065 import org.deegree.model.spatialschema.MultiSurface;
066 import org.deegree.model.spatialschema.Point;
067 import org.deegree.model.spatialschema.Ring;
068 import org.deegree.model.spatialschema.Surface;
069
070 /**
071 * <code>ShapeFile</code> encapsulates and provides access to data and properties of a shapefile. Please note that
072 * writing will probably fail if the data was read by shapefile.
073 *
074 * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
075 * @author last edited by: $Author: mschneider $
076 *
077 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
078 */
079 public class ShapeFile {
080
081 /**
082 * The file type number.
083 */
084 public static final int FILETYPE = 9994;
085
086 /**
087 * The shape file version.
088 */
089 public static final int VERSION = 1000;
090
091 /**
092 * The NULL shape.
093 */
094 public static final int NULL = 0;
095
096 /**
097 * The normal point.
098 */
099 public static final int POINT = 1;
100
101 /**
102 * The normal polyline.
103 */
104 public static final int POLYLINE = 3;
105
106 /**
107 * The normal polygon.
108 */
109 public static final int POLYGON = 5;
110
111 /**
112 * The normal multipoint.
113 */
114 public static final int MULTIPOINT = 8;
115
116 /**
117 * The point with z coordinates.
118 */
119 public static final int POINTZ = 11;
120
121 /**
122 * The polyline with z coordinates.
123 */
124 public static final int POLYLINEZ = 13;
125
126 /**
127 * The polygon with z coordinates.
128 */
129 public static final int POLYGONZ = 15;
130
131 /**
132 * The multipoint with z coordinates.
133 */
134 public static final int MULTIPOINTZ = 18;
135
136 /**
137 * The point with measure.
138 */
139 public static final int POINTM = 21;
140
141 /**
142 * The polyline with measures.
143 */
144 public static final int POLYLINEM = 23;
145
146 /**
147 * The polygon with measures.
148 */
149 public static final int POLYGONM = 25;
150
151 /**
152 * The multipoint with measures.
153 */
154 public static final int MULTIPOINTM = 28;
155
156 /**
157 * The multipatch shape.
158 */
159 public static final int MULTIPATCH = 31;
160
161 private static final ILogger LOG = LoggerFactory.getLogger( ShapeFile.class );
162
163 private LinkedList<Shape> shapes;
164
165 private ShapeEnvelope envelope;
166
167 private List<FieldDescriptor> descriptors;
168
169 private DBaseFile dbf;
170
171 private String baseName;
172
173 /**
174 * @param shapes
175 * the shapes that this shapefile consists of
176 * @param envelope
177 * the envelope of all the shapes
178 * @param dbf
179 * the associated DBase file
180 * @param baseName
181 * the base name
182 */
183 public ShapeFile( LinkedList<Shape> shapes, ShapeEnvelope envelope, DBaseFile dbf, String baseName ) {
184 this.shapes = shapes;
185 this.envelope = envelope;
186 this.dbf = dbf;
187 this.baseName = baseName;
188 }
189
190 /**
191 * Creates shapefile datastructures from the feature collection.
192 *
193 * @param fc
194 * @param baseName
195 * necessary for DBF creation, base filename without .dbf extension
196 * @throws DBaseException
197 * @throws GeometryException
198 */
199 public ShapeFile( FeatureCollection fc, String baseName ) throws DBaseException, GeometryException {
200 this.baseName = baseName;
201 shapes = new LinkedList<Shape>();
202
203 // get all shapes
204 for ( int i = 0; i < fc.size(); ++i ) {
205 Feature f = fc.getFeature( i );
206 Shape s = extractShape( f );
207 shapes.add( s );
208 updateEnvelope( s );
209 }
210
211 createDBF( fc );
212 }
213
214 // this adds the metadata to the dbf
215 private void createDBF( FeatureCollection fc )
216 throws DBaseException {
217 extractDescriptors( fc );
218 dbf = new DBaseFile( baseName, descriptors.toArray( new FieldDescriptor[descriptors.size()] ) );
219
220 for ( int i = 0; i < fc.size(); ++i ) {
221
222 PropertyType[] ftp = fc.getFeature( 0 ).getFeatureType().getProperties();
223 ArrayList<Object> list = new ArrayList<Object>( ftp.length );
224 for ( int j = 0; j < ftp.length; j++ ) {
225 if ( ftp[j].getType() == Types.GEOMETRY ) {
226 continue;
227 }
228 FeatureProperty fp = fc.getFeature( i ).getDefaultProperty( ftp[j].getName() );
229 Object obj = null;
230 if ( fp != null ) {
231 obj = fp.getValue();
232 }
233
234 if ( obj instanceof Object[] ) {
235 obj = ( (Object[]) obj )[0];
236 }
237
238 if ( ( ftp[j].getType() == Types.INTEGER ) || ( ftp[j].getType() == Types.BIGINT )
239 || ( ftp[j].getType() == Types.SMALLINT ) || ( ftp[j].getType() == Types.CHAR )
240 || ( ftp[j].getType() == Types.FLOAT ) || ( ftp[j].getType() == Types.DOUBLE )
241 || ( ftp[j].getType() == Types.NUMERIC ) || ( ftp[j].getType() == Types.VARCHAR )
242 || ( ftp[j].getType() == Types.DATE ) ) {
243 list.add( obj );
244 }
245
246 }
247
248 dbf.setRecord( list );
249 }
250
251 }
252
253 // updates the envelope upon adding a new shape
254 private void updateEnvelope( Shape s ) {
255 if ( s.getEnvelope() != null ) {
256 if ( envelope == null ) {
257 envelope = new ShapeEnvelope( s.getEnvelope() );
258 } else {
259 envelope.fit( s.getEnvelope() );
260 }
261 } else {
262 if ( s instanceof ShapePoint ) {
263 ShapePoint p = (ShapePoint) s;
264 // to avoid envelope extension to (0,0,0):
265 if ( envelope == null ) {
266 envelope = new ShapeEnvelope( true, false );
267 envelope.xmin = p.x;
268 envelope.ymin = p.y;
269 envelope.zmin = p.z;
270 envelope.xmax = p.x;
271 envelope.ymax = p.y;
272 envelope.zmax = p.z;
273 } else {
274 envelope.fit( p.x, p.y, p.z );
275 }
276 }
277 }
278 }
279
280 private ArrayList<Curve> getAsCurves( Surface s )
281 throws GeometryException {
282 ArrayList<Curve> curves = new ArrayList<Curve>( 10 );
283
284 addAllCurves( s, curves );
285
286 return curves;
287 }
288
289 private void addAllCurves( Surface s, List<Curve> curves )
290 throws GeometryException {
291 // add exterior ring first
292 CurveSegment cs = s.getSurfaceBoundary().getExteriorRing().getAsCurveSegment();
293 curves.add( GeometryFactory.createCurve( cs ) );
294
295 // then, add inner rings
296 Ring[] innerRings = s.getSurfaceBoundary().getInteriorRings();
297
298 if ( innerRings != null ) {
299 for ( Ring r : innerRings ) {
300 cs = r.getAsCurveSegment();
301 curves.add( GeometryFactory.createCurve( cs ) );
302 }
303 }
304 }
305
306 // currently just the first geometry is extracted, the others are ignored
307 private Shape extractShape( Feature f )
308 throws GeometryException {
309 Geometry g = f.getDefaultGeometryPropertyValue();
310
311 if ( f.getGeometryPropertyValues().length > 1 ) {
312 LOG.logWarning( "Warning, a Feature had more than one Geometries, only the first one is used. Geometry classes:" );
313 for ( Geometry g1 : f.getGeometryPropertyValues() ) {
314 LOG.logWarning( g1.getClass().getName() );
315 }
316 }
317
318 if ( g instanceof Point ) {
319 return new ShapePoint( (Point) g );
320 }
321
322 if ( g instanceof Curve ) {
323 return new ShapePolyline( (Curve) g );
324 }
325
326 if ( g instanceof Surface ) {
327 return new ShapePolygon( getAsCurves( (Surface) g ) );
328 }
329
330 if ( g instanceof MultiPoint ) {
331 return new ShapeMultiPoint( (MultiPoint) g );
332 }
333
334 if ( g instanceof MultiCurve ) {
335 List<Curve> cs = Arrays.asList( ( (MultiCurve) g ).getAllCurves() );
336 return new ShapePolyline( cs );
337 }
338
339 if ( g instanceof MultiSurface ) {
340 return new ShapeMultiPatch( (MultiSurface) g );
341 }
342
343 return null;
344 }
345
346 private void extractDescriptors( FeatureCollection fc )
347 throws DBaseException {
348 // get feature properties
349 FeatureProperty[] pairs = getFeatureProperties( fc, 0 );
350
351 // count regular fields
352 int cnt = 0;
353 FeatureType featT = fc.getFeature( 0 ).getFeatureType();
354 PropertyType[] ftp = featT.getProperties();
355 for ( int i = 0; i < pairs.length; i++ ) {
356 Object obj = pairs[i].getValue();
357
358 if ( obj instanceof Object[] ) {
359 obj = ( (Object[]) obj )[0];
360 }
361 if ( !( obj instanceof ByteArrayInputStream ) && !( obj instanceof Geometry ) ) {
362 cnt++;
363 }
364 }
365
366 // allocate memory for fielddescriptors
367 descriptors = new ArrayList<FieldDescriptor>( cnt );
368
369 // get properties names and types and create a FieldDescriptor
370 // for each properties except the geometry-property
371 cnt = 0;
372
373 for ( int i = 0; i < ftp.length; i++ ) {
374 int pos = ftp[i].getName().getLocalName().lastIndexOf( '.' );
375 if ( pos < 0 ) {
376 pos = -1;
377 }
378 String s = ftp[i].getName().getLocalName().substring( pos + 1 );
379 if ( ftp[i].getType() == Types.INTEGER ) {
380 descriptors.add( new FieldDescriptor( s, "N", (byte) 20, (byte) 0 ) );
381 } else if ( ftp[i].getType() == Types.BIGINT ) {
382 descriptors.add( new FieldDescriptor( s, "N", (byte) 30, (byte) 0 ) );
383 } else if ( ftp[i].getType() == Types.SMALLINT ) {
384 descriptors.add( new FieldDescriptor( s, "N", (byte) 4, (byte) 0 ) );
385 } else if ( ftp[i].getType() == Types.CHAR ) {
386 descriptors.add( new FieldDescriptor( s, "C", (byte) 1, (byte) 0 ) );
387 } else if ( ftp[i].getType() == Types.FLOAT ) {
388 descriptors.add( new FieldDescriptor( s, "N", (byte) 30, (byte) 10 ) );
389 } else if ( ftp[i].getType() == Types.DOUBLE || ftp[i].getType() == Types.NUMERIC ) {
390 descriptors.add( new FieldDescriptor( s, "N", (byte) 30, (byte) 10 ) );
391 } else if ( ftp[i].getType() == Types.VARCHAR ) {
392 descriptors.add( new FieldDescriptor( s, "C", (byte) 127, (byte) 0 ) );
393 } else if ( ftp[i].getType() == Types.DATE ) {
394 descriptors.add( new FieldDescriptor( s, "D", (byte) 12, (byte) 0 ) );
395 }
396 }
397
398 }
399
400 private FeatureProperty[] getFeatureProperties( FeatureCollection fc, int n ) {
401 Feature feature = null;
402
403 feature = fc.getFeature( n );
404
405 PropertyType[] ftp = feature.getFeatureType().getProperties();
406 FeatureProperty[] fp = new FeatureProperty[ftp.length];
407 FeatureProperty[] fp_ = feature.getProperties();
408 for ( int i = 0; i < ftp.length; i++ ) {
409 FeatureProperty[] tfp = feature.getProperties( ftp[i].getName() );
410 if ( tfp != null && tfp.length > 0 ) {
411 fp[i] = FeatureFactory.createFeatureProperty( ftp[i].getName(), fp_[i].getValue() );
412 } else {
413 fp[i] = FeatureFactory.createFeatureProperty( ftp[i].getName(), "" );
414 }
415 }
416
417 return fp;
418 }
419
420 /**
421 * @return the list of shapes contained within this shape file
422 */
423 public List<Shape> getShapes() {
424 return shapes;
425 }
426
427 /**
428 * @return just the type of the first shape
429 */
430 public int getShapeType() {
431 return shapes.get( 0 ).getType();
432 }
433
434 /**
435 * @return the sum of all shape sizes plus record header lengths, in bytes
436 */
437 public int getSize() {
438 int len = 0;
439 for ( Shape s : shapes ) {
440 len += s.getByteLength() + 8;
441 }
442 return len;
443 }
444
445 /**
446 * @return the envelope of the shapes.
447 */
448 public ShapeEnvelope getEnvelope() {
449 return envelope;
450 }
451
452 /**
453 * This writes the DBF file.
454 *
455 * @throws IOException
456 * @throws DBaseException
457 */
458 public void writeDBF()
459 throws IOException, DBaseException {
460 dbf.writeAllToFile();
461 }
462
463 /**
464 * This method destroys the internal list of shapes and the associated .dbf structure!
465 *
466 * @return a feature collection with all shapes
467 * @throws DBaseException
468 */
469 public FeatureCollection getFeatureCollection()
470 throws DBaseException {
471 FeatureCollection fc = FeatureFactory.createFeatureCollection( baseName, shapes.size() );
472
473 LinkedList<Feature> features = new LinkedList<Feature>();
474 for ( int i = 0; i < shapes.size(); ++i ) {
475 features.add( dbf.getFRow( i + 1 ) );
476 }
477
478 dbf = null;
479
480 int i = 0;
481 while ( shapes.size() > 0 ) {
482 Shape s = shapes.poll();
483 Feature feature = features.poll();
484 if ( i % 10000 == 0 ) {
485 System.out.print( i + " shapes processed.\r" );
486 }
487
488 Geometry geo = s.getGeometry();
489
490 GeometryPropertyType[] geoPTs = feature.getFeatureType().getGeometryProperties();
491 for ( GeometryPropertyType pt : geoPTs ) {
492 FeatureProperty[] geoProp = feature.getProperties( pt.getName() );
493 for ( int j = 0; j < geoProp.length; j++ ) {
494 geoProp[j].setValue( geo );
495 }
496 }
497
498 fc.add( feature );
499 ++i;
500 }
501
502 LOG.logInfo( i + " shapes processed in total." );
503
504 return fc;
505 }
506
507 /**
508 * @return the base name of this shape file
509 */
510 public String getBaseName() {
511 return baseName;
512 }
513
514 }