~ubuntu-branches/debian/sid/gdal/sid

« back to all changes in this revision

Viewing changes to frmts/postgisraster/postgisrasterrasterband.cpp

  • Committer: Package Import Robot
  • Author(s): Francesco Paolo Lovergine
  • Date: 2012-05-07 15:04:42 UTC
  • mfrom: (5.5.16 experimental)
  • Revision ID: package-import@ubuntu.com-20120507150442-2eks97loeh6rq005
Tags: 1.9.0-1
* Ready for sid, starting transition.
* All symfiles updated to latest builds.
* Added dh_numpy call in debian/rules to depend on numpy ABI.
* Policy bumped to 3.9.3, no changes required.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/******************************************************************************
 
2
 * File :    PostGISRasterRasterBand.cpp
 
3
 * Project:  PostGIS Raster driver
 
4
 * Purpose:  GDAL Raster Band implementation for PostGIS Raster driver 
 
5
 * Author:   Jorge Arevalo, jorgearevalo@deimos-space.com
 
6
 * 
 
7
 * Last changes:
 
8
 * $Id: $
 
9
 *
 
10
 ******************************************************************************
 
11
 * Copyright (c) 2009 - 2011, Jorge Arevalo, jorge.arevalo@deimos-space.com
 
12
 *
 
13
 * Permission is hereby granted, free of charge, to any person obtaining a
 
14
 * copy of this software and associated documentation files (the "Software"),
 
15
 * to deal in the Software without restriction, including without limitation
 
16
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 
17
 * and/or sell copies of the Software, and to permit persons to whom the
 
18
 * Software is furnished to do so, subject to the following conditions:
 
19
 *
 
20
 * The above copyright notice and this permission notice shall be included
 
21
 * in all copies or substantial portions of the Software.
 
22
 *
 
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 
24
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 
26
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 
28
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 
29
 * DEALINGS IN THE SOFTWARE.
 
30
 ******************************************************************************/
 
31
#include "postgisraster.h"
 
32
#include "ogr_api.h"
 
33
#include "ogr_geometry.h"
 
34
#include "gdal_priv.h"
 
35
#include "gdal.h"
 
36
#include <string>
 
37
#include "cpl_string.h"
 
38
 
 
39
 
 
40
/**
 
41
 * \brief Constructor.
 
42
 * Parameters:
 
43
 *  - PostGISRasterDataset *: The Dataset this band belongs to
 
44
 *  - int: the band number
 
45
 *  - GDALDataType: The data type for this band
 
46
 *  - double: The nodata value.  Could be any kind of data (GByte, GUInt16,
 
47
 *          GInt32...) but the variable has the bigger type.
 
48
 *  - GBool: if the data type is signed byte or not. If yes, the SIGNEDBYTE
 
49
 *          metadata value is added to IMAGE_STRUCTURE domain
 
50
 *  - int: The bit depth, to add NBITS as metadata value in IMAGE_STRUCTURE
 
51
 *          domain.
 
52
 */
 
53
PostGISRasterRasterBand::PostGISRasterRasterBand(PostGISRasterDataset *poDS,
 
54
        int nBand, GDALDataType hDataType, double dfNodata, GBool bSignedByte,
 
55
        int nBitDepth, int nFactor, GBool bIsOffline, char * pszSchema, 
 
56
        char * pszTable, char * pszColumn)
 
57
{
 
58
 
 
59
    /* Basic properties */
 
60
    this->poDS = poDS;
 
61
    this->nBand = nBand;
 
62
    this->bIsOffline = bIsOffline;
 
63
    this->pszSchema = (pszSchema) ? pszSchema : CPLStrdup(poDS->pszSchema);    
 
64
    this->pszTable = (pszTable) ? pszTable: CPLStrdup(poDS->pszTable);
 
65
    this->pszColumn = (pszColumn) ? pszColumn : CPLStrdup(poDS->pszColumn);
 
66
    this->pszWhere = CPLStrdup(poDS->pszWhere);
 
67
 
 
68
    eAccess = poDS->GetAccess();
 
69
    eDataType = hDataType;
 
70
    dfNoDataValue = dfNodata;
 
71
 
 
72
 
 
73
    /*****************************************************
 
74
     * Check block size issue
 
75
     *****************************************************/
 
76
    nBlockXSize = poDS->nBlockXSize;
 
77
    nBlockYSize = poDS->nBlockYSize;
 
78
 
 
79
    if (nBlockXSize == 0 || nBlockYSize == 0) {
 
80
        CPLError(CE_Warning, CPLE_NotSupported,
 
81
                "This band has irregular blocking, but is not supported yet");
 
82
    }
 
83
 
 
84
        
 
85
    // Add pixeltype to image structure domain
 
86
    if (bSignedByte == true) {
 
87
        SetMetadataItem("PIXELTYPE", "SIGNEDBYTE", "IMAGE_STRUCTURE" );
 
88
    }
 
89
 
 
90
    // Add NBITS to metadata only for sub-byte types
 
91
    if (nBitDepth < 8)
 
92
        SetMetadataItem("NBITS", CPLString().Printf( "%d", nBitDepth ),
 
93
            "IMAGE_STRUCTURE" );
 
94
 
 
95
 
 
96
    nOverviewFactor = nFactor;
 
97
 
 
98
    /**********************************************************
 
99
     * Check overviews. Query RASTER_OVERVIEWS table for
 
100
     * existing overviews, only in case we are on level 0
 
101
     * TODO: can we do this without querying RASTER_OVERVIEWS?
 
102
     * How do we know the number of overviews? Is an inphinite
 
103
     * loop...
 
104
     **********************************************************/
 
105
    if (nOverviewFactor == 0) {    
 
106
 
 
107
        CPLString osCommand;
 
108
        PGresult * poResult = NULL;
 
109
        int i = 0;
 
110
        int nFetchOvFactor = 0;
 
111
        char * pszOvSchema = NULL;
 
112
        char * pszOvTable = NULL;
 
113
        char * pszOvColumn = NULL;
 
114
 
 
115
        nRasterXSize = poDS->GetRasterXSize();
 
116
        nRasterYSize = poDS->GetRasterYSize();
 
117
 
 
118
        osCommand.Printf("select o_table_name, overview_factor, o_column, "
 
119
                "o_table_schema from raster_overviews where r_table_schema = "
 
120
                "'%s' and r_table_name = '%s' and r_column = '%s'",
 
121
                poDS->pszSchema, poDS->pszTable, poDS->pszColumn);
 
122
 
 
123
        poResult = PQexec(poDS->poConn, osCommand.c_str());
 
124
        if (poResult != NULL && PQresultStatus(poResult) == PGRES_TUPLES_OK &&
 
125
                PQntuples(poResult) > 0) {
 
126
            
 
127
            /* Create overviews */
 
128
            nOverviewCount = PQntuples(poResult);           
 
129
            papoOverviews = (PostGISRasterRasterBand **)VSICalloc(nOverviewCount,
 
130
                    sizeof(PostGISRasterRasterBand *));
 
131
            if (papoOverviews == NULL) {
 
132
                CPLError(CE_Warning, CPLE_OutOfMemory, "Couldn't create "
 
133
                        "overviews for band %d\n", nBand);              
 
134
                PQclear(poResult);
 
135
                return;
 
136
            }
 
137
                       
 
138
            for(i = 0; i < nOverviewCount; i++) {
 
139
 
 
140
                nFetchOvFactor = atoi(PQgetvalue(poResult, i, 1));
 
141
                pszOvSchema = CPLStrdup(PQgetvalue(poResult, i, 3));
 
142
                pszOvTable = CPLStrdup(PQgetvalue(poResult, i, 0));
 
143
                pszOvColumn = CPLStrdup(PQgetvalue(poResult, i, 2));
 
144
 
 
145
                /**
 
146
                 * NOTE: Overview bands are not considered to be a part of a
 
147
                 * dataset, but we use the same dataset for all the overview
 
148
                 * bands just for simplification (we'll need to access the table
 
149
                 * and schema names). But in method GetDataset, NULL is return
 
150
                 * if we're talking about an overview band
 
151
                 */
 
152
                papoOverviews[i] = new PostGISRasterRasterBand(poDS, nBand,
 
153
                        hDataType, dfNodata, bSignedByte, nBitDepth,
 
154
                        nFetchOvFactor, bIsOffline, pszOvSchema, pszOvTable, pszOvColumn);
 
155
 
 
156
            }
 
157
 
 
158
            PQclear(poResult);
 
159
 
 
160
        }
 
161
 
 
162
        else {
 
163
 
 
164
            nOverviewCount = 0;
 
165
            papoOverviews = NULL;
 
166
            if (poResult)
 
167
                PQclear(poResult);
 
168
        }
 
169
    }
 
170
 
 
171
    /************************************
 
172
     * We are in an overview level. Set
 
173
     * raster size to its value 
 
174
     ************************************/
 
175
    else {
 
176
 
 
177
        /* 
 
178
         * No overviews inside an overview (all the overviews are from original
 
179
         * band
 
180
         */
 
181
        nOverviewCount = 0;
 
182
        papoOverviews = NULL;
 
183
 
 
184
        nRasterXSize = (int) floor((double)poDS->GetRasterXSize() / nOverviewFactor);
 
185
        nRasterYSize = (int) floor((double)poDS->GetRasterYSize() / nOverviewFactor);        
 
186
    }
 
187
 
 
188
    CPLDebug("PostGIS_Raster", "PostGISRasterRasterBand constructor: Band "
 
189
            "created (srid = %d)", poDS->nSrid);
 
190
}
 
191
 
 
192
/***********************************************
 
193
 * \brief: Band destructor
 
194
 ***********************************************/
 
195
PostGISRasterRasterBand::~PostGISRasterRasterBand()
 
196
{
 
197
    int i;
 
198
 
 
199
    if (pszSchema)
 
200
        CPLFree(pszSchema);
 
201
    if (pszTable)
 
202
        CPLFree(pszTable);
 
203
    if (pszColumn)
 
204
        CPLFree(pszColumn);
 
205
    if (pszWhere)
 
206
        CPLFree(pszWhere);
 
207
 
 
208
    if (papoOverviews) {
 
209
        for(i = 0; i < nOverviewCount; i++)
 
210
            delete papoOverviews[i];
 
211
 
 
212
        CPLFree(papoOverviews);
 
213
    }
 
214
}
 
215
 
 
216
 
 
217
/**
 
218
 * \brief Set the block data to the null value if it is set, or zero if there is
 
219
 * no null data value.
 
220
 * Parameters:
 
221
 *  - void *: the block data
 
222
 * Returns: nothing
 
223
 */
 
224
void PostGISRasterRasterBand::NullBlock(void *pData) 
 
225
{
 
226
    VALIDATE_POINTER0(pData, "NullBlock");
 
227
 
 
228
    int nNaturalBlockXSize = 0;
 
229
    int nNaturalBlockYSize = 0;
 
230
    GetBlockSize(&nNaturalBlockXSize, &nNaturalBlockYSize);
 
231
 
 
232
    int nWords = nNaturalBlockXSize * nNaturalBlockYSize;
 
233
    int nChunkSize = MAX(1, GDALGetDataTypeSize(eDataType) / 8);
 
234
 
 
235
    int bNoDataSet;
 
236
    double dfNoData = GetNoDataValue(&bNoDataSet);
 
237
    if (!bNoDataSet) 
 
238
    {
 
239
        memset(pData, 0, nWords * nChunkSize);
 
240
    } 
 
241
    else 
 
242
    {
 
243
        int i = 0;
 
244
        for (i = 0; i < nWords; i += nChunkSize)
 
245
            memcpy((GByte *) pData + i, &dfNoData, nChunkSize);
 
246
    }
 
247
}
 
248
 
 
249
/**
 
250
 * \brief Set the no data value for this band.
 
251
 * Parameters:
 
252
 *  - double: The nodata value
 
253
 * Returns:
 
254
 *  - CE_None.
 
255
 */
 
256
CPLErr PostGISRasterRasterBand::SetNoDataValue(double dfNewValue) {
 
257
    dfNoDataValue = dfNewValue;
 
258
 
 
259
    return CE_None;
 
260
}
 
261
 
 
262
/**
 
263
 * \brief Fetch the no data value for this band.
 
264
 * Parameters:
 
265
 *  - int *: pointer to a boolean to use to indicate if a value is actually
 
266
 *          associated with this layer. May be NULL (default).
 
267
 *  Returns:
 
268
 *  - double: the nodata value for this band.
 
269
 */
 
270
double PostGISRasterRasterBand::GetNoDataValue(int *pbSuccess) {
 
271
    if (pbSuccess != NULL)
 
272
        *pbSuccess = TRUE;
 
273
 
 
274
    return dfNoDataValue;
 
275
}
 
276
 
 
277
 
 
278
/**
 
279
 * \brief Get the natural block size for this band.
 
280
 * Parameters:
 
281
 *  - int *: pointer to int to store the natural X block size
 
282
 *  - int *: pointer to int to store the natural Y block size
 
283
 * Returns: nothing
 
284
 */
 
285
void PostGISRasterRasterBand::GetBlockSize(int * pnXSize, int *pnYSize)
 
286
 {
 
287
    if (nBlockXSize == 0 || nBlockYSize == 0) {
 
288
        CPLError(CE_Failure, CPLE_AppDefined,
 
289
                "This PostGIS Raster band has non regular blocking arrangement. \
 
290
                This feature is under development");
 
291
 
 
292
        if (pnXSize != NULL)
 
293
            *pnXSize = 0;
 
294
        if (pnYSize != NULL)
 
295
            *pnYSize = 0;
 
296
 
 
297
    }
 
298
    else {
 
299
        GDALRasterBand::GetBlockSize(pnXSize, pnYSize);
 
300
    }
 
301
}
 
302
 
 
303
 
 
304
/*****************************************************
 
305
 * \brief Fetch the band number
 
306
 *****************************************************/
 
307
int PostGISRasterRasterBand::GetBand()
 
308
{
 
309
    return (nOverviewFactor) ? 0 : nBand;
 
310
}
 
311
 
 
312
/*****************************************************
 
313
 * \brief Fetch the owning dataset handle
 
314
 *****************************************************/
 
315
GDALDataset* PostGISRasterRasterBand::GetDataset()
 
316
{
 
317
    return (nOverviewFactor) ? NULL : poDS;
 
318
}
 
319
 
 
320
/****************************************************
 
321
 * \brief Check for arbitrary overviews
 
322
 * The datastore can compute arbitrary overviews 
 
323
 * efficiently, because the overviews are tables, 
 
324
 * like the original raster. The effort is the same.
 
325
 ****************************************************/
 
326
int PostGISRasterRasterBand::HasArbitraryOverviews()
 
327
{
 
328
    return (nOverviewFactor) ? false : true;
 
329
}
 
330
 
 
331
/***************************************************
 
332
 * \brief Return the number of overview layers available
 
333
 ***************************************************/
 
334
int PostGISRasterRasterBand::GetOverviewCount()
 
335
{
 
336
    return (nOverviewFactor) ?
 
337
        0 :
 
338
        nOverviewCount;
 
339
}
 
340
 
 
341
/**********************************************************
 
342
 * \brief Fetch overview raster band object
 
343
 **********************************************************/
 
344
GDALRasterBand * PostGISRasterRasterBand::GetOverview(int i)
 
345
{
 
346
    return (i >= 0 && i < GetOverviewCount()) ? 
 
347
        (GDALRasterBand *)papoOverviews[i] : GDALRasterBand::GetOverview(i);
 
348
}
 
349
 
 
350
/*****************************************************
 
351
 * \brief Read a natural block of raster band data
 
352
 *****************************************************/
 
353
CPLErr PostGISRasterRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void*
 
354
        pImage)
 
355
{
 
356
    PostGISRasterDataset * poPostGISRasterDS = (PostGISRasterDataset*)poDS;
 
357
    CPLString osCommand;
 
358
    PGresult* poResult = NULL;
 
359
    double adfTransform[6];
 
360
    int nTuples = 0;
 
361
    int nNaturalBlockXSize = 0;
 
362
    int nNaturalBlockYSize = 0;
 
363
    int nPixelSize = 0;
 
364
    int nPixelXInitPosition;
 
365
    int nPixelYInitPosition;
 
366
    int nPixelXEndPosition;
 
367
    int nPixelYEndPosition;
 
368
    double dfProjXInit = 0.0;
 
369
    double dfProjYInit = 0.0;
 
370
    double dfProjXEnd = 0.0;
 
371
    double dfProjYEnd = 0.0;
 
372
    double dfProjLowerLeftX = 0.0;
 
373
    double dfProjLowerLeftY = 0.0;
 
374
    double dfProjUpperRightX = 0.0;
 
375
    double dfProjUpperRightY = 0.0;
 
376
    GByte* pbyData = NULL;
 
377
    char* pbyDataToRead = NULL;
 
378
    int nWKBLength = 0;
 
379
    int nExpectedDataSize = 0;
 
380
 
 
381
    /* Get pixel and block size */
 
382
    nPixelSize = MAX(1,GDALGetDataTypeSize(eDataType)/8);
 
383
    GetBlockSize(&nNaturalBlockXSize, &nNaturalBlockYSize);
 
384
    
 
385
    /* Get pixel,line coordinates of the block */
 
386
    /**
 
387
     * TODO: What if the georaster is rotated? Following the gdal_translate
 
388
     * source code, you can't use -projwin option with rotated rasters
 
389
     **/
 
390
    nPixelXInitPosition = nNaturalBlockXSize * nBlockXOff;
 
391
    nPixelYInitPosition = nNaturalBlockYSize * nBlockYOff;
 
392
    nPixelXEndPosition = nPixelXInitPosition + nNaturalBlockXSize;
 
393
    nPixelYEndPosition = nPixelYInitPosition + nNaturalBlockYSize;
 
394
 
 
395
    poPostGISRasterDS->GetGeoTransform(adfTransform);
 
396
 
 
397
    /* Pixel size correction, in case of overviews */
 
398
    if (nOverviewFactor) {
 
399
        adfTransform[1] *= nOverviewFactor;
 
400
        adfTransform[5] *= nOverviewFactor;
 
401
    }
 
402
 
 
403
    /* Calculate the "query box" */
 
404
    dfProjXInit = adfTransform[0] +
 
405
            nPixelXInitPosition * adfTransform[1] +
 
406
            nPixelYInitPosition * adfTransform[2];
 
407
 
 
408
    dfProjYInit = adfTransform[3] +
 
409
            nPixelXInitPosition * adfTransform[4] +
 
410
            nPixelYInitPosition * adfTransform[5];
 
411
 
 
412
    dfProjXEnd = adfTransform[0] +
 
413
            nPixelXEndPosition * adfTransform[1] +
 
414
            nPixelYEndPosition * adfTransform[2];
 
415
 
 
416
    dfProjYEnd = adfTransform[3] +
 
417
            nPixelXEndPosition * adfTransform[4] +
 
418
            nPixelYEndPosition * adfTransform[5];
 
419
 
 
420
     /*************************************************************************
 
421
     * Now we have the block coordinates transformed into coordinates of the
 
422
     * raster reference system. This coordinates are from:
 
423
     *  - Upper left corner
 
424
     *  - Lower right corner
 
425
     * But for ST_MakeBox2D, we'll need block's coordinates of:
 
426
     *  - Lower left corner
 
427
     *  - Upper right corner
 
428
     *************************************************************************/
 
429
    dfProjLowerLeftX = dfProjXInit;
 
430
    dfProjLowerLeftY = dfProjYEnd;
 
431
 
 
432
    dfProjUpperRightX = dfProjXEnd;
 
433
    dfProjUpperRightY = dfProjYInit;
 
434
 
 
435
    /**
 
436
     * Return all raster objects from our raster table that fall reside or
 
437
     * partly reside in a coordinate bounding box.
 
438
     * NOTE: Is it necessary to use a BB optimization like:
 
439
     * select st_makebox2d(...) && geom and (rest of the query)...?
 
440
     **/
 
441
    if (poPostGISRasterDS->pszWhere != NULL)
 
442
    {
 
443
        osCommand.Printf("select rid, %s from %s.%s where %s ~ "
 
444
                "st_setsrid(st_makebox2d(st_point(%f, %f), st_point(%f,"
 
445
                "%f)),%d) and %s", pszColumn, pszSchema, pszTable, pszColumn, 
 
446
                dfProjLowerLeftX, dfProjLowerLeftY, dfProjUpperRightX,
 
447
                dfProjUpperRightY, poPostGISRasterDS->nSrid, pszWhere);
 
448
    }
 
449
 
 
450
    else
 
451
    {
 
452
        osCommand.Printf("select rid, %s from %s.%s where %s ~ "
 
453
                "st_setsrid(st_makebox2d(st_point(%f, %f), st_point(%f,"
 
454
                "%f)),%d)", pszColumn, pszSchema, pszTable, pszColumn, 
 
455
                dfProjLowerLeftX, dfProjLowerLeftY, dfProjUpperRightX, 
 
456
                dfProjUpperRightY, poPostGISRasterDS->nSrid);
 
457
    }
 
458
 
 
459
    CPLDebug("PostGIS_Raster", "PostGISRasterRasterBand::IReadBlock: "
 
460
            "The query = %s", osCommand.c_str());
 
461
 
 
462
    poResult = PQexec(poPostGISRasterDS->poConn, osCommand.c_str());
 
463
    if (poResult == NULL || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
 
464
            PQntuples(poResult) <= 0)
 
465
    {
 
466
        if (poResult)
 
467
            PQclear(poResult);
 
468
 
 
469
        /* TODO: Raise an error and exit? */
 
470
        CPLDebug("PostGIS_Raster", "PostGISRasterRasterBand::IReadBlock: "
 
471
                "The block (%d, %d) is empty", nBlockXOff, nBlockYOff);
 
472
        NullBlock(pImage);
 
473
        return CE_None;
 
474
    }
 
475
 
 
476
    nTuples = PQntuples(poResult);
 
477
 
 
478
    /* No overlapping */
 
479
    if (nTuples == 1)
 
480
    {        
 
481
        /**
 
482
         * Out db band.
 
483
         * TODO: Manage this situation
 
484
         **/
 
485
        if (bIsOffline)
 
486
        {
 
487
            CPLError(CE_Failure, CPLE_AppDefined, "This raster has outdb storage."
 
488
                    "This feature isn\'t still available");
 
489
            return CE_Failure;
 
490
        }
 
491
 
 
492
        int nRid = atoi(PQgetvalue(poResult, 0, 0));
 
493
        
 
494
        /* Only data size, without payload */
 
495
        nExpectedDataSize = nNaturalBlockXSize * nNaturalBlockYSize *
 
496
            nPixelSize;
 
497
        pbyData = CPLHexToBinary(PQgetvalue(poResult, 0, 1), &nWKBLength);
 
498
        pbyDataToRead = (char*)GET_BAND_DATA(pbyData,nBand, nPixelSize,
 
499
                nExpectedDataSize);
 
500
 
 
501
        memcpy(pImage, pbyDataToRead, nExpectedDataSize * sizeof(char));
 
502
        
 
503
        CPLDebug("PostGIS_Raster", "IReadBlock: Copied %d bytes from block "
 
504
                "(%d, %d) (rid = %d) to %p", nExpectedDataSize, nBlockXOff, 
 
505
                nBlockYOff, nRid, pImage);
 
506
 
 
507
        CPLFree(pbyData);
 
508
        PQclear(poResult);
 
509
 
 
510
        return CE_None;
 
511
    }
 
512
 
 
513
    /** Overlapping raster data.
 
514
     * TODO: Manage this situation. Suggestion: open several datasets, because
 
515
     * you can't manage overlapping raster data with only one dataset
 
516
     **/
 
517
    else
 
518
    {
 
519
        CPLError(CE_Failure, CPLE_AppDefined,
 
520
                "Overlapping raster data. Feature under development, not "
 
521
                "available yet");
 
522
        if (poResult)
 
523
            PQclear(poResult);
 
524
 
 
525
        return CE_Failure;
 
526
 
 
527
    }
 
528
}
 
529
 
 
530