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