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 }