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