Line data Source code
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 134 : static void osr_proj_logger(void * /* user_data */, int level,
36 : const char *message)
37 : {
38 134 : if (level == PJ_LOG_ERROR)
39 : {
40 134 : CPLError(CE_Failure, CPLE_AppDefined, "PROJ: %s", message);
41 : }
42 0 : else if (level == PJ_LOG_DEBUG)
43 : {
44 0 : CPLDebug("PROJ", "%s", message);
45 : }
46 0 : else if (level == PJ_LOG_TRACE)
47 : {
48 0 : CPLDebug("PROJ_TRACE", "%s", message);
49 : }
50 134 : }
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 0 : static void ForkOccurred(void)
66 : {
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 1296 : OSRPJContextHolder()
88 1296 : : oCache(init())
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 1295 : std::call_once(
97 : flag,
98 1159 : []()
99 : {
100 1159 : if (pthread_atfork(nullptr, nullptr, ForkOccurred) != 0)
101 : {
102 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
103 : "pthread_atfork() in ogr_proj_p failed");
104 : }
105 1159 : });
106 : #endif
107 1296 : init();
108 1295 : }
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 60024 : static void OSRSetConfigOption(const char *pszKey, const char *pszValue,
126 : bool bThreadLocal, void *)
127 : {
128 60024 : if (!bThreadLocal && pszValue &&
129 2324 : (EQUAL(pszKey, "PROJ_LIB") || EQUAL(pszKey, "PROJ_DATA")))
130 : {
131 2 : const char *const apszSearchPaths[] = {pszValue, nullptr};
132 2 : OSRSetPROJSearchPaths(apszSearchPaths);
133 : }
134 60024 : }
135 :
136 1181 : static void OSRInstallSetConfigOptionCallback()
137 : {
138 : static std::once_flag flag;
139 1181 : std::call_once(
140 : flag,
141 1160 : []() { CPLSubscribeToSetConfigOption(OSRSetConfigOption, nullptr); });
142 1181 : }
143 :
144 1023630 : PJ_CONTEXT *OSRPJContextHolder::init()
145 : {
146 1023630 : if (!context)
147 : {
148 : static std::once_flag flag;
149 1358 : std::call_once(
150 : flag,
151 1159 : []()
152 : {
153 : // Initialize g_aosSearchpaths from PROJ_DATA/PROJ_LIB configuration
154 : // option.
155 2318 : std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
156 1159 : if (g_searchPathGenerationCounter == 0)
157 : {
158 : const char *pszProjData =
159 1145 : CPLGetConfigOption("PROJ_DATA", nullptr);
160 1145 : if (pszProjData == nullptr)
161 1142 : pszProjData = CPLGetConfigOption("PROJ_LIB", nullptr);
162 1145 : if (pszProjData)
163 : {
164 3 : const char *pszSep =
165 : #ifdef _WIN32
166 : ";"
167 : #else
168 : ":"
169 : #endif
170 : ;
171 : g_aosSearchpaths =
172 3 : CSLTokenizeString2(pszProjData, pszSep, 0);
173 3 : g_searchPathGenerationCounter = 1;
174 : }
175 : }
176 :
177 1159 : OSRInstallSetConfigOptionCallback();
178 1159 : });
179 :
180 1358 : context = proj_context_create();
181 1358 : proj_log_func(context, nullptr, osr_proj_logger);
182 : }
183 1023620 : return context;
184 : }
185 :
186 1276 : OSRPJContextHolder::~OSRPJContextHolder()
187 : {
188 1276 : deinit();
189 1276 : }
190 :
191 2214 : void OSRPJContextHolder::deinit()
192 : {
193 2214 : searchPathGenerationCounter = 0;
194 2214 : oCache.clear();
195 :
196 : // Destroy context in last
197 2214 : proj_context_destroy(context);
198 2214 : context = nullptr;
199 2214 : }
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 1073990 : static OSRPJContextHolder &GetProjTLSContextHolder()
236 : {
237 1073990 : 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 : #if defined(HAVE_PTHREAD_ATFORK)
244 1073980 : if (g_bForkOccurred)
245 : #else
246 : const pid_t curpid = getpid();
247 : if (curpid != l_projContext.curpid)
248 : #endif
249 : {
250 : #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 : }
263 :
264 1073990 : return l_projContext;
265 : }
266 : #endif
267 :
268 1020960 : PJ_CONTEXT *OSRGetProjTLSContext()
269 : {
270 1020960 : 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 1021030 : l_projContext.init();
275 : {
276 : // If OSRSetPROJSearchPaths() has been called since we created the
277 : // context, set the new search paths on the context.
278 2042080 : std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
279 1021050 : if (l_projContext.searchPathGenerationCounter !=
280 : g_searchPathGenerationCounter)
281 : {
282 172 : l_projContext.searchPathGenerationCounter =
283 : g_searchPathGenerationCounter;
284 172 : proj_context_set_search_paths(l_projContext.context,
285 : g_aosSearchpaths.Count(),
286 172 : g_aosSearchpaths.List());
287 : }
288 1021050 : if (l_projContext.auxDbPathsGenerationCounter !=
289 : g_auxDbPathsGenerationCounter)
290 : {
291 2 : l_projContext.auxDbPathsGenerationCounter =
292 : g_auxDbPathsGenerationCounter;
293 : std::string oMainPath(
294 4 : proj_context_get_database_path(l_projContext.context));
295 2 : proj_context_set_database_path(l_projContext.context,
296 : oMainPath.c_str(),
297 2 : g_aosAuxDbPaths.List(), nullptr);
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 1021050 : return l_projContext.context;
311 : }
312 :
313 : /************************************************************************/
314 : /* OSRGetProjTLSCache() */
315 : /************************************************************************/
316 :
317 52016 : OSRProjTLSCache *OSRGetProjTLSCache()
318 : {
319 52016 : auto &l_projContext = GetProjTLSContextHolder();
320 52016 : return &l_projContext.oCache;
321 : }
322 :
323 2214 : void OSRProjTLSCache::clear()
324 : {
325 2214 : m_oCacheEPSG.clear();
326 2214 : m_oCacheWKT.clear();
327 2214 : m_tlsContext = nullptr;
328 2214 : }
329 :
330 51171 : PJ_CONTEXT *OSRProjTLSCache::GetPJContext()
331 : {
332 51171 : if (m_tlsContext == nullptr)
333 5 : m_tlsContext = OSRGetProjTLSContext();
334 51171 : return m_tlsContext;
335 : }
336 :
337 34380 : PJ *OSRProjTLSCache::GetPJForEPSGCode(int nCode, bool bUseNonDeprecated,
338 : bool bAddTOWGS84)
339 : {
340 34380 : const EPSGCacheKey key(nCode, bUseNonDeprecated, bAddTOWGS84);
341 34379 : auto cached = m_oCacheEPSG.getPtr(key);
342 34380 : if (cached)
343 : {
344 26442 : return proj_clone(GetPJContext(), cached->get());
345 : }
346 7938 : return nullptr;
347 : }
348 :
349 7917 : void OSRProjTLSCache::CachePJForEPSGCode(int nCode, bool bUseNonDeprecated,
350 : bool bAddTOWGS84, PJ *pj)
351 : {
352 7917 : const EPSGCacheKey key(nCode, bUseNonDeprecated, bAddTOWGS84);
353 7917 : m_oCacheEPSG.insert(key, UniquePtrPJ(proj_clone(GetPJContext(), pj)));
354 7917 : }
355 :
356 17099 : PJ *OSRProjTLSCache::GetPJForWKT(const std::string &wkt)
357 : {
358 17099 : auto cached = m_oCacheWKT.getPtr(wkt);
359 17099 : if (cached)
360 : {
361 15403 : return proj_clone(GetPJContext(), cached->get());
362 : }
363 1696 : return nullptr;
364 : }
365 :
366 1410 : void OSRProjTLSCache::CachePJForWKT(const std::string &wkt, PJ *pj)
367 : {
368 1410 : m_oCacheWKT.insert(wkt, UniquePtrPJ(proj_clone(GetPJContext(), pj)));
369 1410 : }
370 :
371 : /************************************************************************/
372 : /* OSRCleanupTLSContext() */
373 : /************************************************************************/
374 :
375 938 : void OSRCleanupTLSContext()
376 : {
377 938 : GetProjTLSContextHolder().deinit();
378 938 : }
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 22 : void OSRSetPROJSearchPaths(const char *const *papszPaths)
395 : {
396 44 : std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
397 22 : g_searchPathGenerationCounter++;
398 22 : g_aosSearchpaths.Assign(CSLDuplicate(papszPaths), true);
399 22 : OSRInstallSetConfigOptionCallback();
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 24 : char **OSRGetPROJSearchPaths()
413 : {
414 48 : std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
415 24 : if (g_searchPathGenerationCounter > 0 && !g_aosSearchpaths.empty())
416 : {
417 8 : return CSLDuplicate(g_aosSearchpaths.List());
418 : }
419 :
420 16 : const char *pszSep =
421 : #ifdef _WIN32
422 : ";"
423 : #else
424 : ":"
425 : #endif
426 : ;
427 16 : return CSLTokenizeString2(proj_info().searchpath, pszSep, 0);
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 2 : void OSRSetPROJAuxDbPaths(const char *const *papszAux)
442 : {
443 4 : std::lock_guard<std::mutex> oLock(g_oSearchPathMutex);
444 2 : g_auxDbPathsGenerationCounter++;
445 2 : g_aosAuxDbPaths.Assign(CSLDuplicate(papszAux), true);
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 1 : char **OSRGetPROJAuxDbPaths(void)
461 : {
462 2 : 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 2 : return CSLDuplicate(g_aosAuxDbPaths.List());
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 0 : 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 0 : if (enabled)
490 : {
491 0 : 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 0 : 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 0 : 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 184 : void OSRGetPROJVersion(int *pnMajor, int *pnMinor, int *pnPatch)
537 : {
538 184 : auto info = proj_info();
539 184 : if (pnMajor)
540 71 : *pnMajor = info.major;
541 184 : if (pnMinor)
542 64 : *pnMinor = info.minor;
543 184 : if (pnPatch)
544 49 : *pnPatch = info.patch;
545 184 : }
|