Coverage Report

Created: 2025-06-13 06:29

/src/gdal/frmts/gtiff/gt_citation.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  GeoTIFF Driver
4
 * Purpose:  Implements special parsing of Imagine citation strings, and
5
 *           to encode PE String info in citation fields as needed.
6
 * Author:   Xiuguang Zhou (ESRI)
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2008, Xiuguang Zhou (ESRI)
10
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
11
 *
12
 * SPDX-License-Identifier: MIT
13
 ****************************************************************************/
14
15
#include "cpl_port.h"
16
17
#include "gt_citation.h"
18
19
#include <cstddef>
20
#include <cstdlib>
21
#include <cstring>
22
#include <algorithm>
23
#include <string>
24
25
#include "cpl_conv.h"
26
#include "cpl_string.h"
27
#include "geokeys.h"
28
#include "geotiff.h"
29
#include "geovalues.h"
30
#include "gt_wkt_srs_priv.h"
31
#include "ogr_core.h"
32
33
static const char *const apszUnitMap[] = {"meters",
34
                                          "1.0",
35
                                          "meter",
36
                                          "1.0",
37
                                          "m",
38
                                          "1.0",
39
                                          "centimeters",
40
                                          "0.01",
41
                                          "centimeter",
42
                                          "0.01",
43
                                          "cm",
44
                                          "0.01",
45
                                          "millimeters",
46
                                          "0.001",
47
                                          "millimeter",
48
                                          "0.001",
49
                                          "mm",
50
                                          "0.001",
51
                                          "kilometers",
52
                                          "1000.0",
53
                                          "kilometer",
54
                                          "1000.0",
55
                                          "km",
56
                                          "1000.0",
57
                                          "us_survey_feet",
58
                                          "0.3048006096012192",
59
                                          "us_survey_foot",
60
                                          "0.3048006096012192",
61
                                          "feet",
62
                                          "0.3048006096012192",
63
                                          "foot",
64
                                          "0.3048006096012192",
65
                                          "ft",
66
                                          "0.3048006096012192",
67
                                          "international_feet",
68
                                          "0.3048",
69
                                          "international_foot",
70
                                          "0.3048",
71
                                          "inches",
72
                                          "0.0254000508001",
73
                                          "inch",
74
                                          "0.0254000508001",
75
                                          "in",
76
                                          "0.0254000508001",
77
                                          "yards",
78
                                          "0.9144",
79
                                          "yard",
80
                                          "0.9144",
81
                                          "yd",
82
                                          "0.9144",
83
                                          "miles",
84
                                          "1304.544",
85
                                          "mile",
86
                                          "1304.544",
87
                                          "mi",
88
                                          "1304.544",
89
                                          "modified_american_feet",
90
                                          "0.3048122530",
91
                                          "modified_american_foot",
92
                                          "0.3048122530",
93
                                          "clarke_feet",
94
                                          "0.3047972651",
95
                                          "clarke_foot",
96
                                          "0.3047972651",
97
                                          "indian_feet",
98
                                          "0.3047995142",
99
                                          "indian_foot",
100
                                          "0.3047995142",
101
                                          "Yard_Indian",
102
                                          "0.9143985307444408",
103
                                          "Foot_Clarke",
104
                                          "0.30479726540",
105
                                          "Foot_Gold_Coast",
106
                                          "0.3047997101815088",
107
                                          "Link_Clarke",
108
                                          "0.2011661951640",
109
                                          "Yard_Sears",
110
                                          "0.9143984146160287",
111
                                          "50_Kilometers",
112
                                          "50000.0",
113
                                          "150_Kilometers",
114
                                          "150000.0",
115
                                          nullptr,
116
                                          nullptr};
117
118
/************************************************************************/
119
/*                     ImagineCitationTranslation()                     */
120
/*                                                                      */
121
/*      Translate ERDAS Imagine GeoTif citation                         */
122
/************************************************************************/
123
char *ImagineCitationTranslation(char *psCitation, geokey_t keyID)
124
0
{
125
0
    if (!psCitation)
126
0
        return nullptr;
127
0
    char *ret = nullptr;
128
0
    if (STARTS_WITH_CI(psCitation, "IMAGINE GeoTIFF Support"))
129
0
    {
130
0
        static const char *const keyNames[] = {
131
0
            "NAD = ", "Datum = ", "Ellipsoid = ", "Units = ", nullptr};
132
133
        // This is a handle IMAGING style citation.
134
0
        CPLString osName;
135
0
        char *p1 = nullptr;
136
137
0
        char *p = strchr(psCitation, '$');
138
0
        if (p && strchr(p, '\n'))
139
0
            p = strchr(p, '\n') + 1;
140
0
        if (p)
141
0
        {
142
0
            p1 = p + strlen(p);
143
0
            char *p2 = strchr(p, '\n');
144
0
            if (p2)
145
0
                p1 = std::min(p1, p2);
146
0
            p2 = strchr(p, '\0');
147
0
            if (p2)
148
0
                p1 = std::min(p1, p2);
149
150
0
            for (int i = 0; keyNames[i] != nullptr; i++)
151
0
            {
152
0
                p2 = strstr(p, keyNames[i]);
153
0
                if (p2)
154
0
                    p1 = std::min(p1, p2);
155
0
            }
156
0
        }
157
158
        // PCS name, GCS name and PRJ name.
159
0
        if (p && p1)
160
0
        {
161
0
            switch (keyID)
162
0
            {
163
0
                case PCSCitationGeoKey:
164
0
                    if (strstr(psCitation, "Projection = "))
165
0
                        osName = "PRJ Name = ";
166
0
                    else
167
0
                        osName = "PCS Name = ";
168
0
                    break;
169
0
                case GTCitationGeoKey:
170
0
                    osName = "PCS Name = ";
171
0
                    break;
172
0
                case GeogCitationGeoKey:
173
0
                    if (!strstr(p, "Unable to"))
174
0
                        osName = "GCS Name = ";
175
0
                    break;
176
0
                default:
177
0
                    break;
178
0
            }
179
0
            if (!osName.empty())
180
0
            {
181
                // TODO(schwehr): What exactly is this code trying to do?
182
                // Added in r15993 and modified in r21844 by warmerdam.
183
0
                char *p2 = nullptr;
184
0
                if ((p2 = strstr(psCitation, "Projection Name = ")) != nullptr)
185
0
                    p = p2 + strlen("Projection Name = ");
186
0
                if ((p2 = strstr(psCitation, "Projection = ")) != nullptr)
187
0
                    p = p2 + strlen("Projection = ");
188
0
                if (p1[0] == '\0' || p1[0] == '\n' || p1[0] == ' ')
189
0
                    p1--;
190
0
                p2 = p1 - 1;
191
0
                while (p2 != nullptr &&
192
0
                       (p2[0] == ' ' || p2[0] == '\0' || p2[0] == '\n'))
193
0
                {
194
0
                    p2--;
195
0
                }
196
0
                if (p2 != p1 - 1)
197
0
                {
198
0
                    p1 = p2;
199
0
                }
200
0
                if (p1 >= p)
201
0
                {
202
0
                    osName.append(p, p1 - p + 1);
203
0
                    osName += '|';
204
0
                }
205
0
            }
206
0
        }
207
208
        // All other parameters.
209
0
        for (int i = 0; keyNames[i] != nullptr; i++)
210
0
        {
211
0
            p = strstr(psCitation, keyNames[i]);
212
0
            if (p)
213
0
            {
214
0
                p += strlen(keyNames[i]);
215
0
                p1 = p + strlen(p);
216
0
                char *p2 = strchr(p, '\n');
217
0
                if (p2)
218
0
                    p1 = std::min(p1, p2);
219
0
                p2 = strchr(p, '\0');
220
0
                if (p2)
221
0
                    p1 = std::min(p1, p2);
222
0
                for (int j = 0; keyNames[j] != nullptr; j++)
223
0
                {
224
0
                    p2 = strstr(p, keyNames[j]);
225
0
                    if (p2)
226
0
                        p1 = std::min(p1, p2);
227
0
                }
228
0
            }
229
0
            if (p && p1 && p1 > p)
230
0
            {
231
0
                if (EQUAL(keyNames[i], "Units = "))
232
0
                    osName += "LUnits = ";
233
0
                else
234
0
                    osName += keyNames[i];
235
0
                if (p1[0] == '\0' || p1[0] == '\n' || p1[0] == ' ')
236
0
                    p1--;
237
0
                char *p2 = p1 - 1;
238
0
                while (p2 != nullptr &&
239
0
                       (p2[0] == ' ' || p2[0] == '\0' || p2[0] == '\n'))
240
0
                {
241
0
                    p2--;
242
0
                }
243
0
                if (p2 != p1 - 1)
244
0
                {
245
0
                    p1 = p2;
246
0
                }
247
0
                if (p1 >= p)
248
0
                {
249
0
                    osName.append(p, p1 - p + 1);
250
0
                    osName += '|';
251
0
                }
252
0
            }
253
0
        }
254
0
        if (!osName.empty())
255
0
            ret = CPLStrdup(osName);
256
0
    }
257
0
    return ret;
258
0
}
259
260
/************************************************************************/
261
/*                        CitationStringParse()                         */
262
/*                                                                      */
263
/*      Parse a Citation string                                         */
264
/************************************************************************/
265
266
char **CitationStringParse(char *psCitation, geokey_t keyID)
267
0
{
268
0
    if (!psCitation)
269
0
        return nullptr;
270
271
0
    char **ret =
272
0
        static_cast<char **>(CPLCalloc(sizeof(char *), nCitationNameTypes));
273
0
    char *pDelimit = nullptr;
274
0
    char *pStr = psCitation;
275
0
    char name[512] = {'\0'};
276
0
    bool nameSet = false;
277
0
    int nameLen = static_cast<int>(strlen(psCitation));
278
0
    bool nameFound = false;
279
0
    while ((pStr - psCitation + 1) < nameLen)
280
0
    {
281
0
        if ((pDelimit = strstr(pStr, "|")) != nullptr)
282
0
        {
283
0
            strncpy(name, pStr, pDelimit - pStr);
284
0
            name[pDelimit - pStr] = '\0';
285
0
            pStr = pDelimit + 1;
286
0
            nameSet = true;
287
0
        }
288
0
        else
289
0
        {
290
0
            strcpy(name, pStr);
291
0
            pStr += strlen(pStr);
292
0
            nameSet = true;
293
0
        }
294
0
        if (strstr(name, "PCS Name = ") && ret[CitPcsName] == nullptr)
295
0
        {
296
0
            ret[CitPcsName] = CPLStrdup(name + strlen("PCS Name = "));
297
0
            nameFound = true;
298
0
        }
299
0
        if (strstr(name, "PRJ Name = ") && ret[CitProjectionName] == nullptr)
300
0
        {
301
0
            ret[CitProjectionName] = CPLStrdup(name + strlen("PRJ Name = "));
302
0
            nameFound = true;
303
0
        }
304
0
        if (strstr(name, "LUnits = ") && ret[CitLUnitsName] == nullptr)
305
0
        {
306
0
            ret[CitLUnitsName] = CPLStrdup(name + strlen("LUnits = "));
307
0
            nameFound = true;
308
0
        }
309
0
        if (strstr(name, "GCS Name = ") && ret[CitGcsName] == nullptr)
310
0
        {
311
0
            ret[CitGcsName] = CPLStrdup(name + strlen("GCS Name = "));
312
0
            nameFound = true;
313
0
        }
314
0
        if (strstr(name, "Datum = ") && ret[CitDatumName] == nullptr)
315
0
        {
316
0
            ret[CitDatumName] = CPLStrdup(name + strlen("Datum = "));
317
0
            nameFound = true;
318
0
        }
319
0
        if (strstr(name, "Ellipsoid = ") && ret[CitEllipsoidName] == nullptr)
320
0
        {
321
0
            ret[CitEllipsoidName] = CPLStrdup(name + strlen("Ellipsoid = "));
322
0
            nameFound = true;
323
0
        }
324
0
        if (strstr(name, "Primem = ") && ret[CitPrimemName] == nullptr)
325
0
        {
326
0
            ret[CitPrimemName] = CPLStrdup(name + strlen("Primem = "));
327
0
            nameFound = true;
328
0
        }
329
0
        if (strstr(name, "AUnits = ") && ret[CitAUnitsName] == nullptr)
330
0
        {
331
0
            ret[CitAUnitsName] = CPLStrdup(name + strlen("AUnits = "));
332
0
            nameFound = true;
333
0
        }
334
0
    }
335
0
    if (!nameFound && keyID == GeogCitationGeoKey && nameSet)
336
0
    {
337
0
        ret[CitGcsName] = CPLStrdup(name);
338
0
        nameFound = true;
339
0
    }
340
0
    if (!nameFound)
341
0
    {
342
0
        CPLFree(ret);
343
0
        ret = nullptr;
344
0
    }
345
0
    return ret;
346
0
}
347
348
/************************************************************************/
349
/*                       SetLinearUnitCitation()                        */
350
/*                                                                      */
351
/*      Set linear unit Citation string                                 */
352
/************************************************************************/
353
void SetLinearUnitCitation(std::map<geokey_t, std::string> &oMapAsciiKeys,
354
                           const char *pszLinearUOMName)
355
0
{
356
0
    CPLString osCitation;
357
0
    auto oIter = oMapAsciiKeys.find(PCSCitationGeoKey);
358
0
    if (oIter != oMapAsciiKeys.end())
359
0
    {
360
0
        osCitation = oIter->second;
361
0
    }
362
0
    if (!osCitation.empty())
363
0
    {
364
0
        size_t n = osCitation.size();
365
0
        if (osCitation[n - 1] != '|')
366
0
            osCitation += "|";
367
0
        osCitation += "LUnits = ";
368
0
        osCitation += pszLinearUOMName;
369
0
        osCitation += "|";
370
0
    }
371
0
    else
372
0
    {
373
0
        osCitation = "LUnits = ";
374
0
        osCitation += pszLinearUOMName;
375
0
    }
376
0
    oMapAsciiKeys[PCSCitationGeoKey] = std::move(osCitation);
377
0
}
378
379
/************************************************************************/
380
/*                         SetGeogCSCitation()                          */
381
/*                                                                      */
382
/*      Set geogcs Citation string                                      */
383
/************************************************************************/
384
void SetGeogCSCitation(GTIF *psGTIF,
385
                       std::map<geokey_t, std::string> &oMapAsciiKeys,
386
                       const OGRSpatialReference *poSRS,
387
                       const char *angUnitName, int nDatum, short nSpheroid)
388
0
{
389
0
    bool bRewriteGeogCitation = false;
390
0
    CPLString osOriginalGeogCitation;
391
0
    auto oIter = oMapAsciiKeys.find(GeogCitationGeoKey);
392
0
    if (oIter != oMapAsciiKeys.end())
393
0
    {
394
0
        osOriginalGeogCitation = oIter->second;
395
0
    }
396
0
    if (osOriginalGeogCitation.empty())
397
0
        return;
398
399
0
    CPLString osCitation;
400
0
    if (!STARTS_WITH_CI(osOriginalGeogCitation, "GCS Name = "))
401
0
    {
402
0
        osCitation = "GCS Name = ";
403
0
        osCitation += std::move(osOriginalGeogCitation);
404
0
    }
405
0
    else
406
0
    {
407
0
        osCitation = std::move(osOriginalGeogCitation);
408
0
    }
409
410
0
    if (nDatum == KvUserDefined)
411
0
    {
412
0
        const char *datumName = poSRS->GetAttrValue("DATUM");
413
0
        if (datumName && strlen(datumName) > 0)
414
0
        {
415
0
            osCitation += "|Datum = ";
416
0
            osCitation += datumName;
417
0
            bRewriteGeogCitation = true;
418
0
        }
419
0
    }
420
0
    if (nSpheroid == KvUserDefined)
421
0
    {
422
0
        const char *spheroidName = poSRS->GetAttrValue("SPHEROID");
423
0
        if (spheroidName && strlen(spheroidName) > 0)
424
0
        {
425
0
            osCitation += "|Ellipsoid = ";
426
0
            osCitation += spheroidName;
427
0
            bRewriteGeogCitation = true;
428
0
        }
429
0
    }
430
431
0
    const char *primemName = poSRS->GetAttrValue("PRIMEM");
432
0
    if (primemName && strlen(primemName) > 0)
433
0
    {
434
0
        osCitation += "|Primem = ";
435
0
        osCitation += primemName;
436
0
        bRewriteGeogCitation = true;
437
438
0
        double primemValue = poSRS->GetPrimeMeridian(nullptr);
439
0
        if (angUnitName && !EQUAL(angUnitName, "Degree"))
440
0
        {
441
0
            const double aUnit = poSRS->GetAngularUnits(nullptr);
442
0
            primemValue *= aUnit;
443
0
        }
444
0
        GTIFKeySet(psGTIF, GeogPrimeMeridianLongGeoKey, TYPE_DOUBLE, 1,
445
0
                   primemValue);
446
0
    }
447
0
    if (angUnitName && strlen(angUnitName) > 0 && !EQUAL(angUnitName, "Degree"))
448
0
    {
449
0
        osCitation += "|AUnits = ";
450
0
        osCitation += angUnitName;
451
0
        bRewriteGeogCitation = true;
452
0
    }
453
454
0
    if (osCitation.back() != '|')
455
0
        osCitation += "|";
456
457
0
    if (bRewriteGeogCitation)
458
0
    {
459
0
        oMapAsciiKeys[GeogCitationGeoKey] = std::move(osCitation);
460
0
    }
461
0
}
462
463
/************************************************************************/
464
/*                          SetCitationToSRS()                          */
465
/*                                                                      */
466
/*      Parse and set Citation string to SRS                            */
467
/************************************************************************/
468
OGRBoolean SetCitationToSRS(GTIF *hGTIF, char *szCTString, int nCTStringLen,
469
                            geokey_t geoKey, OGRSpatialReference *poSRS,
470
                            OGRBoolean *linearUnitIsSet)
471
0
{
472
0
    OGRBoolean ret = FALSE;
473
0
    const char *lUnitName = nullptr;
474
475
0
    poSRS->GetLinearUnits(&lUnitName);
476
0
    if (!lUnitName || strlen(lUnitName) == 0 || EQUAL(lUnitName, "unknown"))
477
0
        *linearUnitIsSet = FALSE;
478
0
    else
479
0
        *linearUnitIsSet = TRUE;
480
481
0
    char *imgCTName = ImagineCitationTranslation(szCTString, geoKey);
482
0
    if (imgCTName)
483
0
    {
484
0
        strncpy(szCTString, imgCTName, nCTStringLen);
485
0
        szCTString[nCTStringLen - 1] = '\0';
486
0
        CPLFree(imgCTName);
487
0
    }
488
0
    char **ctNames = CitationStringParse(szCTString, geoKey);
489
0
    if (ctNames)
490
0
    {
491
0
        if (poSRS->GetRoot() == nullptr)
492
0
            poSRS->SetNode("PROJCS", "unnamed");
493
0
        if (ctNames[CitPcsName])
494
0
        {
495
0
            poSRS->SetNode("PROJCS", ctNames[CitPcsName]);
496
0
            ret = TRUE;
497
0
        }
498
0
        if (ctNames[CitProjectionName])
499
0
            poSRS->SetProjection(ctNames[CitProjectionName]);
500
501
0
        if (ctNames[CitLUnitsName])
502
0
        {
503
0
            double unitSize = 0.0;
504
0
            int size = static_cast<int>(strlen(ctNames[CitLUnitsName]));
505
0
            if (strchr(ctNames[CitLUnitsName], '\0'))
506
0
                size -= 1;
507
0
            for (int i = 0; apszUnitMap[i] != nullptr; i += 2)
508
0
            {
509
0
                if (EQUALN(apszUnitMap[i], ctNames[CitLUnitsName], size))
510
0
                {
511
0
                    unitSize = CPLAtof(apszUnitMap[i + 1]);
512
0
                    break;
513
0
                }
514
0
            }
515
0
            if (unitSize == 0.0)
516
0
            {
517
0
                CPL_IGNORE_RET_VAL(GDALGTIFKeyGetDOUBLE(
518
0
                    hGTIF, ProjLinearUnitSizeGeoKey, &unitSize, 0, 1));
519
0
            }
520
0
            poSRS->SetLinearUnits(ctNames[CitLUnitsName], unitSize);
521
0
            *linearUnitIsSet = TRUE;
522
0
        }
523
0
        for (int i = 0; i < nCitationNameTypes; i++)
524
0
            CPLFree(ctNames[i]);
525
0
        CPLFree(ctNames);
526
0
    }
527
528
    // If no "PCS Name = " (from Erdas) in GTCitationGeoKey.
529
0
    if (geoKey == GTCitationGeoKey)
530
0
    {
531
0
        if (strlen(szCTString) > 0 && !strstr(szCTString, "PCS Name = "))
532
0
        {
533
0
            const char *pszProjCS = poSRS->GetAttrValue("PROJCS");
534
0
            if ((!(pszProjCS && strlen(pszProjCS) > 0) &&
535
0
                 !strstr(szCTString, "Projected Coordinates")) ||
536
0
                (pszProjCS && strstr(pszProjCS, "unnamed")))
537
0
                poSRS->SetNode("PROJCS", szCTString);
538
0
            ret = TRUE;
539
0
        }
540
0
    }
541
542
0
    return ret;
543
0
}
544
545
/************************************************************************/
546
/*                       GetGeogCSFromCitation()                        */
547
/*                                                                      */
548
/*      Parse and get geogcs names from a Citation string               */
549
/************************************************************************/
550
void GetGeogCSFromCitation(char *szGCSName, int nGCSName, geokey_t geoKey,
551
                           char **ppszGeogName, char **ppszDatumName,
552
                           char **ppszPMName, char **ppszSpheroidName,
553
                           char **ppszAngularUnits)
554
0
{
555
0
    *ppszGeogName = nullptr;
556
0
    *ppszDatumName = nullptr;
557
0
    *ppszPMName = nullptr;
558
0
    *ppszSpheroidName = nullptr;
559
0
    *ppszAngularUnits = nullptr;
560
561
0
    char *imgCTName = ImagineCitationTranslation(szGCSName, geoKey);
562
0
    if (imgCTName)
563
0
    {
564
0
        strncpy(szGCSName, imgCTName, nGCSName);
565
0
        szGCSName[nGCSName - 1] = '\0';
566
0
        CPLFree(imgCTName);
567
0
    }
568
0
    char **ctNames = CitationStringParse(szGCSName, geoKey);
569
0
    if (ctNames)
570
0
    {
571
0
        if (ctNames[CitGcsName])
572
0
            *ppszGeogName = CPLStrdup(ctNames[CitGcsName]);
573
574
0
        if (ctNames[CitDatumName])
575
0
            *ppszDatumName = CPLStrdup(ctNames[CitDatumName]);
576
577
0
        if (ctNames[CitEllipsoidName])
578
0
            *ppszSpheroidName = CPLStrdup(ctNames[CitEllipsoidName]);
579
580
0
        if (ctNames[CitPrimemName])
581
0
            *ppszPMName = CPLStrdup(ctNames[CitPrimemName]);
582
583
0
        if (ctNames[CitAUnitsName])
584
0
            *ppszAngularUnits = CPLStrdup(ctNames[CitAUnitsName]);
585
586
0
        for (int i = 0; i < nCitationNameTypes; i++)
587
0
            CPLFree(ctNames[i]);
588
0
        CPLFree(ctNames);
589
0
    }
590
0
    return;
591
0
}
592
593
/************************************************************************/
594
/*               CheckCitationKeyForStatePlaneUTM()                     */
595
/*                                                                      */
596
/*      Handle state plane and UTM in citation key                      */
597
/************************************************************************/
598
OGRBoolean CheckCitationKeyForStatePlaneUTM(GTIF *hGTIF, GTIFDefn *psDefn,
599
                                            OGRSpatialReference *poSRS,
600
                                            OGRBoolean *pLinearUnitIsSet)
601
0
{
602
0
    if (!hGTIF || !psDefn || !poSRS)
603
0
        return FALSE;
604
605
/* -------------------------------------------------------------------- */
606
/*      For ESRI builds we are interested in maximizing PE              */
607
/*      compatibility, but generally we prefer to use EPSG              */
608
/*      definitions of the coordinate system if PCS is defined.         */
609
/* -------------------------------------------------------------------- */
610
0
#if !defined(ESRI_BUILD)
611
0
    if (psDefn->PCS != KvUserDefined)
612
0
        return FALSE;
613
0
#endif
614
615
0
    char szCTString[512] = {'\0'};
616
617
    // Check units.
618
0
    char units[32] = {'\0'};
619
620
0
    bool hasUnits = false;
621
0
    if (GDALGTIFKeyGetASCII(hGTIF, GTCitationGeoKey, szCTString,
622
0
                            sizeof(szCTString)))
623
0
    {
624
0
        const CPLString osLCCT = CPLString(szCTString).tolower();
625
626
0
        if (strstr(osLCCT, "us") && strstr(osLCCT, "survey") &&
627
0
            (strstr(osLCCT, "feet") || strstr(osLCCT, "foot")))
628
0
            strcpy(units, "us_survey_feet");
629
0
        else if (strstr(osLCCT, "linear_feet") ||
630
0
                 strstr(osLCCT, "linear_foot") ||
631
0
                 strstr(osLCCT, "international"))
632
0
            strcpy(units, "international_feet");
633
0
        else if (strstr(osLCCT, "meter"))
634
0
            strcpy(units, "meters");
635
636
0
        if (strlen(units) > 0)
637
0
            hasUnits = true;
638
639
0
        if (strstr(szCTString, "Projection Name = ") &&
640
0
            strstr(szCTString, "_StatePlane_"))
641
0
        {
642
0
            const char *pStr = strstr(szCTString, "Projection Name = ") +
643
0
                               strlen("Projection Name = ");
644
0
            CPLString osCSName(pStr);
645
0
            const char *pReturn = strchr(pStr, '\n');
646
0
            if (pReturn)
647
0
                osCSName.resize(pReturn - pStr);
648
0
            if (poSRS->ImportFromESRIStatePlaneWKT(0, nullptr, nullptr, 32767,
649
0
                                                   osCSName) == OGRERR_NONE)
650
0
            {
651
                // For some erdas citation keys, the state plane CS name is
652
                // incomplete, the unit check is necessary.
653
0
                bool done = false;
654
0
                if (hasUnits)
655
0
                {
656
0
                    OGR_SRSNode *poUnit = poSRS->GetAttrNode("PROJCS|UNIT");
657
658
0
                    if (poUnit != nullptr && poUnit->GetChildCount() >= 2)
659
0
                    {
660
0
                        const CPLString unitName =
661
0
                            CPLString(poUnit->GetChild(0)->GetValue())
662
0
                                .tolower();
663
664
0
                        if (strstr(units, "us_survey_feet"))
665
0
                        {
666
0
                            if (strstr(unitName, "us_survey_feet") ||
667
0
                                strstr(unitName, "foot_us"))
668
0
                                done = true;
669
0
                        }
670
0
                        else if (strstr(units, "international_feet"))
671
0
                        {
672
0
                            if (strstr(unitName, "feet") ||
673
0
                                strstr(unitName, "foot"))
674
0
                                done = true;
675
0
                        }
676
0
                        else if (strstr(units, "meters"))
677
0
                        {
678
0
                            if (strstr(unitName, "meter"))
679
0
                                done = true;
680
0
                        }
681
0
                    }
682
0
                }
683
0
                if (done)
684
0
                    return true;
685
0
            }
686
0
        }
687
0
    }
688
0
    if (!hasUnits)
689
0
    {
690
0
        char *pszUnitsName = nullptr;
691
0
        GTIFGetUOMLengthInfo(psDefn->UOMLength, &pszUnitsName, nullptr);
692
0
        if (pszUnitsName)
693
0
        {
694
0
            const CPLString osLCCT = CPLString(pszUnitsName).tolower();
695
0
            GTIFFreeMemory(pszUnitsName);
696
697
0
            if (strstr(osLCCT, "us") && strstr(osLCCT, "survey") &&
698
0
                (strstr(osLCCT, "feet") || strstr(osLCCT, "foot")))
699
0
                strcpy(units, "us_survey_feet");
700
0
            else if (strstr(osLCCT, "feet") || strstr(osLCCT, "foot"))
701
0
                strcpy(units, "international_feet");
702
0
            else if (strstr(osLCCT, "meter"))
703
0
                strcpy(units, "meters");
704
            // hasUnits = true;
705
0
        }
706
0
    }
707
708
0
    if (strlen(units) == 0)
709
0
        strcpy(units, "meters");
710
711
    // Check PCSCitationGeoKey if it exists.
712
0
    szCTString[0] = '\0';
713
0
    if (GDALGTIFKeyGetASCII(hGTIF, PCSCitationGeoKey, szCTString,
714
0
                            sizeof(szCTString)))
715
0
    {
716
        // For tif created by LEICA(ERDAS), ESRI state plane pe string was
717
        // used and the state plane zone is given in PCSCitation. Therefore
718
        // try ESRI pe string first.
719
0
        SetCitationToSRS(hGTIF, szCTString,
720
0
                         static_cast<int>(strlen(szCTString)),
721
0
                         PCSCitationGeoKey, poSRS, pLinearUnitIsSet);
722
0
        const char *pcsName = poSRS->GetAttrValue("PROJCS");
723
0
        const char *pStr = nullptr;
724
0
        if ((pcsName &&
725
0
             (pStr = strstr(pcsName, "State Plane Zone ")) != nullptr) ||
726
0
            (pStr = strstr(szCTString, "State Plane Zone ")) != nullptr)
727
0
        {
728
0
            pStr += strlen("State Plane Zone ");
729
0
            int statePlaneZone = atoi(pStr);
730
            // Safe version of statePlaneZone = abs(statePlaneZone), but
731
            // I (ERO)'ve no idea why negative zone number would make sense...
732
0
            if (statePlaneZone < 0 && statePlaneZone > INT_MIN)
733
0
                statePlaneZone = -statePlaneZone;
734
0
            char nad[32];
735
0
            strcpy(nad, "HARN");
736
0
            if (strstr(szCTString, "NAD83") || strstr(szCTString, "NAD = 83"))
737
0
                strcpy(nad, "NAD83");
738
0
            else if (strstr(szCTString, "NAD27") ||
739
0
                     strstr(szCTString, "NAD = 27"))
740
0
                strcpy(nad, "NAD27");
741
0
            if (poSRS->ImportFromESRIStatePlaneWKT(statePlaneZone, nad, units,
742
0
                                                   psDefn->PCS) == OGRERR_NONE)
743
0
                return TRUE;
744
0
        }
745
0
        else if (pcsName &&
746
0
                 (/* pStr = */ strstr(pcsName, "UTM Zone ")) != nullptr)
747
0
        {
748
0
            CheckUTM(psDefn, szCTString);
749
0
        }
750
0
    }
751
752
    // Check state plane again to see if a pe string is available.
753
0
    if (psDefn->PCS != KvUserDefined)
754
0
    {
755
0
        if (poSRS->ImportFromESRIStatePlaneWKT(0, nullptr, units,
756
0
                                               psDefn->PCS) == OGRERR_NONE)
757
0
            return TRUE;
758
0
    }
759
760
0
    return FALSE;
761
0
}
762
763
/************************************************************************/
764
/*                               CheckUTM()                             */
765
/*                                                                      */
766
/*        Check utm proj code by its name.                              */
767
/************************************************************************/
768
void CheckUTM(GTIFDefn *psDefn, const char *pszCtString)
769
0
{
770
0
    if (!psDefn || !pszCtString)
771
0
        return;
772
773
0
    const char *p = strstr(pszCtString, "Datum = ");
774
0
    char datumName[128] = {'\0'};
775
0
    if (p)
776
0
    {
777
0
        p += strlen("Datum = ");
778
0
        const char *p1 = strchr(p, '|');
779
0
        if (p1 && p1 - p < static_cast<int>(sizeof(datumName)))
780
0
        {
781
0
            strncpy(datumName, p, p1 - p);
782
0
            datumName[p1 - p] = '\0';
783
0
        }
784
0
        else
785
0
        {
786
0
            CPLStrlcpy(datumName, p, sizeof(datumName));
787
0
        }
788
0
    }
789
0
    else
790
0
    {
791
0
        datumName[0] = '\0';
792
0
    }
793
794
0
    p = strstr(pszCtString, "UTM Zone ");
795
0
    if (p)
796
0
    {
797
0
        p += strlen("UTM Zone ");
798
0
        const char *p1 = strchr(p, '|');
799
0
        char utmName[64] = {'\0'};
800
0
        if (p1 && p1 - p < static_cast<int>(sizeof(utmName)))
801
0
        {
802
0
            strncpy(utmName, p, p1 - p);
803
0
            utmName[p1 - p] = '\0';
804
0
        }
805
0
        else
806
0
        {
807
0
            CPLStrlcpy(utmName, p, sizeof(utmName));
808
0
        }
809
810
        // Static to get this off the stack and constructed only one time.
811
0
        static const char *const apszUtmProjCode[] = {
812
0
            "PSAD56", "17N", "16017", "PSAD56", "18N",   "16018",
813
0
            "PSAD56", "19N", "16019", "PSAD56", "20N",   "16020",
814
0
            "PSAD56", "21N", "16021", "PSAD56", "17S",   "16117",
815
0
            "PSAD56", "18S", "16118", "PSAD56", "19S",   "16119",
816
0
            "PSAD56", "20S", "16120", "PSAD56", "21S",   "16121",
817
0
            "PSAD56", "22S", "16122", nullptr,  nullptr, nullptr};
818
819
0
        for (int i = 0; apszUtmProjCode[i] != nullptr; i += 3)
820
0
        {
821
0
            if (EQUALN(utmName, apszUtmProjCode[i + 1],
822
0
                       strlen(apszUtmProjCode[i + 1])) &&
823
0
                EQUAL(datumName, apszUtmProjCode[i]))
824
0
            {
825
0
                if (psDefn->ProjCode != atoi(apszUtmProjCode[i + 2]))
826
0
                {
827
0
                    psDefn->ProjCode =
828
0
                        static_cast<short>(atoi(apszUtmProjCode[i + 2]));
829
0
                    GTIFGetProjTRFInfo(psDefn->ProjCode, nullptr,
830
0
                                       &(psDefn->Projection), psDefn->ProjParm);
831
0
                    break;
832
0
                }
833
0
            }
834
0
        }
835
0
    }
836
837
0
    return;
838
0
}