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