001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/shpapi/shape_new/ShapeMultiPatch.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.util.ArrayList; 045 import java.util.LinkedList; 046 import java.util.List; 047 048 import org.deegree.framework.log.ILogger; 049 import org.deegree.framework.log.LoggerFactory; 050 import org.deegree.model.spatialschema.ByteUtils; 051 import org.deegree.model.spatialschema.Curve; 052 import org.deegree.model.spatialschema.CurveSegment; 053 import org.deegree.model.spatialschema.Geometry; 054 import org.deegree.model.spatialschema.GeometryException; 055 import org.deegree.model.spatialschema.GeometryFactory; 056 import org.deegree.model.spatialschema.JTSAdapter; 057 import org.deegree.model.spatialschema.LineString; 058 import org.deegree.model.spatialschema.MultiSurface; 059 import org.deegree.model.spatialschema.Position; 060 import org.deegree.model.spatialschema.Ring; 061 import org.deegree.model.spatialschema.Surface; 062 import org.deegree.model.spatialschema.WKTAdapter; 063 064 import com.vividsolutions.jts.algorithm.CGAlgorithms; 065 066 /** 067 * <code>ShapeMultiPatch</code> corresponds to a shapefile MultiPatch object. 068 * 069 * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a> 070 * @author last edited by: $Author: apoth $ 071 * 072 * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 2007) $ 073 */ 074 public class ShapeMultiPatch implements Shape { 075 076 /** 077 * Triangle strip part type. 078 */ 079 public static final int TRIANGLE_STRIP = 0; 080 081 /** 082 * Triangle fan part type. 083 */ 084 public static final int TRIANGLE_FAN = 1; 085 086 /** 087 * Outer polygon ring part type. 088 */ 089 public static final int OUTER_RING = 2; 090 091 /** 092 * Inner polygon ring part type. 093 */ 094 public static final int INNER_RING = 3; 095 096 /** 097 * First ring of a polygon part type. 098 */ 099 public static final int FIRST_RING = 4; 100 101 /** 102 * Polygon ring part type. 103 */ 104 public static final int RING = 5; 105 106 private static final ILogger LOG = LoggerFactory.getLogger( ShapeMultiPatch.class ); 107 108 private ShapeEnvelope envelope; 109 110 private int numberParts, numberPoints; 111 112 private int[] partTypes; 113 114 private ShapePoint[][] points; 115 116 private int expectedLength; 117 118 /** 119 * Empty constructor, used for reading. 120 * 121 * @param length 122 * expected length for reading 123 */ 124 public ShapeMultiPatch( int length ) { 125 expectedLength = length; 126 } 127 128 /** 129 * Constructs one from deegree MultiSurface. Every Surface is stored as an OUTER_RING followed 130 * by its INNER_RINGs (these are the only two part types used here). 131 * 132 * @param f 133 */ 134 public ShapeMultiPatch( MultiSurface f ) { 135 136 // just an estimation, could be more 137 ArrayList<ShapePoint[]> parts = new ArrayList<ShapePoint[]>( f.getSize() * 2 ); 138 ArrayList<Integer> types = new ArrayList<Integer>( f.getSize() * 2 ); 139 envelope = new ShapeEnvelope( f.getEnvelope() ); 140 try { 141 points = new ShapePoint[f.getSize()][]; 142 Surface[] surfaces = f.getAllSurfaces(); 143 for ( Surface s : surfaces ) { 144 145 // add exterior ring first 146 CurveSegment cs = s.getSurfaceBoundary().getExteriorRing().getAsCurveSegment(); 147 addCurve( parts, types, GeometryFactory.createCurve( cs ), true ); 148 149 // then, add inner rings 150 Ring[] innerRings = s.getSurfaceBoundary().getInteriorRings(); 151 152 if ( innerRings != null ) { 153 for ( Ring r : innerRings ) { 154 cs = r.getAsCurveSegment(); 155 addCurve( parts, types, GeometryFactory.createCurve( cs ), false ); 156 } 157 } 158 159 } 160 161 // this does not exist in Collections or Array?!? D'oh! 162 partTypes = new int[types.size()]; 163 for ( int i = 0; i < types.size(); ++i ) { 164 partTypes[i] = types.get( i ).intValue(); 165 } 166 points = parts.toArray( new ShapePoint[parts.size()][] ); 167 168 numberParts = parts.size(); 169 170 numberPoints = 0; 171 for ( ShapePoint[] ps : points ) { 172 numberPoints += ps.length; 173 } 174 175 } catch ( GeometryException e ) { 176 LOG.logError( "Something was wrong with a Curve object. " + "This will probably lead to followup errors, " 177 + "better check your input data. Stack Trace:", e ); 178 } 179 } 180 181 private void addCurve( List<ShapePoint[]> parts, List<Integer> types, Curve c, boolean outer ) 182 throws GeometryException { 183 if ( outer ) { 184 types.add( new Integer( OUTER_RING ) ); 185 } else { 186 types.add( new Integer( INNER_RING ) ); 187 } 188 LineString ls = c.getAsLineString(); 189 190 ShapePoint[] ps = new ShapePoint[ls.getNumberOfPoints()]; 191 192 ShapePoint p; 193 for ( int i = 0; i < ls.getNumberOfPoints(); i++ ) { 194 p = new ShapePoint( ls.getPositionAt( i ) ); 195 ps[i] = p; 196 envelope.fit( p.x, p.y, p.z ); 197 } 198 parts.add( ps ); 199 } 200 201 /* 202 * (non-Javadoc) 203 * 204 * @see org.deegree.io.shpapi.shape_new.Shape#getByteLength() 205 */ 206 public int getByteLength() { 207 return 44 + 4 * numberParts + 4 * numberParts + 16 * numberPoints + 16 + 8 * numberPoints + 16 + 8 208 * numberPoints; 209 } 210 211 /* 212 * (non-Javadoc) 213 * 214 * @see org.deegree.io.shpapi.shape_new.Shape#getEnvelope() 215 */ 216 public ShapeEnvelope getEnvelope() { 217 return envelope; 218 } 219 220 /* 221 * (non-Javadoc) 222 * 223 * @see org.deegree.io.shpapi.shape_new.Shape#getType() 224 */ 225 public int getType() { 226 return ShapeFile.MULTIPATCH; 227 } 228 229 /* 230 * (non-Javadoc) 231 * 232 * @see org.deegree.io.shpapi.shape_new.Shape#read(byte[], int) 233 */ 234 public int read( byte[] bytes, int offset ) { 235 int back = offset; 236 int type = ByteUtils.readLEInt( bytes, offset ); 237 offset += 4; 238 239 if ( type == ShapeFile.NULL ) { 240 LOG.logInfo( "Read null shape." ); 241 return offset; 242 } 243 244 envelope = new ShapeEnvelope( true, false ); 245 246 if ( type == ShapeFile.MULTIPATCH ) { 247 offset = envelope.read( bytes, offset ); 248 numberParts = ByteUtils.readLEInt( bytes, offset ); 249 offset += 4; 250 numberPoints = ByteUtils.readLEInt( bytes, offset ); 251 offset += 4; 252 253 LOG.logDebug( "Reading " + numberParts + " parts with a total of " + numberPoints + " points." ); 254 255 partTypes = new int[numberParts]; 256 int[] parts = new int[numberParts]; 257 258 // read part info 259 for ( int i = 0; i < numberParts; ++i ) { 260 parts[i] = ByteUtils.readLEInt( bytes, offset ); 261 offset += 4; 262 } 263 for ( int i = 0; i < numberParts; ++i ) { 264 partTypes[i] = ByteUtils.readLEInt( bytes, offset ); 265 offset += 4; 266 } 267 268 // read points 269 points = new ShapePoint[numberParts][]; 270 for ( int i = 0; i < numberParts; ++i ) { 271 // get length of current part 272 int max; 273 if ( i == numberParts - 1 ) { 274 max = numberPoints; 275 } else { 276 max = parts[i + 1]; 277 } 278 points[i] = new ShapePoint[max - parts[i]]; 279 280 // read points for part 281 for ( int k = 0; k < points[i].length; ++k ) { 282 points[i][k] = new ShapePoint( bytes, offset ); 283 offset += 16; 284 } 285 } 286 287 double zmin, zmax, mmin, mmax; 288 zmin = ByteUtils.readLEDouble( bytes, offset ); 289 offset += 8; 290 zmax = ByteUtils.readLEDouble( bytes, offset ); 291 offset += 8; 292 293 double[] zVals = new double[numberPoints]; 294 for ( int i = 0; i < numberPoints; ++i ) { 295 zVals[i] = ByteUtils.readLEDouble( bytes, offset ); 296 offset += 8; 297 } 298 299 // check for broken (?) files that omit the M-section completely: 300 if ( expectedLength == ( ( offset - back ) + 8 ) ) { 301 return offset; 302 } 303 304 mmin = ByteUtils.readLEDouble( bytes, offset ); 305 offset += 8; 306 mmax = ByteUtils.readLEDouble( bytes, offset ); 307 offset += 8; 308 309 double[] mVals = new double[numberPoints]; 310 for ( int i = 0; i < numberPoints; ++i ) { 311 mVals[i] = ByteUtils.readLEDouble( bytes, offset ); 312 offset += 8; 313 } 314 315 int i = 0; 316 for ( ShapePoint[] ps : points ) { 317 for ( ShapePoint p : ps ) { 318 p.extend( zVals[i], mVals[i] ); 319 ++i; 320 } 321 } 322 323 envelope.extend( zmin, zmax, mmin, mmax ); 324 325 } else { 326 LOG.logError( "Shape type was unexpectedly not Multipatch?" ); 327 } 328 329 return offset; 330 } 331 332 /* 333 * (non-Javadoc) 334 * 335 * @see org.deegree.io.shpapi.shape_new.Shape#write(byte[], int) 336 */ 337 public int write( byte[] bytes, int offset ) { 338 ByteUtils.writeLEInt( bytes, offset, ShapeFile.MULTIPATCH ); 339 offset += 4; 340 341 offset = envelope.write( bytes, offset ); 342 343 ByteUtils.writeLEInt( bytes, offset, points.length ); 344 offset += 4; 345 346 ByteUtils.writeLEInt( bytes, offset, numberPoints ); 347 offset += 4; 348 349 int pos = 0; 350 for ( ShapePoint[] ps : points ) { 351 ByteUtils.writeLEInt( bytes, offset, pos ); 352 offset += 4; 353 pos += ps.length; 354 } 355 356 for ( int i = 0; i < partTypes.length; ++i ) { 357 ByteUtils.writeLEInt( bytes, offset, partTypes[i] ); 358 offset += 4; 359 } 360 361 for ( ShapePoint[] ps : points ) { 362 for ( ShapePoint p : ps ) { 363 ByteUtils.writeLEDouble( bytes, offset, p.x ); 364 offset += 8; 365 ByteUtils.writeLEDouble( bytes, offset, p.y ); 366 offset += 8; 367 } 368 } 369 370 // write the m and z part 371 ByteUtils.writeLEDouble( bytes, offset, envelope.zmin ); 372 offset += 8; 373 ByteUtils.writeLEDouble( bytes, offset, envelope.zmax ); 374 offset += 8; 375 376 for ( ShapePoint[] ps : points ) { 377 for ( ShapePoint p : ps ) { 378 ByteUtils.writeLEDouble( bytes, offset, p.z ); 379 offset += 8; 380 } 381 } 382 383 ByteUtils.writeLEDouble( bytes, offset, envelope.mmin ); 384 offset += 8; 385 ByteUtils.writeLEDouble( bytes, offset, envelope.mmax ); 386 offset += 8; 387 388 for ( ShapePoint[] ps : points ) { 389 for ( ShapePoint p : ps ) { 390 ByteUtils.writeLEDouble( bytes, offset, p.m ); 391 offset += 8; 392 } 393 } 394 395 return offset; 396 } 397 398 // converts every triangle to a surface with an outer ring 399 private LinkedList<Surface> fromTriangleStrip( ShapePoint[] points ) 400 throws GeometryException { 401 LinkedList<Position> ps = new LinkedList<Position>(); 402 403 for ( ShapePoint p : points ) { 404 if ( p.isZ ) { 405 ps.add( GeometryFactory.createPosition( p.x, p.y, p.z ) ); 406 } else { 407 ps.add( GeometryFactory.createPosition( p.x, p.y ) ); 408 } 409 } 410 411 LinkedList<Surface> ss = new LinkedList<Surface>(); 412 413 while ( ps.size() > 2 ) { 414 Position[] ring = new Position[4]; 415 ring[0] = ps.get( 0 ); 416 ring[1] = ps.get( 1 ); 417 ring[2] = ps.get( 2 ); 418 ring[3] = ring[0]; 419 ps.poll(); 420 ss.add( GeometryFactory.createSurface( ring, null, null, null ) ); 421 } 422 423 return ss; 424 } 425 426 // just uses an outer ring, all vertices and the first one again 427 private LinkedList<Surface> fromTriangleFan( ShapePoint[] points ) 428 throws GeometryException { 429 LinkedList<Position> ps = new LinkedList<Position>(); 430 431 for ( ShapePoint p : points ) { 432 if ( p.isZ ) { 433 ps.add( GeometryFactory.createPosition( p.x, p.y, p.z ) ); 434 } else { 435 ps.add( GeometryFactory.createPosition( p.x, p.y ) ); 436 } 437 } 438 439 LinkedList<Surface> ss = new LinkedList<Surface>(); 440 441 Position center = ps.poll(); 442 443 while ( ps.size() > 1 ) { 444 Position[] ring = new Position[4]; 445 ring[0] = center; 446 ring[1] = ps.get( 0 ); 447 ring[2] = ps.get( 1 ); 448 ring[3] = center; 449 ps.poll(); 450 ss.add( GeometryFactory.createSurface( ring, null, null, null ) ); 451 } 452 453 return ss; 454 } 455 456 private Position[] fromRing( ShapePoint[] points ) { 457 Position[] ps = new Position[points.length]; 458 459 for ( int i = 0; i < points.length; ++i ) { 460 if ( points[i].isZ ) { 461 ps[i] = GeometryFactory.createPosition( points[i].x, points[i].y, points[i].z ); 462 } else { 463 ps[i] = GeometryFactory.createPosition( points[i].x, points[i].y ); 464 } 465 } 466 467 if ( ps[0] != ps[ps.length - 1] ) { 468 LOG.logDebug( "Ring was not closed as required by the shape file spec!" ); 469 LOG.logDebug( "Trying to recover anyway." ); 470 471 // append first point, have to copy arrays 472 Position[] ps2 = new Position[points.length + 1]; 473 for ( int i = 0; i < ps.length; ++i ) { 474 ps2[i] = ps[i]; 475 } 476 ps2[ps.length] = ps[0]; 477 ps = ps2; 478 } 479 480 return ps; 481 } 482 483 private boolean isClockwise( Position[] ring ) { 484 return !CGAlgorithms.isCCW( JTSAdapter.export( ring ).getCoordinates() ); 485 } 486 487 /** 488 * Creates a MultiSurface object. 489 * 490 * @see org.deegree.io.shpapi.shape_new.Shape#getGeometry() 491 */ 492 public Geometry getGeometry() 493 throws ShapeGeometryException { 494 if ( points == null ) { 495 LOG.logWarning( "Trying to export null geometry." ); 496 return null; 497 } 498 try { 499 LinkedList<Surface> ss = new LinkedList<Surface>(); 500 501 boolean outerRingMode = false; 502 Position[] outerRing = null; 503 LinkedList<Position[]> innerRings = new LinkedList<Position[]>(); 504 505 boolean unknownRingMode = false; 506 Position[] unknownOuterRing = null; 507 LinkedList<Position[]> unknownInnerRings = new LinkedList<Position[]>(); 508 509 for ( int i = 0; i < partTypes.length; ++i ) { 510 switch ( partTypes[i] ) { 511 case TRIANGLE_STRIP: 512 ss.addAll( fromTriangleStrip( points[i] ) ); 513 LOG.logDebug( "Read triangle strip." ); 514 break; 515 case TRIANGLE_FAN: 516 ss.addAll( fromTriangleFan( points[i] ) ); 517 LOG.logDebug( "Read triangle fan." ); 518 break; 519 case OUTER_RING: 520 outerRingMode = true; 521 outerRing = fromRing( points[i] ); 522 LOG.logDebug( "Read outer ring." ); 523 break; 524 case INNER_RING: 525 if ( !outerRingMode ) { 526 LOG.logWarning( "Found inner ring without preceding outer ring." ); 527 break; 528 } 529 innerRings.add( fromRing( points[i] ) ); 530 LOG.logDebug( "Read inner ring." ); 531 break; 532 case FIRST_RING: 533 LOG.logDebug( "Read first ring." ); 534 unknownRingMode = true; 535 Position[] ring = fromRing( points[i] ); 536 if ( isClockwise( ring ) ) { 537 innerRings.add( ring ); 538 } else { 539 outerRing = ring; 540 } 541 break; 542 case RING: 543 LOG.logDebug( "Read ring." ); 544 ring = fromRing( points[i] ); 545 if ( !unknownRingMode ) { 546 ss.add( GeometryFactory.createSurface( ring, null, null, null ) ); 547 } else { 548 if ( isClockwise( ring ) ) { 549 innerRings.add( ring ); 550 } else { 551 outerRing = ring; 552 } 553 } 554 555 break; 556 557 } 558 if ( outerRingMode ) { 559 if ( partTypes[i] != INNER_RING ) { 560 ss.add( GeometryFactory.createSurface( outerRing, 561 innerRings.toArray( new Position[innerRings.size()][] ), 562 null, null ) ); 563 innerRings.clear(); 564 outerRing = null; 565 outerRingMode = false; 566 } 567 } 568 if ( unknownRingMode ) { 569 if ( partTypes[i] != RING ) { 570 if ( unknownOuterRing == null ) { 571 while ( unknownInnerRings.size() != 0 ) { 572 ss.add( GeometryFactory.createSurface( unknownInnerRings.poll(), null, null, null ) ); 573 } 574 } else { 575 ss.add( GeometryFactory.createSurface( 576 unknownOuterRing, 577 unknownInnerRings.toArray( new Position[unknownInnerRings.size()][] ), 578 null, null ) ); 579 } 580 unknownInnerRings.clear(); 581 unknownOuterRing = null; 582 unknownRingMode = false; 583 } 584 } 585 586 } 587 588 if ( ss.size() == 0 ) { 589 return null; 590 } 591 return GeometryFactory.createMultiSurface( ss.toArray( new Surface[ss.size()] ) ); 592 } catch ( GeometryException e ) { 593 throw new ShapeGeometryException( "", e ); 594 } 595 } 596 597 @Override 598 public String toString() { 599 try { 600 return WKTAdapter.export( getGeometry() ).toString(); 601 } catch ( GeometryException e ) { 602 return "(unknown)"; 603 } 604 } 605 606 }