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