Coverage Report

Created: 2025-11-16 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogr_srs_ozi.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  OGRSpatialReference translation from OziExplorer
5
 *           georeferencing information.
6
 * Author:   Andrey Kiselev, dron@ak4719.spb.edu
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2009, Andrey Kiselev <dron@ak4719.spb.edu>
10
 * Copyright (c) 2009-2012, Even Rouault <even dot rouault at spatialys.com>
11
 *
12
 * SPDX-License-Identifier: MIT
13
 ****************************************************************************/
14
15
#include "cpl_port.h"
16
#include "ogr_spatialref.h"
17
18
#include <cstdlib>
19
#include <cstring>
20
21
#include "cpl_conv.h"
22
#include "cpl_csv.h"
23
#include "cpl_error.h"
24
#include "cpl_string.h"
25
#include "ogr_core.h"
26
#include "ogr_srs_api.h"
27
28
/************************************************************************/
29
/*                          OSRImportFromOzi()                          */
30
/************************************************************************/
31
32
/**
33
 * Import coordinate system from OziExplorer projection definition.
34
 *
35
 * This function will import projection definition in style, used by
36
 * OziExplorer software.
37
 *
38
 * @param hSRS spatial reference object.
39
 * @param papszLines Map file lines. This is an array of strings containing
40
 * the whole OziExplorer .MAP file. The array is terminated by a NULL pointer.
41
 *
42
 * @return OGRERR_NONE on success or an error code in case of failure.
43
 */
44
45
OGRErr OSRImportFromOzi(OGRSpatialReferenceH hSRS,
46
                        const char *const *papszLines)
47
48
0
{
49
0
    VALIDATE_POINTER1(hSRS, "OSRImportFromOzi", OGRERR_FAILURE);
50
51
0
    return OGRSpatialReference::FromHandle(hSRS)->importFromOzi(papszLines);
52
0
}
53
54
/************************************************************************/
55
/*                            importFromOzi()                           */
56
/************************************************************************/
57
58
/**
59
 * Import coordinate system from OziExplorer projection definition.
60
 *
61
 * This method will import projection definition in style, used by
62
 * OziExplorer software.
63
 *
64
 * @param papszLines Map file lines. This is an array of strings containing
65
 * the whole OziExplorer .MAP file. The array is terminated by a NULL pointer.
66
 *
67
 * @return OGRERR_NONE on success or an error code in case of failure.
68
 *
69
 */
70
71
OGRErr OGRSpatialReference::importFromOzi(const char *const *papszLines)
72
0
{
73
0
    const char *pszDatum;
74
0
    const char *pszProj = nullptr;
75
0
    const char *pszProjParams = nullptr;
76
77
0
    Clear();
78
79
0
    const int nLines = CSLCount(papszLines);
80
0
    if (nLines < 5)
81
0
        return OGRERR_NOT_ENOUGH_DATA;
82
83
0
    pszDatum = papszLines[4];
84
85
0
    for (int iLine = 5; iLine < nLines; iLine++)
86
0
    {
87
0
        if (STARTS_WITH_CI(papszLines[iLine], "Map Projection"))
88
0
        {
89
0
            pszProj = papszLines[iLine];
90
0
        }
91
0
        else if (STARTS_WITH_CI(papszLines[iLine], "Projection Setup"))
92
0
        {
93
0
            pszProjParams = papszLines[iLine];
94
0
        }
95
0
    }
96
97
0
    if (!(pszDatum && pszProj && pszProjParams))
98
0
        return OGRERR_NOT_ENOUGH_DATA;
99
100
    /* -------------------------------------------------------------------- */
101
    /*      Operate on the basis of the projection name.                    */
102
    /* -------------------------------------------------------------------- */
103
0
    char **papszProj = CSLTokenizeStringComplex(pszProj, ",", TRUE, TRUE);
104
0
    char **papszProjParams =
105
0
        CSLTokenizeStringComplex(pszProjParams, ",", TRUE, TRUE);
106
0
    char **papszDatum = nullptr;
107
108
0
    if (CSLCount(papszProj) < 2)
109
0
    {
110
0
        goto not_enough_data;
111
0
    }
112
113
0
    if (STARTS_WITH_CI(papszProj[1], "Latitude/Longitude"))
114
0
    {
115
        // Do nothing.
116
0
    }
117
0
    else if (STARTS_WITH_CI(papszProj[1], "Mercator"))
118
0
    {
119
0
        if (CSLCount(papszProjParams) < 6)
120
0
            goto not_enough_data;
121
0
        double dfScale = CPLAtof(papszProjParams[3]);
122
        // If unset, default to scale = 1.
123
0
        if (papszProjParams[3][0] == 0)
124
0
            dfScale = 1;
125
0
        SetMercator(CPLAtof(papszProjParams[1]), CPLAtof(papszProjParams[2]),
126
0
                    dfScale, CPLAtof(papszProjParams[4]),
127
0
                    CPLAtof(papszProjParams[5]));
128
0
    }
129
0
    else if (STARTS_WITH_CI(papszProj[1], "Transverse Mercator"))
130
0
    {
131
0
        if (CSLCount(papszProjParams) < 6)
132
0
            goto not_enough_data;
133
0
        SetTM(CPLAtof(papszProjParams[1]), CPLAtof(papszProjParams[2]),
134
0
              CPLAtof(papszProjParams[3]), CPLAtof(papszProjParams[4]),
135
0
              CPLAtof(papszProjParams[5]));
136
0
    }
137
0
    else if (STARTS_WITH_CI(papszProj[1], "Lambert Conformal Conic"))
138
0
    {
139
0
        if (CSLCount(papszProjParams) < 8)
140
0
            goto not_enough_data;
141
0
        SetLCC(CPLAtof(papszProjParams[6]), CPLAtof(papszProjParams[7]),
142
0
               CPLAtof(papszProjParams[1]), CPLAtof(papszProjParams[2]),
143
0
               CPLAtof(papszProjParams[4]), CPLAtof(papszProjParams[5]));
144
0
    }
145
0
    else if (STARTS_WITH_CI(papszProj[1], "Sinusoidal"))
146
0
    {
147
0
        if (CSLCount(papszProjParams) < 6)
148
0
            goto not_enough_data;
149
0
        SetSinusoidal(CPLAtof(papszProjParams[2]), CPLAtof(papszProjParams[4]),
150
0
                      CPLAtof(papszProjParams[5]));
151
0
    }
152
0
    else if (STARTS_WITH_CI(papszProj[1], "Albers Equal Area"))
153
0
    {
154
0
        if (CSLCount(papszProjParams) < 8)
155
0
            goto not_enough_data;
156
0
        SetACEA(CPLAtof(papszProjParams[6]), CPLAtof(papszProjParams[7]),
157
0
                CPLAtof(papszProjParams[1]), CPLAtof(papszProjParams[2]),
158
0
                CPLAtof(papszProjParams[4]), CPLAtof(papszProjParams[5]));
159
0
    }
160
0
    else if (STARTS_WITH_CI(papszProj[1],
161
0
                            "(UTM) Universal Transverse Mercator") &&
162
0
             nLines > 5)
163
0
    {
164
        // Look for the UTM zone in the calibration point data.
165
0
        int iLine = 5;  // Used after for.
166
0
        for (; iLine < nLines; iLine++)
167
0
        {
168
0
            if (STARTS_WITH_CI(papszLines[iLine], "Point"))
169
0
            {
170
0
                char **papszTok = CSLTokenizeString2(papszLines[iLine], ",",
171
0
                                                     CSLT_ALLOWEMPTYTOKENS |
172
0
                                                         CSLT_STRIPLEADSPACES |
173
0
                                                         CSLT_STRIPENDSPACES);
174
0
                if (CSLCount(papszTok) < 17 || EQUAL(papszTok[2], "") ||
175
0
                    EQUAL(papszTok[13], "") || EQUAL(papszTok[14], "") ||
176
0
                    EQUAL(papszTok[15], "") || EQUAL(papszTok[16], ""))
177
0
                {
178
0
                    CSLDestroy(papszTok);
179
0
                    continue;
180
0
                }
181
0
                SetUTM(atoi(papszTok[13]), EQUAL(papszTok[16], "N"));
182
0
                CSLDestroy(papszTok);
183
0
                break;
184
0
            }
185
0
        }
186
0
        if (iLine == nLines)  // Try to guess the UTM zone.
187
0
        {
188
0
            float fMinLongitude = 1000.0f;
189
0
            float fMaxLongitude = -1000.0f;
190
0
            float fMinLatitude = 1000.0f;
191
0
            float fMaxLatitude = -1000.0f;
192
0
            bool bFoundMMPLL = false;
193
0
            for (iLine = 5; iLine < nLines; iLine++)
194
0
            {
195
0
                if (STARTS_WITH_CI(papszLines[iLine], "MMPLL"))
196
0
                {
197
0
                    char **papszTok = CSLTokenizeString2(
198
0
                        papszLines[iLine], ",",
199
0
                        CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES |
200
0
                            CSLT_STRIPENDSPACES);
201
0
                    if (CSLCount(papszTok) < 4)
202
0
                    {
203
0
                        CSLDestroy(papszTok);
204
0
                        continue;
205
0
                    }
206
0
                    const float fLongitude =
207
0
                        static_cast<float>(CPLAtofM(papszTok[2]));
208
0
                    const float fLatitude =
209
0
                        static_cast<float>(CPLAtofM(papszTok[3]));
210
0
                    CSLDestroy(papszTok);
211
212
0
                    bFoundMMPLL = true;
213
214
0
                    if (fMinLongitude > fLongitude)
215
0
                        fMinLongitude = fLongitude;
216
0
                    if (fMaxLongitude < fLongitude)
217
0
                        fMaxLongitude = fLongitude;
218
0
                    if (fMinLatitude > fLatitude)
219
0
                        fMinLatitude = fLatitude;
220
0
                    if (fMaxLatitude < fLatitude)
221
0
                        fMaxLatitude = fLatitude;
222
0
                }
223
0
            }
224
0
            const float fMedianLatitude = (fMinLatitude + fMaxLatitude) / 2;
225
0
            const float fMedianLongitude = (fMinLongitude + fMaxLongitude) / 2;
226
0
            if (bFoundMMPLL && fMaxLatitude <= 90)
227
0
            {
228
0
                int nUtmZone = 0;
229
0
                if (fMedianLatitude >= 56 && fMedianLatitude <= 64 &&
230
0
                    fMedianLongitude >= 3 && fMedianLongitude <= 12)
231
0
                    nUtmZone = 32;  // Norway exception.
232
0
                else if (fMedianLatitude >= 72 && fMedianLatitude <= 84 &&
233
0
                         fMedianLongitude >= 0 && fMedianLongitude <= 42)
234
                    // Svalbard exception.
235
0
                    nUtmZone =
236
0
                        static_cast<int>((fMedianLongitude + 3) / 12) * 2 + 31;
237
0
                else
238
0
                    nUtmZone =
239
0
                        static_cast<int>((fMedianLongitude + 180) / 6) + 1;
240
0
                SetUTM(nUtmZone, fMedianLatitude >= 0);
241
0
            }
242
0
            else
243
0
            {
244
0
                CPLDebug("OSR_Ozi", "UTM Zone not found");
245
0
            }
246
0
        }
247
0
    }
248
0
    else if (STARTS_WITH_CI(papszProj[1], "(I) France Zone I"))
249
0
    {
250
0
        SetLCC1SP(49.5, 2.337229167, 0.99987734, 600000, 1200000);
251
0
    }
252
0
    else if (STARTS_WITH_CI(papszProj[1], "(II) France Zone II"))
253
0
    {
254
0
        SetLCC1SP(46.8, 2.337229167, 0.99987742, 600000, 2200000);
255
0
    }
256
0
    else if (STARTS_WITH_CI(papszProj[1], "(III) France Zone III"))
257
0
    {
258
0
        SetLCC1SP(44.1, 2.337229167, 0.99987750, 600000, 3200000);
259
0
    }
260
0
    else if (STARTS_WITH_CI(papszProj[1], "(IV) France Zone IV"))
261
0
    {
262
0
        SetLCC1SP(42.165, 2.337229167, 0.99994471, 234.358, 4185861.369);
263
0
    }
264
265
    /*
266
     *  Note: The following projections have not been implemented yet
267
     *
268
     */
269
270
    /*
271
        else if( STARTS_WITH_CI(papszProj[1], "(BNG) British National Grid") )
272
        {
273
        }
274
        else if( STARTS_WITH_CI(papszProj[1], "(IG) Irish Grid") )
275
        {
276
        }
277
278
        else if( STARTS_WITH_CI(papszProj[1], "(NZG) New Zealand Grid") )
279
        {
280
        }
281
        else if( STARTS_WITH_CI(papszProj[1], "(NZTM2) New Zealand TM 2000") )
282
        {
283
        }
284
        else if( STARTS_WITH_CI(papszProj[1], "(SG) Swedish Grid") )
285
        {
286
        }
287
        else if( STARTS_WITH_CI(papszProj[1], "(SUI) Swiss Grid") )
288
        {
289
        }
290
        else if( STARTS_WITH_CI(papszProj[1], "(A)Lambert Azimuthual Equal
291
       Area") )
292
        {
293
        }
294
        else if( STARTS_WITH_CI(papszProj[1], "(EQC) Equidistant Conic") )
295
        {
296
        }
297
        else if( STARTS_WITH_CI(papszProj[1], "Polyconic (American)") )
298
        {
299
        }
300
        else if( STARTS_WITH_CI(papszProj[1], "Van Der Grinten") )
301
        {
302
        }
303
        else if( STARTS_WITH_CI(papszProj[1], "Vertical Near-Sided Perspective")
304
       )
305
        {
306
        }
307
        else if( STARTS_WITH_CI(papszProj[1], "(WIV) Wagner IV") )
308
        {
309
        }
310
        else if( STARTS_WITH_CI(papszProj[1], "Bonne") )
311
        {
312
        }
313
        else if( STARTS_WITH_CI(papszProj[1],
314
                                "(MT0) Montana State Plane Zone 2500") )
315
        {
316
        }
317
        else if( STARTS_WITH_CI(papszProj[1], "ITA1) Italy Grid Zone 1") )
318
        {
319
        }
320
        else if( STARTS_WITH_CI(papszProj[1], "ITA2) Italy Grid Zone 2") )
321
        {
322
        }
323
        else if( STARTS_WITH_CI(papszProj[1],
324
                                "(VICMAP-TM) Victoria Aust.(pseudo AMG)") )
325
        {
326
        }
327
        else if( STARTS_WITH_CI(papszProj[1], "VICGRID) Victoria Australia") )
328
        {
329
        }
330
        else if( STARTS_WITH_CI(papszProj[1],
331
                                "(VG94) VICGRID94 Victoria Australia") )
332
        {
333
        }
334
        else if( STARTS_WITH_CI(papszProj[1], "Gnomonic") )
335
        {
336
        }
337
    */
338
0
    else
339
0
    {
340
0
        CPLDebug("OSR_Ozi", "Unsupported projection: \"%s\"", papszProj[1]);
341
0
        SetLocalCS(
342
0
            CPLString().Printf(R"("Ozi" projection "%s")", papszProj[1]));
343
0
    }
344
345
    /* -------------------------------------------------------------------- */
346
    /*      Try to translate the datum/spheroid.                            */
347
    /* -------------------------------------------------------------------- */
348
0
    papszDatum = CSLTokenizeString2(
349
0
        pszDatum, ",",
350
0
        CSLT_ALLOWEMPTYTOKENS | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
351
0
    if (papszDatum == nullptr)
352
0
        goto not_enough_data;
353
354
0
    if (!IsLocal())
355
0
    {
356
        /* --------------------------------------------------------------------
357
         */
358
        /*      Verify that we can find the CSV file containing the datums */
359
        /* --------------------------------------------------------------------
360
         */
361
0
        if (CSVScanFileByName(CSVFilename("ozi_datum.csv"), "EPSG_DATUM_CODE",
362
0
                              "4326", CC_Integer) == nullptr)
363
0
        {
364
0
            CPLError(CE_Failure, CPLE_OpenFailed,
365
0
                     "Unable to open OZI support file %s.  "
366
0
                     "Try setting the GDAL_DATA environment variable to point "
367
0
                     "to the directory containing OZI csv files.",
368
0
                     CSVFilename("ozi_datum.csv"));
369
0
            goto other_error;
370
0
        }
371
372
        /* --------------------------------------------------------------------
373
         */
374
        /*      Search for matching datum */
375
        /* --------------------------------------------------------------------
376
         */
377
0
        const char *pszOziDatum = CSVFilename("ozi_datum.csv");
378
0
        CPLString osDName = CSVGetField(pszOziDatum, "NAME", papszDatum[0],
379
0
                                        CC_ApproxString, "NAME");
380
0
        if (osDName.empty())
381
0
        {
382
0
            CPLError(CE_Failure, CPLE_AppDefined,
383
0
                     "Failed to find datum %s in ozi_datum.csv.",
384
0
                     papszDatum[0]);
385
0
            goto other_error;
386
0
        }
387
388
0
        const int nDatumCode =
389
0
            atoi(CSVGetField(pszOziDatum, "NAME", papszDatum[0],
390
0
                             CC_ApproxString, "EPSG_DATUM_CODE"));
391
392
0
        if (nDatumCode > 0)  // There is a matching EPSG code
393
0
        {
394
0
            OGRSpatialReference oGCS;
395
0
            oGCS.importFromEPSG(nDatumCode);
396
0
            CopyGeogCSFrom(&oGCS);
397
0
        }
398
0
        else  // We use the parameters from the CSV files
399
0
        {
400
0
            CPLString osEllipseCode =
401
0
                CSVGetField(pszOziDatum, "NAME", papszDatum[0], CC_ApproxString,
402
0
                            "ELLIPSOID_CODE");
403
0
            const double dfDeltaX = CPLAtof(CSVGetField(
404
0
                pszOziDatum, "NAME", papszDatum[0], CC_ApproxString, "DELTAX"));
405
0
            const double dfDeltaY = CPLAtof(CSVGetField(
406
0
                pszOziDatum, "NAME", papszDatum[0], CC_ApproxString, "DELTAY"));
407
0
            const double dfDeltaZ = CPLAtof(CSVGetField(
408
0
                pszOziDatum, "NAME", papszDatum[0], CC_ApproxString, "DELTAZ"));
409
410
            /* --------------------------------------------------------------------
411
             */
412
            /*     Verify that we can find the CSV file containing the
413
             * ellipsoids.  */
414
            /* --------------------------------------------------------------------
415
             */
416
0
            if (CSVScanFileByName(CSVFilename("ozi_ellips.csv"),
417
0
                                  "ELLIPSOID_CODE", "20",
418
0
                                  CC_Integer) == nullptr)
419
0
            {
420
0
                CPLError(
421
0
                    CE_Failure, CPLE_OpenFailed,
422
0
                    "Unable to open OZI support file %s.  "
423
0
                    "Try setting the GDAL_DATA environment variable to point "
424
0
                    "to the directory containing OZI csv files.",
425
0
                    CSVFilename("ozi_ellips.csv"));
426
0
                goto other_error;
427
0
            }
428
429
            /* --------------------------------------------------------------------
430
             */
431
            /*      Lookup the ellipse code. */
432
            /* --------------------------------------------------------------------
433
             */
434
0
            const char *pszOziEllipse = CSVFilename("ozi_ellips.csv");
435
436
0
            CPLString osEName =
437
0
                CSVGetField(pszOziEllipse, "ELLIPSOID_CODE", osEllipseCode,
438
0
                            CC_ApproxString, "NAME");
439
0
            if (osEName.empty())
440
0
            {
441
0
                CPLError(CE_Failure, CPLE_AppDefined,
442
0
                         "Failed to find ellipsoid %s in ozi_ellips.csv.",
443
0
                         osEllipseCode.c_str());
444
0
                goto other_error;
445
0
            }
446
447
0
            const double dfA =
448
0
                CPLAtof(CSVGetField(pszOziEllipse, "ELLIPSOID_CODE",
449
0
                                    osEllipseCode, CC_ApproxString, "A"));
450
0
            const double dfInvF =
451
0
                CPLAtof(CSVGetField(pszOziEllipse, "ELLIPSOID_CODE",
452
0
                                    osEllipseCode, CC_ApproxString, "INVF"));
453
454
            /* --------------------------------------------------------------------
455
             */
456
            /*      Create geographic coordinate system. */
457
            /* --------------------------------------------------------------------
458
             */
459
0
            SetGeogCS(osDName, osDName, osEName, dfA, dfInvF);
460
0
            SetTOWGS84(dfDeltaX, dfDeltaY, dfDeltaZ);
461
0
        }
462
0
    }
463
464
    /* -------------------------------------------------------------------- */
465
    /*      Grid units translation                                          */
466
    /* -------------------------------------------------------------------- */
467
0
    if (IsLocal() || IsProjected())
468
0
        SetLinearUnits(SRS_UL_METER, 1.0);
469
470
0
    CSLDestroy(papszProj);
471
0
    CSLDestroy(papszProjParams);
472
0
    CSLDestroy(papszDatum);
473
474
0
    return OGRERR_NONE;
475
476
0
not_enough_data:
477
478
0
    CSLDestroy(papszProj);
479
0
    CSLDestroy(papszProjParams);
480
0
    CSLDestroy(papszDatum);
481
482
0
    return OGRERR_NOT_ENOUGH_DATA;
483
484
0
other_error:
485
486
0
    CSLDestroy(papszProj);
487
0
    CSLDestroy(papszProjParams);
488
0
    CSLDestroy(papszDatum);
489
490
0
    return OGRERR_FAILURE;
491
0
}