/src/gdal/frmts/bsb/bsbdataset.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | BSBRasterBand::BSBRasterBand(BSBDataset *poDSIn) |
91 | | |
92 | 87 | { |
93 | 87 | poDS = poDSIn; |
94 | 87 | nBand = 1; |
95 | | |
96 | 87 | eDataType = GDT_Byte; |
97 | | |
98 | 87 | nBlockXSize = poDS->GetRasterXSize(); |
99 | 87 | nBlockYSize = 1; |
100 | | |
101 | | // Note that the first color table entry is dropped, everything is |
102 | | // shifted down. |
103 | 6.56k | for (int i = 0; i < poDSIn->psInfo->nPCTSize - 1; i++) |
104 | 6.47k | { |
105 | 6.47k | GDALColorEntry oColor = {poDSIn->psInfo->pabyPCT[i * 3 + 0 + 3], |
106 | 6.47k | poDSIn->psInfo->pabyPCT[i * 3 + 1 + 3], |
107 | 6.47k | poDSIn->psInfo->pabyPCT[i * 3 + 2 + 3], 255}; |
108 | | |
109 | 6.47k | oCT.SetColorEntry(i, &oColor); |
110 | 6.47k | } |
111 | 87 | } |
112 | | |
113 | | /************************************************************************/ |
114 | | /* IReadBlock() */ |
115 | | /************************************************************************/ |
116 | | |
117 | | CPLErr BSBRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff, |
118 | | void *pImage) |
119 | 2.84k | { |
120 | 2.84k | BSBDataset *poGDS = cpl::down_cast<BSBDataset *>(poDS); |
121 | 2.84k | GByte *pabyScanline = (GByte *)pImage; |
122 | | |
123 | 2.84k | if (BSBReadScanline(poGDS->psInfo, nBlockYOff, pabyScanline)) |
124 | 2.77k | { |
125 | 1.22M | for (int i = 0; i < nBlockXSize; i++) |
126 | 1.21M | { |
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 | 1.21M | if (pabyScanline[i] > 0) |
131 | 1.21M | pabyScanline[i] -= 1; |
132 | 1.21M | } |
133 | | |
134 | 2.77k | return CE_None; |
135 | 2.77k | } |
136 | | |
137 | 71 | return CE_Failure; |
138 | 2.84k | } |
139 | | |
140 | | /************************************************************************/ |
141 | | /* GetColorTable() */ |
142 | | /************************************************************************/ |
143 | | |
144 | | GDALColorTable *BSBRasterBand::GetColorTable() |
145 | | |
146 | 0 | { |
147 | 0 | return &oCT; |
148 | 0 | } |
149 | | |
150 | | /************************************************************************/ |
151 | | /* GetColorInterpretation() */ |
152 | | /************************************************************************/ |
153 | | |
154 | | GDALColorInterp BSBRasterBand::GetColorInterpretation() |
155 | | |
156 | 0 | { |
157 | 0 | return GCI_PaletteIndex; |
158 | 0 | } |
159 | | |
160 | | /************************************************************************/ |
161 | | /* ==================================================================== */ |
162 | | /* BSBDataset */ |
163 | | /* ==================================================================== */ |
164 | | /************************************************************************/ |
165 | | |
166 | | /************************************************************************/ |
167 | | /* BSBDataset() */ |
168 | | /************************************************************************/ |
169 | | |
170 | | BSBDataset::BSBDataset() |
171 | 807 | : nGCPCount(0), pasGCPList(nullptr), bGeoTransformSet(FALSE), |
172 | 807 | psInfo(nullptr) |
173 | 807 | { |
174 | 807 | m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
175 | 807 | m_oGCPSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG); |
176 | 807 | } |
177 | | |
178 | | /************************************************************************/ |
179 | | /* ~BSBDataset() */ |
180 | | /************************************************************************/ |
181 | | |
182 | | BSBDataset::~BSBDataset() |
183 | | |
184 | 807 | { |
185 | 807 | FlushCache(true); |
186 | | |
187 | 807 | GDALDeinitGCPs(nGCPCount, pasGCPList); |
188 | 807 | CPLFree(pasGCPList); |
189 | | |
190 | 807 | if (psInfo != nullptr) |
191 | 87 | BSBClose(psInfo); |
192 | 807 | } |
193 | | |
194 | | /************************************************************************/ |
195 | | /* GetGeoTransform() */ |
196 | | /************************************************************************/ |
197 | | |
198 | | CPLErr BSBDataset::GetGeoTransform(GDALGeoTransform >) const |
199 | | |
200 | 87 | { |
201 | 87 | gt = m_gt; |
202 | | |
203 | 87 | if (bGeoTransformSet) |
204 | 15 | return CE_None; |
205 | | |
206 | 72 | return CE_Failure; |
207 | 87 | } |
208 | | |
209 | | /************************************************************************/ |
210 | | /* GetSpatialRef() */ |
211 | | /************************************************************************/ |
212 | | |
213 | | const OGRSpatialReference *BSBDataset::GetSpatialRef() const |
214 | | |
215 | 87 | { |
216 | 87 | if (bGeoTransformSet) |
217 | 15 | return &m_oGCPSRS; |
218 | | |
219 | 72 | return nullptr; |
220 | 87 | } |
221 | | |
222 | | /************************************************************************/ |
223 | | /* GDALHeuristicDatelineWrap() */ |
224 | | /************************************************************************/ |
225 | | |
226 | | static void GDALHeuristicDatelineWrap(int nPointCount, double *padfX) |
227 | | |
228 | 47 | { |
229 | 47 | if (nPointCount < 2) |
230 | 0 | 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 | 47 | double dfX_PM_Min = 0.0; |
239 | 47 | double dfX_PM_Max = 0.0; |
240 | 47 | double dfX_Dateline_Min = 0.0; |
241 | 47 | double dfX_Dateline_Max = 0.0; |
242 | | |
243 | 3.24k | for (int i = 0; i < nPointCount; i++) |
244 | 3.19k | { |
245 | 3.19k | double dfX_PM = padfX[i]; |
246 | 3.19k | if (dfX_PM > 180) |
247 | 9 | dfX_PM -= 360.0; |
248 | | |
249 | 3.19k | double dfX_Dateline = padfX[i]; |
250 | 3.19k | if (dfX_Dateline < 0) |
251 | 18 | dfX_Dateline += 360.0; |
252 | | |
253 | 3.19k | if (i == 0) |
254 | 47 | { |
255 | 47 | dfX_PM_Min = dfX_PM; |
256 | 47 | dfX_PM_Max = dfX_PM; |
257 | 47 | dfX_Dateline_Min = dfX_Dateline; |
258 | 47 | dfX_Dateline_Max = dfX_Dateline; |
259 | 47 | } |
260 | 3.14k | else |
261 | 3.14k | { |
262 | 3.14k | dfX_PM_Min = std::min(dfX_PM_Min, dfX_PM); |
263 | 3.14k | dfX_PM_Max = std::max(dfX_PM_Max, dfX_PM); |
264 | 3.14k | dfX_Dateline_Min = std::min(dfX_Dateline_Min, dfX_Dateline); |
265 | 3.14k | dfX_Dateline_Max = std::max(dfX_Dateline_Max, dfX_Dateline); |
266 | 3.14k | } |
267 | 3.19k | } |
268 | | |
269 | | /* -------------------------------------------------------------------- */ |
270 | | /* Do nothing if the range is always fairly small - no apparent */ |
271 | | /* wrapping issues. */ |
272 | | /* -------------------------------------------------------------------- */ |
273 | 47 | if ((dfX_PM_Max - dfX_PM_Min) < 270.0 && |
274 | 47 | (dfX_Dateline_Max - dfX_Dateline_Min) < 270.0) |
275 | 40 | 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 | 7 | if ((dfX_PM_Max - dfX_PM_Min) > 270.0 && |
282 | 7 | (dfX_Dateline_Max - dfX_Dateline_Min) > 270.0) |
283 | 0 | return; |
284 | | |
285 | | /* -------------------------------------------------------------------- */ |
286 | | /* Pick which way to transform things. */ |
287 | | /* -------------------------------------------------------------------- */ |
288 | 7 | bool bUsePMWrap; |
289 | | |
290 | 7 | if ((dfX_PM_Max - dfX_PM_Min) > 270.0 && |
291 | 7 | (dfX_Dateline_Max - dfX_Dateline_Min) < 270.0) |
292 | 1 | bUsePMWrap = false; |
293 | 6 | else |
294 | 6 | bUsePMWrap = true; |
295 | | |
296 | | /* -------------------------------------------------------------------- */ |
297 | | /* Apply rewrapping. */ |
298 | | /* -------------------------------------------------------------------- */ |
299 | 810 | for (int i = 0; i < nPointCount; i++) |
300 | 803 | { |
301 | 803 | if (bUsePMWrap) |
302 | 649 | { |
303 | 649 | if (padfX[i] > 180) |
304 | 7 | padfX[i] -= 360.0; |
305 | 649 | } |
306 | 154 | else |
307 | 154 | { |
308 | 154 | if (padfX[i] < 0) |
309 | 0 | padfX[i] += 360.0; |
310 | 154 | } |
311 | 803 | } |
312 | 7 | } |
313 | | |
314 | | /************************************************************************/ |
315 | | /* GDALHeuristicDatelineWrapGCPs() */ |
316 | | /************************************************************************/ |
317 | | |
318 | | static void GDALHeuristicDatelineWrapGCPs(int nPointCount, GDAL_GCP *pasGCPList) |
319 | 47 | { |
320 | 47 | std::vector<double> oadfX; |
321 | | |
322 | 47 | oadfX.resize(nPointCount); |
323 | 3.24k | for (int i = 0; i < nPointCount; i++) |
324 | 3.19k | oadfX[i] = pasGCPList[i].dfGCPX; |
325 | | |
326 | 47 | GDALHeuristicDatelineWrap(nPointCount, &(oadfX[0])); |
327 | | |
328 | 3.24k | for (int i = 0; i < nPointCount; i++) |
329 | 3.19k | pasGCPList[i].dfGCPX = oadfX[i]; |
330 | 47 | } |
331 | | |
332 | | /************************************************************************/ |
333 | | /* ScanForGCPs() */ |
334 | | /************************************************************************/ |
335 | | |
336 | | void BSBDataset::ScanForGCPs(bool isNos, const char *pszFilename) |
337 | | |
338 | 87 | { |
339 | | /* -------------------------------------------------------------------- */ |
340 | | /* Collect GCPs as appropriate to source. */ |
341 | | /* -------------------------------------------------------------------- */ |
342 | 87 | nGCPCount = 0; |
343 | | |
344 | 87 | if (isNos) |
345 | 0 | { |
346 | 0 | ScanForGCPsNos(pszFilename); |
347 | 0 | } |
348 | 87 | else |
349 | 87 | { |
350 | 87 | ScanForGCPsBSB(); |
351 | 87 | } |
352 | | |
353 | | /* -------------------------------------------------------------------- */ |
354 | | /* Apply heuristics to re-wrap GCPs to maintain continuity */ |
355 | | /* over the international dateline. */ |
356 | | /* -------------------------------------------------------------------- */ |
357 | 87 | if (nGCPCount > 1) |
358 | 47 | GDALHeuristicDatelineWrapGCPs(nGCPCount, pasGCPList); |
359 | | |
360 | | /* -------------------------------------------------------------------- */ |
361 | | /* Collect coordinate system related parameters from header. */ |
362 | | /* -------------------------------------------------------------------- */ |
363 | 87 | const char *pszKNP = nullptr; |
364 | 87 | const char *pszKNQ = nullptr; |
365 | | |
366 | 13.8k | for (int i = 0; psInfo->papszHeader[i] != nullptr; i++) |
367 | 13.8k | { |
368 | 13.8k | if (STARTS_WITH_CI(psInfo->papszHeader[i], "KNP/")) |
369 | 111 | { |
370 | 111 | pszKNP = psInfo->papszHeader[i]; |
371 | 111 | SetMetadataItem("BSB_KNP", pszKNP + 4); |
372 | 111 | } |
373 | 13.8k | if (STARTS_WITH_CI(psInfo->papszHeader[i], "KNQ/")) |
374 | 75 | { |
375 | 75 | pszKNQ = psInfo->papszHeader[i]; |
376 | 75 | SetMetadataItem("BSB_KNQ", pszKNQ + 4); |
377 | 75 | } |
378 | 13.8k | } |
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 | 87 | CPLString osUnderlyingSRS; |
386 | 87 | if (pszKNP != nullptr) |
387 | 85 | { |
388 | 85 | const char *pszPR = strstr(pszKNP, "PR="); |
389 | 85 | const char *pszGD = strstr(pszKNP, "GD="); |
390 | 85 | const char *pszGEOGCS = SRS_WKT_WGS84_LAT_LONG; |
391 | 85 | CPLString osPP; |
392 | | |
393 | | // Capture the PP string. |
394 | 85 | const char *pszValue = strstr(pszKNP, "PP="); |
395 | 85 | const char *pszEnd = pszValue ? strstr(pszValue, ",") : nullptr; |
396 | 85 | if (pszValue && pszEnd) |
397 | 79 | osPP.assign(pszValue + 3, pszEnd - pszValue - 3); |
398 | | |
399 | | // Look at the datum |
400 | 85 | if (pszGD == nullptr) |
401 | 2 | { |
402 | | /* no match. We'll default to EPSG:4326 */ |
403 | 2 | } |
404 | 83 | else if (STARTS_WITH_CI(pszGD, "GD=European 1950")) |
405 | 0 | { |
406 | 0 | pszGEOGCS = |
407 | 0 | "GEOGCS[\"ED50\",DATUM[\"European_Datum_1950\",SPHEROID[" |
408 | 0 | "\"International " |
409 | 0 | "1924\",6378388,297,AUTHORITY[\"EPSG\",\"7022\"]],TOWGS84[-87,-" |
410 | 0 | "98,-121,0,0,0,0],AUTHORITY[\"EPSG\",\"6230\"]],PRIMEM[" |
411 | 0 | "\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\"," |
412 | 0 | "0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[" |
413 | 0 | "\"EPSG\",\"4230\"]]"; |
414 | 0 | } |
415 | | |
416 | | // Look at the projection |
417 | 85 | if (pszPR == nullptr) |
418 | 3 | { |
419 | | /* no match */ |
420 | 3 | } |
421 | 82 | else if (STARTS_WITH_CI(pszPR, "PR=MERCATOR") && nGCPCount > 0) |
422 | 11 | { |
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 | 11 | osUnderlyingSRS.Printf( |
428 | 11 | "PROJCS[\"Global " |
429 | 11 | "Mercator\",%s,PROJECTION[\"Mercator_2SP\"],PARAMETER[" |
430 | 11 | "\"standard_parallel_1\",0],PARAMETER[\"latitude_of_origin\",0]" |
431 | 11 | ",PARAMETER[\"central_meridian\",%d],PARAMETER[\"false_" |
432 | 11 | "easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"Meter\",1]" |
433 | 11 | "]", |
434 | 11 | pszGEOGCS, (int)pasGCPList[0].dfGCPX); |
435 | 11 | } |
436 | | |
437 | 71 | else if (STARTS_WITH_CI(pszPR, "PR=TRANSVERSE MERCATOR") && |
438 | 71 | !osPP.empty()) |
439 | 12 | { |
440 | | |
441 | 12 | osUnderlyingSRS.Printf( |
442 | 12 | "PROJCS[\"unnamed\",%s,PROJECTION[\"Transverse_Mercator\"]," |
443 | 12 | "PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_" |
444 | 12 | "meridian\",%s],PARAMETER[\"scale_factor\",1],PARAMETER[" |
445 | 12 | "\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[" |
446 | 12 | "\"Meter\",1]]", |
447 | 12 | pszGEOGCS, osPP.c_str()); |
448 | 12 | } |
449 | | |
450 | 59 | else if (STARTS_WITH_CI(pszPR, "PR=UNIVERSAL TRANSVERSE MERCATOR") && |
451 | 59 | !osPP.empty()) |
452 | 0 | { |
453 | | // This is not *really* UTM unless the central meridian |
454 | | // matches a zone which it does not in some (most?) maps. |
455 | 0 | osUnderlyingSRS.Printf( |
456 | 0 | "PROJCS[\"unnamed\",%s,PROJECTION[\"Transverse_Mercator\"]," |
457 | 0 | "PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_" |
458 | 0 | "meridian\",%s],PARAMETER[\"scale_factor\",0.9996],PARAMETER[" |
459 | 0 | "\"false_easting\",500000],PARAMETER[\"false_northing\",0]," |
460 | 0 | "UNIT[\"Meter\",1]]", |
461 | 0 | pszGEOGCS, osPP.c_str()); |
462 | 0 | } |
463 | | |
464 | 59 | else if (STARTS_WITH_CI(pszPR, "PR=POLYCONIC") && !osPP.empty()) |
465 | 0 | { |
466 | 0 | osUnderlyingSRS.Printf( |
467 | 0 | "PROJCS[\"unnamed\",%s,PROJECTION[\"Polyconic\"],PARAMETER[" |
468 | 0 | "\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",%s]," |
469 | 0 | "PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0]" |
470 | 0 | ",UNIT[\"Meter\",1]]", |
471 | 0 | pszGEOGCS, osPP.c_str()); |
472 | 0 | } |
473 | | |
474 | 59 | else if (STARTS_WITH_CI(pszPR, "PR=LAMBERT CONFORMAL CONIC") && |
475 | 59 | !osPP.empty() && pszKNQ != nullptr) |
476 | 17 | { |
477 | 17 | CPLString osP2, osP3; |
478 | | |
479 | | // Capture the KNQ/P2 string. |
480 | 17 | pszValue = strstr(pszKNQ, "P2="); |
481 | 17 | if (pszValue) |
482 | 16 | pszEnd = strstr(pszValue, ","); |
483 | 17 | if (pszValue && pszEnd) |
484 | 14 | osP2.assign(pszValue + 3, pszEnd - pszValue - 3); |
485 | | |
486 | | // Capture the KNQ/P3 string. |
487 | 17 | pszValue = strstr(pszKNQ, "P3="); |
488 | 17 | if (pszValue) |
489 | 12 | pszEnd = strstr(pszValue, ","); |
490 | 17 | if (pszValue) |
491 | 12 | { |
492 | 12 | if (pszEnd) |
493 | 12 | osP3.assign(pszValue + 3, pszEnd - pszValue - 3); |
494 | 0 | else |
495 | 0 | osP3.assign(pszValue + 3); |
496 | 12 | } |
497 | | |
498 | 17 | if (!osP2.empty() && !osP3.empty()) |
499 | 12 | osUnderlyingSRS.Printf( |
500 | 12 | "PROJCS[\"unnamed\",%s,PROJECTION[\"Lambert_Conformal_" |
501 | 12 | "Conic_2SP\"],PARAMETER[\"standard_parallel_1\",%s]," |
502 | 12 | "PARAMETER[\"standard_parallel_2\",%s],PARAMETER[" |
503 | 12 | "\"latitude_of_origin\",0.0],PARAMETER[\"central_" |
504 | 12 | "meridian\",%s],PARAMETER[\"false_easting\",0.0],PARAMETER[" |
505 | 12 | "\"false_northing\",0.0],UNIT[\"Meter\",1]]", |
506 | 12 | pszGEOGCS, osP2.c_str(), osP3.c_str(), osPP.c_str()); |
507 | 17 | } |
508 | 85 | } |
509 | | |
510 | | /* -------------------------------------------------------------------- */ |
511 | | /* If we got an alternate underlying coordinate system, try */ |
512 | | /* converting the GCPs to that coordinate system. */ |
513 | | /* -------------------------------------------------------------------- */ |
514 | 87 | if (osUnderlyingSRS.length() > 0) |
515 | 35 | { |
516 | 35 | OGRSpatialReference oGeog_SRS, oProjected_SRS; |
517 | | |
518 | 35 | oProjected_SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
519 | 35 | oProjected_SRS.SetFromUserInput(osUnderlyingSRS); |
520 | 35 | oGeog_SRS.CopyGeogCSFrom(&oProjected_SRS); |
521 | 35 | oGeog_SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
522 | | |
523 | 35 | OGRCoordinateTransformation *poCT = |
524 | 35 | OGRCreateCoordinateTransformation(&oGeog_SRS, &oProjected_SRS); |
525 | 35 | if (poCT != nullptr) |
526 | 30 | { |
527 | 3.12k | for (int i = 0; i < nGCPCount; i++) |
528 | 3.09k | { |
529 | 3.09k | poCT->Transform(1, &(pasGCPList[i].dfGCPX), |
530 | 3.09k | &(pasGCPList[i].dfGCPY), |
531 | 3.09k | &(pasGCPList[i].dfGCPZ)); |
532 | 3.09k | } |
533 | | |
534 | 30 | m_oGCPSRS.importFromWkt(osUnderlyingSRS.c_str()); |
535 | | |
536 | 30 | delete poCT; |
537 | 30 | } |
538 | 5 | else |
539 | 5 | CPLErrorReset(); |
540 | 35 | } |
541 | | |
542 | | /* -------------------------------------------------------------------- */ |
543 | | /* Attempt to prepare a geotransform from the GCPs. */ |
544 | | /* -------------------------------------------------------------------- */ |
545 | 87 | if (GDALGCPsToGeoTransform(nGCPCount, pasGCPList, m_gt.data(), FALSE)) |
546 | 15 | { |
547 | 15 | bGeoTransformSet = TRUE; |
548 | 15 | } |
549 | 87 | } |
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 | | void BSBDataset::ScanForGCPsNos(const char *pszFilename) |
563 | 0 | { |
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 | 0 | { |
570 | 0 | geofile = CPLResetExtensionSafe(pszFilename, "GEO"); |
571 | 0 | } |
572 | 0 | else |
573 | 0 | { |
574 | 0 | geofile = CPLResetExtensionSafe(pszFilename, "geo"); |
575 | 0 | } |
576 | |
|
577 | 0 | FILE *gfp = VSIFOpen(geofile.c_str(), "r"); // Text files |
578 | 0 | if (gfp == nullptr) |
579 | 0 | { |
580 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
581 | 0 | "Couldn't find a matching .GEO file: %s", geofile.c_str()); |
582 | 0 | return; |
583 | 0 | } |
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 | 0 | { |
592 | 0 | if (STARTS_WITH_CI(thisLine, "Point")) |
593 | 0 | fileGCPCount++; |
594 | 0 | } |
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 | 0 | { |
602 | 0 | if (STARTS_WITH_CI(thisLine, "Point")) |
603 | 0 | { |
604 | | // got a point line, turn it into a gcp |
605 | 0 | char **Tokens = |
606 | 0 | CSLTokenizeStringComplex(thisLine, "= ", FALSE, FALSE); |
607 | 0 | if (CSLCount(Tokens) >= 5) |
608 | 0 | { |
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 | 0 | 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 | 0 | } |
622 | 0 | CSLDestroy(Tokens); |
623 | 0 | } |
624 | 0 | } |
625 | |
|
626 | 0 | CPLFree(thisLine); |
627 | 0 | VSIFClose(gfp); |
628 | 0 | } |
629 | | |
630 | | /************************************************************************/ |
631 | | /* ScanForGCPsBSB() */ |
632 | | /************************************************************************/ |
633 | | |
634 | | void BSBDataset::ScanForGCPsBSB() |
635 | 87 | { |
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 | 87 | int fileGCPCount = 0; |
643 | | |
644 | | // Count the GCPs (reference points) in psInfo->papszHeader |
645 | 13.8k | for (int i = 0; psInfo->papszHeader[i] != nullptr; i++) |
646 | 13.8k | if (STARTS_WITH_CI(psInfo->papszHeader[i], "REF/")) |
647 | 3.21k | fileGCPCount++; |
648 | | |
649 | | // Memory has not been allocated to fileGCPCount yet |
650 | 87 | pasGCPList = (GDAL_GCP *)CPLCalloc(sizeof(GDAL_GCP), fileGCPCount + 1); |
651 | | |
652 | 13.8k | for (int i = 0; psInfo->papszHeader[i] != nullptr; i++) |
653 | 13.8k | { |
654 | | |
655 | 13.8k | if (!STARTS_WITH_CI(psInfo->papszHeader[i], "REF/")) |
656 | 10.5k | continue; |
657 | | |
658 | 3.21k | char **papszTokens = CSLTokenizeStringComplex( |
659 | 3.21k | psInfo->papszHeader[i] + 4, ",", FALSE, FALSE); |
660 | | |
661 | 3.21k | if (CSLCount(papszTokens) > 4) |
662 | 3.19k | { |
663 | 3.19k | GDALInitGCPs(1, pasGCPList + nGCPCount); |
664 | | |
665 | 3.19k | pasGCPList[nGCPCount].dfGCPX = CPLAtof(papszTokens[4]); |
666 | 3.19k | pasGCPList[nGCPCount].dfGCPY = CPLAtof(papszTokens[3]); |
667 | 3.19k | pasGCPList[nGCPCount].dfGCPPixel = CPLAtof(papszTokens[1]); |
668 | 3.19k | pasGCPList[nGCPCount].dfGCPLine = CPLAtof(papszTokens[2]); |
669 | | |
670 | 3.19k | CPLFree(pasGCPList[nGCPCount].pszId); |
671 | 3.19k | if (CSLCount(papszTokens) > 5) |
672 | 38 | { |
673 | 38 | pasGCPList[nGCPCount].pszId = CPLStrdup(papszTokens[5]); |
674 | 38 | } |
675 | 3.15k | else |
676 | 3.15k | { |
677 | 3.15k | char szName[50]; |
678 | 3.15k | snprintf(szName, sizeof(szName), "GCP_%d", nGCPCount + 1); |
679 | 3.15k | pasGCPList[nGCPCount].pszId = CPLStrdup(szName); |
680 | 3.15k | } |
681 | | |
682 | 3.19k | nGCPCount++; |
683 | 3.19k | } |
684 | 3.21k | CSLDestroy(papszTokens); |
685 | 3.21k | } |
686 | 87 | } |
687 | | |
688 | | /************************************************************************/ |
689 | | /* ScanForCutline() */ |
690 | | /************************************************************************/ |
691 | | |
692 | | void BSBDataset::ScanForCutline() |
693 | 87 | { |
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 | 87 | std::string wkt; |
705 | 13.8k | for (int i = 0; psInfo->papszHeader[i] != nullptr; i++) |
706 | 13.8k | { |
707 | 13.8k | if (!STARTS_WITH_CI(psInfo->papszHeader[i], "PLY/")) |
708 | 13.2k | continue; |
709 | | |
710 | 507 | const CPLStringList aosTokens( |
711 | 507 | CSLTokenizeString2(psInfo->papszHeader[i] + 4, ",", 0)); |
712 | | |
713 | 507 | if (aosTokens.size() >= 3) |
714 | 478 | { |
715 | 478 | if (wkt.empty()) |
716 | 30 | wkt = "POLYGON (("; |
717 | 448 | else |
718 | 448 | wkt += ','; |
719 | 478 | wkt += aosTokens[2]; |
720 | 478 | wkt += ' '; |
721 | 478 | wkt += aosTokens[1]; |
722 | 478 | } |
723 | 507 | } |
724 | | |
725 | 87 | if (!wkt.empty()) |
726 | 30 | { |
727 | 30 | wkt += "))"; |
728 | 30 | SetMetadataItem("BSB_CUTLINE", wkt.c_str()); |
729 | 30 | } |
730 | 87 | } |
731 | | |
732 | | /************************************************************************/ |
733 | | /* IdentifyInternal() */ |
734 | | /************************************************************************/ |
735 | | |
736 | | int BSBDataset::IdentifyInternal(GDALOpenInfo *poOpenInfo, bool &isNosOut) |
737 | | |
738 | 602k | { |
739 | | /* -------------------------------------------------------------------- */ |
740 | | /* Check for BSB/ keyword. */ |
741 | | /* -------------------------------------------------------------------- */ |
742 | 602k | isNosOut = false; |
743 | | |
744 | 602k | if (poOpenInfo->nHeaderBytes < 1000) |
745 | 533k | return FALSE; |
746 | | |
747 | 69.0k | int i = 0; |
748 | 74.3M | for (; i < poOpenInfo->nHeaderBytes - 4; i++) |
749 | 74.3M | { |
750 | 74.3M | if (poOpenInfo->pabyHeader[i + 0] == 'B' && |
751 | 74.3M | poOpenInfo->pabyHeader[i + 1] == 'S' && |
752 | 74.3M | poOpenInfo->pabyHeader[i + 2] == 'B' && |
753 | 74.3M | poOpenInfo->pabyHeader[i + 3] == '/') |
754 | 1.94k | break; |
755 | 74.3M | if (poOpenInfo->pabyHeader[i + 0] == 'N' && |
756 | 74.3M | poOpenInfo->pabyHeader[i + 1] == 'O' && |
757 | 74.3M | poOpenInfo->pabyHeader[i + 2] == 'S' && |
758 | 74.3M | poOpenInfo->pabyHeader[i + 3] == '/') |
759 | 0 | { |
760 | 0 | isNosOut = true; |
761 | 0 | break; |
762 | 0 | } |
763 | 74.3M | if (poOpenInfo->pabyHeader[i + 0] == 'W' && |
764 | 74.3M | poOpenInfo->pabyHeader[i + 1] == 'X' && |
765 | 74.3M | poOpenInfo->pabyHeader[i + 2] == '\\' && |
766 | 74.3M | poOpenInfo->pabyHeader[i + 3] == '8') |
767 | 0 | break; |
768 | 74.3M | } |
769 | | |
770 | 69.0k | if (i == poOpenInfo->nHeaderBytes - 4) |
771 | 67.1k | return FALSE; |
772 | | |
773 | | /* Additional test to avoid false positive. See #2881 */ |
774 | 1.94k | const char *pszHeader = |
775 | 1.94k | reinterpret_cast<const char *>(poOpenInfo->pabyHeader); |
776 | 1.94k | const char *pszShiftedHeader = pszHeader + i; |
777 | 1.94k | const char *pszRA = strstr(pszShiftedHeader, "RA="); |
778 | 1.94k | if (pszRA == nullptr) /* This may be a NO1 file */ |
779 | 850 | pszRA = strstr(pszShiftedHeader, "[JF"); |
780 | 1.94k | if (pszRA == nullptr) |
781 | 290 | return FALSE; |
782 | 1.65k | if (pszRA - pszShiftedHeader > 100 && !strstr(pszHeader, "VER/") && |
783 | 1.65k | !strstr(pszHeader, "KNP/") && !strstr(pszHeader, "KNQ/") && |
784 | 1.65k | !strstr(pszHeader, "RGB/")) |
785 | 44 | return FALSE; |
786 | | |
787 | 1.61k | return TRUE; |
788 | 1.65k | } |
789 | | |
790 | | /************************************************************************/ |
791 | | /* Identify() */ |
792 | | /************************************************************************/ |
793 | | |
794 | | int BSBDataset::Identify(GDALOpenInfo *poOpenInfo) |
795 | | |
796 | 602k | { |
797 | 602k | bool isNos; |
798 | 602k | return IdentifyInternal(poOpenInfo, isNos); |
799 | 602k | } |
800 | | |
801 | | /************************************************************************/ |
802 | | /* Open() */ |
803 | | /************************************************************************/ |
804 | | |
805 | | GDALDataset *BSBDataset::Open(GDALOpenInfo *poOpenInfo) |
806 | | |
807 | 807 | { |
808 | 807 | bool isNos = false; |
809 | 807 | if (!IdentifyInternal(poOpenInfo, isNos)) |
810 | 0 | return nullptr; |
811 | | |
812 | 807 | if (poOpenInfo->eAccess == GA_Update) |
813 | 0 | { |
814 | 0 | ReportUpdateNotSupportedByDriver("BSB"); |
815 | 0 | return nullptr; |
816 | 0 | } |
817 | | |
818 | | /* -------------------------------------------------------------------- */ |
819 | | /* Create a corresponding GDALDataset. */ |
820 | | /* -------------------------------------------------------------------- */ |
821 | 807 | BSBDataset *poDS = new BSBDataset(); |
822 | | |
823 | | /* -------------------------------------------------------------------- */ |
824 | | /* Open the file. */ |
825 | | /* -------------------------------------------------------------------- */ |
826 | 807 | poDS->psInfo = BSBOpen(poOpenInfo->pszFilename); |
827 | 807 | if (poDS->psInfo == nullptr) |
828 | 720 | { |
829 | 720 | delete poDS; |
830 | 720 | return nullptr; |
831 | 720 | } |
832 | | |
833 | 87 | poDS->nRasterXSize = poDS->psInfo->nXSize; |
834 | 87 | poDS->nRasterYSize = poDS->psInfo->nYSize; |
835 | | |
836 | | /* -------------------------------------------------------------------- */ |
837 | | /* Create band information objects. */ |
838 | | /* -------------------------------------------------------------------- */ |
839 | 87 | poDS->SetBand(1, new BSBRasterBand(poDS)); |
840 | | |
841 | 87 | poDS->ScanForGCPs(isNos, poOpenInfo->pszFilename); |
842 | | |
843 | | /* -------------------------------------------------------------------- */ |
844 | | /* Set CUTLINE metadata if a bounding polygon is available */ |
845 | | /* -------------------------------------------------------------------- */ |
846 | 87 | poDS->ScanForCutline(); |
847 | | |
848 | | /* -------------------------------------------------------------------- */ |
849 | | /* Initialize any PAM information. */ |
850 | | /* -------------------------------------------------------------------- */ |
851 | 87 | poDS->SetDescription(poOpenInfo->pszFilename); |
852 | 87 | poDS->TryLoadXML(); |
853 | | |
854 | 87 | poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename); |
855 | | |
856 | 87 | return poDS; |
857 | 807 | } |
858 | | |
859 | | /************************************************************************/ |
860 | | /* GetGCPCount() */ |
861 | | /************************************************************************/ |
862 | | |
863 | | int BSBDataset::GetGCPCount() |
864 | | |
865 | 87 | { |
866 | 87 | return nGCPCount; |
867 | 87 | } |
868 | | |
869 | | /************************************************************************/ |
870 | | /* GetGCPSpatialRef() */ |
871 | | /************************************************************************/ |
872 | | |
873 | | const OGRSpatialReference *BSBDataset::GetGCPSpatialRef() const |
874 | | |
875 | 87 | { |
876 | 87 | return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS; |
877 | 87 | } |
878 | | |
879 | | /************************************************************************/ |
880 | | /* GetGCP() */ |
881 | | /************************************************************************/ |
882 | | |
883 | | const GDAL_GCP *BSBDataset::GetGCPs() |
884 | | |
885 | 87 | { |
886 | 87 | return pasGCPList; |
887 | 87 | } |
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 | | void GDALRegister_BSB() |
1194 | | |
1195 | 22 | { |
1196 | 22 | if (GDALGetDriverByName("BSB") != nullptr) |
1197 | 0 | return; |
1198 | | |
1199 | 22 | GDALDriver *poDriver = new GDALDriver(); |
1200 | | |
1201 | 22 | poDriver->SetDescription("BSB"); |
1202 | 22 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
1203 | 22 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Maptech BSB Nautical Charts"); |
1204 | 22 | poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/bsb.html"); |
1205 | 22 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
1206 | 22 | poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "kap"); |
1207 | | #ifdef BSB_CREATE |
1208 | | poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte"); |
1209 | | #endif |
1210 | 22 | poDriver->pfnOpen = BSBDataset::Open; |
1211 | 22 | poDriver->pfnIdentify = BSBDataset::Identify; |
1212 | | #ifdef BSB_CREATE |
1213 | | poDriver->pfnCreateCopy = BSBCreateCopy; |
1214 | | #endif |
1215 | | |
1216 | 22 | GetGDALDriverManager()->RegisterDriver(poDriver); |
1217 | 22 | } |