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