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    }