Coverage Report

Created: 2025-06-13 06:29

/src/gdal/ogr/ogr_proj_p.cpp
Line
Count
Source (jump to first uncovered line)
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,
36
                            const char *message)
37
0
{
38
0
    if (level == PJ_LOG_ERROR)
39
0
    {
40
0
        CPLError(CE_Failure, CPLE_AppDefined, "PROJ: %s", message);
41
0
    }
42
0
    else if (level == PJ_LOG_DEBUG)
43
0
    {
44
0
        CPLDebug("PROJ", "%s", message);
45
0
    }
46
0
    else if (level == PJ_LOG_TRACE)
47
0
    {
48
0
        CPLDebug("PROJ_TRACE", "%s", message);
49
0
    }
50
0
}
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
0
{
67
0
    g_bForkOccurred = true;
68
0
}
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()
88
0
        : oCache(init())
89
#if !defined(HAVE_PTHREAD_ATFORK)
90
          ,
91
          curpid(getpid())
92
#endif
93
0
    {
94
0
#if HAVE_PTHREAD_ATFORK
95
0
        static std::once_flag flag;
96
0
        std::call_once(
97
0
            flag,
98
0
            []()
99
0
            {
100
0
                if (pthread_atfork(nullptr, nullptr, ForkOccurred) != 0)
101
0
                {
102
0
                    CPLError(CE_Failure, CPLE_OutOfMemory,
103
0
                             "pthread_atfork() in ogr_proj_p failed");
104
0
                }
105
0
            });
106
0
#endif
107
0
        init();
108
0
    }
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,
126
                               bool bThreadLocal, void *)
127
2.24k
{
128
2.24k
    if (!bThreadLocal && pszValue &&
129
2.24k
        (EQUAL(pszKey, "PROJ_LIB") || EQUAL(pszKey, "PROJ_DATA")))
130
0
    {
131
0
        const char *const apszSearchPaths[] = {pszValue, nullptr};
132
0
        OSRSetPROJSearchPaths(apszSearchPaths);
133
0
    }
134
2.24k
}
135
136
static void OSRInstallSetConfigOptionCallback()
137
795
{
138
795
    static std::once_flag flag;
139
795
    std::call_once(
140
795
        flag,
141
795
        []() { CPLSubscribeToSetConfigOption(OSRSetConfigOption, nullptr); });
142
795
}
143
144
PJ_CONTEXT *OSRPJContextHolder::init()
145
0
{
146
0
    if (!context)
147
0
    {
148
0
        static std::once_flag flag;
149
0
        std::call_once(
150
0
            flag,
151
0
            []()
152
0
            {
153
                // Initialize g_aosSearchpaths from PROJ_DATA/PROJ_LIB configuration
154
                // option.
155
0
                std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
156
0
                if (g_searchPathGenerationCounter == 0)
157
0
                {
158
0
                    const char *pszProjData =
159
0
                        CPLGetConfigOption("PROJ_DATA", nullptr);
160
0
                    if (pszProjData == nullptr)
161
0
                        pszProjData = CPLGetConfigOption("PROJ_LIB", nullptr);
162
0
                    if (pszProjData)
163
0
                    {
164
0
                        const char *pszSep =
165
#ifdef _WIN32
166
                            ";"
167
#else
168
0
                            ":"
169
0
#endif
170
0
                            ;
171
0
                        g_aosSearchpaths =
172
0
                            CSLTokenizeString2(pszProjData, pszSep, 0);
173
0
                        g_searchPathGenerationCounter = 1;
174
0
                    }
175
0
                }
176
177
0
                OSRInstallSetConfigOptionCallback();
178
0
            });
179
180
0
        context = proj_context_create();
181
0
        proj_log_func(context, nullptr, osr_proj_logger);
182
0
    }
183
0
    return context;
184
0
}
185
186
OSRPJContextHolder::~OSRPJContextHolder()
187
0
{
188
0
    deinit();
189
0
}
190
191
void OSRPJContextHolder::deinit()
192
0
{
193
0
    searchPathGenerationCounter = 0;
194
0
    oCache.clear();
195
196
    // Destroy context in last
197
0
    proj_context_destroy(context);
198
0
    context = nullptr;
199
0
}
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()
236
0
{
237
0
    OSRPJContextHolder &l_projContext = g_tls_projContext;
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
0
#if defined(HAVE_PTHREAD_ATFORK)
244
0
    if (g_bForkOccurred)
245
#else
246
    const pid_t curpid = getpid();
247
    if (curpid != l_projContext.curpid)
248
#endif
249
0
    {
250
0
#if defined(HAVE_PTHREAD_ATFORK)
251
0
        g_bForkOccurred = false;
252
#else
253
        l_projContext.curpid = curpid;
254
#endif
255
0
        const auto osr_proj_logger_none = [](void *, int, const char *) {};
256
0
        proj_log_func(l_projContext.context, nullptr, osr_proj_logger_none);
257
0
        proj_context_set_autoclose_database(l_projContext.context, true);
258
        // dummy call to cause the database to be closed
259
0
        proj_context_get_database_path(l_projContext.context);
260
0
        proj_context_set_autoclose_database(l_projContext.context, false);
261
0
        proj_log_func(l_projContext.context, nullptr, osr_proj_logger);
262
0
    }
263
264
0
    return l_projContext;
265
0
}
266
#endif
267
268
PJ_CONTEXT *OSRGetProjTLSContext()
269
0
{
270
0
    auto &l_projContext = GetProjTLSContextHolder();
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
0
    l_projContext.init();
275
0
    {
276
        // If OSRSetPROJSearchPaths() has been called since we created the
277
        // context, set the new search paths on the context.
278
0
        std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
279
0
        if (l_projContext.searchPathGenerationCounter !=
280
0
            g_searchPathGenerationCounter)
281
0
        {
282
0
            l_projContext.searchPathGenerationCounter =
283
0
                g_searchPathGenerationCounter;
284
0
            proj_context_set_search_paths(l_projContext.context,
285
0
                                          g_aosSearchpaths.Count(),
286
0
                                          g_aosSearchpaths.List());
287
0
        }
288
0
        if (l_projContext.auxDbPathsGenerationCounter !=
289
0
            g_auxDbPathsGenerationCounter)
290
0
        {
291
0
            l_projContext.auxDbPathsGenerationCounter =
292
0
                g_auxDbPathsGenerationCounter;
293
0
            std::string oMainPath(
294
0
                proj_context_get_database_path(l_projContext.context));
295
0
            proj_context_set_database_path(l_projContext.context,
296
0
                                           oMainPath.c_str(),
297
0
                                           g_aosAuxDbPaths.List(), nullptr);
298
0
        }
299
0
#if PROJ_VERSION_MAJOR >= 7
300
0
        if (l_projContext.projNetworkEnabledGenerationCounter !=
301
0
            g_projNetworkEnabledGenerationCounter)
302
0
        {
303
0
            l_projContext.projNetworkEnabledGenerationCounter =
304
0
                g_projNetworkEnabledGenerationCounter;
305
0
            proj_context_set_enable_network(l_projContext.context,
306
0
                                            g_projNetworkEnabled);
307
0
        }
308
0
#endif
309
0
    }
310
0
    return l_projContext.context;
311
0
}
312
313
/************************************************************************/
314
/*                         OSRGetProjTLSCache()                         */
315
/************************************************************************/
316
317
OSRProjTLSCache *OSRGetProjTLSCache()
318
0
{
319
0
    auto &l_projContext = GetProjTLSContextHolder();
320
0
    return &l_projContext.oCache;
321
0
}
322
323
void OSRProjTLSCache::clear()
324
0
{
325
0
    m_oCacheEPSG.clear();
326
0
    m_oCacheWKT.clear();
327
0
    m_tlsContext = nullptr;
328
0
}
329
330
PJ_CONTEXT *OSRProjTLSCache::GetPJContext()
331
0
{
332
0
    if (m_tlsContext == nullptr)
333
0
        m_tlsContext = OSRGetProjTLSContext();
334
0
    return m_tlsContext;
335
0
}
336
337
PJ *OSRProjTLSCache::GetPJForEPSGCode(int nCode, bool bUseNonDeprecated,
338
                                      bool bAddTOWGS84)
339
0
{
340
0
    const EPSGCacheKey key(nCode, bUseNonDeprecated, bAddTOWGS84);
341
0
    auto cached = m_oCacheEPSG.getPtr(key);
342
0
    if (cached)
343
0
    {
344
0
        return proj_clone(GetPJContext(), cached->get());
345
0
    }
346
0
    return nullptr;
347
0
}
348
349
void OSRProjTLSCache::CachePJForEPSGCode(int nCode, bool bUseNonDeprecated,
350
                                         bool bAddTOWGS84, PJ *pj)
351
0
{
352
0
    const EPSGCacheKey key(nCode, bUseNonDeprecated, bAddTOWGS84);
353
0
    m_oCacheEPSG.insert(key, UniquePtrPJ(proj_clone(GetPJContext(), pj)));
354
0
}
355
356
PJ *OSRProjTLSCache::GetPJForWKT(const std::string &wkt)
357
0
{
358
0
    auto cached = m_oCacheWKT.getPtr(wkt);
359
0
    if (cached)
360
0
    {
361
0
        return proj_clone(GetPJContext(), cached->get());
362
0
    }
363
0
    return nullptr;
364
0
}
365
366
void OSRProjTLSCache::CachePJForWKT(const std::string &wkt, PJ *pj)
367
0
{
368
0
    m_oCacheWKT.insert(wkt, UniquePtrPJ(proj_clone(GetPJContext(), pj)));
369
0
}
370
371
/************************************************************************/
372
/*                         OSRCleanupTLSContext()                       */
373
/************************************************************************/
374
375
void OSRCleanupTLSContext()
376
0
{
377
0
    GetProjTLSContextHolder().deinit();
378
0
}
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)
395
795
{
396
795
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
397
795
    g_searchPathGenerationCounter++;
398
795
    g_aosSearchpaths.Assign(CSLDuplicate(papszPaths), true);
399
795
    OSRInstallSetConfigOptionCallback();
400
795
}
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()
413
0
{
414
0
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
415
0
    if (g_searchPathGenerationCounter > 0 && !g_aosSearchpaths.empty())
416
0
    {
417
0
        return CSLDuplicate(g_aosSearchpaths.List());
418
0
    }
419
420
0
    const char *pszSep =
421
#ifdef _WIN32
422
        ";"
423
#else
424
0
        ":"
425
0
#endif
426
0
        ;
427
0
    return CSLTokenizeString2(proj_info().searchpath, pszSep, 0);
428
0
}
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)
442
0
{
443
0
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
444
0
    g_auxDbPathsGenerationCounter++;
445
0
    g_aosAuxDbPaths.Assign(CSLDuplicate(papszAux), true);
446
0
}
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)
461
0
{
462
0
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
463
    // Unfortunately, there is no getter for auxiliary database list at PROJ.
464
    // So, return our copy for now.
465
0
    return CSLDuplicate(g_aosAuxDbPaths.List());
466
0
}
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
0
{
481
0
#if PROJ_VERSION_MAJOR >= 7
482
0
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
483
0
    if (g_projNetworkEnabled != enabled)
484
0
    {
485
0
        g_projNetworkEnabled = enabled;
486
0
        g_projNetworkEnabledGenerationCounter++;
487
0
    }
488
#else
489
    if (enabled)
490
    {
491
        CPLError(CE_Failure, CPLE_NotSupported,
492
                 "OSRSetPROJEnableNetwork() requires PROJ >= 7");
493
    }
494
#endif
495
0
}
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
0
{
510
0
#if PROJ_VERSION_MAJOR >= 7
511
0
    std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
512
0
    if (g_projNetworkEnabled < 0)
513
0
    {
514
0
        g_oSearchPathMutex.unlock();
515
0
        const int ret = proj_context_is_network_enabled(OSRGetProjTLSContext());
516
0
        g_oSearchPathMutex.lock();
517
0
        g_projNetworkEnabled = ret;
518
0
    }
519
0
    return g_projNetworkEnabled;
520
#else
521
    return FALSE;
522
#endif
523
0
}
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)
537
0
{
538
0
    auto info = proj_info();
539
0
    if (pnMajor)
540
0
        *pnMajor = info.major;
541
0
    if (pnMinor)
542
0
        *pnMinor = info.minor;
543
0
    if (pnPatch)
544
0
        *pnPatch = info.patch;
545
0
}