1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Raster to Polygon Converter
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2008, Frank Warmerdam
9 : *
10 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "gdal_alg_priv.h"
30 : #include "cpl_conv.h"
31 : #include <vector>
32 :
33 : CPL_CVSID("$Id: gdalsievefilter.cpp 16327 2009-02-14 11:18:38Z rouault $");
34 :
35 : #define GP_NODATA_MARKER -51502112
36 :
37 : /*
38 : * General Plan
39 : *
40 : * 1) make a pass with the polygon enumerator to build up the
41 : * polygon map array. Also accumulate polygon size information.
42 : *
43 : * 2) Identify the polygons that need to be merged.
44 : *
45 : * 3) Make a pass with the polygon enumerator. For each "to be merged"
46 : * polygon keep track of it's largest neighbour.
47 : *
48 : * 4) Fix up remappings that would go to polygons smaller than the seive
49 : * size. Ensure these in term map to the largest neighbour of the
50 : * "to be seieved" polygons.
51 : *
52 : * 5) Make another pass with the polygon enumerator. This time we remap
53 : * the actual pixel values of all polygons to be merged.
54 : *
55 : */
56 :
57 : /************************************************************************/
58 : /* GPMaskImageData() */
59 : /* */
60 : /* Mask out image pixels to a special nodata value if the mask */
61 : /* band is zero. */
62 : /************************************************************************/
63 :
64 : static CPLErr
65 21 : GPMaskImageData( GDALRasterBandH hMaskBand, GByte *pabyMaskLine, int iY, int nXSize,
66 : GInt32 *panImageLine )
67 :
68 : {
69 : CPLErr eErr;
70 :
71 : eErr = GDALRasterIO( hMaskBand, GF_Read, 0, iY, nXSize, 1,
72 21 : pabyMaskLine, nXSize, 1, GDT_Byte, 0, 0 );
73 21 : if( eErr == CE_None )
74 : {
75 : int i;
76 126 : for( i = 0; i < nXSize; i++ )
77 : {
78 105 : if( pabyMaskLine[i] == 0 )
79 0 : panImageLine[i] = GP_NODATA_MARKER;
80 : }
81 : }
82 :
83 21 : return eErr;
84 : }
85 :
86 : /************************************************************************/
87 : /* CompareNeighbour() */
88 : /* */
89 : /* Compare two neighbouring polygons, and update eaches */
90 : /* "biggest neighbour" if the other is larger than it's current */
91 : /* largest neighbour. */
92 : /* */
93 : /* Note that this should end up with each polygon knowing the */
94 : /* id of it's largest neighbour. No attempt is made to */
95 : /* restrict things to small polygons that we will be merging, */
96 : /* nor to exclude assigning "biggest neighbours" that are still */
97 : /* smaller than our sieve threshold. */
98 : /************************************************************************/
99 :
100 470 : static inline void CompareNeighbour( int nPolyId1, int nPolyId2,
101 : int *panPolyIdMap,
102 : int *panPolyValue,
103 : std::vector<int> &anPolySizes,
104 : std::vector<int> &anBigNeighbour )
105 :
106 : {
107 : // make sure we are working with the final merged polygon ids.
108 470 : nPolyId1 = panPolyIdMap[nPolyId1];
109 470 : nPolyId2 = panPolyIdMap[nPolyId2];
110 :
111 470 : if( nPolyId1 == nPolyId2 )
112 185 : return;
113 :
114 : // nodata polygon do not need neighbours, and cannot be neighbours
115 : // to valid polygons.
116 570 : if( panPolyValue[nPolyId1] == GP_NODATA_MARKER
117 285 : || panPolyValue[nPolyId2] == GP_NODATA_MARKER )
118 0 : return;
119 :
120 285 : if( anBigNeighbour[nPolyId1] == -1
121 : || anPolySizes[anBigNeighbour[nPolyId1]] < anPolySizes[nPolyId2] )
122 104 : anBigNeighbour[nPolyId1] = nPolyId2;
123 :
124 285 : if( anBigNeighbour[nPolyId2] == -1
125 : || anPolySizes[anBigNeighbour[nPolyId2]] < anPolySizes[nPolyId1] )
126 42 : anBigNeighbour[nPolyId2] = nPolyId1;
127 : }
128 :
129 : /************************************************************************/
130 : /* GDALSieveFilter() */
131 : /************************************************************************/
132 :
133 : /**
134 : * Removes small raster polygons.
135 : *
136 : * The function removes raster polygons smaller than a provided
137 : * threshold size (in pixels) and replaces replaces them with the pixel value
138 : * of the largest neighbour polygon.
139 : *
140 : * Polygon are determined (per GDALRasterPolygonEnumerator) as regions of
141 : * the raster where the pixels all have the same value, and that are contiguous
142 : * (connected).
143 : *
144 : * Pixels determined to be "nodata" per hMaskBand will not be treated as part
145 : * of a polygon regardless of their pixel values. Nodata areas will never be
146 : * changed nor affect polygon sizes.
147 : *
148 : * Polygons smaller than the threshold with no neighbours that are as large
149 : * as the threshold will not be altered. Polygons surrounded by nodata areas
150 : * will therefore not be altered.
151 : *
152 : * The algorithm makes three passes over the input file to enumerate the
153 : * polygons and collect limited information about them. Memory use is
154 : * proportional to the number of polygons (roughly 24 bytes per polygon), but
155 : * is not directly related to the size of the raster. So very large raster
156 : * files can be processed effectively if there aren't too many polygons. But
157 : * extremely noisy rasters with many one pixel polygons will end up being
158 : * expensive (in memory) to process.
159 : *
160 : * @param hSrcBand the source raster band to be processed.
161 : * @param hMaskBand an optional mask band. All pixels in the mask band with a
162 : * value other than zero will be considered suitable for inclusion in polygons.
163 : * @param hDstBand the output raster band. It may be the same as hSrcBand
164 : * to update the source in place.
165 : * @param nSizeThreshold raster polygons with sizes smaller than this will
166 : * be merged into their largest neighbour.
167 : * @param nConnectedness either 4 indicating that diagonal pixels are not
168 : * considered directly adjacent for polygon membership purposes or 8
169 : * indicating they are.
170 : * @param papszOption algorithm options in name=value list form. None currently
171 : * supported.
172 : * @param pfnProgress callback for reporting algorithm progress matching the
173 : * GDALProgressFunc() semantics. May be NULL.
174 : * @param pProgressArg callback argument passed to pfnProgress.
175 : *
176 : * @return CE_None on success or CE_Failure if an error occurs.
177 : */
178 :
179 : CPLErr CPL_STDCALL
180 5 : GDALSieveFilter( GDALRasterBandH hSrcBand, GDALRasterBandH hMaskBand,
181 : GDALRasterBandH hDstBand,
182 : int nSizeThreshold, int nConnectedness,
183 : char **papszOptions,
184 : GDALProgressFunc pfnProgress,
185 : void * pProgressArg )
186 :
187 : {
188 5 : VALIDATE_POINTER1( hSrcBand, "GDALSieveFilter", CE_Failure );
189 5 : VALIDATE_POINTER1( hDstBand, "GDALSieveFilter", CE_Failure );
190 :
191 5 : if( pfnProgress == NULL )
192 5 : pfnProgress = GDALDummyProgress;
193 :
194 : /* -------------------------------------------------------------------- */
195 : /* Allocate working buffers. */
196 : /* -------------------------------------------------------------------- */
197 5 : CPLErr eErr = CE_None;
198 5 : int nXSize = GDALGetRasterBandXSize( hSrcBand );
199 5 : int nYSize = GDALGetRasterBandYSize( hSrcBand );
200 5 : GInt32 *panLastLineVal = (GInt32 *) VSIMalloc2(sizeof(GInt32), nXSize);
201 5 : GInt32 *panThisLineVal = (GInt32 *) VSIMalloc2(sizeof(GInt32), nXSize);
202 5 : GInt32 *panLastLineId = (GInt32 *) VSIMalloc2(sizeof(GInt32), nXSize);
203 5 : GInt32 *panThisLineId = (GInt32 *) VSIMalloc2(sizeof(GInt32), nXSize);
204 5 : GInt32 *panThisLineWriteVal = (GInt32 *) VSIMalloc2(sizeof(GInt32), nXSize);
205 5 : GByte *pabyMaskLine = (hMaskBand != NULL) ? (GByte *) VSIMalloc(nXSize) : NULL;
206 5 : if (panLastLineVal == NULL || panThisLineVal == NULL ||
207 : panLastLineId == NULL || panThisLineId == NULL ||
208 : panThisLineWriteVal == NULL ||
209 : (hMaskBand != NULL && pabyMaskLine == NULL))
210 : {
211 : CPLError(CE_Failure, CPLE_OutOfMemory,
212 0 : "Could not allocate enough memory for temporary buffers");
213 0 : CPLFree( panThisLineId );
214 0 : CPLFree( panLastLineId );
215 0 : CPLFree( panThisLineVal );
216 0 : CPLFree( panLastLineVal );
217 0 : CPLFree( panThisLineWriteVal );
218 0 : CPLFree( pabyMaskLine );
219 0 : return CE_Failure;
220 : }
221 :
222 : /* -------------------------------------------------------------------- */
223 : /* The first pass over the raster is only used to build up the */
224 : /* polygon id map so we will know in advance what polygons are */
225 : /* what on the second pass. */
226 : /* -------------------------------------------------------------------- */
227 : int iY, iX, iPoly;
228 5 : GDALRasterPolygonEnumerator oFirstEnum( nConnectedness );
229 5 : std::vector<int> anPolySizes;
230 :
231 41 : for( iY = 0; eErr == CE_None && iY < nYSize; iY++ )
232 : {
233 : eErr = GDALRasterIO(
234 : hSrcBand,
235 : GF_Read, 0, iY, nXSize, 1,
236 36 : panThisLineVal, nXSize, 1, GDT_Int32, 0, 0 );
237 :
238 36 : if( eErr == CE_None && hMaskBand != NULL )
239 7 : eErr = GPMaskImageData( hMaskBand, pabyMaskLine, iY, nXSize, panThisLineVal );
240 :
241 36 : if( iY == 0 )
242 : oFirstEnum.ProcessLine(
243 5 : NULL, panThisLineVal, NULL, panThisLineId, nXSize );
244 : else
245 : oFirstEnum.ProcessLine(
246 : panLastLineVal, panThisLineVal,
247 : panLastLineId, panThisLineId,
248 31 : nXSize );
249 :
250 : /* -------------------------------------------------------------------- */
251 : /* Accumulate polygon sizes. */
252 : /* -------------------------------------------------------------------- */
253 36 : if( oFirstEnum.nNextPolygonId > (int) anPolySizes.size() )
254 32 : anPolySizes.resize( oFirstEnum.nNextPolygonId );
255 :
256 256 : for( iX = 0; iX < nXSize; iX++ )
257 : {
258 220 : iPoly = panThisLineId[iX];
259 :
260 : CPLAssert( iPoly >= 0 );
261 220 : anPolySizes[iPoly] += 1;
262 : }
263 :
264 : /* -------------------------------------------------------------------- */
265 : /* swap this/last lines. */
266 : /* -------------------------------------------------------------------- */
267 36 : GInt32 *panTmp = panLastLineVal;
268 36 : panLastLineVal = panThisLineVal;
269 36 : panThisLineVal = panTmp;
270 :
271 36 : panTmp = panThisLineId;
272 36 : panThisLineId = panLastLineId;
273 36 : panLastLineId = panTmp;
274 :
275 : /* -------------------------------------------------------------------- */
276 : /* Report progress, and support interrupts. */
277 : /* -------------------------------------------------------------------- */
278 36 : if( eErr == CE_None
279 : && !pfnProgress( 0.25 * ((iY+1) / (double) nYSize),
280 : "", pProgressArg ) )
281 : {
282 0 : CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
283 0 : eErr = CE_Failure;
284 : }
285 : }
286 :
287 : /* -------------------------------------------------------------------- */
288 : /* Make a pass through the maps, ensuring every polygon id */
289 : /* points to the final id it should use, not an intermediate */
290 : /* value. */
291 : /* -------------------------------------------------------------------- */
292 5 : oFirstEnum.CompleteMerges();
293 :
294 : /* -------------------------------------------------------------------- */
295 : /* Push the sizes of merged polygon fragments into the the */
296 : /* merged polygon id's count. */
297 : /* -------------------------------------------------------------------- */
298 100 : for( iPoly = 0; iPoly < oFirstEnum.nNextPolygonId; iPoly++ )
299 : {
300 95 : if( oFirstEnum.panPolyIdMap[iPoly] != iPoly )
301 : {
302 9 : anPolySizes[oFirstEnum.panPolyIdMap[iPoly]] += anPolySizes[iPoly];
303 9 : anPolySizes[iPoly] = 0;
304 : }
305 : }
306 :
307 : /* -------------------------------------------------------------------- */
308 : /* We will use a new enumerator for the second pass primariliy */
309 : /* so we can preserve the first pass map. */
310 : /* -------------------------------------------------------------------- */
311 5 : GDALRasterPolygonEnumerator oSecondEnum( nConnectedness );
312 :
313 5 : std::vector<int> anBigNeighbour;
314 5 : anBigNeighbour.resize( anPolySizes.size() );
315 :
316 100 : for( iPoly = 0; iPoly < (int) anPolySizes.size(); iPoly++ )
317 95 : anBigNeighbour[iPoly] = -1;
318 :
319 : /* ==================================================================== */
320 : /* Second pass ... identify the largest neighbour for each */
321 : /* polygon. */
322 : /* ==================================================================== */
323 41 : for( iY = 0; eErr == CE_None && iY < nYSize; iY++ )
324 : {
325 : /* -------------------------------------------------------------------- */
326 : /* Read the image data. */
327 : /* -------------------------------------------------------------------- */
328 : eErr = GDALRasterIO( hSrcBand, GF_Read, 0, iY, nXSize, 1,
329 36 : panThisLineVal, nXSize, 1, GDT_Int32, 0, 0 );
330 :
331 36 : if( eErr == CE_None && hMaskBand != NULL )
332 7 : eErr = GPMaskImageData( hMaskBand, pabyMaskLine, iY, nXSize, panThisLineVal );
333 :
334 36 : if( eErr != CE_None )
335 0 : continue;
336 :
337 : /* -------------------------------------------------------------------- */
338 : /* Determine what polygon the various pixels belong to (redoing */
339 : /* the same thing done in the first pass above). */
340 : /* -------------------------------------------------------------------- */
341 36 : if( iY == 0 )
342 : oSecondEnum.ProcessLine(
343 5 : NULL, panThisLineVal, NULL, panThisLineId, nXSize );
344 : else
345 : oSecondEnum.ProcessLine(
346 : panLastLineVal, panThisLineVal,
347 : panLastLineId, panThisLineId,
348 31 : nXSize );
349 :
350 : /* -------------------------------------------------------------------- */
351 : /* Check our neighbours, and update our biggest neighbour map */
352 : /* as appropriate. */
353 : /* -------------------------------------------------------------------- */
354 256 : for( iX = 0; iX < nXSize; iX++ )
355 : {
356 220 : if( iY > 0 )
357 : {
358 : CompareNeighbour( panThisLineId[iX],
359 : panLastLineId[iX],
360 : oFirstEnum.panPolyIdMap,
361 : oFirstEnum.panPolyValue,
362 190 : anPolySizes, anBigNeighbour );
363 :
364 190 : if( iX > 0 && nConnectedness == 8 )
365 : CompareNeighbour( panThisLineId[iX],
366 : panLastLineId[iX-1],
367 : oFirstEnum.panPolyIdMap,
368 : oFirstEnum.panPolyValue,
369 48 : anPolySizes, anBigNeighbour );
370 :
371 190 : if( iX < nXSize-1 && nConnectedness == 8 )
372 : CompareNeighbour( panThisLineId[iX],
373 : panLastLineId[iX+1],
374 : oFirstEnum.panPolyIdMap,
375 : oFirstEnum.panPolyValue,
376 48 : anPolySizes, anBigNeighbour );
377 :
378 : }
379 :
380 220 : if( iX > 0 )
381 : CompareNeighbour( panThisLineId[iX],
382 : panThisLineId[iX-1],
383 : oFirstEnum.panPolyIdMap,
384 : oFirstEnum.panPolyValue,
385 184 : anPolySizes, anBigNeighbour );
386 :
387 : // We don't need to compare to next pixel or next line
388 : // since they will be compared to us.
389 : }
390 :
391 : /* -------------------------------------------------------------------- */
392 : /* Swap pixel value, and polygon id lines to be ready for the */
393 : /* next line. */
394 : /* -------------------------------------------------------------------- */
395 36 : GInt32 *panTmp = panLastLineVal;
396 36 : panLastLineVal = panThisLineVal;
397 36 : panThisLineVal = panTmp;
398 :
399 36 : panTmp = panThisLineId;
400 36 : panThisLineId = panLastLineId;
401 36 : panLastLineId = panTmp;
402 :
403 : /* -------------------------------------------------------------------- */
404 : /* Report progress, and support interrupts. */
405 : /* -------------------------------------------------------------------- */
406 36 : if( eErr == CE_None
407 : && !pfnProgress( 0.25 + 0.25 * ((iY+1) / (double) nYSize),
408 : "", pProgressArg ) )
409 : {
410 0 : CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
411 0 : eErr = CE_Failure;
412 : }
413 : }
414 :
415 : /* -------------------------------------------------------------------- */
416 : /* If our biggest neighbour is still smaller than the */
417 : /* threshold, then try tracking to that polygons biggest */
418 : /* neighbour, and so forth. */
419 : /* -------------------------------------------------------------------- */
420 5 : int nFailedMerges = 0;
421 5 : int nIsolatedSmall = 0;
422 5 : int nSieveTargets = 0;
423 :
424 100 : for( iPoly = 0; iPoly < (int) anPolySizes.size(); iPoly++ )
425 : {
426 95 : if( oFirstEnum.panPolyIdMap[iPoly] != iPoly )
427 9 : continue;
428 :
429 : // Ignore nodata polygons.
430 86 : if( oFirstEnum.panPolyValue[iPoly] == GP_NODATA_MARKER )
431 0 : continue;
432 :
433 : // Don't try to merge polygons larger than the threshold.
434 86 : if( anPolySizes[iPoly] >= nSizeThreshold )
435 : {
436 18 : anBigNeighbour[iPoly] = -1;
437 18 : continue;
438 : }
439 :
440 68 : nSieveTargets++;
441 :
442 : // if we have no neighbours but we are small, what shall we do?
443 68 : if( anBigNeighbour[iPoly] == -1 )
444 : {
445 0 : nIsolatedSmall++;
446 0 : continue;
447 : }
448 :
449 : // If our biggest neighbour is larger than the threshold
450 : // then we are golden.
451 68 : if( anPolySizes[anBigNeighbour[iPoly]] >= nSizeThreshold )
452 42 : continue;
453 :
454 : #ifdef notdef
455 : // Will our neighbours biggest neighbour do?
456 : // Eventually we need something sort of recursive here with
457 : // loop detection.
458 : if( anPolySizes[anBigNeighbour[anBigNeighbour[iPoly]]]
459 : >= nSizeThreshold )
460 : {
461 : anBigNeighbour[iPoly] = anBigNeighbour[anBigNeighbour[iPoly]];
462 : continue;
463 : }
464 : #endif
465 :
466 26 : nFailedMerges++;
467 26 : anBigNeighbour[iPoly] = -1;
468 : }
469 :
470 : CPLDebug( "GDALSieveFilter",
471 : "Small Polygons: %d, Isolated: %d, Unmergable: %d",
472 5 : nSieveTargets, nIsolatedSmall, nFailedMerges );
473 :
474 : /* ==================================================================== */
475 : /* Make a third pass over the image, actually applying the */
476 : /* merges. We reuse the second enumerator but preserve the */
477 : /* "final maps" from the first. */
478 : /* ==================================================================== */
479 5 : oSecondEnum.Clear();
480 :
481 :
482 41 : for( iY = 0; eErr == CE_None && iY < nYSize; iY++ )
483 : {
484 : /* -------------------------------------------------------------------- */
485 : /* Read the image data. */
486 : /* -------------------------------------------------------------------- */
487 : eErr = GDALRasterIO( hSrcBand, GF_Read, 0, iY, nXSize, 1,
488 36 : panThisLineVal, nXSize, 1, GDT_Int32, 0, 0 );
489 :
490 36 : memcpy( panThisLineWriteVal, panThisLineVal, 4 * nXSize );
491 :
492 36 : if( eErr == CE_None && hMaskBand != NULL )
493 7 : eErr = GPMaskImageData( hMaskBand, pabyMaskLine, iY, nXSize, panThisLineVal );
494 :
495 36 : if( eErr != CE_None )
496 0 : continue;
497 :
498 : /* -------------------------------------------------------------------- */
499 : /* Determine what polygon the various pixels belong to (redoing */
500 : /* the same thing done in the first pass above). */
501 : /* -------------------------------------------------------------------- */
502 36 : if( iY == 0 )
503 : oSecondEnum.ProcessLine(
504 5 : NULL, panThisLineVal, NULL, panThisLineId, nXSize );
505 : else
506 : oSecondEnum.ProcessLine(
507 : panLastLineVal, panThisLineVal,
508 : panLastLineId, panThisLineId,
509 31 : nXSize );
510 :
511 : /* -------------------------------------------------------------------- */
512 : /* Reprocess the actual pixel values according to the polygon */
513 : /* merging, and write out this line of image data. */
514 : /* -------------------------------------------------------------------- */
515 256 : for( iX = 0; iX < nXSize; iX++ )
516 : {
517 220 : int iThisPoly = oFirstEnum.panPolyIdMap[panThisLineId[iX]];
518 :
519 220 : if( anBigNeighbour[iThisPoly] != -1 )
520 : {
521 42 : panThisLineWriteVal[iX] =
522 : oFirstEnum.panPolyValue[
523 42 : anBigNeighbour[iThisPoly]];
524 : }
525 : }
526 :
527 : /* -------------------------------------------------------------------- */
528 : /* Write the update data out. */
529 : /* -------------------------------------------------------------------- */
530 : eErr = GDALRasterIO( hDstBand, GF_Write, 0, iY, nXSize, 1,
531 36 : panThisLineWriteVal, nXSize, 1, GDT_Int32, 0, 0 );
532 :
533 : /* -------------------------------------------------------------------- */
534 : /* Swap pixel value, and polygon id lines to be ready for the */
535 : /* next line. */
536 : /* -------------------------------------------------------------------- */
537 36 : GInt32 *panTmp = panLastLineVal;
538 36 : panLastLineVal = panThisLineVal;
539 36 : panThisLineVal = panTmp;
540 :
541 36 : panTmp = panThisLineId;
542 36 : panThisLineId = panLastLineId;
543 36 : panLastLineId = panTmp;
544 :
545 : /* -------------------------------------------------------------------- */
546 : /* Report progress, and support interrupts. */
547 : /* -------------------------------------------------------------------- */
548 36 : if( eErr == CE_None
549 : && !pfnProgress( 0.5 + 0.5 * ((iY+1) / (double) nYSize),
550 : "", pProgressArg ) )
551 : {
552 0 : CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
553 0 : eErr = CE_Failure;
554 : }
555 : }
556 :
557 : /* -------------------------------------------------------------------- */
558 : /* Cleanup */
559 : /* -------------------------------------------------------------------- */
560 5 : CPLFree( panThisLineId );
561 5 : CPLFree( panLastLineId );
562 5 : CPLFree( panThisLineVal );
563 5 : CPLFree( panLastLineVal );
564 5 : CPLFree( panThisLineWriteVal );
565 5 : CPLFree( pabyMaskLine );
566 :
567 5 : return eErr;
568 : }
569 :
|