Coverage Report

Created: 2025-06-13 06:29

/src/gdal/ogr/ogr_fromepsg.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Generate an OGRSpatialReference object based on an EPSG
5
 *           PROJCS, or GEOGCS code.
6
 * Author:   Frank Warmerdam, warmerdam@pobox.com
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2000, Frank Warmerdam
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
#include "ogr_srs_api.h"
17
18
#include <cctype>
19
20
#include <cmath>
21
#include <cstddef>
22
#include <cstdio>
23
#include <cstring>
24
#include <cstdlib>
25
#include <map>
26
#include <memory>
27
#include <string>
28
#include <vector>
29
#include <limits>
30
31
#include "cpl_conv.h"
32
#include "cpl_csv.h"
33
#include "cpl_error.h"
34
#include "cpl_multiproc.h"
35
#include "cpl_string.h"
36
#include "ogr_core.h"
37
#include "ogr_p.h"
38
#include "ogr_proj_p.h"
39
#include "ogr_spatialref.h"
40
41
#include "proj.h"
42
43
extern void OGRsnPrintDouble(char *pszStrBuf, size_t size, double dfValue);
44
45
/************************************************************************/
46
/*                         OSRGetEllipsoidInfo()                        */
47
/************************************************************************/
48
49
/**
50
 * Fetch info about an ellipsoid.
51
 *
52
 * This helper function will return ellipsoid parameters corresponding to EPSG
53
 * code provided. Axes are always returned in meters.  Semi major computed
54
 * based on inverse flattening where that is provided.
55
 *
56
 * @param nCode EPSG code of the requested ellipsoid
57
 *
58
 * @param ppszName pointer to string where ellipsoid name will be returned. It
59
 * is caller responsibility to free this string after using with CPLFree().
60
 *
61
 * @param pdfSemiMajor pointer to variable where semi major axis will be
62
 * returned.
63
 *
64
 * @param pdfInvFlattening pointer to variable where inverse flattening will
65
 * be returned.
66
 *
67
 * @return OGRERR_NONE on success or an error code in case of failure.
68
 **/
69
70
OGRErr OSRGetEllipsoidInfo(int nCode, char **ppszName, double *pdfSemiMajor,
71
                           double *pdfInvFlattening)
72
73
0
{
74
0
    CPLString osCode;
75
0
    osCode.Printf("%d", nCode);
76
0
    auto ellipsoid = proj_create_from_database(
77
0
        OSRGetProjTLSContext(), "EPSG", osCode.c_str(), PJ_CATEGORY_ELLIPSOID,
78
0
        false, nullptr);
79
0
    if (!ellipsoid)
80
0
    {
81
0
        return OGRERR_UNSUPPORTED_SRS;
82
0
    }
83
84
0
    if (ppszName)
85
0
    {
86
0
        *ppszName = CPLStrdup(proj_get_name(ellipsoid));
87
0
    }
88
0
    proj_ellipsoid_get_parameters(OSRGetProjTLSContext(), ellipsoid,
89
0
                                  pdfSemiMajor, nullptr, nullptr,
90
0
                                  pdfInvFlattening);
91
0
    proj_destroy(ellipsoid);
92
93
0
    return OGRERR_NONE;
94
0
}
95
96
/************************************************************************/
97
/*                           SetStatePlane()                            */
98
/************************************************************************/
99
100
/**
101
 * \brief Set State Plane projection definition.
102
 *
103
 * This will attempt to generate a complete definition of a state plane
104
 * zone based on generating the entire SRS from the EPSG tables.  If the
105
 * EPSG tables are unavailable, it will produce a stubbed LOCAL_CS definition
106
 * and return OGRERR_FAILURE.
107
 *
108
 * This method is the same as the C function OSRSetStatePlaneWithUnits().
109
 *
110
 * @param nZone State plane zone number, in the USGS numbering scheme (as
111
 * distinct from the Arc/Info and Erdas numbering scheme.
112
 *
113
 * @param bNAD83 TRUE if the NAD83 zone definition should be used or FALSE
114
 * if the NAD27 zone definition should be used.
115
 *
116
 * @param pszOverrideUnitName Linear unit name to apply overriding the
117
 * legal definition for this zone.
118
 *
119
 * @param dfOverrideUnit Linear unit conversion factor to apply overriding
120
 * the legal definition for this zone.
121
 *
122
 * @return OGRERR_NONE on success, or OGRERR_FAILURE on failure, mostly likely
123
 * due to the EPSG tables not being accessible.
124
 */
125
126
OGRErr OGRSpatialReference::SetStatePlane(int nZone, int bNAD83,
127
                                          const char *pszOverrideUnitName,
128
                                          double dfOverrideUnit)
129
130
0
{
131
132
    /* -------------------------------------------------------------------- */
133
    /*      Get the index id from stateplane.csv.                           */
134
    /* -------------------------------------------------------------------- */
135
136
0
    if (!bNAD83 && nZone > INT_MAX - 10000)
137
0
        return OGRERR_FAILURE;
138
139
0
    const int nAdjustedId = bNAD83 ? nZone : nZone + 10000;
140
141
    /* -------------------------------------------------------------------- */
142
    /*      Turn this into a PCS code.  We assume there will only be one    */
143
    /*      PCS corresponding to each Proj_ code since the proj code        */
144
    /*      already effectively indicates NAD27 or NAD83.                   */
145
    /* -------------------------------------------------------------------- */
146
0
    char szID[32] = {};
147
0
    snprintf(szID, sizeof(szID), "%d", nAdjustedId);
148
0
    const int nPCSCode = atoi(CSVGetField(CSVFilename("stateplane.csv"), "ID",
149
0
                                          szID, CC_Integer, "EPSG_PCS_CODE"));
150
0
    if (nPCSCode < 1)
151
0
    {
152
0
        static bool bFailureReported = false;
153
154
0
        if (!bFailureReported)
155
0
        {
156
0
            bFailureReported = true;
157
0
            CPLError(CE_Warning, CPLE_OpenFailed,
158
0
                     "Unable to find state plane zone in stateplane.csv, "
159
0
                     "likely because the GDAL data files cannot be found.  "
160
0
                     "Using incomplete definition of state plane zone.");
161
0
        }
162
163
0
        Clear();
164
0
        if (bNAD83)
165
0
        {
166
0
            char szName[128] = {};
167
0
            snprintf(szName, sizeof(szName), "State Plane Zone %d / NAD83",
168
0
                     nZone);
169
0
            SetLocalCS(szName);
170
0
            SetLinearUnits(SRS_UL_METER, 1.0);
171
0
        }
172
0
        else
173
0
        {
174
0
            char szName[128] = {};
175
0
            snprintf(szName, sizeof(szName), "State Plane Zone %d / NAD27",
176
0
                     nZone);
177
0
            SetLocalCS(szName);
178
0
            SetLinearUnits(SRS_UL_US_FOOT, CPLAtof(SRS_UL_US_FOOT_CONV));
179
0
        }
180
181
0
        return OGRERR_FAILURE;
182
0
    }
183
184
    /* -------------------------------------------------------------------- */
185
    /*      Define based on a full EPSG definition of the zone.             */
186
    /* -------------------------------------------------------------------- */
187
0
    OGRErr eErr = importFromEPSG(nPCSCode);
188
189
0
    if (eErr != OGRERR_NONE)
190
0
        return eErr;
191
192
    /* -------------------------------------------------------------------- */
193
    /*      Apply units override if required.                               */
194
    /*                                                                      */
195
    /*      We will need to adjust the linear projection parameter to       */
196
    /*      match the provided units, and clear the authority code.         */
197
    /* -------------------------------------------------------------------- */
198
0
    if (pszOverrideUnitName != nullptr && dfOverrideUnit != 0.0 &&
199
0
        fabs(dfOverrideUnit - GetLinearUnits()) > 0.0000000001)
200
0
    {
201
0
        const double dfFalseEasting = GetNormProjParm(SRS_PP_FALSE_EASTING);
202
0
        const double dfFalseNorthing = GetNormProjParm(SRS_PP_FALSE_NORTHING);
203
204
0
        SetLinearUnits(pszOverrideUnitName, dfOverrideUnit);
205
206
0
        SetNormProjParm(SRS_PP_FALSE_EASTING, dfFalseEasting);
207
0
        SetNormProjParm(SRS_PP_FALSE_NORTHING, dfFalseNorthing);
208
209
0
        OGR_SRSNode *const poPROJCS = GetAttrNode("PROJCS");
210
0
        if (poPROJCS != nullptr && poPROJCS->FindChild("AUTHORITY") != -1)
211
0
        {
212
0
            poPROJCS->DestroyChild(poPROJCS->FindChild("AUTHORITY"));
213
0
        }
214
0
    }
215
216
0
    return OGRERR_NONE;
217
0
}
218
219
/************************************************************************/
220
/*                          OSRSetStatePlane()                          */
221
/************************************************************************/
222
223
/**
224
 * \brief Set State Plane projection definition.
225
 *
226
 * This function is the same as OGRSpatialReference::SetStatePlane().
227
 */
228
229
OGRErr OSRSetStatePlane(OGRSpatialReferenceH hSRS, int nZone, int bNAD83)
230
231
0
{
232
0
    VALIDATE_POINTER1(hSRS, "OSRSetStatePlane", OGRERR_FAILURE);
233
234
0
    return reinterpret_cast<OGRSpatialReference *>(hSRS)->SetStatePlane(nZone,
235
0
                                                                        bNAD83);
236
0
}
237
238
/************************************************************************/
239
/*                     OSRSetStatePlaneWithUnits()                      */
240
/************************************************************************/
241
242
/**
243
 * \brief Set State Plane projection definition.
244
 *
245
 * This function is the same as OGRSpatialReference::SetStatePlane().
246
 */
247
248
OGRErr OSRSetStatePlaneWithUnits(OGRSpatialReferenceH hSRS, int nZone,
249
                                 int bNAD83, const char *pszOverrideUnitName,
250
                                 double dfOverrideUnit)
251
252
0
{
253
0
    VALIDATE_POINTER1(hSRS, "OSRSetStatePlaneWithUnits", OGRERR_FAILURE);
254
255
0
    return reinterpret_cast<OGRSpatialReference *>(hSRS)->SetStatePlane(
256
0
        nZone, bNAD83, pszOverrideUnitName, dfOverrideUnit);
257
0
}
258
259
/************************************************************************/
260
/*                          AutoIdentifyEPSG()                          */
261
/************************************************************************/
262
263
/**
264
 * \brief Set EPSG authority info if possible.
265
 *
266
 * This method inspects a WKT definition, and adds EPSG authority nodes
267
 * where an aspect of the coordinate system can be easily and safely
268
 * corresponded with an EPSG identifier.  In practice, this method will
269
 * evolve over time.  In theory it can add authority nodes for any object
270
 * (i.e. spheroid, datum, GEOGCS, units, and PROJCS) that could have an
271
 * authority node.  Mostly this is useful to inserting appropriate
272
 * PROJCS codes for common formulations (like UTM n WGS84).
273
 *
274
 * If it success the OGRSpatialReference is updated in place, and the
275
 * method return OGRERR_NONE.  If the method fails to identify the
276
 * general coordinate system OGRERR_UNSUPPORTED_SRS is returned but no
277
 * error message is posted via CPLError().
278
 *
279
 * This method is the same as the C function OSRAutoIdentifyEPSG().
280
 *
281
 * Since GDAL 2.3, the FindMatches() method can also be used for improved
282
 * matching by researching the EPSG catalog.
283
 *
284
 * @return OGRERR_NONE or OGRERR_UNSUPPORTED_SRS.
285
 *
286
 * @see OGRSpatialReference::FindBestMatch()
287
 */
288
289
OGRErr OGRSpatialReference::AutoIdentifyEPSG()
290
291
0
{
292
    /* -------------------------------------------------------------------- */
293
    /*      Do we have a GEOGCS node, but no authority?  If so, try         */
294
    /*      guessing it.                                                    */
295
    /* -------------------------------------------------------------------- */
296
0
    if ((IsProjected() || IsGeographic()) &&
297
0
        GetAuthorityCode("GEOGCS") == nullptr &&
298
0
        GetAttrValue("PROJCRS|BASEGEOGCRS|ID") == nullptr)
299
0
    {
300
0
        const int nGCS = GetEPSGGeogCS();
301
0
        if (nGCS != -1)
302
0
            SetAuthority("GEOGCS", "EPSG", nGCS);
303
0
    }
304
305
0
    if (IsProjected() && GetAuthorityCode("PROJCS") == nullptr)
306
0
    {
307
0
        const char *pszProjection = GetAttrValue("PROJECTION");
308
309
        /* --------------------------------------------------------------------
310
         */
311
        /*      Is this a UTM coordinate system with a common GEOGCS? */
312
        /* --------------------------------------------------------------------
313
         */
314
0
        int nZone = 0;
315
0
        int bNorth = FALSE;
316
0
        if ((nZone = GetUTMZone(&bNorth)) != 0)
317
0
        {
318
0
            const char *pszAuthName = GetAuthorityName("PROJCS|GEOGCS");
319
0
            const char *pszAuthCode = GetAuthorityCode("PROJCS|GEOGCS");
320
321
0
            if (pszAuthName == nullptr || pszAuthCode == nullptr)
322
0
            {
323
                // Don't exactly recognise datum.
324
0
            }
325
0
            else if (EQUAL(pszAuthName, "EPSG") && atoi(pszAuthCode) == 4326)
326
0
            {
327
                // WGS84
328
0
                if (bNorth)
329
0
                    SetAuthority("PROJCS", "EPSG", 32600 + nZone);
330
0
                else
331
0
                    SetAuthority("PROJCS", "EPSG", 32700 + nZone);
332
0
            }
333
0
            else if (EQUAL(pszAuthName, "EPSG") && atoi(pszAuthCode) == 4267 &&
334
0
                     nZone >= 3 && nZone <= 22 && bNorth)
335
0
            {
336
0
                SetAuthority("PROJCS", "EPSG", 26700 + nZone);  // NAD27
337
0
            }
338
0
            else if (EQUAL(pszAuthName, "EPSG") && atoi(pszAuthCode) == 4269 &&
339
0
                     nZone >= 3 && nZone <= 23 && bNorth)
340
0
            {
341
0
                SetAuthority("PROJCS", "EPSG", 26900 + nZone);  // NAD83
342
0
            }
343
0
            else if (EQUAL(pszAuthName, "EPSG") && atoi(pszAuthCode) == 4322)
344
0
            {  // WGS72
345
0
                if (bNorth)
346
0
                    SetAuthority("PROJCS", "EPSG", 32200 + nZone);
347
0
                else
348
0
                    SetAuthority("PROJCS", "EPSG", 32300 + nZone);
349
0
            }
350
0
        }
351
352
        /* --------------------------------------------------------------------
353
         */
354
        /*      Is this a Polar Stereographic system on WGS 84 ? */
355
        /* --------------------------------------------------------------------
356
         */
357
0
        else if (pszProjection != nullptr &&
358
0
                 EQUAL(pszProjection, SRS_PT_POLAR_STEREOGRAPHIC))
359
0
        {
360
0
            const char *pszAuthName = GetAuthorityName("PROJCS|GEOGCS");
361
0
            const char *pszAuthCode = GetAuthorityCode("PROJCS|GEOGCS");
362
0
            const double dfLatOrigin =
363
0
                GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
364
365
0
            if (pszAuthName != nullptr && EQUAL(pszAuthName, "EPSG") &&
366
0
                pszAuthCode != nullptr && atoi(pszAuthCode) == 4326 &&
367
0
                fabs(fabs(dfLatOrigin) - 71.0) < 1e-15 &&
368
0
                fabs(GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0)) < 1e-15 &&
369
0
                fabs(GetProjParm(SRS_PP_SCALE_FACTOR, 1.0) - 1.0) < 1e-15 &&
370
0
                fabs(GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0)) < 1e-15 &&
371
0
                fabs(GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0)) < 1e-15 &&
372
0
                fabs(GetLinearUnits() - 1.0) < 1e-15)
373
0
            {
374
0
                if (dfLatOrigin > 0)
375
                    // Arctic Polar Stereographic
376
0
                    SetAuthority("PROJCS", "EPSG", 3995);
377
0
                else
378
                    // Antarctic Polar Stereographic
379
0
                    SetAuthority("PROJCS", "EPSG", 3031);
380
0
            }
381
0
        }
382
0
    }
383
384
    /* -------------------------------------------------------------------- */
385
    /*      Return.                                                         */
386
    /* -------------------------------------------------------------------- */
387
0
    if (IsProjected() && GetAuthorityCode("PROJCS") != nullptr)
388
0
        return OGRERR_NONE;
389
390
0
    if (IsGeographic() && GetAuthorityCode("GEOGCS") != nullptr)
391
0
        return OGRERR_NONE;
392
393
0
    return OGRERR_UNSUPPORTED_SRS;
394
0
}
395
396
/************************************************************************/
397
/*                        OSRAutoIdentifyEPSG()                         */
398
/************************************************************************/
399
400
/**
401
 * \brief Set EPSG authority info if possible.
402
 *
403
 * This function is the same as OGRSpatialReference::AutoIdentifyEPSG().
404
 *
405
 * Since GDAL 2.3, the OSRFindMatches() function can also be used for improved
406
 * matching by researching the EPSG catalog.
407
 *
408
 */
409
410
OGRErr OSRAutoIdentifyEPSG(OGRSpatialReferenceH hSRS)
411
412
0
{
413
0
    VALIDATE_POINTER1(hSRS, "OSRAutoIdentifyEPSG", OGRERR_FAILURE);
414
415
0
    return reinterpret_cast<OGRSpatialReference *>(hSRS)->AutoIdentifyEPSG();
416
0
}