001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/shpapi/shape_new/ShapeMultiPatch.java $
002 /*---------------- FILE HEADER ------------------------------------------
003 This file is part of deegree.
004 Copyright (C) 2001-2006 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: 7672 $, $Date: 2007-06-28 10:39:54 +0200 (Do, 28 Jun 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 ps.add( GeometryFactory.createPosition( p.x, p.y, p.z ) );
405 }
406
407 LinkedList<Surface> ss = new LinkedList<Surface>();
408
409 while ( ps.size() > 2 ) {
410 Position[] ring = new Position[4];
411 ring[0] = ps.get( 0 );
412 ring[1] = ps.get( 1 );
413 ring[2] = ps.get( 2 );
414 ring[3] = ring[0];
415 ps.poll();
416 ss.add( GeometryFactory.createSurface( ring, null, null, null ) );
417 }
418
419 return ss;
420 }
421
422 // just uses an outer ring, all vertices and the first one again
423 private LinkedList<Surface> fromTriangleFan( ShapePoint[] points )
424 throws GeometryException {
425 LinkedList<Position> ps = new LinkedList<Position>();
426
427 for ( ShapePoint p : points ) {
428 ps.add( GeometryFactory.createPosition( p.x, p.y, p.z ) );
429 }
430
431 LinkedList<Surface> ss = new LinkedList<Surface>();
432
433 Position center = ps.poll();
434
435 while ( ps.size() > 1 ) {
436 Position[] ring = new Position[4];
437 ring[0] = center;
438 ring[1] = ps.get( 0 );
439 ring[2] = ps.get( 1 );
440 ring[3] = center;
441 ps.poll();
442 ss.add( GeometryFactory.createSurface( ring, null, null, null ) );
443 }
444
445 return ss;
446 }
447
448 private Position[] fromRing( ShapePoint[] points ) {
449 Position[] ps = new Position[points.length];
450
451 for ( int i = 0; i < points.length; ++i ) {
452 ps[i] = GeometryFactory.createPosition( points[i].x, points[i].y, points[i].z );
453 }
454
455 if ( ps[0] != ps[ps.length - 1] ) {
456 LOG.logDebug( "Ring was not closed as required by the shape file spec!" );
457 LOG.logDebug( "Trying to recover anyway." );
458
459 // append first point, have to copy arrays
460 Position[] ps2 = new Position[points.length + 1];
461 for ( int i = 0; i < ps.length; ++i ) {
462 ps2[i] = ps[i];
463 }
464 ps2[ps.length] = ps[0];
465 ps = ps2;
466 }
467
468 return ps;
469 }
470
471 private boolean isClockwise( Position[] ring ) {
472 return !CGAlgorithms.isCCW( JTSAdapter.export( ring ).getCoordinates() );
473 }
474
475 /**
476 * Creates a MultiSurface object.
477 *
478 * @see org.deegree.io.shpapi.shape_new.Shape#getGeometry()
479 */
480 public Geometry getGeometry()
481 throws ShapeGeometryException {
482 if ( points == null ) {
483 LOG.logWarning( "Trying to export null geometry." );
484 return null;
485 }
486 try {
487 LinkedList<Surface> ss = new LinkedList<Surface>();
488
489 boolean outerRingMode = false;
490 Position[] outerRing = null;
491 LinkedList<Position[]> innerRings = new LinkedList<Position[]>();
492
493 boolean unknownRingMode = false;
494 Position[] unknownOuterRing = null;
495 LinkedList<Position[]> unknownInnerRings = new LinkedList<Position[]>();
496
497 for ( int i = 0; i < partTypes.length; ++i ) {
498 switch ( partTypes[i] ) {
499 case TRIANGLE_STRIP:
500 ss.addAll( fromTriangleStrip( points[i] ) );
501 LOG.logDebug( "Read triangle strip." );
502 break;
503 case TRIANGLE_FAN:
504 ss.addAll( fromTriangleFan( points[i] ) );
505 LOG.logDebug( "Read triangle fan." );
506 break;
507 case OUTER_RING:
508 outerRingMode = true;
509 outerRing = fromRing( points[i] );
510 LOG.logDebug( "Read outer ring." );
511 break;
512 case INNER_RING:
513 if ( !outerRingMode ) {
514 LOG.logWarning( "Found inner ring without preceding outer ring." );
515 break;
516 }
517 innerRings.add( fromRing( points[i] ) );
518 LOG.logDebug( "Read inner ring." );
519 break;
520 case FIRST_RING:
521 LOG.logDebug( "Read first ring." );
522 unknownRingMode = true;
523 Position[] ring = fromRing( points[i] );
524 if ( isClockwise( ring ) ) {
525 innerRings.add( ring );
526 } else {
527 outerRing = ring;
528 }
529 break;
530 case RING:
531 LOG.logDebug( "Read ring." );
532 ring = fromRing( points[i] );
533 if ( !unknownRingMode ) {
534 ss.add( GeometryFactory.createSurface( ring, null, null, null ) );
535 } else {
536 if ( isClockwise( ring ) ) {
537 innerRings.add( ring );
538 } else {
539 outerRing = ring;
540 }
541 }
542
543 break;
544
545 }
546 if ( outerRingMode ) {
547 if ( partTypes[i] != INNER_RING ) {
548 ss.add( GeometryFactory.createSurface( outerRing,
549 innerRings.toArray( new Position[innerRings.size()][] ),
550 null, null ) );
551 innerRings.clear();
552 outerRing = null;
553 outerRingMode = false;
554 }
555 }
556 if ( unknownRingMode ) {
557 if ( partTypes[i] != RING ) {
558 if ( unknownOuterRing == null ) {
559 while ( unknownInnerRings.size() != 0 ) {
560 ss.add( GeometryFactory.createSurface( unknownInnerRings.poll(), null, null, null ) );
561 }
562 } else {
563 ss.add( GeometryFactory.createSurface(
564 unknownOuterRing,
565 unknownInnerRings.toArray( new Position[unknownInnerRings.size()][] ),
566 null, null ) );
567 }
568 unknownInnerRings.clear();
569 unknownOuterRing = null;
570 unknownRingMode = false;
571 }
572 }
573
574 }
575
576 if ( ss.size() == 0 ) {
577 return null;
578 }
579 return GeometryFactory.createMultiSurface( ss.toArray( new Surface[ss.size()] ) );
580 } catch ( GeometryException e ) {
581 throw new ShapeGeometryException( "", e );
582 }
583 }
584
585 @Override
586 public String toString() {
587 try {
588 return WKTAdapter.export( getGeometry() ).toString();
589 } catch ( GeometryException e ) {
590 return "(unknown)";
591 }
592 }
593
594 }