Line data Source code
1 : /******************************************************************************
2 : * $Id$
3 : *
4 : * Project: DTED Translator
5 : * Purpose: DTED Point Stream Writer.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2001, Frank Warmerdam
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "dted_api.h"
15 :
16 : typedef struct
17 : {
18 : char *pszFilename;
19 : DTEDInfo *psInfo;
20 :
21 : GInt16 **papanProfiles;
22 :
23 : int nLLLong;
24 : int nLLLat;
25 : } DTEDCachedFile;
26 :
27 : typedef struct
28 : {
29 : int nLevel;
30 : char *pszPath;
31 :
32 : double dfPixelSize;
33 :
34 : int nOpenFiles;
35 : DTEDCachedFile *pasCF;
36 :
37 : int nLastFile;
38 :
39 : char *apszMetadata[DTEDMD_MAX + 1];
40 : } DTEDPtStream;
41 :
42 : /************************************************************************/
43 : /* DTEDCreatePtStream() */
44 : /************************************************************************/
45 :
46 0 : void *DTEDCreatePtStream(const char *pszPath, int nLevel)
47 :
48 : {
49 : DTEDPtStream *psStream;
50 : int i;
51 : VSIStatBuf sStat;
52 :
53 : /* -------------------------------------------------------------------- */
54 : /* Does the target directory already exist? If not try to */
55 : /* create it. */
56 : /* -------------------------------------------------------------------- */
57 0 : if (CPLStat(pszPath, &sStat) != 0)
58 : {
59 0 : if (VSIMkdir(pszPath, 0755) != 0)
60 : {
61 : #ifndef AVOID_CPL
62 0 : CPLError(CE_Failure, CPLE_OpenFailed,
63 : "Unable to find, or create directory `%s'.", pszPath);
64 : #endif
65 0 : return NULL;
66 : }
67 : }
68 :
69 : /* -------------------------------------------------------------------- */
70 : /* Create the stream and initialize it. */
71 : /* -------------------------------------------------------------------- */
72 :
73 0 : psStream = (DTEDPtStream *)CPLCalloc(sizeof(DTEDPtStream), 1);
74 0 : psStream->nLevel = nLevel;
75 0 : psStream->pszPath = CPLStrdup(pszPath);
76 0 : psStream->nOpenFiles = 0;
77 0 : psStream->pasCF = NULL;
78 0 : psStream->nLastFile = -1;
79 :
80 0 : for (i = 0; i < DTEDMD_MAX + 1; i++)
81 0 : psStream->apszMetadata[i] = NULL;
82 :
83 0 : if (nLevel == 0)
84 0 : psStream->dfPixelSize = 1.0 / 120.0;
85 0 : else if (nLevel == 1)
86 0 : psStream->dfPixelSize = 1.0 / 1200.0;
87 : else /* if( nLevel == 2 ) */
88 0 : psStream->dfPixelSize = 1.0 / 3600.0;
89 :
90 0 : return (void *)psStream;
91 : }
92 :
93 : /************************************************************************/
94 : /* DTEDPtStreamNewTile() */
95 : /* */
96 : /* Create a new DTED file file, add it to our list, and make it */
97 : /* "current". */
98 : /************************************************************************/
99 :
100 0 : static int DTEDPtStreamNewTile(DTEDPtStream *psStream, int nCrLong, int nCrLat)
101 :
102 : {
103 : DTEDInfo *psInfo;
104 : char szFile[128];
105 : char chNSHemi, chEWHemi;
106 : char *pszFullFilename;
107 : const char *pszError;
108 :
109 : /* work out filename */
110 0 : if (nCrLat < 0)
111 0 : chNSHemi = 's';
112 : else
113 0 : chNSHemi = 'n';
114 :
115 0 : if (nCrLong < 0)
116 0 : chEWHemi = 'w';
117 : else
118 0 : chEWHemi = 'e';
119 :
120 0 : snprintf(szFile, sizeof(szFile), "%c%03d%c%03d.dt%d", chEWHemi,
121 : ABS(nCrLong), chNSHemi, ABS(nCrLat), psStream->nLevel);
122 :
123 : pszFullFilename =
124 0 : CPLStrdup(CPLFormFilename(psStream->pszPath, szFile, NULL));
125 :
126 : /* create the dted file */
127 0 : pszError = DTEDCreate(pszFullFilename, psStream->nLevel, nCrLat, nCrLong);
128 0 : if (pszError != NULL)
129 : {
130 : #ifndef AVOID_CPL
131 0 : CPLError(CE_Failure, CPLE_OpenFailed,
132 : "Failed to create DTED file `%s'.\n%s", pszFullFilename,
133 : pszError);
134 : #endif
135 0 : return FALSE;
136 : }
137 :
138 0 : psInfo = DTEDOpen(pszFullFilename, "rb+", FALSE);
139 :
140 0 : if (psInfo == NULL)
141 : {
142 0 : CPLFree(pszFullFilename);
143 0 : return FALSE;
144 : }
145 :
146 : /* add cached file to stream */
147 0 : psStream->nOpenFiles++;
148 0 : psStream->pasCF = CPLRealloc(psStream->pasCF,
149 0 : sizeof(DTEDCachedFile) * psStream->nOpenFiles);
150 :
151 0 : psStream->pasCF[psStream->nOpenFiles - 1].psInfo = psInfo;
152 0 : psStream->pasCF[psStream->nOpenFiles - 1].papanProfiles =
153 0 : CPLCalloc(sizeof(GInt16 *), psInfo->nXSize);
154 0 : psStream->pasCF[psStream->nOpenFiles - 1].pszFilename = pszFullFilename;
155 0 : psStream->pasCF[psStream->nOpenFiles - 1].nLLLat = nCrLat;
156 0 : psStream->pasCF[psStream->nOpenFiles - 1].nLLLong = nCrLong;
157 :
158 0 : psStream->nLastFile = psStream->nOpenFiles - 1;
159 :
160 0 : return TRUE;
161 : }
162 :
163 : /************************************************************************/
164 : /* DTEDWritePtLL() */
165 : /************************************************************************/
166 :
167 0 : static int DTEDWritePtLL(CPL_UNUSED DTEDPtStream *psStream,
168 : DTEDCachedFile *psCF, double dfLong, double dfLat,
169 : double dfElev)
170 : {
171 : /* -------------------------------------------------------------------- */
172 : /* Determine what profile this belongs in, and initialize the */
173 : /* profile if it doesn't already exist. */
174 : /* -------------------------------------------------------------------- */
175 0 : DTEDInfo *psInfo = psCF->psInfo;
176 : int iProfile, i, iRow;
177 :
178 0 : iProfile = (int)((dfLong - psInfo->dfULCornerX) / psInfo->dfPixelSizeX);
179 0 : iProfile = MAX(0, MIN(psInfo->nXSize - 1, iProfile));
180 :
181 0 : if (psCF->papanProfiles[iProfile] == NULL)
182 : {
183 0 : psCF->papanProfiles[iProfile] =
184 0 : CPLMalloc(sizeof(GInt16) * psInfo->nYSize);
185 :
186 0 : for (i = 0; i < psInfo->nYSize; i++)
187 0 : psCF->papanProfiles[iProfile][i] = DTED_NODATA_VALUE;
188 : }
189 :
190 : /* -------------------------------------------------------------------- */
191 : /* Establish where we fit in the profile. */
192 : /* -------------------------------------------------------------------- */
193 0 : iRow = (int)((psInfo->dfULCornerY - dfLat) / psInfo->dfPixelSizeY);
194 0 : iRow = MAX(0, MIN(psInfo->nYSize - 1, iRow));
195 :
196 0 : psCF->papanProfiles[iProfile][iRow] = (GInt16)floor(dfElev + 0.5);
197 :
198 0 : return TRUE;
199 : }
200 :
201 : /************************************************************************/
202 : /* DTEDWritePt() */
203 : /* */
204 : /* Write a single point out, creating a new file if necessary */
205 : /* to hold it. */
206 : /************************************************************************/
207 :
208 0 : int DTEDWritePt(void *hStream, double dfLong, double dfLat, double dfElev)
209 :
210 : {
211 0 : DTEDPtStream *psStream = (DTEDPtStream *)hStream;
212 : int i;
213 : DTEDInfo *psInfo;
214 0 : int bOnBoundary = FALSE;
215 :
216 : /* -------------------------------------------------------------------- */
217 : /* Determine if we are in a boundary region ... that is in the */
218 : /* area of the edge "pixel" that is shared with adjacent */
219 : /* tiles. */
220 : /* -------------------------------------------------------------------- */
221 0 : if ((floor(dfLong - 0.5 * psStream->dfPixelSize) !=
222 0 : floor(dfLong + 0.5 * psStream->dfPixelSize)) ||
223 0 : (floor(dfLat - 0.5 * psStream->dfPixelSize) !=
224 0 : floor(dfLat + 0.5 * psStream->dfPixelSize)))
225 : {
226 0 : bOnBoundary = TRUE;
227 0 : psStream->nLastFile = -1;
228 : }
229 :
230 : /* ==================================================================== */
231 : /* Handle case where the tile is not on a boundary. We only */
232 : /* need one output tile. */
233 : /* ==================================================================== */
234 : /* -------------------------------------------------------------------- */
235 : /* Is the last file used still applicable? */
236 : /* -------------------------------------------------------------------- */
237 0 : if (!bOnBoundary)
238 : {
239 0 : if (psStream->nLastFile != -1)
240 : {
241 0 : psInfo = psStream->pasCF[psStream->nLastFile].psInfo;
242 :
243 0 : if (dfLat > psInfo->dfULCornerY ||
244 0 : dfLat < psInfo->dfULCornerY - 1.0 - psInfo->dfPixelSizeY ||
245 0 : dfLong < psInfo->dfULCornerX ||
246 0 : dfLong > psInfo->dfULCornerX + 1.0 + psInfo->dfPixelSizeX)
247 0 : psStream->nLastFile = -1;
248 : }
249 :
250 : /* --------------------------------------------------------------------
251 : */
252 : /* Search for the file to write to. */
253 : /* --------------------------------------------------------------------
254 : */
255 0 : for (i = 0; i < psStream->nOpenFiles && psStream->nLastFile == -1; i++)
256 : {
257 0 : psInfo = psStream->pasCF[i].psInfo;
258 :
259 0 : if (!(dfLat > psInfo->dfULCornerY ||
260 0 : dfLat < psInfo->dfULCornerY - 1.0 - psInfo->dfPixelSizeY ||
261 0 : dfLong < psInfo->dfULCornerX ||
262 0 : dfLong > psInfo->dfULCornerX + 1.0 + psInfo->dfPixelSizeX))
263 : {
264 0 : psStream->nLastFile = i;
265 : }
266 : }
267 :
268 : /* --------------------------------------------------------------------
269 : */
270 : /* If none found, create a new file. */
271 : /* --------------------------------------------------------------------
272 : */
273 0 : if (psStream->nLastFile == -1)
274 : {
275 : int nCrLong, nCrLat;
276 :
277 0 : nCrLong = (int)floor(dfLong);
278 0 : nCrLat = (int)floor(dfLat);
279 :
280 0 : if (!DTEDPtStreamNewTile(psStream, nCrLong, nCrLat))
281 0 : return FALSE;
282 : }
283 :
284 : /* --------------------------------------------------------------------
285 : */
286 : /* Write data out to selected tile. */
287 : /* --------------------------------------------------------------------
288 : */
289 0 : return DTEDWritePtLL(psStream, psStream->pasCF + psStream->nLastFile,
290 : dfLong, dfLat, dfElev);
291 : }
292 :
293 : /* ==================================================================== */
294 : /* Handle case where we are on a boundary. We may be writing */
295 : /* the value to as many as four tiles. */
296 : /* ==================================================================== */
297 : else
298 : {
299 : int nLatMin, nLatMax, nLongMin, nLongMax;
300 : int nCrLong, nCrLat;
301 :
302 0 : nLongMin = (int)floor(dfLong - 0.5 * psStream->dfPixelSize);
303 0 : nLongMax = (int)floor(dfLong + 0.5 * psStream->dfPixelSize);
304 0 : nLatMin = (int)floor(dfLat - 0.5 * psStream->dfPixelSize);
305 0 : nLatMax = (int)floor(dfLat + 0.5 * psStream->dfPixelSize);
306 :
307 0 : for (nCrLong = nLongMin; nCrLong <= nLongMax; nCrLong++)
308 : {
309 0 : for (nCrLat = nLatMin; nCrLat <= nLatMax; nCrLat++)
310 : {
311 0 : psStream->nLastFile = -1;
312 :
313 : /* --------------------------------------------------------------------
314 : */
315 : /* Find this tile in our existing list. */
316 : /* --------------------------------------------------------------------
317 : */
318 0 : for (i = 0; i < psStream->nOpenFiles; i++)
319 : {
320 0 : if (psStream->pasCF[i].nLLLong == nCrLong &&
321 0 : psStream->pasCF[i].nLLLat == nCrLat)
322 : {
323 0 : psStream->nLastFile = i;
324 0 : break;
325 : }
326 : }
327 :
328 : /* --------------------------------------------------------------------
329 : */
330 : /* Create the tile if not found. */
331 : /* --------------------------------------------------------------------
332 : */
333 0 : if (psStream->nLastFile == -1)
334 : {
335 0 : if (!DTEDPtStreamNewTile(psStream, nCrLong, nCrLat))
336 0 : return FALSE;
337 : }
338 :
339 : /* --------------------------------------------------------------------
340 : */
341 : /* Write to the tile. */
342 : /* --------------------------------------------------------------------
343 : */
344 0 : if (!DTEDWritePtLL(psStream,
345 0 : psStream->pasCF + psStream->nLastFile,
346 : dfLong, dfLat, dfElev))
347 0 : return FALSE;
348 : }
349 : }
350 : }
351 :
352 0 : return TRUE;
353 : }
354 :
355 : /************************************************************************/
356 : /* DTEDClosePtStream() */
357 : /************************************************************************/
358 :
359 0 : void DTEDClosePtStream(void *hStream)
360 :
361 : {
362 0 : DTEDPtStream *psStream = (DTEDPtStream *)hStream;
363 : int iFile, iMD;
364 :
365 : /* -------------------------------------------------------------------- */
366 : /* Flush all DTED files. */
367 : /* -------------------------------------------------------------------- */
368 0 : for (iFile = 0; iFile < psStream->nOpenFiles; iFile++)
369 : {
370 : int iProfile;
371 0 : DTEDCachedFile *psCF = psStream->pasCF + iFile;
372 :
373 0 : for (iProfile = 0; iProfile < psCF->psInfo->nXSize; iProfile++)
374 : {
375 0 : if (psCF->papanProfiles[iProfile] != NULL)
376 : {
377 0 : DTEDWriteProfile(psCF->psInfo, iProfile,
378 0 : psCF->papanProfiles[iProfile]);
379 0 : CPLFree(psCF->papanProfiles[iProfile]);
380 : }
381 : }
382 :
383 0 : CPLFree(psCF->papanProfiles);
384 :
385 0 : for (iMD = 0; iMD <= DTEDMD_MAX; iMD++)
386 : {
387 0 : if (psStream->apszMetadata[iMD] != NULL)
388 0 : DTEDSetMetadata(psCF->psInfo, (DTEDMetaDataCode)iMD,
389 0 : psStream->apszMetadata[iMD]);
390 : }
391 :
392 0 : DTEDClose(psCF->psInfo);
393 : }
394 :
395 : /* -------------------------------------------------------------------- */
396 : /* Final cleanup. */
397 : /* -------------------------------------------------------------------- */
398 :
399 0 : for (iMD = 0; iMD < DTEDMD_MAX + 1; iMD++)
400 0 : CPLFree(psStream->apszMetadata[iMD]);
401 :
402 0 : CPLFree(psStream->pasCF);
403 0 : CPLFree(psStream->pszPath);
404 0 : CPLFree(psStream);
405 0 : }
406 :
407 : /************************************************************************/
408 : /* DTEDFillPixel() */
409 : /************************************************************************/
410 0 : static void DTEDFillPixel(DTEDInfo *psInfo, GInt16 **papanProfiles,
411 : GInt16 **papanDstProfiles, int iX, int iY,
412 : int nPixelSearchDist, float *pafKernel)
413 :
414 : {
415 0 : int nKernelWidth = 2 * nPixelSearchDist + 1;
416 : int nXMin, nXMax, nYMin, nYMax;
417 0 : double dfCoefSum = 0.0, dfValueSum = 0.0;
418 : int iXS, iYS;
419 :
420 0 : nXMin = MAX(0, iX - nPixelSearchDist);
421 0 : nXMax = MIN(psInfo->nXSize - 1, iX + nPixelSearchDist);
422 0 : nYMin = MAX(0, iY - nPixelSearchDist);
423 0 : nYMax = MIN(psInfo->nYSize - 1, iY + nPixelSearchDist);
424 :
425 0 : for (iXS = nXMin; iXS <= nXMax; iXS++)
426 : {
427 0 : GInt16 *panThisProfile = papanProfiles[iXS];
428 :
429 0 : if (panThisProfile == NULL)
430 0 : continue;
431 :
432 0 : for (iYS = nYMin; iYS <= nYMax; iYS++)
433 : {
434 0 : if (panThisProfile[iYS] != DTED_NODATA_VALUE)
435 : {
436 : int iXK, iYK;
437 : float fKernelCoef;
438 :
439 0 : iXK = iXS - iX + nPixelSearchDist;
440 0 : iYK = iYS - iY + nPixelSearchDist;
441 :
442 0 : fKernelCoef = pafKernel[iXK + iYK * nKernelWidth];
443 0 : dfCoefSum += fKernelCoef;
444 0 : dfValueSum += (double)fKernelCoef * (double)panThisProfile[iYS];
445 : }
446 : }
447 : }
448 :
449 0 : if (dfCoefSum == 0.0)
450 0 : papanDstProfiles[iX][iY] = DTED_NODATA_VALUE;
451 : else
452 0 : papanDstProfiles[iX][iY] = (GInt16)floor(dfValueSum / dfCoefSum + 0.5);
453 0 : }
454 :
455 : /************************************************************************/
456 : /* DTEDFillPtStream() */
457 : /* */
458 : /* Apply simple inverse distance interpolator to all no-data */
459 : /* pixels based on available values within the indicated search */
460 : /* distance (rectangular). */
461 : /************************************************************************/
462 :
463 0 : void DTEDFillPtStream(void *hStream, int nPixelSearchDist)
464 :
465 : {
466 0 : DTEDPtStream *psStream = (DTEDPtStream *)hStream;
467 : int iFile, nKernelWidth;
468 : float *pafKernel;
469 : int iX, iY;
470 :
471 : /* -------------------------------------------------------------------- */
472 : /* Setup inverse distance weighting kernel. */
473 : /* -------------------------------------------------------------------- */
474 0 : nKernelWidth = 2 * nPixelSearchDist + 1;
475 0 : pafKernel = (float *)CPLMalloc(sizeof(float) * nKernelWidth * nKernelWidth);
476 :
477 0 : for (iX = 0; iX < nKernelWidth; iX++)
478 : {
479 0 : for (iY = 0; iY < nKernelWidth; iY++)
480 : {
481 0 : pafKernel[iX + iY * nKernelWidth] =
482 0 : (float)(1.0 /
483 0 : sqrt((nPixelSearchDist - iX) * (nPixelSearchDist - iX) +
484 0 : (nPixelSearchDist - iY) *
485 0 : (nPixelSearchDist - iY)));
486 : }
487 : }
488 :
489 : /* ==================================================================== */
490 : /* Process each cached file. */
491 : /* ==================================================================== */
492 0 : for (iFile = 0; iFile < psStream->nOpenFiles; iFile++)
493 : {
494 0 : DTEDInfo *psInfo = psStream->pasCF[iFile].psInfo;
495 0 : GInt16 **papanProfiles = psStream->pasCF[iFile].papanProfiles;
496 : GInt16 **papanDstProfiles;
497 :
498 : papanDstProfiles =
499 0 : (GInt16 **)CPLCalloc(sizeof(GInt16 *), psInfo->nXSize);
500 :
501 : /* --------------------------------------------------------------------
502 : */
503 : /* Setup output image. */
504 : /* --------------------------------------------------------------------
505 : */
506 0 : for (iX = 0; iX < psInfo->nXSize; iX++)
507 : {
508 0 : papanDstProfiles[iX] =
509 0 : (GInt16 *)CPLMalloc(sizeof(GInt16) * psInfo->nYSize);
510 : }
511 :
512 : /* --------------------------------------------------------------------
513 : */
514 : /* Interpolate all missing values, and copy over available values.
515 : */
516 : /* --------------------------------------------------------------------
517 : */
518 0 : for (iX = 0; iX < psInfo->nXSize; iX++)
519 : {
520 0 : for (iY = 0; iY < psInfo->nYSize; iY++)
521 : {
522 0 : if (papanProfiles[iX] == NULL ||
523 0 : papanProfiles[iX][iY] == DTED_NODATA_VALUE)
524 : {
525 0 : DTEDFillPixel(psInfo, papanProfiles, papanDstProfiles, iX,
526 : iY, nPixelSearchDist, pafKernel);
527 : }
528 : else
529 : {
530 0 : papanDstProfiles[iX][iY] = papanProfiles[iX][iY];
531 : }
532 : }
533 : }
534 : /* --------------------------------------------------------------------
535 : */
536 : /* Push new values back into cache. */
537 : /* --------------------------------------------------------------------
538 : */
539 0 : for (iX = 0; iX < psInfo->nXSize; iX++)
540 : {
541 0 : CPLFree(papanProfiles[iX]);
542 0 : papanProfiles[iX] = papanDstProfiles[iX];
543 : }
544 :
545 0 : CPLFree(papanDstProfiles);
546 : }
547 :
548 0 : CPLFree(pafKernel);
549 0 : }
550 :
551 : /************************************************************************/
552 : /* DTEDPtStreamSetMetadata() */
553 : /************************************************************************/
554 :
555 0 : void DTEDPtStreamSetMetadata(void *hStream, DTEDMetaDataCode eCode,
556 : const char *pszValue)
557 :
558 : {
559 0 : DTEDPtStream *psStream = (DTEDPtStream *)hStream;
560 :
561 0 : if ((int)eCode >= 0 && eCode < DTEDMD_MAX + 1)
562 : {
563 0 : CPLFree(psStream->apszMetadata[eCode]);
564 0 : psStream->apszMetadata[eCode] = CPLStrdup(pszValue);
565 : }
566 0 : }
567 :
568 : /************************************************************************/
569 : /* DTEDPtStreamTrimEdgeOnlyTiles() */
570 : /* */
571 : /* Erase all tiles that only have boundary values set. */
572 : /************************************************************************/
573 :
574 0 : void DTEDPtStreamTrimEdgeOnlyTiles(void *hStream)
575 :
576 : {
577 0 : DTEDPtStream *psStream = (DTEDPtStream *)hStream;
578 : int iFile;
579 :
580 0 : for (iFile = psStream->nOpenFiles - 1; iFile >= 0; iFile--)
581 : {
582 0 : DTEDInfo *psInfo = psStream->pasCF[iFile].psInfo;
583 0 : GInt16 **papanProfiles = psStream->pasCF[iFile].papanProfiles;
584 0 : int iProfile, iPixel, bGotNonEdgeData = FALSE;
585 :
586 0 : for (iProfile = 1; iProfile < psInfo->nXSize - 1; iProfile++)
587 : {
588 0 : if (papanProfiles[iProfile] == NULL)
589 0 : continue;
590 :
591 0 : for (iPixel = 1; iPixel < psInfo->nYSize - 1; iPixel++)
592 : {
593 0 : if (papanProfiles[iProfile][iPixel] != DTED_NODATA_VALUE)
594 : {
595 0 : bGotNonEdgeData = TRUE;
596 0 : break;
597 : }
598 : }
599 : }
600 :
601 0 : if (bGotNonEdgeData)
602 0 : continue;
603 :
604 : /* Remove this tile */
605 :
606 0 : for (iProfile = 0; iProfile < psInfo->nXSize; iProfile++)
607 : {
608 0 : if (papanProfiles[iProfile] != NULL)
609 0 : CPLFree(papanProfiles[iProfile]);
610 : }
611 0 : CPLFree(papanProfiles);
612 :
613 0 : DTEDClose(psInfo);
614 :
615 0 : VSIUnlink(psStream->pasCF[iFile].pszFilename);
616 0 : CPLFree(psStream->pasCF[iFile].pszFilename);
617 :
618 0 : memmove(psStream->pasCF + iFile, psStream->pasCF + iFile + 1,
619 0 : sizeof(DTEDCachedFile) * (psStream->nOpenFiles - iFile - 1));
620 0 : psStream->nOpenFiles--;
621 : }
622 0 : }
|