1 /******************************************************************************
2 *
3 * Project: UK NTF Reader
4 * Purpose: Implements OGRNTFDataSource class
5 * Author: Frank Warmerdam, warmerdam@pobox.com
6 *
7 ******************************************************************************
8 * Copyright (c) 1999, 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 "ntf.h"
30 #include "cpl_conv.h"
31 #include "cpl_string.h"
32
33 CPL_CVSID("$Id: ogrntfdatasource.cpp 8e5eeb35bf76390e3134a4ea7076dab7d478ea0e 2018-11-14 22:55:13 +0100 Even Rouault $")
34
35 /************************************************************************/
36 /* OGRNTFDataSource() */
37 /************************************************************************/
38
OGRNTFDataSource()39 OGRNTFDataSource::OGRNTFDataSource() :
40 pszName(nullptr),
41 nLayers(0),
42 papoLayers(nullptr),
43 poFCLayer(nullptr),
44 iCurrentFC(0),
45 iCurrentReader(-1),
46 nCurrentPos(0),
47 nCurrentFID(0),
48 nNTFFileCount(0),
49 papoNTFFileReader(nullptr),
50 nFCCount(0),
51 papszFCNum(nullptr),
52 papszFCName(nullptr),
53 poSpatialRef(new OGRSpatialReference(
54 "PROJCS[\"OSGB 1936 / British National Grid\",GEOGCS[\"OSGB 1936\","
55 "DATUM[\"OSGB_1936\",SPHEROID[\"Airy 1830\",6377563.396,299.3249646,"
56 "AUTHORITY[\"EPSG\",\"7001\"]],AUTHORITY[\"EPSG\",\"6277\"]],"
57 "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
58 "UNIT[\"degree\",0.0174532925199433],AUTHORITY[\"EPSG\",\"4277\"]],"
59 "PROJECTION[\"Transverse_Mercator\"],"
60 "PARAMETER[\"latitude_of_origin\",49],"
61 "PARAMETER[\"central_meridian\",-2],"
62 "PARAMETER[\"scale_factor\",0.999601272],"
63 "PARAMETER[\"false_easting\",400000],"
64 "PARAMETER[\"false_northing\",-100000],"
65 "UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],"
66 "AUTHORITY[\"EPSG\",\"27700\"]]")),
67 papszOptions(nullptr)
68 {
69 poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
70
71 /* -------------------------------------------------------------------- */
72 /* Allow initialization of options from the environment. */
73 /* -------------------------------------------------------------------- */
74 if( getenv("OGR_NTF_OPTIONS") != nullptr )
75 {
76 papszOptions =
77 CSLTokenizeStringComplex( getenv("OGR_NTF_OPTIONS"), ",",
78 FALSE, FALSE );
79 }
80 }
81
82 /************************************************************************/
83 /* ~OGRNTFDataSource() */
84 /************************************************************************/
85
~OGRNTFDataSource()86 OGRNTFDataSource::~OGRNTFDataSource()
87
88 {
89 for( int i = 0; i < nNTFFileCount; i++ )
90 delete papoNTFFileReader[i];
91
92 CPLFree( papoNTFFileReader );
93
94 for( int i = 0; i < nLayers; i++ )
95 delete papoLayers[i];
96
97 if( poFCLayer != nullptr )
98 delete poFCLayer;
99
100 CPLFree( papoLayers );
101
102 CPLFree( pszName );
103
104 CSLDestroy( papszOptions );
105
106 CSLDestroy( papszFCNum );
107 CSLDestroy( papszFCName );
108
109 if( poSpatialRef )
110 poSpatialRef->Release();
111 }
112
113 /************************************************************************/
114 /* TestCapability() */
115 /************************************************************************/
116
TestCapability(const char *)117 int OGRNTFDataSource::TestCapability( const char * )
118
119 {
120 return FALSE;
121 }
122
123 /************************************************************************/
124 /* GetNamedLayer() */
125 /************************************************************************/
126
GetNamedLayer(const char * pszNameIn)127 OGRNTFLayer * OGRNTFDataSource::GetNamedLayer( const char * pszNameIn )
128
129 {
130 for( int i = 0; i < nLayers; i++ )
131 {
132 if( EQUAL(papoLayers[i]->GetLayerDefn()->GetName(),pszNameIn) )
133 return static_cast<OGRNTFLayer *>(papoLayers[i]);
134 }
135
136 return nullptr;
137 }
138
139 /************************************************************************/
140 /* AddLayer() */
141 /************************************************************************/
142
AddLayer(OGRLayer * poNewLayer)143 void OGRNTFDataSource::AddLayer( OGRLayer * poNewLayer )
144
145 {
146 papoLayers = static_cast<OGRLayer **>(
147 CPLRealloc( papoLayers, sizeof(void*) * ++nLayers ) );
148
149 papoLayers[nLayers-1] = poNewLayer;
150 }
151
152 /************************************************************************/
153 /* GetLayer() */
154 /************************************************************************/
155
GetLayer(int iLayer)156 OGRLayer *OGRNTFDataSource::GetLayer( int iLayer )
157
158 {
159 if( iLayer < 0 || iLayer > nLayers )
160 return nullptr;
161 else if( iLayer == nLayers )
162 return poFCLayer;
163 else
164 return papoLayers[iLayer];
165 }
166
167 /************************************************************************/
168 /* GetLayerCount() */
169 /************************************************************************/
170
GetLayerCount()171 int OGRNTFDataSource::GetLayerCount()
172
173 {
174 if( poFCLayer == nullptr )
175 return nLayers;
176 else
177 return nLayers + 1;
178 }
179
180 /************************************************************************/
181 /* Open() */
182 /************************************************************************/
183
Open(const char * pszFilename,int bTestOpen,char ** papszLimitedFileList)184 int OGRNTFDataSource::Open( const char * pszFilename, int bTestOpen,
185 char ** papszLimitedFileList )
186
187 {
188 VSIStatBufL stat;
189 char **papszFileList = nullptr;
190
191 pszName = CPLStrdup( pszFilename );
192
193 /* -------------------------------------------------------------------- */
194 /* Is the given path a directory or a regular file? */
195 /* -------------------------------------------------------------------- */
196 if( VSIStatL( pszFilename, &stat ) != 0
197 || (!VSI_ISDIR(stat.st_mode) && !VSI_ISREG(stat.st_mode)) )
198 {
199 if( !bTestOpen )
200 CPLError( CE_Failure, CPLE_AppDefined,
201 "%s is neither a file or directory, NTF access failed.\n",
202 pszFilename );
203
204 return FALSE;
205 }
206
207 /* -------------------------------------------------------------------- */
208 /* Build a list of filenames we figure are NTF files. */
209 /* -------------------------------------------------------------------- */
210 if( VSI_ISREG(stat.st_mode) )
211 {
212 papszFileList = CSLAddString( nullptr, pszFilename );
213 }
214 else
215 {
216 char **candidateFileList = VSIReadDir( pszFilename );
217
218 for( int i = 0;
219 candidateFileList != nullptr && candidateFileList[i] != nullptr;
220 i++ )
221 {
222 if( papszLimitedFileList != nullptr
223 && CSLFindString(papszLimitedFileList,
224 candidateFileList[i]) == -1 )
225 {
226 continue;
227 }
228
229 if( strlen(candidateFileList[i]) > 4
230 && STARTS_WITH_CI(candidateFileList[i] + strlen(candidateFileList[i])-4, ".ntf") )
231 {
232 char fullFilename[2048];
233
234 snprintf( fullFilename, sizeof(fullFilename), "%s%c%s",
235 pszFilename,
236 #ifdef WIN32
237 '\\',
238 #else
239 '/',
240 #endif
241 candidateFileList[i] );
242
243 papszFileList = CSLAddString( papszFileList, fullFilename );
244 }
245 }
246
247 CSLDestroy( candidateFileList );
248
249 if( CSLCount(papszFileList) == 0 )
250 {
251 if( !bTestOpen )
252 CPLError( CE_Failure, CPLE_OpenFailed,
253 "No candidate NTF files (.ntf) found in\n"
254 "directory: %s",
255 pszFilename );
256 CSLDestroy(papszFileList);
257 return FALSE;
258 }
259 }
260
261 /* -------------------------------------------------------------------- */
262 /* Loop over all these files trying to open them. In testopen */
263 /* mode we first read the first 80 characters, to verify that */
264 /* it looks like an NTF file. Note that we don't keep the file */
265 /* open ... we don't want to occupy a lot of file handles when */
266 /* handling a whole directory. */
267 /* -------------------------------------------------------------------- */
268 papoNTFFileReader = static_cast<NTFFileReader **>(
269 CPLCalloc(sizeof(void*), CSLCount(papszFileList)));
270
271 for( int i = 0; papszFileList != nullptr && papszFileList[i] != nullptr; i++ )
272 {
273 if( bTestOpen )
274 {
275 VSILFILE *fp = VSIFOpenL( papszFileList[i], "rb" );
276 if( fp == nullptr )
277 continue;
278
279 char szHeader[80] = {};
280 if( VSIFReadL( szHeader, 80, 1, fp ) < 1 )
281 {
282 VSIFCloseL( fp );
283 continue;
284 }
285
286 VSIFCloseL( fp );
287
288 if( !STARTS_WITH_CI(szHeader, "01") )
289 continue;
290
291 int j = 0; // Used after for.
292 for( ; j < 80; j++ )
293 {
294 if( szHeader[j] == 10 || szHeader[j] == 13 )
295 break;
296 }
297
298 if( j == 80 || (j > 0 && szHeader[j-1] != '%') )
299 continue;
300 }
301
302 NTFFileReader *poFR = new NTFFileReader( this );
303
304 if( !poFR->Open( papszFileList[i] ) )
305 {
306 delete poFR;
307 CSLDestroy( papszFileList );
308
309 return FALSE;
310 }
311
312 poFR->SetBaseFID( nNTFFileCount * 1000000 + 1 );
313 poFR->Close();
314
315 EnsureTileNameUnique( poFR );
316
317 papoNTFFileReader[nNTFFileCount++] = poFR;
318 }
319
320 CSLDestroy( papszFileList );
321
322 if( nNTFFileCount == 0 )
323 return FALSE;
324
325 /* -------------------------------------------------------------------- */
326 /* Establish generic layers. */
327 /* -------------------------------------------------------------------- */
328 EstablishGenericLayers();
329
330 /* -------------------------------------------------------------------- */
331 /* Loop over all the files, collecting a unique feature class */
332 /* listing. */
333 /* -------------------------------------------------------------------- */
334 for( int iSrcFile = 0; iSrcFile < nNTFFileCount; iSrcFile++ )
335 {
336 NTFFileReader *poSrcReader = papoNTFFileReader[iSrcFile];
337
338 for( int iSrcFC = 0; iSrcFC < poSrcReader->GetFCCount(); iSrcFC++ )
339 {
340 char *pszSrcFCName = nullptr;
341 char *pszSrcFCNum = nullptr;
342
343 poSrcReader->GetFeatureClass( iSrcFC, &pszSrcFCNum, &pszSrcFCName);
344
345 int iDstFC = 0;
346 for( ; iDstFC < nFCCount; iDstFC++ )
347 {
348 if( EQUAL(pszSrcFCNum,papszFCNum[iDstFC]) )
349 break;
350 }
351
352 if( iDstFC >= nFCCount )
353 {
354 nFCCount++;
355 papszFCNum = CSLAddString(papszFCNum,pszSrcFCNum);
356 papszFCName = CSLAddString(papszFCName,pszSrcFCName);
357 }
358 }
359 }
360
361 /* -------------------------------------------------------------------- */
362 /* Create a new layer specifically for feature classes. */
363 /* -------------------------------------------------------------------- */
364 if( nFCCount > 0 )
365 poFCLayer = new OGRNTFFeatureClassLayer( this );
366 else
367 poFCLayer = nullptr;
368
369 return TRUE;
370 }
371
372 /************************************************************************/
373 /* ResetReading() */
374 /* */
375 /* Cleanup, and start over. */
376 /************************************************************************/
377
ResetReading()378 void OGRNTFDataSource::ResetReading()
379
380 {
381 for( int i = 0; i < nNTFFileCount; i++ )
382 papoNTFFileReader[i]->Close();
383
384 iCurrentReader = -1;
385 nCurrentPos = (vsi_l_offset)-1;
386 nCurrentFID = 1;
387 iCurrentFC = 0;
388 }
389
390 /************************************************************************/
391 /* GetNextFeature() */
392 /************************************************************************/
393
GetNextFeature(OGRLayer ** ppoBelongingLayer,double * pdfProgressPct,GDALProgressFunc,void *)394 OGRFeature *OGRNTFDataSource::GetNextFeature( OGRLayer** ppoBelongingLayer,
395 double* pdfProgressPct,
396 GDALProgressFunc /* pfnProgress */,
397 void* /* pProgressData */ )
398
399 {
400 if( pdfProgressPct != nullptr )
401 *pdfProgressPct = 0.0;
402 if( ppoBelongingLayer != nullptr )
403 *ppoBelongingLayer = nullptr;
404
405 OGRFeature *poFeature = nullptr;
406
407 /* -------------------------------------------------------------------- */
408 /* If we have already read all the conventional features, we */
409 /* should try and return feature class features. */
410 /* -------------------------------------------------------------------- */
411 if( iCurrentReader == nNTFFileCount )
412 {
413 if( iCurrentFC < nFCCount )
414 return poFCLayer->GetFeature( iCurrentFC++ );
415 else
416 return nullptr;
417 }
418
419 /* -------------------------------------------------------------------- */
420 /* Do we need to open a file? */
421 /* -------------------------------------------------------------------- */
422 if( iCurrentReader == -1 )
423 {
424 iCurrentReader++;
425 nCurrentPos = (vsi_l_offset)-1;
426 }
427
428 if( papoNTFFileReader[iCurrentReader]->GetFP() == nullptr )
429 {
430 papoNTFFileReader[iCurrentReader]->Open();
431 }
432
433 /* -------------------------------------------------------------------- */
434 /* Ensure we are reading on from the same point we were reading */
435 /* from for the last feature, even if some other access */
436 /* mechanism has moved the file pointer. */
437 /* -------------------------------------------------------------------- */
438 if( nCurrentPos != (vsi_l_offset)-1 )
439 papoNTFFileReader[iCurrentReader]->SetFPPos( nCurrentPos,
440 nCurrentFID );
441
442 /* -------------------------------------------------------------------- */
443 /* Read a feature. If we get NULL the file must be all */
444 /* consumed, advance to the next file. */
445 /* -------------------------------------------------------------------- */
446 poFeature = papoNTFFileReader[iCurrentReader]->ReadOGRFeature();
447 if( poFeature == nullptr )
448 {
449 papoNTFFileReader[iCurrentReader]->Close();
450 if( GetOption("CACHING") != nullptr
451 && EQUAL(GetOption("CACHING"),"OFF") )
452 papoNTFFileReader[iCurrentReader]->DestroyIndex();
453
454 iCurrentReader++;
455 nCurrentPos = (vsi_l_offset)-1;
456 nCurrentFID = 1;
457
458 poFeature = GetNextFeature(nullptr, nullptr, nullptr, nullptr);
459 }
460 else
461 {
462 papoNTFFileReader[iCurrentReader]->GetFPPos(&nCurrentPos,
463 &nCurrentFID);
464 }
465
466 return poFeature;
467 }
468
469 /************************************************************************/
470 /* GetFeatureClass() */
471 /************************************************************************/
472
GetFeatureClass(int iFCIndex,char ** ppszFCId,char ** ppszFCName)473 int OGRNTFDataSource::GetFeatureClass( int iFCIndex,
474 char ** ppszFCId,
475 char ** ppszFCName )
476
477 {
478 if( iFCIndex < 0 || iFCIndex >= nFCCount )
479 {
480 *ppszFCId = nullptr;
481 *ppszFCName = nullptr;
482 return FALSE;
483 }
484 else
485 {
486 *ppszFCId = papszFCNum[iFCIndex];
487 *ppszFCName = papszFCName[iFCIndex];
488 return TRUE;
489 }
490 }
491
492 /************************************************************************/
493 /* SetOptions() */
494 /************************************************************************/
495
SetOptionList(char ** papszNewOptions)496 void OGRNTFDataSource::SetOptionList( char ** papszNewOptions )
497
498 {
499 CSLDestroy( papszOptions );
500 papszOptions = CSLDuplicate( papszNewOptions );
501 }
502
503 /************************************************************************/
504 /* GetOption() */
505 /************************************************************************/
506
GetOption(const char * pszOption)507 const char *OGRNTFDataSource::GetOption( const char * pszOption )
508
509 {
510 return CSLFetchNameValue( papszOptions, pszOption );
511 }
512
513 /************************************************************************/
514 /* EnsureTileNameUnique() */
515 /* */
516 /* This method is called with an NTFFileReader to ensure that */
517 /* its tilename is unique relative to all the readers already */
518 /* assigned to this data source. If not, a unique name is */
519 /* selected for it and assigned. This method should not be */
520 /* called with readers that are already attached to the data */
521 /* source. */
522 /************************************************************************/
523
EnsureTileNameUnique(NTFFileReader * poNewReader)524 void OGRNTFDataSource::EnsureTileNameUnique( NTFFileReader *poNewReader )
525
526 {
527 int iSequenceNumber = -1;
528 bool bIsUnique = false;
529 char szCandidateName[11] = {};
530
531 do
532 {
533 bIsUnique = TRUE;
534 if( iSequenceNumber++ == -1 )
535 strncpy( szCandidateName, poNewReader->GetTileName(),
536 sizeof(szCandidateName) - 1 );
537 else
538 snprintf( szCandidateName, sizeof(szCandidateName), "%010d", iSequenceNumber );
539
540 for( int iReader = 0; iReader < nNTFFileCount && bIsUnique; iReader++ )
541 {
542 const char* pszTileName = GetFileReader( iReader )->GetTileName();
543 if( pszTileName != nullptr &&
544 strcmp( szCandidateName, pszTileName ) == 0 )
545 {
546 bIsUnique = FALSE;
547 }
548 }
549 } while( !bIsUnique );
550
551 if( iSequenceNumber > 0 )
552 {
553 poNewReader->OverrideTileName( szCandidateName );
554 CPLError( CE_Warning, CPLE_AppDefined,
555 "Forcing TILE_REF to `%s' on file %s\n"
556 "to avoid conflict with other tiles in this data source.",
557 szCandidateName, poNewReader->GetFilename() );
558 }
559 }
560