Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogr_srs_pci.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  OGRSpatialReference translation to/from PCI georeferencing
5
 *           information.
6
 * Author:   Andrey Kiselev, dron@ak4719.spb.edu
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2003, Andrey Kiselev <dron@ak4719.spb.edu>
10
 * Copyright (c) 2009-2011, Even Rouault <even dot rouault at spatialys.com>
11
 *
12
 * SPDX-License-Identifier: MIT
13
 ****************************************************************************/
14
15
#include "cpl_port.h"
16
#include "ogr_srs_api.h"
17
18
#include <cctype>
19
#include <cstdio>
20
#include <cstdlib>
21
#include <cstring>
22
23
#include "cpl_conv.h"
24
#include "cpl_csv.h"
25
#include "cpl_error.h"
26
#include "cpl_string.h"
27
#include "cpl_vsi.h"
28
#include "ogr_core.h"
29
#include "ogr_p.h"
30
#include "ogr_spatialref.h"
31
32
// PCI uses a 16-character string for coordinate system and datum/ellipsoid.
33
constexpr int knProjSize = 16;
34
35
struct PCIDatums
36
{
37
    const char *pszPCIDatum;
38
    int nEPSGCode;
39
};
40
41
static const PCIDatums asDatums[] = {
42
    {"D-01", 4267},  // NAD27 (USA, NADCON)
43
    {"D-03", 4267},  // NAD27 (Canada, NTv1)
44
    {"D-02", 4269},  // NAD83 (USA, NADCON)
45
    {"D-04", 4269},  // NAD83 (Canada, NTv1)
46
    {"D-25", 7844},  // GDA2020 (Australia)
47
    {"D000", 4326},  // WGS 1984
48
    {"D001", 4322},  // WGS 1972
49
    {"D008", 4296},  // Sudan
50
    {"D013", 4601},  // Antigua Island Astro 1943
51
    {"D029", 4202},  // Australian Geodetic 1966
52
    {"D030", 4203},  // Australian Geodetic 1984
53
    {"D033", 4216},  // Bermuda 1957
54
    {"D034", 4165},  // Bissau
55
    {"D036", 4219},  // Bukit Rimpah
56
    {"D038", 4221},  // Campo Inchauspe
57
    {"D040", 4222},  // Cape
58
    {"D042", 4223},  // Carthage
59
    {"D044", 4224},  // Chua Astro
60
    {"D045", 4225},  // Corrego Alegre
61
    {"D046", 4155},  // Dabola (Guinea)
62
    {"D066", 4272},  // Geodetic Datum 1949 (New Zealand)
63
    {"D071", 4255},  // Herat North (Afghanistan)
64
    {"D077", 4239},  // Indian 1954 (Thailand, Vietnam)
65
    {"D078", 4240},  // Indian 1975 (Thailand)
66
    {"D083", 4244},  // Kandawala (Sri Lanka)
67
    {"D085", 4245},  // Kertau 1948 (West Malaysia & Singapore)
68
    {"D088", 4250},  // Leigon (Ghana)
69
    {"D089", 4251},  // Liberia 1964 (Liberia)
70
    {"D092", 4256},  // Mahe 1971 (Mahe Island)
71
    {"D093", 4262},  // Massawa (Ethiopia (Eritrea))
72
    {"D094", 4261},  // Merchich (Morocco)
73
    {"D098",
74
     4604},  // Montserrat Island Astro 1958 (Montserrat (Leeward Islands))
75
    {"D110", 4267},  // NAD27 / Alaska
76
    {"D139", 4282},  // Pointe Noire 1948 (Congo)
77
    {"D140", 4615},  // Porto Santo 1936 (Porto Santo, Madeira Islands)
78
    {"D151", 4139},  // Puerto Rico (Puerto Rico, Virgin Islands)
79
    {"D153", 4287},  // Qornoq (Greenland (South))
80
    {"D158", 4292},  // Sapper Hill 1943 (East Falkland Island)
81
    {"D159", 4293},  // Schwarzeck (Namibia)
82
    {"D160", 4616},  // Selvagem Grande 1938 (Salvage Islands)
83
    {"D176", 4297},  // Tananarive Observatory 1925 (Madagascar)
84
    {"D177", 4298},  // Timbalai 1948 (Brunei, East Malaysia (Sabah, Sarawak))
85
    {"D187", 4309},  // Yacare (Uruguay)
86
    {"D188", 4311},  // Zanderij (Suriname)
87
    {"D401", 4124},  // RT90 (Sweden)
88
    {"D501", 4312},  // MGI (Hermannskogel, Austria)
89
    {"D536", 4283},  // GDA94 (Australia)
90
    {nullptr, 0}};
91
92
static const PCIDatums asEllips[] = {
93
    {"E000", 7008},  // Clarke, 1866 (NAD1927)
94
    {"E001", 7034},  // Clarke, 1880
95
    {"E002", 7004},  // Bessel, 1841
96
    {"E004", 7022},  // International, 1924 (Hayford, 1909)
97
    {"E005", 7043},  // WGS, 1972
98
    {"E006", 7042},  // Everest, 1830
99
    {"E008", 7019},  // GRS, 1980 (NAD1983)
100
    {"E009", 7001},  // Airy, 1830
101
    {"E010", 7018},  // Modified Everest
102
    {"E011", 7002},  // Modified Airy
103
    {"E012", 7030},  // WGS, 1984 (GPS)
104
    {"E014", 7003},  // Australian National, 1965
105
    {"E015", 7024},  // Krassovsky, 1940
106
    {"E016", 7053},  // Hough
107
    {"E019", 7052},  // normal sphere
108
    {"E333", 7046},  // Bessel 1841 (Japan By Law)
109
    {"E900", 7006},  // Bessel, 1841 (Namibia)
110
    {"E901", 7044},  // Everest, 1956
111
    {"E902", 7056},  // Everest, 1969
112
    {"E903", 7016},  // Everest (Sabah & Sarawak)
113
    {"E904", 7020},  // Helmert, 1906
114
    {"E907", 7036},  // South American, 1969
115
    {"E910", 7041},  // ATS77
116
    {nullptr, 0}};
117
118
/************************************************************************/
119
/*                          OSRImportFromPCI()                          */
120
/************************************************************************/
121
122
/**
123
 * \brief Import coordinate system from PCI projection definition.
124
 *
125
 * This function is the same as OGRSpatialReference::importFromPCI().
126
 */
127
128
OGRErr OSRImportFromPCI(OGRSpatialReferenceH hSRS, const char *pszProj,
129
                        const char *pszUnits, double *padfPrjParams)
130
131
0
{
132
0
    VALIDATE_POINTER1(hSRS, "OSRImportFromPCI", OGRERR_FAILURE);
133
134
0
    return OGRSpatialReference::FromHandle(hSRS)->importFromPCI(
135
0
        pszProj, pszUnits, padfPrjParams);
136
0
}
137
138
/************************************************************************/
139
/*                           importFromPCI()                            */
140
/************************************************************************/
141
142
/**
143
 * \brief Import coordinate system from PCI projection definition.
144
 *
145
 * PCI software uses 16-character string to specify coordinate system
146
 * and datum/ellipsoid. You should supply at least this string to the
147
 * importFromPCI() function.
148
 *
149
 * This function is the equivalent of the C function OSRImportFromPCI().
150
 *
151
 * @param pszProj NULL terminated string containing the definition. Looks
152
 * like "pppppppppppp Ennn" or "pppppppppppp Dnnn", where "pppppppppppp" is
153
 * a projection code, "Ennn" is an ellipsoid code, "Dnnn" a datum code.
154
 *
155
 * @param pszUnits Grid units code ("DEGREE" or "METRE"). If NULL "METRE" will
156
 * be used.
157
 *
158
 * @param padfPrjParams Array of 17 coordinate system parameters:
159
 *
160
 * [0]  Spheroid semi major axis
161
 * [1]  Spheroid semi minor axis
162
 * [2]  Reference Longitude
163
 * [3]  Reference Latitude
164
 * [4]  First Standard Parallel
165
 * [5]  Second Standard Parallel
166
 * [6]  False Easting
167
 * [7]  False Northing
168
 * [8]  Scale Factor
169
 * [9]  Height above sphere surface
170
 * [10] Longitude of 1st point on center line
171
 * [11] Latitude of 1st point on center line
172
 * [12] Longitude of 2nd point on center line
173
 * [13] Latitude of 2nd point on center line
174
 * [14] Azimuth east of north for center line
175
 * [15] Landsat satellite number
176
 * [16] Landsat path number
177
 *
178
 * Particular projection uses different parameters, unused ones may be set to
179
 * zero. If NULL is supplied instead of an array pointer, default values will be
180
 * used (i.e., zeroes).
181
 *
182
 * @return OGRERR_NONE on success or an error code in case of failure.
183
 */
184
185
OGRErr OGRSpatialReference::importFromPCI(const char *pszProj,
186
                                          const char *pszUnits,
187
                                          const double *padfPrjParams)
188
189
5.21k
{
190
5.21k
    Clear();
191
192
5.21k
    if (pszProj == nullptr ||
193
5.21k
        CPLStrnlen(pszProj, knProjSize) < static_cast<size_t>(knProjSize))
194
13
    {
195
13
        return OGRERR_CORRUPT_DATA;
196
13
    }
197
198
5.20k
    CPLDebug("OSR_PCI", "Trying to import projection \"%s\"", pszProj);
199
200
    /* -------------------------------------------------------------------- */
201
    /*      Use safe defaults if projection parameters are not supplied.    */
202
    /* -------------------------------------------------------------------- */
203
5.20k
    static const double adfZeroedPrjParams[17] = {0};
204
5.20k
    if (padfPrjParams == nullptr)
205
0
    {
206
0
        padfPrjParams = adfZeroedPrjParams;
207
0
    }
208
209
    /* -------------------------------------------------------------------- */
210
    /*      Extract and "normalize" the earthmodel to look like E001,       */
211
    /*      D-02 or D109.                                                   */
212
    /* -------------------------------------------------------------------- */
213
5.20k
    char szEarthModel[5] = {};
214
215
5.20k
    strcpy(szEarthModel, "");
216
5.20k
    const char *pszEM = pszProj + strlen(pszProj) - 1;
217
55.1k
    while (pszEM != pszProj)
218
55.1k
    {
219
55.1k
        if (*pszEM == 'e' || *pszEM == 'E' || *pszEM == 'd' || *pszEM == 'D')
220
5.20k
        {
221
5.20k
            int nCode = atoi(pszEM + 1);
222
223
5.20k
            if (nCode >= -99 && nCode <= 999)
224
5.20k
                snprintf(szEarthModel, sizeof(szEarthModel), "%c%03d",
225
5.20k
                         CPLToupper(static_cast<unsigned char>(*pszEM)), nCode);
226
227
5.20k
            break;
228
5.20k
        }
229
230
49.9k
        pszEM--;
231
49.9k
    }
232
233
5.20k
    const bool bIsNAD27 = EQUAL(pszEM, "E000") || EQUAL(pszEM, "D-01") ||
234
5.15k
                          EQUAL(pszEM, "D-03") || EQUAL(pszEM, "D-07") ||
235
5.15k
                          EQUAL(pszEM, "D-09") || EQUAL(pszEM, "D-11") ||
236
5.15k
                          EQUAL(pszEM, "D-13") || EQUAL(pszEM, "D-17");
237
238
    /* -------------------------------------------------------------------- */
239
    /*      Operate on the basis of the projection name.                    */
240
    /* -------------------------------------------------------------------- */
241
5.20k
    if (STARTS_WITH_CI(pszProj, "LONG/LAT"))
242
233
    {
243
        // TODO(schwehr): A NOP is okay?
244
233
    }
245
4.96k
    else if (STARTS_WITH_CI(pszProj, "METER") ||
246
4.96k
             STARTS_WITH_CI(pszProj, "METRE"))
247
4.28k
    {
248
4.28k
        SetLocalCS("METER");
249
4.28k
        SetLinearUnits("METER", 1.0);
250
4.28k
    }
251
678
    else if (STARTS_WITH_CI(pszProj, "FEET") || STARTS_WITH_CI(pszProj, "FOOT"))
252
0
    {
253
0
        SetLocalCS("FEET");
254
0
        SetLinearUnits("FEET", CPLAtof(SRS_UL_FOOT_CONV));
255
0
    }
256
678
    else if (STARTS_WITH_CI(pszProj, "ACEA"))
257
9
    {
258
9
        SetACEA(padfPrjParams[4], padfPrjParams[5], padfPrjParams[3],
259
9
                padfPrjParams[2], padfPrjParams[6], padfPrjParams[7]);
260
9
    }
261
669
    else if (STARTS_WITH_CI(pszProj, "AE"))
262
0
    {
263
0
        SetAE(padfPrjParams[3], padfPrjParams[2], padfPrjParams[6],
264
0
              padfPrjParams[7]);
265
0
    }
266
669
    else if (STARTS_WITH_CI(pszProj, "CASS "))
267
0
    {
268
0
        SetCS(padfPrjParams[3], padfPrjParams[2], padfPrjParams[6],
269
0
              padfPrjParams[7]);
270
0
    }
271
669
    else if (STARTS_WITH_CI(pszProj, "EC"))
272
0
    {
273
0
        SetEC(padfPrjParams[4], padfPrjParams[5], padfPrjParams[3],
274
0
              padfPrjParams[2], padfPrjParams[6], padfPrjParams[7]);
275
0
    }
276
669
    else if (STARTS_WITH_CI(pszProj, "ER"))
277
0
    {
278
        // PCI and GCTP don't support natural origin lat.
279
0
        SetEquirectangular2(0.0, padfPrjParams[2], padfPrjParams[3],
280
0
                            padfPrjParams[6], padfPrjParams[7]);
281
0
    }
282
669
    else if (STARTS_WITH_CI(pszProj, "GNO"))
283
0
    {
284
0
        SetGnomonic(padfPrjParams[3], padfPrjParams[2], padfPrjParams[6],
285
0
                    padfPrjParams[7]);
286
0
    }
287
    // FIXME: GVNP --- General Vertical Near- Side Perspective skipped.
288
    // FIXME: GOOD -- Our Goode's is not the interrupted version from PCI.
289
669
    else if (STARTS_WITH_CI(pszProj, "LAEA"))
290
0
    {
291
0
        SetLAEA(padfPrjParams[3], padfPrjParams[2], padfPrjParams[6],
292
0
                padfPrjParams[7]);
293
0
    }
294
669
    else if (STARTS_WITH_CI(pszProj, "LCC "))
295
176
    {
296
176
        SetLCC(padfPrjParams[4], padfPrjParams[5], padfPrjParams[3],
297
176
               padfPrjParams[2], padfPrjParams[6], padfPrjParams[7]);
298
176
    }
299
493
    else if (STARTS_WITH_CI(pszProj, "LCC_1SP "))
300
9
    {
301
9
        SetLCC1SP(padfPrjParams[3], padfPrjParams[2], padfPrjParams[8],
302
9
                  padfPrjParams[6], padfPrjParams[7]);
303
9
    }
304
484
    else if (STARTS_WITH_CI(pszProj, "MC"))
305
0
    {
306
0
        SetMC(padfPrjParams[3], padfPrjParams[2], padfPrjParams[6],
307
0
              padfPrjParams[7]);
308
0
    }
309
484
    else if (STARTS_WITH_CI(pszProj, "MER"))
310
0
    {
311
0
        if (EQUAL(pszEM, "D894") && padfPrjParams[3] == 0 &&
312
0
            padfPrjParams[2] == 0 && padfPrjParams[8] == 1.0 &&
313
0
            padfPrjParams[6] == 0 && padfPrjParams[7] == 0)
314
0
        {
315
            // Special case for Web Mercator
316
0
            return importFromEPSG(3857);
317
0
        }
318
0
        SetMercator(padfPrjParams[3], padfPrjParams[2],
319
0
                    (padfPrjParams[8] != 0.0) ? padfPrjParams[8] : 1.0,
320
0
                    padfPrjParams[6], padfPrjParams[7]);
321
0
    }
322
484
    else if (STARTS_WITH_CI(pszProj, "OG"))
323
0
    {
324
0
        SetOrthographic(padfPrjParams[3], padfPrjParams[2], padfPrjParams[6],
325
0
                        padfPrjParams[7]);
326
0
    }
327
484
    else if (STARTS_WITH_CI(pszProj, "OM "))
328
53
    {
329
53
        if (padfPrjParams[10] == 0.0 && padfPrjParams[11] == 0.0 &&
330
53
            padfPrjParams[12] == 0.0 && padfPrjParams[13] == 0.0)
331
53
        {
332
53
            SetHOM(padfPrjParams[3], padfPrjParams[2], padfPrjParams[14],
333
53
                   padfPrjParams[14],  // Use azimuth for grid angle.
334
53
                   padfPrjParams[8], padfPrjParams[6], padfPrjParams[7]);
335
53
        }
336
0
        else
337
0
        {
338
0
            SetHOM2PNO(padfPrjParams[3], padfPrjParams[11], padfPrjParams[10],
339
0
                       padfPrjParams[13], padfPrjParams[12], padfPrjParams[8],
340
0
                       padfPrjParams[6], padfPrjParams[7]);
341
0
        }
342
53
    }
343
431
    else if (STARTS_WITH_CI(pszProj, "PC"))
344
0
    {
345
0
        SetPolyconic(padfPrjParams[3], padfPrjParams[2], padfPrjParams[6],
346
0
                     padfPrjParams[7]);
347
0
    }
348
431
    else if (STARTS_WITH_CI(pszProj, "PS"))
349
11
    {
350
11
        SetPS(padfPrjParams[3], padfPrjParams[2],
351
11
              (padfPrjParams[8] != 0.0) ? padfPrjParams[8] : 1.0,
352
11
              padfPrjParams[6], padfPrjParams[7]);
353
11
    }
354
420
    else if (STARTS_WITH_CI(pszProj, "ROB"))
355
0
    {
356
0
        SetRobinson(padfPrjParams[2], padfPrjParams[6], padfPrjParams[7]);
357
0
    }
358
359
420
    else if (STARTS_WITH_CI(pszProj, "SGDO"))
360
0
    {
361
0
        SetOS(padfPrjParams[3], padfPrjParams[2],
362
0
              (padfPrjParams[8] != 0.0) ? padfPrjParams[8] : 1.0,
363
0
              padfPrjParams[6], padfPrjParams[7]);
364
0
    }
365
420
    else if (STARTS_WITH_CI(pszProj, "SG"))
366
0
    {
367
0
        SetStereographic(padfPrjParams[3], padfPrjParams[2],
368
0
                         (padfPrjParams[8] != 0.0) ? padfPrjParams[8] : 1.0,
369
0
                         padfPrjParams[6], padfPrjParams[7]);
370
0
    }
371
420
    else if (STARTS_WITH_CI(pszProj, "SIN"))
372
0
    {
373
0
        SetSinusoidal(padfPrjParams[2], padfPrjParams[6], padfPrjParams[7]);
374
0
    }
375
    // FIXME: SOM --- Space Oblique Mercator skipped.
376
420
    else if (STARTS_WITH_CI(pszProj, "SPCS"))
377
0
    {
378
0
        const int iZone =
379
0
            static_cast<int>(CPLScanLong(const_cast<char *>(pszProj) + 5, 4));
380
381
0
        SetStatePlane(iZone, !bIsNAD27);
382
0
        SetLinearUnitsAndUpdateParameters(SRS_UL_METER, 1.0);
383
0
    }
384
420
    else if (STARTS_WITH_CI(pszProj, "SPIF"))
385
0
    {
386
0
        const int iZone =
387
0
            static_cast<int>(CPLScanLong(const_cast<char *>(pszProj) + 5, 4));
388
389
0
        SetStatePlane(iZone, !bIsNAD27);
390
0
        SetLinearUnitsAndUpdateParameters(SRS_UL_FOOT,
391
0
                                          CPLAtof(SRS_UL_FOOT_CONV));
392
0
    }
393
420
    else if (STARTS_WITH_CI(pszProj, "SPAF"))
394
0
    {
395
0
        const int iZone =
396
0
            static_cast<int>(CPLScanLong(const_cast<char *>(pszProj) + 5, 4));
397
398
0
        SetStatePlane(iZone, !bIsNAD27);
399
0
        SetLinearUnitsAndUpdateParameters(SRS_UL_US_FOOT,
400
0
                                          CPLAtof(SRS_UL_US_FOOT_CONV));
401
0
    }
402
420
    else if (STARTS_WITH_CI(pszProj, "TM"))
403
123
    {
404
123
        SetTM(padfPrjParams[3], padfPrjParams[2],
405
123
              (padfPrjParams[8] != 0.0) ? padfPrjParams[8] : 1.0,
406
123
              padfPrjParams[6], padfPrjParams[7]);
407
123
    }
408
409
297
    else if (STARTS_WITH_CI(pszProj, "UTM"))
410
143
    {
411
143
        bool bNorth = true;
412
413
143
        int iZone =
414
143
            static_cast<int>(CPLScanLong(const_cast<char *>(pszProj) + 4, 5));
415
143
        if (iZone < 0)
416
3
        {
417
3
            iZone = -iZone;
418
3
            bNorth = false;
419
3
        }
420
421
        // Check for a zone letter. PCI uses, accidentally, MGRS
422
        // type row lettering in its UTM projection.
423
143
        char byZoneID = 0;
424
425
143
        if (strlen(pszProj) > 10 && pszProj[10] != ' ')
426
37
            byZoneID = pszProj[10];
427
428
        // Determine if the MGRS zone falls above or below the equator.
429
143
        if (byZoneID != 0)
430
37
        {
431
37
            CPLDebug("OSR_PCI", "Found MGRS zone in UTM projection string: %c",
432
37
                     byZoneID);
433
434
37
            if (byZoneID >= 'N' && byZoneID <= 'X')
435
36
            {
436
36
                bNorth = true;
437
36
            }
438
1
            else if (byZoneID >= 'C' && byZoneID <= 'M')
439
0
            {
440
0
                bNorth = false;
441
0
            }
442
1
            else
443
1
            {
444
                // Yikes.  Most likely we got something that was not really
445
                // an MGRS zone code so we ignore it.
446
1
            }
447
37
        }
448
449
143
        SetUTM(iZone, bNorth);
450
143
    }
451
154
    else if (STARTS_WITH_CI(pszProj, "VDG"))
452
0
    {
453
0
        SetVDG(padfPrjParams[2], padfPrjParams[6], padfPrjParams[7]);
454
0
    }
455
154
    else
456
154
    {
457
154
        CPLDebug("OSR_PCI", "Unsupported projection: %s", pszProj);
458
154
        SetLocalCS(pszProj);
459
154
    }
460
461
    /* ==================================================================== */
462
    /*      Translate the datum/spheroid.                                   */
463
    /* ==================================================================== */
464
465
    /* -------------------------------------------------------------------- */
466
    /*      We have an earthmodel string, look it up in the datum list.     */
467
    /* -------------------------------------------------------------------- */
468
5.20k
    if (strlen(szEarthModel) > 0 &&
469
5.20k
        (GetRoot() == nullptr || IsProjected() || IsGeographic()))
470
757
    {
471
757
        const PCIDatums *pasDatum = asDatums;
472
473
        // Search for matching datum.
474
35.5k
        while (pasDatum->pszPCIDatum)
475
34.8k
        {
476
34.8k
            if (EQUALN(szEarthModel, pasDatum->pszPCIDatum, 4))
477
18
            {
478
18
                OGRSpatialReference oGCS;
479
18
                oGCS.importFromEPSG(pasDatum->nEPSGCode);
480
18
                CopyGeogCSFrom(&oGCS);
481
18
                break;
482
18
            }
483
34.8k
            pasDatum++;
484
34.8k
        }
485
486
        /* --------------------------------------------------------------------
487
         */
488
        /*      If we did not find a datum definition in our in-code EPSG */
489
        /*      lookup table, then try fetching from the pci_datum.txt */
490
        /*      file. */
491
        /* --------------------------------------------------------------------
492
         */
493
757
        char **papszDatumDefn = nullptr;
494
495
757
        if (!pasDatum->pszPCIDatum && szEarthModel[0] == 'D')
496
0
        {
497
0
            const char *pszDatumCSV = CSVFilename("pci_datum.txt");
498
0
            VSILFILE *fp = pszDatumCSV ? VSIFOpenL(pszDatumCSV, "r") : nullptr;
499
500
0
            if (fp != nullptr)
501
0
            {
502
0
                char **papszLineItems = nullptr;
503
504
0
                while ((papszLineItems = CSVReadParseLineL(fp)) != nullptr)
505
0
                {
506
0
                    if (CSLCount(papszLineItems) > 3 &&
507
0
                        EQUALN(papszLineItems[0], szEarthModel,
508
0
                               sizeof(szEarthModel) - 1))
509
0
                    {
510
0
                        papszDatumDefn = papszLineItems;
511
0
                        strncpy(szEarthModel, papszLineItems[2],
512
0
                                sizeof(szEarthModel) - 1);
513
0
                        break;
514
0
                    }
515
0
                    CSLDestroy(papszLineItems);
516
0
                }
517
518
0
                VSIFCloseL(fp);
519
0
            }
520
0
        }
521
522
        /* --------------------------------------------------------------------
523
         */
524
        /*      If not, look in the ellipsoid/EPSG matching list. */
525
        /* --------------------------------------------------------------------
526
         */
527
757
        if (!pasDatum->pszPCIDatum)  // No matching; search for ellipsoids.
528
739
        {
529
739
            char *pszName = nullptr;
530
739
            double dfSemiMajor = 0.0;
531
739
            double dfInvFlattening = 0.0;
532
739
            int nEPSGCode = 0;
533
534
739
            pasDatum = asEllips;
535
536
8.29k
            while (pasDatum->pszPCIDatum)
537
8.15k
            {
538
8.15k
                if (EQUALN(szEarthModel, pasDatum->pszPCIDatum, 4))
539
594
                {
540
594
                    nEPSGCode = pasDatum->nEPSGCode;
541
594
                    CPL_IGNORE_RET_VAL(
542
594
                        OSRGetEllipsoidInfo(pasDatum->nEPSGCode, &pszName,
543
594
                                            &dfSemiMajor, &dfInvFlattening));
544
594
                    break;
545
594
                }
546
7.56k
                pasDatum++;
547
7.56k
            }
548
549
            /* --------------------------------------------------------------------
550
             */
551
            /*      If we don't find it in that list, do a lookup in the */
552
            /*      pci_ellips.txt file. */
553
            /* --------------------------------------------------------------------
554
             */
555
739
            if (!pasDatum->pszPCIDatum && szEarthModel[0] == 'E')
556
145
            {
557
145
                const char *pszCSV = CSVFilename("pci_ellips.txt");
558
145
                VSILFILE *fp = pszCSV ? VSIFOpenL(pszCSV, "r") : nullptr;
559
560
145
                if (fp != nullptr)
561
0
                {
562
0
                    char **papszLineItems = nullptr;
563
564
0
                    while ((papszLineItems = CSVReadParseLineL(fp)) != nullptr)
565
0
                    {
566
0
                        if (CSLCount(papszLineItems) > 3 &&
567
0
                            EQUALN(papszLineItems[0], szEarthModel, 4))
568
0
                        {
569
0
                            dfSemiMajor = CPLAtof(papszLineItems[2]);
570
0
                            const double dfSemiMinor =
571
0
                                CPLAtof(papszLineItems[3]);
572
0
                            dfInvFlattening =
573
0
                                OSRCalcInvFlattening(dfSemiMajor, dfSemiMinor);
574
0
                            break;
575
0
                        }
576
0
                        CSLDestroy(papszLineItems);
577
0
                    }
578
0
                    CSLDestroy(papszLineItems);
579
580
0
                    VSIFCloseL(fp);
581
0
                }
582
145
            }
583
584
            /* --------------------------------------------------------------------
585
             */
586
            /*      Custom spheroid? */
587
            /* --------------------------------------------------------------------
588
             */
589
739
            if (dfSemiMajor == 0.0 && STARTS_WITH_CI(szEarthModel, "E999") &&
590
145
                padfPrjParams[0] != 0.0)
591
76
            {
592
76
                dfSemiMajor = padfPrjParams[0];
593
76
                double dfSemiMinor = padfPrjParams[1];
594
76
                dfInvFlattening =
595
76
                    OSRCalcInvFlattening(dfSemiMajor, dfSemiMinor);
596
76
            }
597
598
            /* --------------------------------------------------------------------
599
             */
600
            /*      If nothing else, fall back to WGS84 parameters. */
601
            /* --------------------------------------------------------------------
602
             */
603
739
            if (dfSemiMajor == 0.0)
604
69
            {
605
69
                dfSemiMajor = SRS_WGS84_SEMIMAJOR;
606
69
                dfInvFlattening = SRS_WGS84_INVFLATTENING;
607
69
            }
608
609
            /* --------------------------------------------------------------------
610
             */
611
            /*      Now try to put this all together into a GEOGCS definition.
612
             */
613
            /* --------------------------------------------------------------------
614
             */
615
616
739
            CPLString osEllipseName;
617
739
            if (pszName)
618
594
                osEllipseName = pszName;
619
145
            else
620
145
                osEllipseName.Printf("Unknown - PCI %s", szEarthModel);
621
739
            CPLFree(pszName);
622
623
739
            CPLString osDatumName;
624
739
            if (papszDatumDefn)
625
0
                osDatumName = papszDatumDefn[1];
626
739
            else
627
739
                osDatumName.Printf("Unknown - PCI %s", szEarthModel);
628
629
739
            const CPLString osGCSName = osDatumName;
630
631
739
            SetGeogCS(osGCSName, osDatumName, osEllipseName, dfSemiMajor,
632
739
                      dfInvFlattening);
633
634
            // Do we have an ellipsoid EPSG code?
635
739
            if (nEPSGCode != 0)
636
594
                SetAuthority("SPHEROID", "EPSG", nEPSGCode);
637
638
            // Do we have 7 datum shift parameters?
639
739
            if (papszDatumDefn != nullptr && CSLCount(papszDatumDefn) >= 15 &&
640
0
                CPLAtof(papszDatumDefn[14]) != 0.0)
641
0
            {
642
0
                double dfScale = CPLAtof(papszDatumDefn[14]);
643
644
                // we want scale in parts per million off 1.0
645
                // but pci uses a mix of forms.
646
0
                if (dfScale >= 0.999 && dfScale <= 1.001)
647
0
                    dfScale = (dfScale - 1.0) * 1000000.0;
648
649
0
                SetTOWGS84(
650
0
                    CPLAtof(papszDatumDefn[3]), CPLAtof(papszDatumDefn[4]),
651
0
                    CPLAtof(papszDatumDefn[5]), CPLAtof(papszDatumDefn[11]),
652
0
                    CPLAtof(papszDatumDefn[12]), CPLAtof(papszDatumDefn[13]),
653
0
                    dfScale);
654
0
            }
655
656
            // Do we have 7 datum shift parameters?
657
739
            else if (papszDatumDefn != nullptr &&
658
0
                     CSLCount(papszDatumDefn) == 11 &&
659
0
                     (CPLAtof(papszDatumDefn[3]) != 0.0 ||
660
0
                      CPLAtof(papszDatumDefn[4]) != 0.0 ||
661
0
                      CPLAtof(papszDatumDefn[5]) != 0.0))
662
0
            {
663
0
                SetTOWGS84(CPLAtof(papszDatumDefn[3]),
664
0
                           CPLAtof(papszDatumDefn[4]),
665
0
                           CPLAtof(papszDatumDefn[5]));
666
0
            }
667
739
        }
668
669
757
        CSLDestroy(papszDatumDefn);
670
757
    }
671
672
    /* -------------------------------------------------------------------- */
673
    /*      Grid units translation                                          */
674
    /* -------------------------------------------------------------------- */
675
5.20k
    if ((IsLocal() || IsProjected()) && pszUnits)
676
0
    {
677
0
        if (EQUAL(pszUnits, "METRE"))
678
0
            SetLinearUnits(SRS_UL_METER, 1.0);
679
0
        else if (EQUAL(pszUnits, "DEGREE"))
680
0
            SetAngularUnits(SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV));
681
0
        else
682
0
            SetLinearUnits(SRS_UL_METER, 1.0);
683
0
    }
684
685
5.20k
    return OGRERR_NONE;
686
5.20k
}
687
688
/************************************************************************/
689
/*                           OSRExportToPCI()                           */
690
/************************************************************************/
691
/**
692
 * \brief Export coordinate system in PCI projection definition.
693
 *
694
 * This function is the same as OGRSpatialReference::exportToPCI().
695
 */
696
OGRErr OSRExportToPCI(OGRSpatialReferenceH hSRS, char **ppszProj,
697
                      char **ppszUnits, double **ppadfPrjParams)
698
699
0
{
700
0
    VALIDATE_POINTER1(hSRS, "OSRExportToPCI", OGRERR_FAILURE);
701
702
0
    *ppszProj = nullptr;
703
0
    *ppszUnits = nullptr;
704
0
    *ppadfPrjParams = nullptr;
705
706
0
    return OGRSpatialReference::FromHandle(hSRS)->exportToPCI(
707
0
        ppszProj, ppszUnits, ppadfPrjParams);
708
0
}
709
710
/************************************************************************/
711
/*                            exportToPCI()                             */
712
/************************************************************************/
713
714
/**
715
 * \brief Export coordinate system in PCI projection definition.
716
 *
717
 * Converts the loaded coordinate reference system into PCI projection
718
 * definition to the extent possible. The strings returned in ppszProj,
719
 * ppszUnits and ppadfPrjParams array should be deallocated by the caller
720
 * with CPLFree() when no longer needed.
721
 *
722
 * LOCAL_CS coordinate systems are not translatable.  An empty string
723
 * will be returned along with OGRERR_NONE.
724
 *
725
 * This method is the equivalent of the C function OSRExportToPCI().
726
 *
727
 * @param ppszProj pointer to which dynamically allocated PCI projection
728
 * definition will be assigned.
729
 *
730
 * @param ppszUnits pointer to which dynamically allocated units definition
731
 * will be assigned.
732
 *
733
 * @param ppadfPrjParams pointer to which dynamically allocated array of
734
 * 17 projection parameters will be assigned. See importFromPCI() for the list
735
 * of parameters.
736
 *
737
 * @return OGRERR_NONE on success or an error code on failure.
738
 */
739
740
OGRErr OGRSpatialReference::exportToPCI(char **ppszProj, char **ppszUnits,
741
                                        double **ppadfPrjParams) const
742
743
356
{
744
356
    const char *pszProjection = GetAttrValue("PROJECTION");
745
746
    /* -------------------------------------------------------------------- */
747
    /*      Fill all projection parameters with zero.                       */
748
    /* -------------------------------------------------------------------- */
749
356
    *ppadfPrjParams = static_cast<double *>(CPLMalloc(17 * sizeof(double)));
750
6.40k
    for (int i = 0; i < 17; i++)
751
6.05k
        (*ppadfPrjParams)[i] = 0.0;
752
753
/* -------------------------------------------------------------------- */
754
/*      Get the prime meridian info.                                    */
755
/* -------------------------------------------------------------------- */
756
#if 0
757
    const OGR_SRSNode *poPRIMEM = GetAttrNode( "PRIMEM" );
758
    double dfFromGreenwich = 0.0;
759
760
    if( poPRIMEM != NULL && poPRIMEM->GetChildCount() >= 2
761
        && CPLAtof(poPRIMEM->GetChild(1)->GetValue()) != 0.0 )
762
    {
763
        dfFromGreenwich = CPLAtof(poPRIMEM->GetChild(1)->GetValue());
764
    }
765
#endif
766
767
    /* ==================================================================== */
768
    /*      Handle the projection definition.                               */
769
    /* ==================================================================== */
770
356
    char szProj[knProjSize + 1] = {};
771
772
356
    if (IsLocal())
773
0
    {
774
0
        if (GetLinearUnits() > 0.30479999 && GetLinearUnits() < 0.3048010)
775
0
            CPLPrintStringFill(szProj, "FEET", knProjSize);
776
0
        else
777
0
            CPLPrintStringFill(szProj, "METER", knProjSize);
778
0
    }
779
356
    else if (pszProjection == nullptr)
780
95
    {
781
95
        CPLPrintStringFill(szProj, "LONG/LAT", knProjSize);
782
95
    }
783
261
    else if (EQUAL(pszProjection, SRS_PT_ALBERS_CONIC_EQUAL_AREA))
784
3
    {
785
3
        CPLPrintStringFill(szProj, "ACEA", knProjSize);
786
3
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
787
3
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
788
3
        (*ppadfPrjParams)[4] = GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
789
3
        (*ppadfPrjParams)[5] = GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
790
3
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
791
3
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
792
3
    }
793
258
    else if (EQUAL(pszProjection, SRS_PT_AZIMUTHAL_EQUIDISTANT))
794
0
    {
795
0
        CPLPrintStringFill(szProj, "AE", knProjSize);
796
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
797
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
798
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
799
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
800
0
    }
801
258
    else if (EQUAL(pszProjection, SRS_PT_CASSINI_SOLDNER))
802
0
    {
803
0
        CPLPrintStringFill(szProj, "CASS", knProjSize);
804
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
805
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
806
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
807
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
808
0
    }
809
258
    else if (EQUAL(pszProjection, SRS_PT_EQUIDISTANT_CONIC))
810
0
    {
811
0
        CPLPrintStringFill(szProj, "EC", knProjSize);
812
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
813
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
814
0
        (*ppadfPrjParams)[4] = GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
815
0
        (*ppadfPrjParams)[5] = GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
816
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
817
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
818
0
    }
819
258
    else if (EQUAL(pszProjection, SRS_PT_EQUIRECTANGULAR))
820
0
    {
821
0
        CPLPrintStringFill(szProj, "ER", knProjSize);
822
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
823
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
824
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
825
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
826
0
    }
827
258
    else if (EQUAL(pszProjection, SRS_PT_GNOMONIC))
828
0
    {
829
0
        CPLPrintStringFill(szProj, "GNO", knProjSize);
830
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
831
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
832
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
833
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
834
0
    }
835
258
    else if (EQUAL(pszProjection, SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA))
836
0
    {
837
0
        CPLPrintStringFill(szProj, "LAEA", knProjSize);
838
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
839
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
840
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
841
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
842
0
    }
843
258
    else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
844
70
    {
845
70
        CPLPrintStringFill(szProj, "LCC", knProjSize);
846
70
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
847
70
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
848
70
        (*ppadfPrjParams)[4] = GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
849
70
        (*ppadfPrjParams)[5] = GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
850
70
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
851
70
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
852
70
    }
853
188
    else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
854
3
    {
855
3
        CPLPrintStringFill(szProj, "LCC_1SP", knProjSize);
856
3
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
857
3
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
858
3
        (*ppadfPrjParams)[8] = GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
859
3
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
860
3
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
861
3
    }
862
185
    else if (EQUAL(pszProjection, SRS_PT_MILLER_CYLINDRICAL))
863
0
    {
864
0
        CPLPrintStringFill(szProj, "MC", knProjSize);
865
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
866
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
867
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
868
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
869
0
    }
870
185
    else if (EQUAL(pszProjection, SRS_PT_MERCATOR_1SP))
871
0
    {
872
0
        CPLPrintStringFill(szProj, "MER", knProjSize);
873
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
874
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
875
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
876
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
877
0
        (*ppadfPrjParams)[8] = GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
878
0
    }
879
185
    else if (EQUAL(pszProjection, SRS_PT_ORTHOGRAPHIC))
880
0
    {
881
0
        CPLPrintStringFill(szProj, "OG", knProjSize);
882
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
883
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
884
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
885
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
886
0
    }
887
185
    else if (EQUAL(pszProjection, SRS_PT_HOTINE_OBLIQUE_MERCATOR))
888
21
    {
889
21
        CPLPrintStringFill(szProj, "OM", knProjSize);
890
21
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
891
21
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
892
21
        (*ppadfPrjParams)[14] = GetNormProjParm(SRS_PP_AZIMUTH, 0.0);
893
        // Note: Ignoring rectified_grid_angle which has no PCI analog.
894
21
        (*ppadfPrjParams)[8] = GetNormProjParm(SRS_PP_SCALE_FACTOR, 0.0);
895
21
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
896
21
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
897
21
    }
898
164
    else if (EQUAL(pszProjection,
899
164
                   SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
900
0
    {
901
0
        CPLPrintStringFill(szProj, "OM", knProjSize);
902
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
903
0
        (*ppadfPrjParams)[11] =
904
0
            GetNormProjParm(SRS_PP_LATITUDE_OF_POINT_1, 0.0);
905
0
        (*ppadfPrjParams)[10] =
906
0
            GetNormProjParm(SRS_PP_LONGITUDE_OF_POINT_1, 0.0);
907
0
        (*ppadfPrjParams)[13] =
908
0
            GetNormProjParm(SRS_PP_LATITUDE_OF_POINT_2, 0.0);
909
0
        (*ppadfPrjParams)[12] =
910
0
            GetNormProjParm(SRS_PP_LONGITUDE_OF_POINT_2, 0.0);
911
0
        (*ppadfPrjParams)[8] = GetNormProjParm(SRS_PP_SCALE_FACTOR, 0.0);
912
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
913
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
914
0
    }
915
164
    else if (EQUAL(pszProjection, SRS_PT_POLYCONIC))
916
0
    {
917
0
        CPLPrintStringFill(szProj, "PC", knProjSize);
918
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
919
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
920
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
921
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
922
0
    }
923
164
    else if (EQUAL(pszProjection, SRS_PT_POLAR_STEREOGRAPHIC))
924
5
    {
925
5
        CPLPrintStringFill(szProj, "PS", knProjSize);
926
5
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
927
5
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
928
5
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
929
5
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
930
5
        (*ppadfPrjParams)[8] = GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
931
5
    }
932
159
    else if (EQUAL(pszProjection, SRS_PT_ROBINSON))
933
0
    {
934
0
        CPLPrintStringFill(szProj, "ROB", knProjSize);
935
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
936
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
937
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
938
0
    }
939
159
    else if (EQUAL(pszProjection, SRS_PT_OBLIQUE_STEREOGRAPHIC))
940
0
    {
941
0
        CPLPrintStringFill(szProj, "SGDO", knProjSize);
942
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
943
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
944
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
945
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
946
0
        (*ppadfPrjParams)[8] = GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
947
0
    }
948
159
    else if (EQUAL(pszProjection, SRS_PT_STEREOGRAPHIC))
949
0
    {
950
0
        CPLPrintStringFill(szProj, "SG", knProjSize);
951
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
952
0
        (*ppadfPrjParams)[3] = GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
953
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
954
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
955
0
        (*ppadfPrjParams)[8] = GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
956
0
    }
957
159
    else if (EQUAL(pszProjection, SRS_PT_SINUSOIDAL))
958
0
    {
959
0
        CPLPrintStringFill(szProj, "SIN", knProjSize);
960
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
961
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
962
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
963
0
    }
964
159
    else if (EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
965
97
    {
966
97
        int bNorth = FALSE;
967
97
        int nZone = GetUTMZone(&bNorth);
968
969
97
        if (nZone != 0)
970
52
        {
971
52
            CPLPrintStringFill(szProj, "UTM", knProjSize);
972
52
            if (bNorth)
973
51
                CPLPrintInt32(szProj + 5, nZone, 4);
974
1
            else
975
1
                CPLPrintInt32(szProj + 5, -nZone, 4);
976
52
        }
977
45
        else
978
45
        {
979
45
            CPLPrintStringFill(szProj, "TM", knProjSize);
980
45
            (*ppadfPrjParams)[2] =
981
45
                GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
982
45
            (*ppadfPrjParams)[3] =
983
45
                GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
984
45
            (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
985
45
            (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
986
45
            (*ppadfPrjParams)[8] = GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
987
45
        }
988
97
    }
989
62
    else if (EQUAL(pszProjection, SRS_PT_VANDERGRINTEN))
990
0
    {
991
0
        CPLPrintStringFill(szProj, "VDG", knProjSize);
992
0
        (*ppadfPrjParams)[2] = GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
993
0
        (*ppadfPrjParams)[6] = GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
994
0
        (*ppadfPrjParams)[7] = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
995
0
    }
996
    // Projection unsupported by PCI.
997
62
    else
998
62
    {
999
62
        CPLDebug("OSR_PCI",
1000
62
                 "Projection \"%s\" unsupported by PCI. "
1001
62
                 "PIXEL value will be used.",
1002
62
                 pszProjection);
1003
62
        CPLPrintStringFill(szProj, "PIXEL", knProjSize);
1004
62
    }
1005
1006
    /* ==================================================================== */
1007
    /*      Translate the earth model.                                      */
1008
    /* ==================================================================== */
1009
1010
    /* -------------------------------------------------------------------- */
1011
    /*      Is this a well known datum?                                     */
1012
    /* -------------------------------------------------------------------- */
1013
356
    const char *pszDatum = GetAttrValue("DATUM");
1014
356
    char szEarthModel[5] = {};
1015
1016
356
    if (pszDatum == nullptr || strlen(pszDatum) == 0)
1017
57
    {
1018
        // Do nothing.
1019
57
    }
1020
299
    else if (EQUAL(pszDatum, SRS_DN_NAD27))
1021
4
    {
1022
4
        CPLPrintStringFill(szEarthModel, "D-01", 4);
1023
4
    }
1024
295
    else if (EQUAL(pszDatum, SRS_DN_NAD83))
1025
0
    {
1026
0
        CPLPrintStringFill(szEarthModel, "D-02", 4);
1027
0
    }
1028
295
    else if (EQUAL(pszDatum, SRS_DN_WGS84))
1029
67
    {
1030
67
        CPLPrintStringFill(szEarthModel, "D000", 4);
1031
67
        if (pszProjection && EQUAL(pszProjection, SRS_PT_MERCATOR_1SP))
1032
0
        {
1033
            // Special case for Web Mercator
1034
0
            const char *method_name = "";
1035
0
            GetWKT2ProjectionMethod(&method_name, nullptr, nullptr);
1036
0
            if (EQUAL(method_name, "Popular Visualisation Pseudo Mercator"))
1037
0
            {
1038
0
                CPLPrintStringFill(szEarthModel, "D894", 4);
1039
0
            }
1040
0
        }
1041
67
    }
1042
1043
    /* -------------------------------------------------------------------- */
1044
    /*      If not a very well known datum, try for an EPSG based           */
1045
    /*      translation.                                                    */
1046
    /* -------------------------------------------------------------------- */
1047
356
    if (szEarthModel[0] == '\0')
1048
285
    {
1049
285
        const char *pszAuthority = GetAuthorityName("GEOGCS");
1050
1051
285
        if (pszAuthority && EQUAL(pszAuthority, "EPSG"))
1052
228
        {
1053
228
            const int nGCS_EPSG = atoi(GetAuthorityCode("GEOGCS"));
1054
1055
10.9k
            for (int i = 0; asDatums[i].nEPSGCode != 0; i++)
1056
10.7k
            {
1057
10.7k
                if (asDatums[i].nEPSGCode == nGCS_EPSG)
1058
1
                {
1059
1
                    snprintf(szEarthModel, sizeof(szEarthModel), "%s",
1060
1
                             asDatums[i].pszPCIDatum);
1061
1
                    break;
1062
1
                }
1063
10.7k
            }
1064
228
        }
1065
285
    }
1066
1067
    /* -------------------------------------------------------------------- */
1068
    /*      If we haven't found something yet, try translating the          */
1069
    /*      ellipsoid.                                                      */
1070
    /* -------------------------------------------------------------------- */
1071
356
    const double dfSemiMajor = GetSemiMajor();
1072
356
    const double dfInvFlattening = GetInvFlattening();
1073
356
    if (szEarthModel[0] == '\0')
1074
284
    {
1075
284
        const PCIDatums *pasDatum = asEllips;
1076
1077
3.50k
        while (pasDatum->pszPCIDatum)
1078
3.43k
        {
1079
3.43k
            double dfSM = 0.0;
1080
3.43k
            double dfIF = 0.0;
1081
1082
3.43k
            if (OSRGetEllipsoidInfo(pasDatum->nEPSGCode, nullptr, &dfSM,
1083
3.43k
                                    &dfIF) == OGRERR_NONE &&
1084
3.43k
                CPLIsEqual(dfSemiMajor, dfSM) &&
1085
272
                CPLIsEqual(dfInvFlattening, dfIF))
1086
215
            {
1087
215
                CPLPrintStringFill(szEarthModel, pasDatum->pszPCIDatum, 4);
1088
215
                break;
1089
215
            }
1090
1091
3.21k
            pasDatum++;
1092
3.21k
        }
1093
284
    }
1094
1095
    // Try to find in pci_ellips.txt.
1096
356
    if (szEarthModel[0] == '\0')
1097
69
    {
1098
69
        const char *pszCSV = CSVFilename("pci_ellips.txt");
1099
69
        const double dfSemiMinor =
1100
69
            OSRCalcSemiMinorFromInvFlattening(dfSemiMajor, dfInvFlattening);
1101
1102
69
        VSILFILE *fp = pszCSV ? VSIFOpenL(pszCSV, "r") : nullptr;
1103
1104
69
        if (fp != nullptr)
1105
0
        {
1106
0
            char **papszLineItems = nullptr;
1107
1108
0
            while ((papszLineItems = CSVReadParseLineL(fp)) != nullptr)
1109
0
            {
1110
0
                if (CSLCount(papszLineItems) >= 4 &&
1111
0
                    CPLIsEqual(dfSemiMajor, CPLAtof(papszLineItems[2])) &&
1112
0
                    CPLIsEqual(dfSemiMinor, CPLAtof(papszLineItems[3])))
1113
0
                {
1114
0
                    snprintf(szEarthModel, sizeof(szEarthModel), "%s",
1115
0
                             papszLineItems[0]);
1116
0
                    break;
1117
0
                }
1118
1119
0
                CSLDestroy(papszLineItems);
1120
0
            }
1121
1122
0
            CSLDestroy(papszLineItems);
1123
0
            VSIFCloseL(fp);
1124
0
        }
1125
69
    }
1126
1127
    // Custom ellipsoid parameters.
1128
356
    if (szEarthModel[0] == '\0')
1129
69
    {
1130
69
        CPLPrintStringFill(szEarthModel, "E999", 4);
1131
69
        (*ppadfPrjParams)[0] = dfSemiMajor;
1132
69
        (*ppadfPrjParams)[1] =
1133
69
            OSRCalcSemiMinorFromInvFlattening(dfSemiMajor, dfInvFlattening);
1134
69
    }
1135
1136
    /* -------------------------------------------------------------------- */
1137
    /*      If we have a non-parameteric ellipsoid, scan the                */
1138
    /*      pci_datum.txt for a match.                                      */
1139
    /* -------------------------------------------------------------------- */
1140
356
    if (szEarthModel[0] == 'E' && !EQUAL(szEarthModel, "E999") &&
1141
215
        pszDatum != nullptr)
1142
158
    {
1143
158
        const char *pszDatumCSV = CSVFilename("pci_datum.txt");
1144
158
        double adfTOWGS84[7] = {};
1145
158
        const bool bHaveTOWGS84 = GetTOWGS84(adfTOWGS84, 7) == OGRERR_NONE;
1146
1147
158
        VSILFILE *fp = pszDatumCSV ? VSIFOpenL(pszDatumCSV, "r") : nullptr;
1148
1149
158
        if (fp != nullptr)
1150
0
        {
1151
0
            char **papszLineItems = nullptr;
1152
1153
0
            while ((papszLineItems = CSVReadParseLineL(fp)) != nullptr)
1154
0
            {
1155
                // Compare based on datum name.  This is mostly for
1156
                // PCI round-tripping.  We won't usually get exact matches
1157
                // from other sources.
1158
0
                if (CSLCount(papszLineItems) > 3 &&
1159
0
                    EQUAL(papszLineItems[1], pszDatum) &&
1160
0
                    EQUAL(papszLineItems[2], szEarthModel))
1161
0
                {
1162
0
                    snprintf(szEarthModel, sizeof(szEarthModel), "%s",
1163
0
                             papszLineItems[0]);
1164
0
                    break;
1165
0
                }
1166
1167
0
                bool bTOWGS84Match = bHaveTOWGS84;
1168
1169
0
                if (CSLCount(papszLineItems) < 11)
1170
0
                    bTOWGS84Match = false;
1171
1172
0
                if (bTOWGS84Match &&
1173
0
                    (!CPLIsEqual(adfTOWGS84[0], CPLAtof(papszLineItems[3])) ||
1174
0
                     !CPLIsEqual(adfTOWGS84[1], CPLAtof(papszLineItems[4])) ||
1175
0
                     !CPLIsEqual(adfTOWGS84[2], CPLAtof(papszLineItems[5]))))
1176
0
                    bTOWGS84Match = false;
1177
1178
0
                if (bTOWGS84Match && CSLCount(papszLineItems) >= 15 &&
1179
0
                    (!CPLIsEqual(adfTOWGS84[3], CPLAtof(papszLineItems[11])) ||
1180
0
                     !CPLIsEqual(adfTOWGS84[4], CPLAtof(papszLineItems[12])) ||
1181
0
                     !CPLIsEqual(adfTOWGS84[5], CPLAtof(papszLineItems[13]))))
1182
0
                    bTOWGS84Match = false;
1183
1184
0
                if (bTOWGS84Match && CSLCount(papszLineItems) >= 15)
1185
0
                {
1186
0
                    double dfScale = CPLAtof(papszLineItems[14]);
1187
1188
                    // Convert to parts per million if is a 1 based scaling.
1189
0
                    if (dfScale >= 0.999 && dfScale <= 1.001)
1190
0
                        dfScale = (dfScale - 1.0) * 1000000.0;
1191
1192
0
                    if (fabs(adfTOWGS84[6] - dfScale) >
1193
0
                        1e-11 * fabs(adfTOWGS84[6]))
1194
0
                        bTOWGS84Match = false;
1195
0
                }
1196
1197
0
                if (bTOWGS84Match && CSLCount(papszLineItems) < 15 &&
1198
0
                    (!CPLIsEqual(adfTOWGS84[3], 0.0) ||
1199
0
                     !CPLIsEqual(adfTOWGS84[4], 0.0) ||
1200
0
                     !CPLIsEqual(adfTOWGS84[5], 0.0) ||
1201
0
                     !CPLIsEqual(adfTOWGS84[6], 0.0)))
1202
0
                    bTOWGS84Match = false;
1203
1204
0
                if (bTOWGS84Match)
1205
0
                {
1206
0
                    snprintf(szEarthModel, sizeof(szEarthModel), "%s",
1207
0
                             papszLineItems[0]);
1208
0
                    break;
1209
0
                }
1210
1211
0
                CSLDestroy(papszLineItems);
1212
0
            }
1213
1214
0
            CSLDestroy(papszLineItems);
1215
0
            VSIFCloseL(fp);
1216
0
        }
1217
158
    }
1218
1219
356
    CPLPrintStringFill(szProj + 12, szEarthModel, 4);
1220
1221
356
    CPLDebug("OSR_PCI", "Translated as '%s'", szProj);
1222
1223
    /* -------------------------------------------------------------------- */
1224
    /*      Translate the linear units.                                     */
1225
    /* -------------------------------------------------------------------- */
1226
356
    const char *pszUnits =
1227
356
        STARTS_WITH_CI(szProj, "LONG/LAT") ? "DEGREE" : "METRE";
1228
1229
    /* -------------------------------------------------------------------- */
1230
    /*      Report results.                                                 */
1231
    /* -------------------------------------------------------------------- */
1232
356
    szProj[knProjSize] = '\0';
1233
356
    *ppszProj = CPLStrdup(szProj);
1234
1235
356
    *ppszUnits = CPLStrdup(pszUnits);
1236
1237
356
    return OGRERR_NONE;
1238
356
}