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