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    }