001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }