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