• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 14945877415

10 May 2025 01:37PM UTC coverage: 70.838% (+0.004%) from 70.834%
14945877415

Pull #12331

github

web-flow
Merge e74fde6a5 into a1ee70739
Pull Request #12331: GDALG: do not Open() in update mode, and 100% code coverage

12 of 12 new or added lines in 2 files covered. (100.0%)

73 existing lines in 35 files now uncovered.

565410 of 798178 relevant lines covered (70.84%)

234821.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.0
/ogr/ogr_proj_p.cpp
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  PROJ-related functionality
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12

13
#include "cpl_error.h"
14
#include "cpl_multiproc.h"
15
#include "cpl_string.h"
16

17
#include "ogr_proj_p.h"
18
#include "ogr_srs_api.h"
19

20
#include "proj.h"
21

22
#ifndef _WIN32
23
#include <sys/types.h>
24
#include <unistd.h>
25
#if defined(HAVE_PTHREAD_ATFORK)
26
#include <pthread.h>
27
#endif
28
#endif
29

30
#include <mutex>
31
#include <vector>
32

33
/*! @cond Doxygen_Suppress */
34

35
static void osr_proj_logger(void * /* user_data */, int level,
157✔
36
                            const char *message)
37
{
38
    if (level == PJ_LOG_ERROR)
157✔
39
    {
40
        CPLError(CE_Failure, CPLE_AppDefined, "PROJ: %s", message);
157✔
41
    }
42
    else if (level == PJ_LOG_DEBUG)
×
43
    {
44
        CPLDebug("PROJ", "%s", message);
×
45
    }
46
    else if (level == PJ_LOG_TRACE)
×
47
    {
48
        CPLDebug("PROJ_TRACE", "%s", message);
×
49
    }
50
}
157✔
51

52
static unsigned g_searchPathGenerationCounter = 0;
53
static unsigned g_auxDbPathsGenerationCounter = 0;
54
static std::mutex g_oSearchPathMutex;
55
static CPLStringList g_aosSearchpaths;
56
static CPLStringList g_aosAuxDbPaths;
57
#if PROJ_VERSION_MAJOR >= 7
58
static int g_projNetworkEnabled = -1;
59
static unsigned g_projNetworkEnabledGenerationCounter = 0;
60
#endif
61

62
#if !defined(_WIN32) && defined(HAVE_PTHREAD_ATFORK)
63
static bool g_bForkOccurred = false;
64

65
static void ForkOccurred(void)
×
66
{
67
    g_bForkOccurred = true;
×
68
}
×
69
#endif
70

71
struct OSRPJContextHolder
72
{
73
    unsigned searchPathGenerationCounter = 0;
74
    unsigned auxDbPathsGenerationCounter = 0;
75
#if PROJ_VERSION_MAJOR >= 7
76
    unsigned projNetworkEnabledGenerationCounter = 0;
77
#endif
78
    PJ_CONTEXT *context = nullptr;
79
    OSRProjTLSCache oCache;
80
#if !defined(_WIN32)
81
#if !defined(HAVE_PTHREAD_ATFORK)
82
    pid_t curpid = 0;
83
#endif
84
#endif
85

86
#if !defined(_WIN32)
87
    OSRPJContextHolder()
1,491✔
88
        : oCache(init())
1,491✔
89
#if !defined(HAVE_PTHREAD_ATFORK)
90
          ,
91
          curpid(getpid())
92
#endif
93
    {
94
#if HAVE_PTHREAD_ATFORK
95
        static std::once_flag flag;
96
        std::call_once(
1,490✔
97
            flag,
98
            []()
1,185✔
99
            {
100
                if (pthread_atfork(nullptr, nullptr, ForkOccurred) != 0)
1,185✔
101
                {
102
                    CPLError(CE_Failure, CPLE_OutOfMemory,
×
103
                             "pthread_atfork() in ogr_proj_p failed");
104
                }
105
            });
1,185✔
106
#endif
107
        init();
1,490✔
108
    }
1,489✔
109
#else
110
    OSRPJContextHolder() : oCache(init())
111
    {
112
    }
113
#endif
114

115
    ~OSRPJContextHolder();
116

117
    PJ_CONTEXT *init();
118
    void deinit();
119

120
  private:
121
    OSRPJContextHolder(const OSRPJContextHolder &) = delete;
122
    OSRPJContextHolder &operator=(const OSRPJContextHolder &) = delete;
123
};
124

125
static void OSRSetConfigOption(const char *pszKey, const char *pszValue,
62,992✔
126
                               bool bThreadLocal, void *)
127
{
128
    if (!bThreadLocal && pszValue &&
62,992✔
129
        (EQUAL(pszKey, "PROJ_LIB") || EQUAL(pszKey, "PROJ_DATA")))
2,325✔
130
    {
131
        const char *const apszSearchPaths[] = {pszValue, nullptr};
2✔
132
        OSRSetPROJSearchPaths(apszSearchPaths);
2✔
133
    }
134
}
62,992✔
135

136
static void OSRInstallSetConfigOptionCallback()
1,207✔
137
{
138
    static std::once_flag flag;
139
    std::call_once(
1,207✔
140
        flag,
141
        []() { CPLSubscribeToSetConfigOption(OSRSetConfigOption, nullptr); });
1,186✔
142
}
1,207✔
143

144
PJ_CONTEXT *OSRPJContextHolder::init()
3,104,230✔
145
{
146
    if (!context)
3,104,230✔
147
    {
148
        static std::once_flag flag;
149
        std::call_once(
1,556✔
150
            flag,
151
            []()
1,185✔
152
            {
153
                // Initialize g_aosSearchpaths from PROJ_DATA/PROJ_LIB configuration
154
                // option.
155
                std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
2,370✔
156
                if (g_searchPathGenerationCounter == 0)
1,185✔
157
                {
158
                    const char *pszProjData =
159
                        CPLGetConfigOption("PROJ_DATA", nullptr);
1,171✔
160
                    if (pszProjData == nullptr)
1,171✔
161
                        pszProjData = CPLGetConfigOption("PROJ_LIB", nullptr);
1,168✔
162
                    if (pszProjData)
1,171✔
163
                    {
164
                        const char *pszSep =
3✔
165
#ifdef _WIN32
166
                            ";"
167
#else
168
                            ":"
169
#endif
170
                            ;
171
                        g_aosSearchpaths =
172
                            CSLTokenizeString2(pszProjData, pszSep, 0);
3✔
173
                        g_searchPathGenerationCounter = 1;
3✔
174
                    }
175
                }
176

177
                OSRInstallSetConfigOptionCallback();
1,185✔
178
            });
1,185✔
179

180
        context = proj_context_create();
1,556✔
181
        proj_log_func(context, nullptr, osr_proj_logger);
1,556✔
182
    }
183
    return context;
3,104,220✔
184
}
185

186
OSRPJContextHolder::~OSRPJContextHolder()
1,410✔
187
{
188
    deinit();
1,409✔
189
}
1,410✔
190

191
void OSRPJContextHolder::deinit()
2,330✔
192
{
193
    searchPathGenerationCounter = 0;
2,330✔
194
    oCache.clear();
2,330✔
195

196
    // Destroy context in last
197
    proj_context_destroy(context);
2,331✔
198
    context = nullptr;
2,331✔
199
}
2,331✔
200

201
#ifdef _WIN32
202
// Currently thread_local and C++ objects don't work well with DLL on Windows
203
static void FreeProjTLSContextHolder(void *pData)
204
{
205
    delete static_cast<OSRPJContextHolder *>(pData);
206
}
207

208
static OSRPJContextHolder &GetProjTLSContextHolder()
209
{
210
    static OSRPJContextHolder dummy;
211
    int bMemoryErrorOccurred = false;
212
    void *pData = CPLGetTLSEx(CTLS_PROJCONTEXTHOLDER, &bMemoryErrorOccurred);
213
    if (bMemoryErrorOccurred)
214
    {
215
        return dummy;
216
    }
217
    if (pData == nullptr)
218
    {
219
        auto pHolder = new OSRPJContextHolder();
220
        CPLSetTLSWithFreeFuncEx(CTLS_PROJCONTEXTHOLDER, pHolder,
221
                                FreeProjTLSContextHolder,
222
                                &bMemoryErrorOccurred);
223
        if (bMemoryErrorOccurred)
224
        {
225
            delete pHolder;
226
            return dummy;
227
        }
228
        return *pHolder;
229
    }
230
    return *static_cast<OSRPJContextHolder *>(pData);
231
}
232
#else
233
static thread_local OSRPJContextHolder g_tls_projContext;
234

235
static OSRPJContextHolder &GetProjTLSContextHolder()
3,157,490✔
236
{
237
    OSRPJContextHolder &l_projContext = g_tls_projContext;
3,157,490✔
238

239
    // Detect if we are now running in a child process created by fork()
240
    // In that situation we must make sure *not* to use the same underlying
241
    // file open descriptor to the sqlite3 database, since seeks&reads in one
242
    // of the parent or child will affect the other end.
243
#if defined(HAVE_PTHREAD_ATFORK)
244
    if (g_bForkOccurred)
3,157,420✔
245
#else
246
    const pid_t curpid = getpid();
247
    if (curpid != l_projContext.curpid)
248
#endif
249
    {
250
#if defined(HAVE_PTHREAD_ATFORK)
UNCOV
251
        g_bForkOccurred = false;
×
252
#else
253
        l_projContext.curpid = curpid;
254
#endif
255
        const auto osr_proj_logger_none = [](void *, int, const char *) {};
×
UNCOV
256
        proj_log_func(l_projContext.context, nullptr, osr_proj_logger_none);
×
257
        proj_context_set_autoclose_database(l_projContext.context, true);
×
258
        // dummy call to cause the database to be closed
259
        proj_context_get_database_path(l_projContext.context);
×
260
        proj_context_set_autoclose_database(l_projContext.context, false);
×
261
        proj_log_func(l_projContext.context, nullptr, osr_proj_logger);
×
262
    }
263

264
    return l_projContext;
3,157,430✔
265
}
266
#endif
267

268
PJ_CONTEXT *OSRGetProjTLSContext()
3,101,320✔
269
{
270
    auto &l_projContext = GetProjTLSContextHolder();
3,101,320✔
271
    // This .init() must be kept, even if OSRPJContextHolder constructor
272
    // calls it. The reason is that OSRCleanupTLSContext() calls deinit(),
273
    // so if reusing the object, we must re-init again.
274
    l_projContext.init();
3,101,230✔
275
    {
276
        // If OSRSetPROJSearchPaths() has been called since we created the
277
        // context, set the new search paths on the context.
278
        std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
6,202,670✔
279
        if (l_projContext.searchPathGenerationCounter !=
3,101,430✔
280
            g_searchPathGenerationCounter)
281
        {
282
            l_projContext.searchPathGenerationCounter =
335✔
283
                g_searchPathGenerationCounter;
284
            proj_context_set_search_paths(l_projContext.context,
335✔
285
                                          g_aosSearchpaths.Count(),
286
                                          g_aosSearchpaths.List());
335✔
287
        }
288
        if (l_projContext.auxDbPathsGenerationCounter !=
3,101,430✔
289
            g_auxDbPathsGenerationCounter)
290
        {
291
            l_projContext.auxDbPathsGenerationCounter =
2✔
292
                g_auxDbPathsGenerationCounter;
293
            std::string oMainPath(
294
                proj_context_get_database_path(l_projContext.context));
4✔
295
            proj_context_set_database_path(l_projContext.context,
2✔
296
                                           oMainPath.c_str(),
297
                                           g_aosAuxDbPaths.List(), nullptr);
2✔
298
        }
299
#if PROJ_VERSION_MAJOR >= 7
300
        if (l_projContext.projNetworkEnabledGenerationCounter !=
301
            g_projNetworkEnabledGenerationCounter)
302
        {
303
            l_projContext.projNetworkEnabledGenerationCounter =
304
                g_projNetworkEnabledGenerationCounter;
305
            proj_context_set_enable_network(l_projContext.context,
306
                                            g_projNetworkEnabled);
307
        }
308
#endif
309
    }
310
    return l_projContext.context;
3,101,430✔
311
}
312

313
/************************************************************************/
314
/*                         OSRGetProjTLSCache()                         */
315
/************************************************************************/
316

317
OSRProjTLSCache *OSRGetProjTLSCache()
55,266✔
318
{
319
    auto &l_projContext = GetProjTLSContextHolder();
55,266✔
320
    return &l_projContext.oCache;
55,266✔
321
}
322

323
void OSRProjTLSCache::clear()
2,331✔
324
{
325
    m_oCacheEPSG.clear();
2,331✔
326
    m_oCacheWKT.clear();
2,330✔
327
    m_tlsContext = nullptr;
2,331✔
328
}
2,331✔
329

330
PJ_CONTEXT *OSRProjTLSCache::GetPJContext()
54,390✔
331
{
332
    if (m_tlsContext == nullptr)
54,390✔
333
        m_tlsContext = OSRGetProjTLSContext();
5✔
334
    return m_tlsContext;
54,390✔
335
}
336

337
PJ *OSRProjTLSCache::GetPJForEPSGCode(int nCode, bool bUseNonDeprecated,
38,347✔
338
                                      bool bAddTOWGS84)
339
{
340
    const EPSGCacheKey key(nCode, bUseNonDeprecated, bAddTOWGS84);
38,347✔
341
    auto cached = m_oCacheEPSG.getPtr(key);
38,347✔
342
    if (cached)
38,347✔
343
    {
344
        return proj_clone(GetPJContext(), cached->get());
30,176✔
345
    }
346
    return nullptr;
8,171✔
347
}
348

349
void OSRProjTLSCache::CachePJForEPSGCode(int nCode, bool bUseNonDeprecated,
8,147✔
350
                                         bool bAddTOWGS84, PJ *pj)
351
{
352
    const EPSGCacheKey key(nCode, bUseNonDeprecated, bAddTOWGS84);
8,147✔
353
    m_oCacheEPSG.insert(key, UniquePtrPJ(proj_clone(GetPJContext(), pj)));
8,147✔
354
}
8,146✔
355

356
PJ *OSRProjTLSCache::GetPJForWKT(const std::string &wkt)
16,345✔
357
{
358
    auto cached = m_oCacheWKT.getPtr(wkt);
16,345✔
359
    if (cached)
16,345✔
360
    {
361
        return proj_clone(GetPJContext(), cached->get());
14,535✔
362
    }
363
    return nullptr;
1,810✔
364
}
365

366
void OSRProjTLSCache::CachePJForWKT(const std::string &wkt, PJ *pj)
1,532✔
367
{
368
    m_oCacheWKT.insert(wkt, UniquePtrPJ(proj_clone(GetPJContext(), pj)));
1,532✔
369
}
1,532✔
370

371
/************************************************************************/
372
/*                         OSRCleanupTLSContext()                       */
373
/************************************************************************/
374

375
void OSRCleanupTLSContext()
921✔
376
{
377
    GetProjTLSContextHolder().deinit();
921✔
378
}
921✔
379

380
/*! @endcond */
381

382
/************************************************************************/
383
/*                        OSRSetPROJSearchPaths()                       */
384
/************************************************************************/
385

386
/** \brief Set the search path(s) for PROJ resource files.
387
 *
388
 * Note: starting with GDAL 3.7, CPLSetConfigOption("PROJ_DATA", ...) can
389
 * also been used for the same effect.
390
 *
391
 * @param papszPaths NULL terminated list of directory paths.
392
 * @since GDAL 3.0
393
 */
394
void OSRSetPROJSearchPaths(const char *const *papszPaths)
22✔
395
{
396
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
44✔
397
    g_searchPathGenerationCounter++;
22✔
398
    g_aosSearchpaths.Assign(CSLDuplicate(papszPaths), true);
22✔
399
    OSRInstallSetConfigOptionCallback();
22✔
400
}
22✔
401

402
/************************************************************************/
403
/*                        OSRGetPROJSearchPaths()                       */
404
/************************************************************************/
405

406
/** \brief Get the search path(s) for PROJ resource files.
407
 *
408
 * @return NULL terminated list of directory paths. To be freed with
409
 * CSLDestroy()
410
 * @since GDAL 3.0.3
411
 */
412
char **OSRGetPROJSearchPaths()
24✔
413
{
414
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
48✔
415
    if (g_searchPathGenerationCounter > 0 && !g_aosSearchpaths.empty())
24✔
416
    {
417
        return CSLDuplicate(g_aosSearchpaths.List());
8✔
418
    }
419

420
    const char *pszSep =
16✔
421
#ifdef _WIN32
422
        ";"
423
#else
424
        ":"
425
#endif
426
        ;
427
    return CSLTokenizeString2(proj_info().searchpath, pszSep, 0);
16✔
428
}
429

430
/************************************************************************/
431
/*                        OSRSetPROJAuxDbPaths()                        */
432
/************************************************************************/
433

434
/** \brief Set list of PROJ auxiliary database filenames.
435
 *
436
 * @param papszAux NULL-terminated list of auxiliary database filenames, or NULL
437
 * @since GDAL 3.3
438
 *
439
 * @see OSRGetPROJAuxDbPaths, proj_context_set_database_path
440
 */
441
void OSRSetPROJAuxDbPaths(const char *const *papszAux)
2✔
442
{
443
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
4✔
444
    g_auxDbPathsGenerationCounter++;
2✔
445
    g_aosAuxDbPaths.Assign(CSLDuplicate(papszAux), true);
2✔
446
}
2✔
447

448
/************************************************************************/
449
/*                        OSRGetPROJAuxDbPaths()                        */
450
/************************************************************************/
451

452
/** \brief Get PROJ auxiliary database filenames.
453
 *
454
 * @return NULL terminated list of PROJ auxiliary database filenames. To be
455
 * freed with CSLDestroy()
456
 * @since GDAL 3.3.0
457
 *
458
 * @see OSRSetPROJAuxDbPaths, proj_context_set_database_path
459
 */
460
char **OSRGetPROJAuxDbPaths(void)
1✔
461
{
462
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
2✔
463
    // Unfortunately, there is no getter for auxiliary database list at PROJ.
464
    // So, return our copy for now.
465
    return CSLDuplicate(g_aosAuxDbPaths.List());
2✔
466
}
467

468
/************************************************************************/
469
/*                       OSRSetPROJEnableNetwork()                      */
470
/************************************************************************/
471

472
/** \brief Enable or disable PROJ networking capabilities.
473
 *
474
 * @param enabled Set to TRUE to enable networking capabilities.
475
 * @since GDAL 3.4 and PROJ 7
476
 *
477
 * @see OSRGetPROJEnableNetwork, proj_context_set_enable_network
478
 */
479
void OSRSetPROJEnableNetwork(int enabled)
×
480
{
481
#if PROJ_VERSION_MAJOR >= 7
482
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
483
    if (g_projNetworkEnabled != enabled)
484
    {
485
        g_projNetworkEnabled = enabled;
486
        g_projNetworkEnabledGenerationCounter++;
487
    }
488
#else
489
    if (enabled)
×
490
    {
491
        CPLError(CE_Failure, CPLE_NotSupported,
×
492
                 "OSRSetPROJEnableNetwork() requires PROJ >= 7");
493
    }
494
#endif
495
}
×
496

497
/************************************************************************/
498
/*                        OSRGetPROJEnableNetwork()                     */
499
/************************************************************************/
500

501
/** \brief Get whether PROJ networking capabilities are enabled.
502
 *
503
 * @return TRUE if PROJ networking capabilities are enabled.
504
 * @since GDAL 3.4 and PROJ 7
505
 *
506
 * @see OSRSetPROJEnableNetwork, proj_context_is_network_enabled
507
 */
508
int OSRGetPROJEnableNetwork(void)
×
509
{
510
#if PROJ_VERSION_MAJOR >= 7
511
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
512
    if (g_projNetworkEnabled < 0)
513
    {
514
        g_oSearchPathMutex.unlock();
515
        const int ret = proj_context_is_network_enabled(OSRGetProjTLSContext());
516
        g_oSearchPathMutex.lock();
517
        g_projNetworkEnabled = ret;
518
    }
519
    return g_projNetworkEnabled;
520
#else
521
    return FALSE;
×
522
#endif
523
}
524

525
/************************************************************************/
526
/*                         OSRGetPROJVersion()                          */
527
/************************************************************************/
528

529
/** \brief Get the PROJ version
530
 *
531
 * @param pnMajor Pointer to major version number, or NULL
532
 * @param pnMinor Pointer to minor version number, or NULL
533
 * @param pnPatch Pointer to patch version number, or NULL
534
 * @since GDAL 3.0.1
535
 */
536
void OSRGetPROJVersion(int *pnMajor, int *pnMinor, int *pnPatch)
201✔
537
{
538
    auto info = proj_info();
201✔
539
    if (pnMajor)
201✔
540
        *pnMajor = info.major;
77✔
541
    if (pnMinor)
201✔
542
        *pnMinor = info.minor;
68✔
543
    if (pnPatch)
201✔
544
        *pnPatch = info.patch;
56✔
545
}
201✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc