001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/sdeapi/SpatialQuery.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.sdeapi;
037    
038    import java.io.ByteArrayInputStream;
039    import java.util.ArrayList;
040    import java.util.Vector;
041    
042    import org.deegree.datatypes.Types;
043    import org.deegree.framework.log.ILogger;
044    import org.deegree.framework.log.LoggerFactory;
045    import org.deegree.framework.util.StringTools;
046    import org.deegree.model.spatialschema.Curve;
047    import org.deegree.model.spatialschema.Envelope;
048    import org.deegree.model.spatialschema.Geometry;
049    import org.deegree.model.spatialschema.GeometryFactory;
050    import org.deegree.model.spatialschema.MultiCurve;
051    import org.deegree.model.spatialschema.MultiPoint;
052    import org.deegree.model.spatialschema.MultiSurface;
053    import org.deegree.model.spatialschema.Point;
054    import org.deegree.model.spatialschema.Position;
055    import org.deegree.model.spatialschema.Surface;
056    import org.deegree.model.spatialschema.SurfaceInterpolation;
057    import org.deegree.model.spatialschema.SurfaceInterpolationImpl;
058    import org.deegree.model.table.DefaultTable;
059    import org.deegree.model.table.Table;
060    import org.deegree.model.table.TableException;
061    
062    import com.esri.sde.sdk.client.SDEPoint;
063    import com.esri.sde.sdk.client.SeColumnDefinition;
064    import com.esri.sde.sdk.client.SeConnection;
065    import com.esri.sde.sdk.client.SeException;
066    import com.esri.sde.sdk.client.SeExtent;
067    import com.esri.sde.sdk.client.SeFilter;
068    import com.esri.sde.sdk.client.SeLayer;
069    import com.esri.sde.sdk.client.SeQuery;
070    import com.esri.sde.sdk.client.SeRow;
071    import com.esri.sde.sdk.client.SeShape;
072    import com.esri.sde.sdk.client.SeShapeFilter;
073    import com.esri.sde.sdk.client.SeSqlConstruct;
074    import com.esri.sde.sdk.client.SeTable;
075    
076    /**
077     * This class handles a complete ArcSDE request: If instanciated, the class can open a
078     * connection/instance of the specified ArcSDE server, set a bounding box as a spatial filter to
079     * query the defined layer. The resultset of the query contains the geometries as well as the
080     * tabular data associated with them. The table is stored as a deegree Table object whereas the
081     * geometries are stored as an array of deegree GM_Objects. Depending on the datatype of the
082     * geometries, the array of GM_Objects might be GM_Point, GM_Curve etc.
083     * <p>
084     * Some bits of sample code to create a query:
085     * <p>
086     * <code>
087     *        SpatialQuery sq = new SpatialQuery();<br>
088     *        try {<br>
089     *        &nbsp;sq.openConnection(server, instance, database, user, password);<br>
090     *        &nbsp;sq.setLayer(layer);<br>
091     *        &nbsp;sq.setSpatialFilter(minX, minY, maxX, maxY);<br>
092     *        &nbsp;sp.runSpatialQuery();<br>
093     *        &nbsp;GM_Object[] deegree_gm_obj = sq.getGeometries();<br>
094     *        &nbsp;Table deegree_table = sq.getTable();<br>
095     *        &nbsp;sq.closeConnection();<br>
096     *        } catch ( SeException sexp ) {<br>
097     *        }<br>
098     * </code>
099     *
100     * @author <a href="mailto:bedel@giub.uni-bonn.de">Markus Bedel</a>
101     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
102     * @version $Revision: 18195 $ $Date: 2009-06-18 17:55:39 +0200 (Do, 18 Jun 2009) $
103     */
104    public class SpatialQuery {
105    
106        private static final ILogger LOG = LoggerFactory.getLogger( SpatialQuery.class );
107    
108        // Connection to SDE
109        private SeConnection conn = null;
110    
111        // Currently opened Layer and associated Table
112        private SeLayer layer = null;
113    
114        // Current Spatial Filter - a BoundingBox
115        private SeShape spatialFilter = null;
116    
117        private SeTable table = null;
118    
119        // The Query ResultObjects
120        private Geometry[] deegreeGeom = null;
121    
122        /**
123         * Creates a new SpatialQuery object.
124         *
125         * @param server
126         * @param port
127         * @param database
128         * @param user
129         * @param password
130         *
131         * @throws SeException
132         */
133        public SpatialQuery( String server, int port, String database, String user, String password ) throws SeException {
134            openConnection( server, port, database, user, password );
135        }
136    
137        /**
138         * Connect to the ArcSDE server <br>
139         * throws SeException
140         *
141         * @param server
142         * @param port
143         * @param database
144         * @param user
145         * @param password
146         * @throws SeException
147         */
148        public void openConnection( String server, int port, String database, String user, String password )
149                                throws SeException {
150            conn = new SeConnection( server, port, database, user, password );
151        }
152    
153        /**
154         * Close the current connection to the ArcSDE server <br>
155         * throws SeException
156         *
157         * @throws SeException
158         */
159        public void closeConnection()
160                                throws SeException {
161            conn.close();
162        }
163    
164        /**
165         * Set a SDE layer to work on and appropriate table <br>
166         * throws SeException
167         *
168         * @param layername
169         * @throws SeException
170         */
171        public void setLayer( String layername )
172                                throws SeException {
173            Vector<?> layerList = conn.getLayers();
174            String spatialCol = null;
175    
176            for ( int i = 0; i < layerList.size(); i++ ) {
177                SeLayer layer = (SeLayer) layerList.elementAt( i );
178                if ( layer.getQualifiedName().trim().equalsIgnoreCase( layername ) ) {
179                    spatialCol = layer.getSpatialColumn();
180                    break;
181                }
182            }
183    
184            layer = new SeLayer( conn, layername, spatialCol );
185            table = new SeTable( conn, layer.getQualifiedName() );
186    
187        }
188    
189        /**
190         * Get the current SDE layer <br>
191         * returns null if it not yet set.
192         *
193         * @return null if it not yet set.
194         */
195        public SeLayer getLayer() {
196            return layer;
197        }
198    
199        /**
200         * Set a SpatialFilter to Query (BoundingBox) <br>
201         * throws SeException
202         *
203         * @param minx
204         * @param miny
205         * @param maxx
206         * @param maxy
207         * @throws SeException
208         */
209        public void setSpatialFilter( double minx, double miny, double maxx, double maxy )
210                                throws SeException {
211    
212            Envelope layerBBox = GeometryFactory.createEnvelope( layer.getExtent().getMinX(), layer.getExtent().getMinY(),
213                                                                 layer.getExtent().getMaxX(), layer.getExtent().getMaxY(),
214                                                                 null );
215    
216            Envelope query = GeometryFactory.createEnvelope( minx, miny, maxx, maxy, null );
217            query = query.createIntersection( layerBBox );
218    
219            if ( query != null ) {
220                spatialFilter = new SeShape( layer.getCoordRef() );
221                SeExtent extent = new SeExtent( query.getMin().getX(), query.getMin().getY(), query.getMax().getX(),
222                                                query.getMax().getY() );
223                spatialFilter.generateRectangle( extent );
224            } else {
225                spatialFilter = null;
226            }
227    
228        }
229    
230        /**
231         * Get the current Spatial Filter <br>
232         * returns null if it not yet set.
233         *
234         * @return null if it not yet set.
235         */
236        public SeShape getSpatialFilter() {
237            return spatialFilter;
238        }
239    
240        /**
241         * Get GM_Object[] containing the queried Geometries <br>
242         * returns null if no query has been done yet.
243         *
244         * @return null if no query has been done yet.
245         */
246        public Geometry[] getGeometries() {
247            return deegreeGeom;
248        }
249    
250        /**
251         * Runs a spatial query against the opened layer using the specified spatial filter. <br>
252         * throws SeException
253         *
254         * @param cols
255         * @return the resulting table
256         * @throws SeException
257         * @throws DeegreeSeException
258         */
259        public Table runSpatialQuery( String[] cols )
260                                throws SeException, DeegreeSeException {
261    
262            Table deegreeTable = null;
263            if ( spatialFilter != null ) {
264                SeShapeFilter[] filters = new SeShapeFilter[1];
265    
266                filters[0] = new SeShapeFilter( layer.getQualifiedName(), layer.getSpatialColumn(), spatialFilter,
267                                                SeFilter.METHOD_ENVP );
268    
269                SeColumnDefinition[] tableDef = table.describe();
270                if ( cols == null || cols.length == 0 ) {
271                    cols = new String[tableDef.length];
272                    for ( int i = 0; i < tableDef.length; i++ ) {
273                        cols[i] = tableDef[i].getName();
274                    }
275                }
276    
277                SeSqlConstruct sqlCons = new SeSqlConstruct( layer.getQualifiedName() );
278                SeQuery spatialQuery = new SeQuery( conn, cols, sqlCons );
279    
280                spatialQuery.prepareQuery();
281                spatialQuery.setSpatialConstraints( SeQuery.SE_OPTIMIZE, false, filters );
282                spatialQuery.execute();
283    
284                SeRow row = spatialQuery.fetch();
285    
286                int numRows = 0;
287                if ( row != null ) {
288                    int numCols = row.getNumColumns();
289                    // Fetch all the features that satisfied the query
290                    deegreeTable = initTable( row );
291    
292                    ArrayList<Geometry> list = new ArrayList<Geometry>( 20000 );
293                    Object[] tableObj = null;
294    
295                    while ( row != null ) {
296                        int colNum = 0;
297                        tableObj = new Object[deegreeTable.getColumnCount()];
298    
299                        for ( int i = 0; i < numCols; i++ ) {
300                            SeColumnDefinition colDef = row.getColumnDef( i );
301    
302                            if ( row.getIndicator( (short) i ) != SeRow.SE_IS_NULL_VALUE ) {
303                                switch ( colDef.getType() ) {
304                                case SeColumnDefinition.TYPE_INT16:
305                                    tableObj[colNum++] = row.getShort( i );
306                                    break;
307                                case SeColumnDefinition.TYPE_INT32:
308                                    tableObj[colNum++] = row.getInteger( i );
309                                    break;
310                                case SeColumnDefinition.TYPE_FLOAT32:
311                                    tableObj[colNum++] = row.getFloat( i );
312                                    break;
313                                case SeColumnDefinition.TYPE_FLOAT64:
314                                    tableObj[colNum++] = row.getDouble( i );
315                                    break;
316                                case SeColumnDefinition.TYPE_STRING:
317                                    tableObj[colNum++] = row.getString( i );
318                                    break;
319                                case SeColumnDefinition.TYPE_BLOB:
320                                    ByteArrayInputStream bis = (ByteArrayInputStream) row.getObject( i );
321                                    tableObj[colNum++] = bis;
322                                    break;
323                                case SeColumnDefinition.TYPE_DATE:
324                                    tableObj[colNum++] = row.getTime( i ).getTime();
325                                    break;
326                                case SeColumnDefinition.TYPE_RASTER:
327                                    LOG.logInfo( colDef.getName() + " : Cant handle this" );
328                                    break;
329                                case SeColumnDefinition.TYPE_SHAPE:
330                                    SeShape spVal = row.getShape( i );
331                                    createGeometry( spVal, list );
332                                    break;
333                                default:
334                                    LOG.logInfo( "Unknown Table DataType" );
335                                    break;
336                                } // End switch(type)
337                            } // End if
338                        } // End for
339    
340                        numRows++;
341    
342                        try {
343                            deegreeTable.appendRow( tableObj );
344                        } catch ( TableException tex ) {
345                            throw new DeegreeSeException( tex.toString() );
346                        }
347    
348                        row = spatialQuery.fetch();
349                    } // End while
350                    spatialQuery.close();
351    
352                    deegreeGeom = list.toArray( new Geometry[list.size()] );
353                } else {
354                    try {
355                        deegreeTable = new DefaultTable( layer.getQualifiedName(), new String[] { "NONE" },
356                                                         new int[] { Types.VARCHAR }, 2 );
357                    } catch ( Exception e ) {
358                        e.printStackTrace();
359                    }
360                    deegreeGeom = new Geometry[0];
361                }
362            } else {
363                try {
364                    deegreeTable = new DefaultTable( layer.getQualifiedName(), new String[] { "NONE" },
365                                                     new int[] { Types.VARCHAR }, 2 );
366                } catch ( Exception e ) {
367                    e.printStackTrace();
368                }
369                deegreeGeom = new Geometry[0];
370            }
371    
372            return deegreeTable;
373        } // End method runSpatialQuery
374    
375        /**
376         * Initialize Table object - used with first row of the SpatialQuery This method sets the
377         * TableName, TableColumnNames and their DataTypes <br>
378         * throws SeException
379         */
380        private Table initTable( SeRow row )
381                                throws SeException, DeegreeSeException {
382            ArrayList<String> colNames = new ArrayList<String>( 50 );
383            ArrayList<Integer> colTypes = new ArrayList<Integer>( 50 );
384            Table deegreeTable = null;
385            SeColumnDefinition colDef = null;
386    
387            for ( int i = 0; i < row.getNumColumns(); i++ ) {
388                try {
389                    colDef = row.getColumnDef( i );
390                } catch ( SeException sexp ) {
391                    sexp.printStackTrace();
392                    throw new DeegreeSeException( sexp.toString() );
393                }
394    
395                switch ( colDef.getType() ) {
396                case SeColumnDefinition.TYPE_INT16:
397                    colNames.add( colDef.getName().toUpperCase() );
398                    colTypes.add( new Integer( Types.SMALLINT ) );
399                    break;
400                case SeColumnDefinition.TYPE_INT32:
401                    colNames.add( colDef.getName().toUpperCase() );
402                    colTypes.add( new Integer( Types.INTEGER ) );
403                    break;
404                case SeColumnDefinition.TYPE_FLOAT32:
405                    colNames.add( colDef.getName().toUpperCase() );
406                    colTypes.add( new Integer( Types.FLOAT ) );
407                    break;
408                case SeColumnDefinition.TYPE_FLOAT64:
409                    colNames.add( colDef.getName().toUpperCase() );
410                    colTypes.add( new Integer( Types.DOUBLE ) );
411                    break;
412                case SeColumnDefinition.TYPE_STRING:
413                    colNames.add( colDef.getName().toUpperCase() );
414                    colTypes.add( new Integer( Types.VARCHAR ) );
415                    break;
416                case SeColumnDefinition.TYPE_BLOB:
417                    // there is an open issue with fetching blobs,
418                    // look at this document:
419                    // "ArcSDE 8.1 Java API - BLOB columns"
420                    // http://support.esri.com/Search/KbDocument.asp?dbid=17068
421                    colNames.add( colDef.getName().toUpperCase() );
422                    colTypes.add( new Integer( Types.ARRAY ) );
423                    break;
424                case SeColumnDefinition.TYPE_DATE:
425                    colNames.add( colDef.getName().toUpperCase() );
426                    colTypes.add( new Integer( Types.DATE ) );
427                    break;
428                default:
429                    break;
430                }
431            }
432    
433            String[] colN = new String[colNames.size()];
434            colN = colNames.toArray( colN );
435    
436            int[] colT = new int[colTypes.size()];
437            for ( int i = 0; i < colT.length; i++ ) {
438                colT[i] = colTypes.get( i ).intValue();
439            }
440    
441            try {
442                deegreeTable = new DefaultTable( layer.getQualifiedName(), colN, colT, 20000 );
443            } catch ( TableException tex ) {
444                tex.printStackTrace();
445                throw new DeegreeSeException( tex.toString() );
446            }
447            return deegreeTable;
448        } // End Method initTable
449    
450        /**
451         * CreateGeometry - used with every row of the SpatialQuery Depending on the layers' geometries
452         * datatype different operations are made to create the appropriate object. <br>
453         * Available ArcSDE ShapeTypes: <br>
454         * TYPE_POINT (impl) <br>
455         * TYPE_MULTI_POINT (impl) <br>
456         * TYPE_SIMPLE_LINE (impl) <br>
457         * TYPE_MULTI_SIMPLE_LINE (impl) <br>
458         * TYPE_LINE (impl) <br>
459         * TYPE_MULTI_LINE (impl) <br>
460         * TYPE_POLYGON (impl) <br>
461         * TYPE_MULTI_POLYGON (impl) <br>
462         * TYPE_NIL (impl)
463         *
464         * <br>
465         * throws SeException
466         */
467        private void createGeometry( SeShape shape, ArrayList<Geometry> list )
468                                throws SeException, DeegreeSeException {
469    
470            int shptype = shape.getType();
471    
472            ArrayList<?> al = shape.getAllPoints( SeShape.TURN_DEFAULT, true );
473            // Retrieve the array of SDEPoints
474            SDEPoint[] points = (SDEPoint[]) al.get( 0 );
475            // Retrieve the part offsets array.
476            int[] partOffset = (int[]) al.get( 1 );
477            // Retrieve the sub-part offsets array.
478            int[] subPartOffset = (int[]) al.get( 2 );
479    
480            int numPoints = shape.getNumOfPoints();
481    
482            int numParts = shape.getNumParts();
483    
484            switch ( shptype ) {
485            // a single point
486            case SeShape.TYPE_NIL:
487                Point gmPoint = GeometryFactory.createPoint( -9E9, -9E9, null );
488                list.add( gmPoint );
489                LOG.logInfo( "Found SeShape.TYPE_NIL." );
490                LOG.logInfo( "The queried layer does not have valid geometries" );
491                break;
492            // a single point
493            case SeShape.TYPE_POINT:
494                gmPoint = GeometryFactory.createPoint( points[0].getX(), points[0].getY(), null );
495                list.add( gmPoint );
496                break;
497            // an array of points
498            case SeShape.TYPE_MULTI_POINT:
499                Point[] gmPoints = new Point[numPoints];
500    
501                for ( int pt = 0; pt < numPoints; pt++ ) {
502                    gmPoints[pt] = GeometryFactory.createPoint( points[pt].getX(), points[pt].getY(), null );
503                }
504    
505                try {
506                    MultiPoint gmMultiPoint = GeometryFactory.createMultiPoint( gmPoints );
507                    list.add( gmMultiPoint );
508                } catch ( Exception gme ) {
509                    gme.printStackTrace();
510                    throw new DeegreeSeException( gme.toString() );
511                }
512    
513                break;
514            // a single line, simple as it does not intersect itself
515            case SeShape.TYPE_SIMPLE_LINE:
516                // or a single, non-simple line
517            case SeShape.TYPE_LINE:
518    
519                Position[] gmSimpleLinePosition = new Position[numPoints];
520    
521                for ( int pt = 0; pt < numPoints; pt++ ) {
522                    gmSimpleLinePosition[pt] = GeometryFactory.createPosition( points[pt].getX(), points[pt].getY() );
523                }
524    
525                try {
526                    Curve gmCurve = GeometryFactory.createCurve( gmSimpleLinePosition, null );
527                    list.add( gmCurve );
528                } catch ( Exception gme ) {
529                    gme.printStackTrace();
530                    throw new DeegreeSeException( gme.toString() );
531                }
532    
533                break;
534            // an array of lines, simple as they do not intersect with themself
535            case SeShape.TYPE_MULTI_SIMPLE_LINE:
536                // or an array of non-simple lines
537            case SeShape.TYPE_MULTI_LINE:
538    
539                Curve[] gmCurves = new Curve[numParts];
540    
541                for ( int partNo = 0; partNo < numParts; partNo++ ) {
542                    int lastPoint = shape.getNumPoints( partNo + 1, 1 ) + partOffset[partNo];
543                    Position[] gmMultiSimpleLinePosition = new Position[shape.getNumPoints( partNo + 1, 1 )];
544                    int i = 0;
545    
546                    for ( int pt = partOffset[partNo]; pt < lastPoint; pt++ ) {
547                        gmMultiSimpleLinePosition[i] = GeometryFactory.createPosition( points[pt].getX(), points[pt].getY() );
548                        i++;
549                    }
550    
551                    try {
552                        gmCurves[partNo] = GeometryFactory.createCurve( gmMultiSimpleLinePosition, null );
553                    } catch ( Exception gme ) {
554                        gme.printStackTrace();
555                        throw new DeegreeSeException( gme.toString() );
556                    }
557                }
558    
559                try {
560                    MultiCurve gmMultiCurve = GeometryFactory.createMultiCurve( gmCurves );
561                    list.add( gmMultiCurve );
562                } catch ( Exception gme ) {
563                    gme.printStackTrace();
564                    throw new DeegreeSeException( gme.toString() );
565                }
566    
567                break;
568            // a single polygon which might contain islands
569            case SeShape.TYPE_POLYGON:
570    
571                int numSubParts = shape.getNumSubParts( 1 );
572                Position[] gmPolygonExteriorRing = new Position[shape.getNumPoints( 1, 1 )];
573    
574                int kk = shape.getNumPoints( 1, 1 );
575                for ( int pt = 0; pt < kk; pt++ ) {
576                    gmPolygonExteriorRing[pt] = GeometryFactory.createPosition( points[pt].getX(), points[pt].getY() );
577                }
578    
579                Position[][] gmPolygonInteriorRings = null;
580    
581                // if it is a donut create inner rings
582                if ( numSubParts > 1 ) {
583                    gmPolygonInteriorRings = new Position[numSubParts - 1][];
584    
585                    int j = 0;
586    
587                    for ( int subPartNo = 1; subPartNo < numSubParts; subPartNo++ ) {
588                        int lastPoint = shape.getNumPoints( 1, subPartNo + 1 ) + subPartOffset[subPartNo];
589                        Position[] gmPolygonPosition = new Position[shape.getNumPoints( 1, subPartNo + 1 )];
590                        int i = 0;
591    
592                        for ( int pt = subPartOffset[subPartNo]; pt < lastPoint; pt++ ) {
593                            gmPolygonPosition[i] = GeometryFactory.createPosition( points[pt].getX(), points[pt].getY() );
594                            i++;
595                        }
596    
597                        gmPolygonInteriorRings[j] = gmPolygonPosition;
598                        j++;
599                    }
600                }
601    
602                try {
603                    Surface gmSurface = GeometryFactory.createSurface( gmPolygonExteriorRing, gmPolygonInteriorRings,
604                                                                       new SurfaceInterpolationImpl(), null );
605                    list.add( gmSurface );
606                } catch ( Exception gme ) {
607                    gme.printStackTrace();
608                    throw new DeegreeSeException( gme.toString() );
609                }
610    
611                break;
612            // an array of polygons which might contain islands
613            case SeShape.TYPE_MULTI_POLYGON:
614    
615                Surface[] gmMultiPolygonSurface = getMultiPolygon( shape, points, partOffset, subPartOffset );
616    
617                try {
618                    MultiSurface gmMultiSurface = GeometryFactory.createMultiSurface( gmMultiPolygonSurface );
619                    list.add( gmMultiSurface );
620                } catch ( Exception gme ) {
621                    gme.printStackTrace();
622                    throw new DeegreeSeException( gme.toString() );
623                }
624    
625                break;
626            default:
627                LOG.logInfo( "Unknown GeometryType - ID: " + shape.getType() );
628                break;
629            } // End of switch
630        } // End Method createGeometry
631    
632        /**
633         * @param shape
634         * @param points
635         * @param partOffset
636         * @param subPartOffset
637         * @throws SeException
638         */
639        private Surface[] getMultiPolygon( SeShape shape, SDEPoint[] points, int[] partOffset, int[] subPartOffset )
640                                throws SeException, DeegreeSeException {
641            Surface[] surfaces = new Surface[partOffset.length];
642            int hh = 0;
643            for ( int i = 0; i < partOffset.length; i++ ) {
644                // cnt = number of all rings of the current polygon (part)
645                int cnt = shape.getNumSubParts( i + 1 );
646    
647                // exterior ring
648                int count = shape.getNumPoints( i + 1, 1 );
649                Position[] ex = new Position[count];
650                int off = subPartOffset[hh];
651                for ( int j = 0; j < count; j++ ) {
652                    ex[j] = GeometryFactory.createPosition( points[j + off].getX(), points[j + off].getY() );
653                }
654    
655                // interior ring
656                Position[][] inn = null;
657                if ( cnt > 1 ) {
658                    inn = new Position[cnt - 1][];
659                }
660                hh++;
661                for ( int j = 1; j < cnt; j++ ) {
662                    inn[j - 1] = new Position[shape.getNumPoints( i + 1, j + 1 )];
663                    off = subPartOffset[hh];
664                    for ( int k = 0; k < inn[j - 1].length; k++ ) {
665                        inn[j - 1][k] = GeometryFactory.createPosition( points[j + off - 1].getX(),
666                                                                        points[j + off - 1].getY() );
667                    }
668                    hh++;
669                }
670    
671                try {
672                    SurfaceInterpolation si = new SurfaceInterpolationImpl();
673                    surfaces[i] = GeometryFactory.createSurface( ex, inn, si, null );
674                } catch ( Exception e ) {
675                    throw new DeegreeSeException( StringTools.stackTraceToString( e ) );
676                }
677            }
678    
679            return surfaces;
680        }
681    
682    } // End Class SpatialQueryEx