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