001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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 }