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    }