Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: BSB Reader
4 : * Purpose: BSBDataset implementation for BSB format.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2001, Frank Warmerdam <warmerdam@pobox.com>
9 : * Copyright (c) 2008-2012, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "bsb_read.h"
15 : #include "cpl_string.h"
16 : #include "gdal_frmts.h"
17 : #include "gdal_colortable.h"
18 : #include "gdal_pam.h"
19 : #include "gdal_driver.h"
20 : #include "gdal_drivermanager.h"
21 : #include "gdal_openinfo.h"
22 : #include "gdal_cpp_functions.h"
23 : #include "ogr_spatialref.h"
24 :
25 : #include <cstdlib>
26 : #include <algorithm>
27 :
28 : // Disabled as people may worry about the BSB patent
29 : // #define BSB_CREATE
30 :
31 : /************************************************************************/
32 : /* ==================================================================== */
33 : /* BSBDataset */
34 : /* ==================================================================== */
35 : /************************************************************************/
36 :
37 : class BSBRasterBand;
38 :
39 : class BSBDataset final : public GDALPamDataset
40 : {
41 : int nGCPCount;
42 : GDAL_GCP *pasGCPList;
43 : OGRSpatialReference m_oGCPSRS{};
44 :
45 : GDALGeoTransform m_gt{};
46 : int bGeoTransformSet;
47 :
48 : void ScanForGCPs(bool isNos, const char *pszFilename);
49 : void ScanForGCPsNos(const char *pszFilename);
50 : void ScanForGCPsBSB();
51 :
52 : void ScanForCutline();
53 :
54 : static int IdentifyInternal(GDALOpenInfo *, bool &isNosOut);
55 :
56 : public:
57 : BSBDataset();
58 : ~BSBDataset() override;
59 :
60 : BSBInfo *psInfo;
61 :
62 : static GDALDataset *Open(GDALOpenInfo *);
63 : static int Identify(GDALOpenInfo *);
64 :
65 : int GetGCPCount() override;
66 : const OGRSpatialReference *GetSpatialRef() const override;
67 : const GDAL_GCP *GetGCPs() override;
68 :
69 : CPLErr GetGeoTransform(GDALGeoTransform >) const override;
70 : const OGRSpatialReference *GetGCPSpatialRef() const override;
71 : };
72 :
73 : /************************************************************************/
74 : /* ==================================================================== */
75 : /* BSBRasterBand */
76 : /* ==================================================================== */
77 : /************************************************************************/
78 :
79 : class BSBRasterBand final : public GDALPamRasterBand
80 : {
81 : GDALColorTable oCT;
82 :
83 : public:
84 : explicit BSBRasterBand(BSBDataset *);
85 :
86 : CPLErr IReadBlock(int, int, void *) override;
87 : GDALColorTable *GetColorTable() override;
88 : GDALColorInterp GetColorInterpretation() override;
89 : };
90 :
91 : /************************************************************************/
92 : /* BSBRasterBand() */
93 : /************************************************************************/
94 :
95 13 : BSBRasterBand::BSBRasterBand(BSBDataset *poDSIn)
96 :
97 : {
98 13 : poDS = poDSIn;
99 13 : nBand = 1;
100 :
101 13 : eDataType = GDT_Byte;
102 :
103 13 : nBlockXSize = poDS->GetRasterXSize();
104 13 : nBlockYSize = 1;
105 :
106 : // Note that the first color table entry is dropped, everything is
107 : // shifted down.
108 1430 : for (int i = 0; i < poDSIn->psInfo->nPCTSize - 1; i++)
109 : {
110 1417 : GDALColorEntry oColor = {poDSIn->psInfo->pabyPCT[i * 3 + 0 + 3],
111 1417 : poDSIn->psInfo->pabyPCT[i * 3 + 1 + 3],
112 1417 : poDSIn->psInfo->pabyPCT[i * 3 + 2 + 3], 255};
113 :
114 1417 : oCT.SetColorEntry(i, &oColor);
115 : }
116 13 : }
117 :
118 : /************************************************************************/
119 : /* IReadBlock() */
120 : /************************************************************************/
121 :
122 250 : CPLErr BSBRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
123 : void *pImage)
124 : {
125 250 : BSBDataset *poGDS = cpl::down_cast<BSBDataset *>(poDS);
126 250 : GByte *pabyScanline = (GByte *)pImage;
127 :
128 250 : if (BSBReadScanline(poGDS->psInfo, nBlockYOff, pabyScanline))
129 : {
130 12648 : for (int i = 0; i < nBlockXSize; i++)
131 : {
132 : /* The indices start at 1, except in case of some charts */
133 : /* where there are missing values, which are filled to 0 */
134 : /* by BSBReadScanline */
135 12400 : if (pabyScanline[i] > 0)
136 12400 : pabyScanline[i] -= 1;
137 : }
138 :
139 248 : return CE_None;
140 : }
141 :
142 2 : return CE_Failure;
143 : }
144 :
145 : /************************************************************************/
146 : /* GetColorTable() */
147 : /************************************************************************/
148 :
149 0 : GDALColorTable *BSBRasterBand::GetColorTable()
150 :
151 : {
152 0 : return &oCT;
153 : }
154 :
155 : /************************************************************************/
156 : /* GetColorInterpretation() */
157 : /************************************************************************/
158 :
159 0 : GDALColorInterp BSBRasterBand::GetColorInterpretation()
160 :
161 : {
162 0 : return GCI_PaletteIndex;
163 : }
164 :
165 : /************************************************************************/
166 : /* ==================================================================== */
167 : /* BSBDataset */
168 : /* ==================================================================== */
169 : /************************************************************************/
170 :
171 : /************************************************************************/
172 : /* BSBDataset() */
173 : /************************************************************************/
174 :
175 13 : BSBDataset::BSBDataset()
176 : : nGCPCount(0), pasGCPList(nullptr), bGeoTransformSet(FALSE),
177 13 : psInfo(nullptr)
178 : {
179 13 : m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
180 13 : m_oGCPSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
181 13 : }
182 :
183 : /************************************************************************/
184 : /* ~BSBDataset() */
185 : /************************************************************************/
186 :
187 26 : BSBDataset::~BSBDataset()
188 :
189 : {
190 13 : FlushCache(true);
191 :
192 13 : GDALDeinitGCPs(nGCPCount, pasGCPList);
193 13 : CPLFree(pasGCPList);
194 :
195 13 : if (psInfo != nullptr)
196 13 : BSBClose(psInfo);
197 26 : }
198 :
199 : /************************************************************************/
200 : /* GetGeoTransform() */
201 : /************************************************************************/
202 :
203 1 : CPLErr BSBDataset::GetGeoTransform(GDALGeoTransform >) const
204 :
205 : {
206 1 : gt = m_gt;
207 :
208 1 : if (bGeoTransformSet)
209 1 : return CE_None;
210 :
211 0 : return CE_Failure;
212 : }
213 :
214 : /************************************************************************/
215 : /* GetSpatialRef() */
216 : /************************************************************************/
217 :
218 1 : const OGRSpatialReference *BSBDataset::GetSpatialRef() const
219 :
220 : {
221 1 : if (bGeoTransformSet)
222 1 : return &m_oGCPSRS;
223 :
224 0 : return nullptr;
225 : }
226 :
227 : /************************************************************************/
228 : /* GDALHeuristicDatelineWrap() */
229 : /************************************************************************/
230 :
231 3 : static void GDALHeuristicDatelineWrap(int nPointCount, double *padfX)
232 :
233 : {
234 3 : if (nPointCount < 2)
235 3 : return;
236 :
237 : /* -------------------------------------------------------------------- */
238 : /* Work out what the longitude range will be centering on the */
239 : /* prime meridian (-180 to 180) and centering on the dateline */
240 : /* (0 to 360). */
241 : /* -------------------------------------------------------------------- */
242 : /* Following inits are useless but keep GCC happy */
243 3 : double dfX_PM_Min = 0.0;
244 3 : double dfX_PM_Max = 0.0;
245 3 : double dfX_Dateline_Min = 0.0;
246 3 : double dfX_Dateline_Max = 0.0;
247 :
248 110 : for (int i = 0; i < nPointCount; i++)
249 : {
250 107 : double dfX_PM = padfX[i];
251 107 : if (dfX_PM > 180)
252 0 : dfX_PM -= 360.0;
253 :
254 107 : double dfX_Dateline = padfX[i];
255 107 : if (dfX_Dateline < 0)
256 0 : dfX_Dateline += 360.0;
257 :
258 107 : if (i == 0)
259 : {
260 3 : dfX_PM_Min = dfX_PM;
261 3 : dfX_PM_Max = dfX_PM;
262 3 : dfX_Dateline_Min = dfX_Dateline;
263 3 : dfX_Dateline_Max = dfX_Dateline;
264 : }
265 : else
266 : {
267 104 : dfX_PM_Min = std::min(dfX_PM_Min, dfX_PM);
268 104 : dfX_PM_Max = std::max(dfX_PM_Max, dfX_PM);
269 104 : dfX_Dateline_Min = std::min(dfX_Dateline_Min, dfX_Dateline);
270 104 : dfX_Dateline_Max = std::max(dfX_Dateline_Max, dfX_Dateline);
271 : }
272 : }
273 :
274 : /* -------------------------------------------------------------------- */
275 : /* Do nothing if the range is always fairly small - no apparent */
276 : /* wrapping issues. */
277 : /* -------------------------------------------------------------------- */
278 3 : if ((dfX_PM_Max - dfX_PM_Min) < 270.0 &&
279 3 : (dfX_Dateline_Max - dfX_Dateline_Min) < 270.0)
280 3 : return;
281 :
282 : /* -------------------------------------------------------------------- */
283 : /* Do nothing if both approach have a wide range - best not to */
284 : /* fiddle if we aren't sure we are improving things. */
285 : /* -------------------------------------------------------------------- */
286 0 : if ((dfX_PM_Max - dfX_PM_Min) > 270.0 &&
287 0 : (dfX_Dateline_Max - dfX_Dateline_Min) > 270.0)
288 0 : return;
289 :
290 : /* -------------------------------------------------------------------- */
291 : /* Pick which way to transform things. */
292 : /* -------------------------------------------------------------------- */
293 : bool bUsePMWrap;
294 :
295 0 : if ((dfX_PM_Max - dfX_PM_Min) > 270.0 &&
296 0 : (dfX_Dateline_Max - dfX_Dateline_Min) < 270.0)
297 0 : bUsePMWrap = false;
298 : else
299 0 : bUsePMWrap = true;
300 :
301 : /* -------------------------------------------------------------------- */
302 : /* Apply rewrapping. */
303 : /* -------------------------------------------------------------------- */
304 0 : for (int i = 0; i < nPointCount; i++)
305 : {
306 0 : if (bUsePMWrap)
307 : {
308 0 : if (padfX[i] > 180)
309 0 : padfX[i] -= 360.0;
310 : }
311 : else
312 : {
313 0 : if (padfX[i] < 0)
314 0 : padfX[i] += 360.0;
315 : }
316 : }
317 : }
318 :
319 : /************************************************************************/
320 : /* GDALHeuristicDatelineWrapGCPs() */
321 : /************************************************************************/
322 :
323 3 : static void GDALHeuristicDatelineWrapGCPs(int nPointCount, GDAL_GCP *pasGCPList)
324 : {
325 6 : std::vector<double> oadfX;
326 :
327 3 : oadfX.resize(nPointCount);
328 110 : for (int i = 0; i < nPointCount; i++)
329 107 : oadfX[i] = pasGCPList[i].dfGCPX;
330 :
331 3 : GDALHeuristicDatelineWrap(nPointCount, &(oadfX[0]));
332 :
333 110 : for (int i = 0; i < nPointCount; i++)
334 107 : pasGCPList[i].dfGCPX = oadfX[i];
335 3 : }
336 :
337 : /************************************************************************/
338 : /* ScanForGCPs() */
339 : /************************************************************************/
340 :
341 13 : void BSBDataset::ScanForGCPs(bool isNos, const char *pszFilename)
342 :
343 : {
344 : /* -------------------------------------------------------------------- */
345 : /* Collect GCPs as appropriate to source. */
346 : /* -------------------------------------------------------------------- */
347 13 : nGCPCount = 0;
348 :
349 13 : if (isNos)
350 : {
351 0 : ScanForGCPsNos(pszFilename);
352 : }
353 : else
354 : {
355 13 : ScanForGCPsBSB();
356 : }
357 :
358 : /* -------------------------------------------------------------------- */
359 : /* Apply heuristics to re-wrap GCPs to maintain continuity */
360 : /* over the international dateline. */
361 : /* -------------------------------------------------------------------- */
362 13 : if (nGCPCount > 1)
363 3 : GDALHeuristicDatelineWrapGCPs(nGCPCount, pasGCPList);
364 :
365 : /* -------------------------------------------------------------------- */
366 : /* Collect coordinate system related parameters from header. */
367 : /* -------------------------------------------------------------------- */
368 13 : const char *pszKNP = nullptr;
369 13 : const char *pszKNQ = nullptr;
370 :
371 1765 : for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
372 : {
373 1752 : if (STARTS_WITH_CI(psInfo->papszHeader[i], "KNP/"))
374 : {
375 13 : pszKNP = psInfo->papszHeader[i];
376 13 : SetMetadataItem("BSB_KNP", pszKNP + 4);
377 : }
378 1752 : if (STARTS_WITH_CI(psInfo->papszHeader[i], "KNQ/"))
379 : {
380 3 : pszKNQ = psInfo->papszHeader[i];
381 3 : SetMetadataItem("BSB_KNQ", pszKNQ + 4);
382 : }
383 : }
384 :
385 : /* -------------------------------------------------------------------- */
386 : /* Can we derive a reasonable coordinate system definition for */
387 : /* this file? For now we keep it simple, just handling */
388 : /* mercator. In the future we should consider others. */
389 : /* -------------------------------------------------------------------- */
390 26 : CPLString osUnderlyingSRS;
391 13 : if (pszKNP != nullptr)
392 : {
393 13 : const char *pszPR = strstr(pszKNP, "PR=");
394 13 : const char *pszGD = strstr(pszKNP, "GD=");
395 13 : const char *pszGEOGCS = SRS_WKT_WGS84_LAT_LONG;
396 26 : CPLString osPP;
397 :
398 : // Capture the PP string.
399 13 : const char *pszValue = strstr(pszKNP, "PP=");
400 13 : const char *pszEnd = pszValue ? strstr(pszValue, ",") : nullptr;
401 13 : if (pszValue && pszEnd)
402 13 : osPP.assign(pszValue + 3, pszEnd - pszValue - 3);
403 :
404 : // Look at the datum
405 13 : if (pszGD == nullptr)
406 : {
407 : /* no match. We'll default to EPSG:4326 */
408 : }
409 13 : else if (STARTS_WITH_CI(pszGD, "GD=European 1950"))
410 : {
411 0 : pszGEOGCS =
412 : "GEOGCS[\"ED50\",DATUM[\"European_Datum_1950\",SPHEROID["
413 : "\"International "
414 : "1924\",6378388,297,AUTHORITY[\"EPSG\",\"7022\"]],TOWGS84[-87,-"
415 : "98,-121,0,0,0,0],AUTHORITY[\"EPSG\",\"6230\"]],PRIMEM["
416 : "\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\","
417 : "0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY["
418 : "\"EPSG\",\"4230\"]]";
419 : }
420 :
421 : // Look at the projection
422 13 : if (pszPR == nullptr)
423 : {
424 : /* no match */
425 : }
426 13 : else if (STARTS_WITH_CI(pszPR, "PR=MERCATOR") && nGCPCount > 0)
427 : {
428 : // We somewhat arbitrarily select our first GCPX as our
429 : // central meridian. This is mostly helpful to ensure
430 : // that regions crossing the dateline will be contiguous
431 : // in mercator.
432 1 : osUnderlyingSRS.Printf(
433 : "PROJCS[\"Global "
434 : "Mercator\",%s,PROJECTION[\"Mercator_2SP\"],PARAMETER["
435 : "\"standard_parallel_1\",0],PARAMETER[\"latitude_of_origin\",0]"
436 : ",PARAMETER[\"central_meridian\",%d],PARAMETER[\"false_"
437 : "easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"Meter\",1]"
438 : "]",
439 1 : pszGEOGCS, (int)pasGCPList[0].dfGCPX);
440 : }
441 :
442 13 : else if (STARTS_WITH_CI(pszPR, "PR=TRANSVERSE MERCATOR") &&
443 1 : !osPP.empty())
444 : {
445 :
446 : osUnderlyingSRS.Printf(
447 : "PROJCS[\"unnamed\",%s,PROJECTION[\"Transverse_Mercator\"],"
448 : "PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_"
449 : "meridian\",%s],PARAMETER[\"scale_factor\",1],PARAMETER["
450 : "\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT["
451 : "\"Meter\",1]]",
452 1 : pszGEOGCS, osPP.c_str());
453 : }
454 :
455 11 : else if (STARTS_WITH_CI(pszPR, "PR=UNIVERSAL TRANSVERSE MERCATOR") &&
456 0 : !osPP.empty())
457 : {
458 : // This is not *really* UTM unless the central meridian
459 : // matches a zone which it does not in some (most?) maps.
460 : osUnderlyingSRS.Printf(
461 : "PROJCS[\"unnamed\",%s,PROJECTION[\"Transverse_Mercator\"],"
462 : "PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_"
463 : "meridian\",%s],PARAMETER[\"scale_factor\",0.9996],PARAMETER["
464 : "\"false_easting\",500000],PARAMETER[\"false_northing\",0],"
465 : "UNIT[\"Meter\",1]]",
466 0 : pszGEOGCS, osPP.c_str());
467 : }
468 :
469 11 : else if (STARTS_WITH_CI(pszPR, "PR=POLYCONIC") && !osPP.empty())
470 : {
471 : osUnderlyingSRS.Printf(
472 : "PROJCS[\"unnamed\",%s,PROJECTION[\"Polyconic\"],PARAMETER["
473 : "\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",%s],"
474 : "PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0]"
475 : ",UNIT[\"Meter\",1]]",
476 0 : pszGEOGCS, osPP.c_str());
477 : }
478 :
479 23 : else if (STARTS_WITH_CI(pszPR, "PR=LAMBERT CONFORMAL CONIC") &&
480 11 : !osPP.empty() && pszKNQ != nullptr)
481 : {
482 2 : CPLString osP2, osP3;
483 :
484 : // Capture the KNQ/P2 string.
485 1 : pszValue = strstr(pszKNQ, "P2=");
486 1 : if (pszValue)
487 1 : pszEnd = strstr(pszValue, ",");
488 1 : if (pszValue && pszEnd)
489 1 : osP2.assign(pszValue + 3, pszEnd - pszValue - 3);
490 :
491 : // Capture the KNQ/P3 string.
492 1 : pszValue = strstr(pszKNQ, "P3=");
493 1 : if (pszValue)
494 1 : pszEnd = strstr(pszValue, ",");
495 1 : if (pszValue)
496 : {
497 1 : if (pszEnd)
498 1 : osP3.assign(pszValue + 3, pszEnd - pszValue - 3);
499 : else
500 0 : osP3.assign(pszValue + 3);
501 : }
502 :
503 1 : if (!osP2.empty() && !osP3.empty())
504 : osUnderlyingSRS.Printf(
505 : "PROJCS[\"unnamed\",%s,PROJECTION[\"Lambert_Conformal_"
506 : "Conic_2SP\"],PARAMETER[\"standard_parallel_1\",%s],"
507 : "PARAMETER[\"standard_parallel_2\",%s],PARAMETER["
508 : "\"latitude_of_origin\",0.0],PARAMETER[\"central_"
509 : "meridian\",%s],PARAMETER[\"false_easting\",0.0],PARAMETER["
510 : "\"false_northing\",0.0],UNIT[\"Meter\",1]]",
511 1 : pszGEOGCS, osP2.c_str(), osP3.c_str(), osPP.c_str());
512 : }
513 : }
514 :
515 : /* -------------------------------------------------------------------- */
516 : /* If we got an alternate underlying coordinate system, try */
517 : /* converting the GCPs to that coordinate system. */
518 : /* -------------------------------------------------------------------- */
519 13 : if (osUnderlyingSRS.length() > 0)
520 : {
521 6 : OGRSpatialReference oGeog_SRS, oProjected_SRS;
522 :
523 3 : oProjected_SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
524 3 : oProjected_SRS.SetFromUserInput(osUnderlyingSRS);
525 3 : oGeog_SRS.CopyGeogCSFrom(&oProjected_SRS);
526 3 : oGeog_SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
527 :
528 : OGRCoordinateTransformation *poCT =
529 3 : OGRCreateCoordinateTransformation(&oGeog_SRS, &oProjected_SRS);
530 3 : if (poCT != nullptr)
531 : {
532 110 : for (int i = 0; i < nGCPCount; i++)
533 : {
534 107 : poCT->Transform(1, &(pasGCPList[i].dfGCPX),
535 107 : &(pasGCPList[i].dfGCPY),
536 107 : &(pasGCPList[i].dfGCPZ));
537 : }
538 :
539 3 : m_oGCPSRS.importFromWkt(osUnderlyingSRS.c_str());
540 :
541 3 : delete poCT;
542 : }
543 : else
544 0 : CPLErrorReset();
545 : }
546 :
547 : /* -------------------------------------------------------------------- */
548 : /* Attempt to prepare a geotransform from the GCPs. */
549 : /* -------------------------------------------------------------------- */
550 13 : if (GDALGCPsToGeoTransform(nGCPCount, pasGCPList, m_gt.data(), FALSE))
551 : {
552 2 : bGeoTransformSet = TRUE;
553 : }
554 13 : }
555 :
556 : /************************************************************************/
557 : /* ScanForGCPsNos() */
558 : /* */
559 : /* Nos files have an accompanying .geo file, that contains some */
560 : /* of the information normally contained in the header section */
561 : /* with BSB files. we try and open a file with the same name, */
562 : /* but a .geo extension, and look for lines like... */
563 : /* PointX=long lat line pixel (using the same naming system */
564 : /* as BSB) Point1=-22.0000 64.250000 197 744 */
565 : /************************************************************************/
566 :
567 0 : void BSBDataset::ScanForGCPsNos(const char *pszFilename)
568 : {
569 0 : const std::string extension = CPLGetExtensionSafe(pszFilename);
570 :
571 : // pseudointelligently try and guess whether we want a .geo or a .GEO
572 0 : std::string geofile;
573 0 : if (extension.size() >= 2 && extension[1] == 'O')
574 : {
575 0 : geofile = CPLResetExtensionSafe(pszFilename, "GEO");
576 : }
577 : else
578 : {
579 0 : geofile = CPLResetExtensionSafe(pszFilename, "geo");
580 : }
581 :
582 0 : FILE *gfp = VSIFOpen(geofile.c_str(), "r"); // Text files
583 0 : if (gfp == nullptr)
584 : {
585 0 : CPLError(CE_Failure, CPLE_OpenFailed,
586 : "Couldn't find a matching .GEO file: %s", geofile.c_str());
587 0 : return;
588 : }
589 :
590 0 : char *thisLine = (char *)CPLMalloc(80); // FIXME
591 :
592 : // Count the GCPs (reference points) and seek the file pointer 'gfp' to the
593 : // starting point
594 0 : int fileGCPCount = 0;
595 0 : while (fgets(thisLine, 80, gfp))
596 : {
597 0 : if (STARTS_WITH_CI(thisLine, "Point"))
598 0 : fileGCPCount++;
599 : }
600 0 : VSIRewind(gfp);
601 :
602 : // Memory has not been allocated to fileGCPCount yet
603 0 : pasGCPList = (GDAL_GCP *)CPLCalloc(sizeof(GDAL_GCP), fileGCPCount + 1);
604 :
605 0 : while (fgets(thisLine, 80, gfp))
606 : {
607 0 : if (STARTS_WITH_CI(thisLine, "Point"))
608 : {
609 : // got a point line, turn it into a gcp
610 : char **Tokens =
611 0 : CSLTokenizeStringComplex(thisLine, "= ", FALSE, FALSE);
612 0 : if (CSLCount(Tokens) >= 5)
613 : {
614 0 : GDALInitGCPs(1, pasGCPList + nGCPCount);
615 0 : pasGCPList[nGCPCount].dfGCPX = CPLAtof(Tokens[1]);
616 0 : pasGCPList[nGCPCount].dfGCPY = CPLAtof(Tokens[2]);
617 0 : pasGCPList[nGCPCount].dfGCPPixel = CPLAtof(Tokens[4]);
618 0 : pasGCPList[nGCPCount].dfGCPLine = CPLAtof(Tokens[3]);
619 :
620 0 : CPLFree(pasGCPList[nGCPCount].pszId);
621 : char szName[50];
622 0 : snprintf(szName, sizeof(szName), "GCP_%d", nGCPCount + 1);
623 0 : pasGCPList[nGCPCount].pszId = CPLStrdup(szName);
624 :
625 0 : nGCPCount++;
626 : }
627 0 : CSLDestroy(Tokens);
628 : }
629 : }
630 :
631 0 : CPLFree(thisLine);
632 0 : VSIFClose(gfp);
633 : }
634 :
635 : /************************************************************************/
636 : /* ScanForGCPsBSB() */
637 : /************************************************************************/
638 :
639 13 : void BSBDataset::ScanForGCPsBSB()
640 : {
641 : /* -------------------------------------------------------------------- */
642 : /* Collect standalone GCPs. They look like: */
643 : /* */
644 : /* REF/1,115,2727,32.346666666667,-60.881666666667 */
645 : /* REF/n,pixel,line,lat,long */
646 : /* -------------------------------------------------------------------- */
647 13 : int fileGCPCount = 0;
648 :
649 : // Count the GCPs (reference points) in psInfo->papszHeader
650 1765 : for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
651 1752 : if (STARTS_WITH_CI(psInfo->papszHeader[i], "REF/"))
652 107 : fileGCPCount++;
653 :
654 : // Memory has not been allocated to fileGCPCount yet
655 13 : pasGCPList = (GDAL_GCP *)CPLCalloc(sizeof(GDAL_GCP), fileGCPCount + 1);
656 :
657 1765 : for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
658 : {
659 :
660 1752 : if (!STARTS_WITH_CI(psInfo->papszHeader[i], "REF/"))
661 1645 : continue;
662 :
663 214 : char **papszTokens = CSLTokenizeStringComplex(
664 107 : psInfo->papszHeader[i] + 4, ",", FALSE, FALSE);
665 :
666 107 : if (CSLCount(papszTokens) > 4)
667 : {
668 107 : GDALInitGCPs(1, pasGCPList + nGCPCount);
669 :
670 107 : pasGCPList[nGCPCount].dfGCPX = CPLAtof(papszTokens[4]);
671 107 : pasGCPList[nGCPCount].dfGCPY = CPLAtof(papszTokens[3]);
672 107 : pasGCPList[nGCPCount].dfGCPPixel = CPLAtof(papszTokens[1]);
673 107 : pasGCPList[nGCPCount].dfGCPLine = CPLAtof(papszTokens[2]);
674 :
675 107 : CPLFree(pasGCPList[nGCPCount].pszId);
676 107 : if (CSLCount(papszTokens) > 5)
677 : {
678 0 : pasGCPList[nGCPCount].pszId = CPLStrdup(papszTokens[5]);
679 : }
680 : else
681 : {
682 : char szName[50];
683 107 : snprintf(szName, sizeof(szName), "GCP_%d", nGCPCount + 1);
684 107 : pasGCPList[nGCPCount].pszId = CPLStrdup(szName);
685 : }
686 :
687 107 : nGCPCount++;
688 : }
689 107 : CSLDestroy(papszTokens);
690 : }
691 13 : }
692 :
693 : /************************************************************************/
694 : /* ScanForCutline() */
695 : /************************************************************************/
696 :
697 13 : void BSBDataset::ScanForCutline()
698 : {
699 : /* PLY: Border Polygon Record - coordinates of the panel within the
700 : * raster image, given in chart datum lat/long. Any shape polygon.
701 : * They look like:
702 : * PLY/1,32.346666666667,-60.881666666667
703 : * PLY/n,lat,long
704 : *
705 : * If found then we return it via a BSB_CUTLINE metadata item as a WKT
706 : * POLYGON.
707 : */
708 :
709 26 : std::string wkt;
710 1765 : for (int i = 0; psInfo->papszHeader[i] != nullptr; i++)
711 : {
712 1752 : if (!STARTS_WITH_CI(psInfo->papszHeader[i], "PLY/"))
713 1731 : continue;
714 :
715 : const CPLStringList aosTokens(
716 42 : CSLTokenizeString2(psInfo->papszHeader[i] + 4, ",", 0));
717 :
718 21 : if (aosTokens.size() >= 3)
719 : {
720 21 : if (wkt.empty())
721 2 : wkt = "POLYGON ((";
722 : else
723 19 : wkt += ',';
724 21 : wkt += aosTokens[2];
725 21 : wkt += ' ';
726 21 : wkt += aosTokens[1];
727 : }
728 : }
729 :
730 13 : if (!wkt.empty())
731 : {
732 2 : wkt += "))";
733 2 : SetMetadataItem("BSB_CUTLINE", wkt.c_str());
734 : }
735 13 : }
736 :
737 : /************************************************************************/
738 : /* IdentifyInternal() */
739 : /************************************************************************/
740 :
741 62377 : int BSBDataset::IdentifyInternal(GDALOpenInfo *poOpenInfo, bool &isNosOut)
742 :
743 : {
744 : /* -------------------------------------------------------------------- */
745 : /* Check for BSB/ keyword. */
746 : /* -------------------------------------------------------------------- */
747 62377 : isNosOut = false;
748 :
749 62377 : if (poOpenInfo->nHeaderBytes < 1000)
750 58126 : return FALSE;
751 :
752 4251 : int i = 0;
753 5674640 : for (; i < poOpenInfo->nHeaderBytes - 4; i++)
754 : {
755 5670420 : if (poOpenInfo->pabyHeader[i + 0] == 'B' &&
756 15871 : poOpenInfo->pabyHeader[i + 1] == 'S' &&
757 105 : poOpenInfo->pabyHeader[i + 2] == 'B' &&
758 28 : poOpenInfo->pabyHeader[i + 3] == '/')
759 26 : break;
760 5670390 : if (poOpenInfo->pabyHeader[i + 0] == 'N' &&
761 16605 : poOpenInfo->pabyHeader[i + 1] == 'O' &&
762 600 : poOpenInfo->pabyHeader[i + 2] == 'S' &&
763 12 : poOpenInfo->pabyHeader[i + 3] == '/')
764 : {
765 0 : isNosOut = true;
766 0 : break;
767 : }
768 5670390 : if (poOpenInfo->pabyHeader[i + 0] == 'W' &&
769 5092 : poOpenInfo->pabyHeader[i + 1] == 'X' &&
770 133 : poOpenInfo->pabyHeader[i + 2] == '\\' &&
771 0 : poOpenInfo->pabyHeader[i + 3] == '8')
772 0 : break;
773 : }
774 :
775 4251 : if (i == poOpenInfo->nHeaderBytes - 4)
776 4228 : return FALSE;
777 :
778 : /* Additional test to avoid false positive. See #2881 */
779 23 : const char *pszHeader =
780 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
781 23 : const char *pszShiftedHeader = pszHeader + i;
782 23 : const char *pszRA = strstr(pszShiftedHeader, "RA=");
783 23 : if (pszRA == nullptr) /* This may be a NO1 file */
784 0 : pszRA = strstr(pszShiftedHeader, "[JF");
785 23 : if (pszRA == nullptr)
786 0 : return FALSE;
787 23 : if (pszRA - pszShiftedHeader > 100 && !strstr(pszHeader, "VER/") &&
788 0 : !strstr(pszHeader, "KNP/") && !strstr(pszHeader, "KNQ/") &&
789 0 : !strstr(pszHeader, "RGB/"))
790 0 : return FALSE;
791 :
792 23 : return TRUE;
793 : }
794 :
795 : /************************************************************************/
796 : /* Identify() */
797 : /************************************************************************/
798 :
799 62367 : int BSBDataset::Identify(GDALOpenInfo *poOpenInfo)
800 :
801 : {
802 : bool isNos;
803 62367 : return IdentifyInternal(poOpenInfo, isNos);
804 : }
805 :
806 : /************************************************************************/
807 : /* Open() */
808 : /************************************************************************/
809 :
810 13 : GDALDataset *BSBDataset::Open(GDALOpenInfo *poOpenInfo)
811 :
812 : {
813 13 : bool isNos = false;
814 13 : if (!IdentifyInternal(poOpenInfo, isNos))
815 0 : return nullptr;
816 :
817 13 : if (poOpenInfo->eAccess == GA_Update)
818 : {
819 0 : ReportUpdateNotSupportedByDriver("BSB");
820 0 : return nullptr;
821 : }
822 :
823 : /* -------------------------------------------------------------------- */
824 : /* Create a corresponding GDALDataset. */
825 : /* -------------------------------------------------------------------- */
826 13 : BSBDataset *poDS = new BSBDataset();
827 :
828 : /* -------------------------------------------------------------------- */
829 : /* Open the file. */
830 : /* -------------------------------------------------------------------- */
831 13 : poDS->psInfo = BSBOpen(poOpenInfo->pszFilename);
832 13 : if (poDS->psInfo == nullptr)
833 : {
834 0 : delete poDS;
835 0 : return nullptr;
836 : }
837 :
838 13 : poDS->nRasterXSize = poDS->psInfo->nXSize;
839 13 : poDS->nRasterYSize = poDS->psInfo->nYSize;
840 :
841 : /* -------------------------------------------------------------------- */
842 : /* Create band information objects. */
843 : /* -------------------------------------------------------------------- */
844 13 : poDS->SetBand(1, new BSBRasterBand(poDS));
845 :
846 13 : poDS->ScanForGCPs(isNos, poOpenInfo->pszFilename);
847 :
848 : /* -------------------------------------------------------------------- */
849 : /* Set CUTLINE metadata if a bounding polygon is available */
850 : /* -------------------------------------------------------------------- */
851 13 : poDS->ScanForCutline();
852 :
853 : /* -------------------------------------------------------------------- */
854 : /* Initialize any PAM information. */
855 : /* -------------------------------------------------------------------- */
856 13 : poDS->SetDescription(poOpenInfo->pszFilename);
857 13 : poDS->TryLoadXML();
858 :
859 13 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
860 :
861 13 : return poDS;
862 : }
863 :
864 : /************************************************************************/
865 : /* GetGCPCount() */
866 : /************************************************************************/
867 :
868 2 : int BSBDataset::GetGCPCount()
869 :
870 : {
871 2 : return nGCPCount;
872 : }
873 :
874 : /************************************************************************/
875 : /* GetGCPSpatialRef() */
876 : /************************************************************************/
877 :
878 1 : const OGRSpatialReference *BSBDataset::GetGCPSpatialRef() const
879 :
880 : {
881 1 : return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
882 : }
883 :
884 : /************************************************************************/
885 : /* GetGCP() */
886 : /************************************************************************/
887 :
888 1 : const GDAL_GCP *BSBDataset::GetGCPs()
889 :
890 : {
891 1 : return pasGCPList;
892 : }
893 :
894 : #ifdef BSB_CREATE
895 :
896 : /************************************************************************/
897 : /* BSBIsSRSOK() */
898 : /************************************************************************/
899 :
900 : static int BSBIsSRSOK(const char *pszWKT)
901 : {
902 : bool bOK = false;
903 : OGRSpatialReference oSRS, oSRS_WGS84, oSRS_NAD83;
904 :
905 : if (pszWKT != NULL && pszWKT[0] != '\0')
906 : {
907 : oSRS.importFromWkt(pszWKT);
908 :
909 : oSRS_WGS84.SetWellKnownGeogCS("WGS84");
910 : oSRS_NAD83.SetWellKnownGeogCS("NAD83");
911 : if ((oSRS.IsSameGeogCS(&oSRS_WGS84) ||
912 : oSRS.IsSameGeogCS(&oSRS_NAD83)) &&
913 : oSRS.IsGeographic() && oSRS.GetPrimeMeridian() == 0.0)
914 : {
915 : bOK = true;
916 : }
917 : }
918 :
919 : if (!bOK)
920 : {
921 : CPLError(CE_Warning, CPLE_NotSupported,
922 : "BSB only supports WGS84 or NAD83 geographic projections.\n");
923 : }
924 :
925 : return bOK;
926 : }
927 :
928 : /************************************************************************/
929 : /* BSBCreateCopy() */
930 : /************************************************************************/
931 :
932 : static GDALDataset *BSBCreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
933 : int bStrict, char ** /*papszOptions*/,
934 : GDALProgressFunc /*pfnProgress*/,
935 : void * /*pProgressData*/)
936 :
937 : {
938 : /* -------------------------------------------------------------------- */
939 : /* Some some rudimentary checks */
940 : /* -------------------------------------------------------------------- */
941 : const int nBands = poSrcDS->GetRasterCount();
942 : if (nBands != 1)
943 : {
944 : CPLError(CE_Failure, CPLE_NotSupported,
945 : "BSB driver only supports one band images.\n");
946 :
947 : return NULL;
948 : }
949 :
950 : if (poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte && bStrict)
951 : {
952 : CPLError(CE_Failure, CPLE_NotSupported,
953 : "BSB driver doesn't support data type %s. "
954 : "Only eight bit bands supported.\n",
955 : GDALGetDataTypeName(
956 : poSrcDS->GetRasterBand(1)->GetRasterDataType()));
957 :
958 : return NULL;
959 : }
960 :
961 : const int nXSize = poSrcDS->GetRasterXSize();
962 : const int nYSize = poSrcDS->GetRasterYSize();
963 :
964 : /* -------------------------------------------------------------------- */
965 : /* Open the output file. */
966 : /* -------------------------------------------------------------------- */
967 : BSBInfo *psBSB = BSBCreate(pszFilename, 0, 200, nXSize, nYSize);
968 : if (psBSB == NULL)
969 : return NULL;
970 :
971 : /* -------------------------------------------------------------------- */
972 : /* Prepare initial color table.colortable. */
973 : /* -------------------------------------------------------------------- */
974 : GDALRasterBand *poBand = poSrcDS->GetRasterBand(1);
975 : unsigned char abyPCT[771];
976 : int nPCTSize;
977 : int anRemap[256];
978 :
979 : abyPCT[0] = 0;
980 : abyPCT[1] = 0;
981 : abyPCT[2] = 0;
982 :
983 : if (poBand->GetColorTable() == NULL)
984 : {
985 : /* map greyscale down to 63 grey levels. */
986 : for (int iColor = 0; iColor < 256; iColor++)
987 : {
988 : int nOutValue = (int)(iColor / 4.1) + 1;
989 :
990 : anRemap[iColor] = nOutValue;
991 : abyPCT[nOutValue * 3 + 0] = (unsigned char)iColor;
992 : abyPCT[nOutValue * 3 + 1] = (unsigned char)iColor;
993 : abyPCT[nOutValue * 3 + 2] = (unsigned char)iColor;
994 : }
995 : nPCTSize = 64;
996 : }
997 : else
998 : {
999 : GDALColorTable *poCT = poBand->GetColorTable();
1000 : int nColorTableSize = poCT->GetColorEntryCount();
1001 : if (nColorTableSize > 255)
1002 : nColorTableSize = 255;
1003 :
1004 : for (int iColor = 0; iColor < nColorTableSize; iColor++)
1005 : {
1006 : GDALColorEntry sEntry;
1007 :
1008 : poCT->GetColorEntryAsRGB(iColor, &sEntry);
1009 :
1010 : anRemap[iColor] = iColor + 1;
1011 : abyPCT[(iColor + 1) * 3 + 0] = (unsigned char)sEntry.c1;
1012 : abyPCT[(iColor + 1) * 3 + 1] = (unsigned char)sEntry.c2;
1013 : abyPCT[(iColor + 1) * 3 + 2] = (unsigned char)sEntry.c3;
1014 : }
1015 :
1016 : nPCTSize = nColorTableSize + 1;
1017 :
1018 : // Add entries for pixel values which apparently will not occur.
1019 : for (int iColor = nPCTSize; iColor < 256; iColor++)
1020 : anRemap[iColor] = 1;
1021 : }
1022 :
1023 : /* -------------------------------------------------------------------- */
1024 : /* Boil out all duplicate entries. */
1025 : /* -------------------------------------------------------------------- */
1026 : for (int i = 1; i < nPCTSize - 1; i++)
1027 : {
1028 : for (int j = i + 1; j < nPCTSize; j++)
1029 : {
1030 : if (abyPCT[i * 3 + 0] == abyPCT[j * 3 + 0] &&
1031 : abyPCT[i * 3 + 1] == abyPCT[j * 3 + 1] &&
1032 : abyPCT[i * 3 + 2] == abyPCT[j * 3 + 2])
1033 : {
1034 : nPCTSize--;
1035 : abyPCT[j * 3 + 0] = abyPCT[nPCTSize * 3 + 0];
1036 : abyPCT[j * 3 + 1] = abyPCT[nPCTSize * 3 + 1];
1037 : abyPCT[j * 3 + 2] = abyPCT[nPCTSize * 3 + 2];
1038 :
1039 : for (int k = 0; k < 256; k++)
1040 : {
1041 : // merge matching entries.
1042 : if (anRemap[k] == j)
1043 : anRemap[k] = i;
1044 :
1045 : // shift the last PCT entry into the new hole.
1046 : if (anRemap[k] == nPCTSize)
1047 : anRemap[k] = j;
1048 : }
1049 : }
1050 : }
1051 : }
1052 :
1053 : /* -------------------------------------------------------------------- */
1054 : /* Boil out all duplicate entries. */
1055 : /* -------------------------------------------------------------------- */
1056 : if (nPCTSize > 128)
1057 : {
1058 : CPLError(CE_Warning, CPLE_AppDefined,
1059 : "Having to merge color table entries to reduce %d real\n"
1060 : "color table entries down to 127 values.",
1061 : nPCTSize);
1062 : }
1063 :
1064 : while (nPCTSize > 128)
1065 : {
1066 : int nBestRange = 768;
1067 : int iBestMatch1 = -1;
1068 : int iBestMatch2 = -1;
1069 :
1070 : // Find the closest pair of color table entries.
1071 :
1072 : for (int i = 1; i < nPCTSize - 1; i++)
1073 : {
1074 : for (int j = i + 1; j < nPCTSize; j++)
1075 : {
1076 : int nRange = std::abs(abyPCT[i * 3 + 0] - abyPCT[j * 3 + 0]) +
1077 : std::abs(abyPCT[i * 3 + 1] - abyPCT[j * 3 + 1]) +
1078 : std::abs(abyPCT[i * 3 + 2] - abyPCT[j * 3 + 2]);
1079 :
1080 : if (nRange < nBestRange)
1081 : {
1082 : iBestMatch1 = i;
1083 : iBestMatch2 = j;
1084 : nBestRange = nRange;
1085 : }
1086 : }
1087 : }
1088 :
1089 : // Merge the second entry into the first.
1090 : nPCTSize--;
1091 : abyPCT[iBestMatch2 * 3 + 0] = abyPCT[nPCTSize * 3 + 0];
1092 : abyPCT[iBestMatch2 * 3 + 1] = abyPCT[nPCTSize * 3 + 1];
1093 : abyPCT[iBestMatch2 * 3 + 2] = abyPCT[nPCTSize * 3 + 2];
1094 :
1095 : for (int i = 0; i < 256; i++)
1096 : {
1097 : // merge matching entries.
1098 : if (anRemap[i] == iBestMatch2)
1099 : anRemap[i] = iBestMatch1;
1100 :
1101 : // shift the last PCT entry into the new hole.
1102 : if (anRemap[i] == nPCTSize)
1103 : anRemap[i] = iBestMatch2;
1104 : }
1105 : }
1106 :
1107 : /* -------------------------------------------------------------------- */
1108 : /* Write the PCT. */
1109 : /* -------------------------------------------------------------------- */
1110 : if (!BSBWritePCT(psBSB, nPCTSize, abyPCT))
1111 : {
1112 : BSBClose(psBSB);
1113 : return NULL;
1114 : }
1115 :
1116 : /* -------------------------------------------------------------------- */
1117 : /* Write the GCPs. */
1118 : /* -------------------------------------------------------------------- */
1119 : GDALGeoTransform gt;
1120 : int nGCPCount = poSrcDS->GetGCPCount();
1121 : if (nGCPCount)
1122 : {
1123 : const char *pszGCPProjection = poSrcDS->GetGCPProjection();
1124 : if (BSBIsSRSOK(pszGCPProjection))
1125 : {
1126 : const GDAL_GCP *pasGCPList = poSrcDS->GetGCPs();
1127 : for (int i = 0; i < nGCPCount; i++)
1128 : {
1129 : VSIFPrintfL(psBSB->fp, "REF/%d,%f,%f,%f,%f\n", i + 1,
1130 : pasGCPList[i].dfGCPPixel, pasGCPList[i].dfGCPLine,
1131 : pasGCPList[i].dfGCPY, pasGCPList[i].dfGCPX);
1132 : }
1133 : }
1134 : }
1135 : else if (poSrcDS->GetGeoTransform(gt) == CE_None)
1136 : {
1137 : const char *pszProjection = poSrcDS->GetProjectionRef();
1138 : if (BSBIsSRSOK(pszProjection))
1139 : {
1140 : VSIFPrintfL(psBSB->fp, "REF/%d,%d,%d,%f,%f\n", 1, 0, 0,
1141 : gt[3] + 0 * gt[4] + 0 * gt[5],
1142 : gt[0] + 0 * gt[1] + 0 * gt[2]);
1143 : VSIFPrintfL(psBSB->fp, "REF/%d,%d,%d,%f,%f\n", 2, nXSize, 0,
1144 : gt[3] + nXSize * gt[4] + 0 * gt[5],
1145 : gt[0] + nXSize * gt[1] + 0 * gt[2]);
1146 : VSIFPrintfL(psBSB->fp, "REF/%d,%d,%d,%f,%f\n", 3, nXSize, nYSize,
1147 : gt[3] + nXSize * gt[4] + nYSize * gt[5],
1148 : gt[0] + nXSize * gt[1] + nYSize * gt[2]);
1149 : VSIFPrintfL(psBSB->fp, "REF/%d,%d,%d,%f,%f\n", 4, 0, nYSize,
1150 : gt[3] + 0 * gt[4] + nYSize * gt[5],
1151 : gt[0] + 0 * gt[1] + nYSize * gt[2]);
1152 : }
1153 : }
1154 :
1155 : /* -------------------------------------------------------------------- */
1156 : /* Loop over image, copying image data. */
1157 : /* -------------------------------------------------------------------- */
1158 : CPLErr eErr = CE_None;
1159 :
1160 : GByte *pabyScanline = (GByte *)CPLMalloc(nXSize);
1161 :
1162 : for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
1163 : {
1164 : eErr =
1165 : poBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, pabyScanline, nXSize,
1166 : 1, GDT_Byte, nBands, nBands * nXSize, NULL);
1167 : if (eErr == CE_None)
1168 : {
1169 : for (int i = 0; i < nXSize; i++)
1170 : pabyScanline[i] = (GByte)anRemap[pabyScanline[i]];
1171 :
1172 : if (!BSBWriteScanline(psBSB, pabyScanline))
1173 : eErr = CE_Failure;
1174 : }
1175 : }
1176 :
1177 : CPLFree(pabyScanline);
1178 :
1179 : /* -------------------------------------------------------------------- */
1180 : /* cleanup */
1181 : /* -------------------------------------------------------------------- */
1182 : BSBClose(psBSB);
1183 :
1184 : if (eErr != CE_None)
1185 : {
1186 : VSIUnlink(pszFilename);
1187 : return NULL;
1188 : }
1189 :
1190 : return (GDALDataset *)GDALOpen(pszFilename, GA_ReadOnly);
1191 : }
1192 : #endif
1193 :
1194 : /************************************************************************/
1195 : /* GDALRegister_BSB() */
1196 : /************************************************************************/
1197 :
1198 2033 : void GDALRegister_BSB()
1199 :
1200 : {
1201 2033 : if (GDALGetDriverByName("BSB") != nullptr)
1202 283 : return;
1203 :
1204 1750 : GDALDriver *poDriver = new GDALDriver();
1205 :
1206 1750 : poDriver->SetDescription("BSB");
1207 1750 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1208 1750 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Maptech BSB Nautical Charts");
1209 1750 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/bsb.html");
1210 1750 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1211 1750 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "kap");
1212 : #ifdef BSB_CREATE
1213 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte");
1214 : #endif
1215 1750 : poDriver->pfnOpen = BSBDataset::Open;
1216 1750 : poDriver->pfnIdentify = BSBDataset::Identify;
1217 : #ifdef BSB_CREATE
1218 : poDriver->pfnCreateCopy = BSBCreateCopy;
1219 : #endif
1220 :
1221 1750 : GetGDALDriverManager()->RegisterDriver(poDriver);
1222 : }
|