LCOV - code coverage report
Current view: top level - ogr - ogr_geocoding.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 617 709 87.0 %
Date: 2025-10-21 22:35:35 Functions: 17 18 94.4 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Client of geocoding service.
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2012-2013, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "ogr_geocoding.h"
      15             : 
      16             : #include <cstddef>
      17             : #include <cstring>
      18             : #include <string>
      19             : 
      20             : #include "cpl_conv.h"
      21             : #include "cpl_error.h"
      22             : #include "cpl_http.h"
      23             : #include "cpl_minixml.h"
      24             : #include "cpl_multiproc.h"
      25             : #include "cpl_string.h"
      26             : #include "ogr_core.h"
      27             : #include "ogr_feature.h"
      28             : #include "ogr_geometry.h"
      29             : #include "memdataset.h"
      30             : #include "ogrsf_frmts.h"
      31             : 
      32             : // Emulation of gettimeofday() for Windows.
      33             : #ifdef _WIN32
      34             : 
      35             : #include <time.h>
      36             : #include <windows.h>
      37             : #include <winsock.h>
      38             : 
      39             : // Recent mingw define struct timezone.
      40             : #if !(defined(__GNUC__) && defined(_TIMEZONE_DEFINED))
      41             : struct timezone
      42             : {
      43             :     int tz_minuteswest;  // Minutes W of Greenwich.
      44             :     int tz_dsttime;      // Type of DST correction.
      45             : };
      46             : #endif
      47             : 
      48             : constexpr int MICROSEC_IN_SEC = 1000000;
      49             : 
      50             : static int OGR_gettimeofday(struct timeval *tv,
      51             :                             struct timezone * /* tzIgnored */)
      52             : {
      53             :     FILETIME ft;
      54             :     GetSystemTimeAsFileTime(&ft);
      55             : 
      56             :     // In 100-nanosecond intervals since January 1, 1601 (UTC).
      57             :     GUIntBig nVal =
      58             :         (static_cast<GUIntBig>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
      59             :     nVal /= 10;  // To microseconds.
      60             :     // There are 11 644 473 600 seconds between 1601 and 1970.
      61             :     nVal -= static_cast<GUIntBig>(116444736) * 100 * MICROSEC_IN_SEC;
      62             :     tv->tv_sec = static_cast<long>(nVal / MICROSEC_IN_SEC);
      63             :     tv->tv_usec = static_cast<long>(nVal % MICROSEC_IN_SEC);
      64             : 
      65             :     return 0;
      66             : }
      67             : 
      68             : #define gettimeofday OGR_gettimeofday
      69             : 
      70             : #else  // !defined WIN32
      71             : #include <sys/time.h>
      72             : #endif  // WIN32
      73             : 
      74             : struct _OGRGeocodingSessionHS
      75             : {
      76             :     char *pszCacheFilename;
      77             :     char *pszGeocodingService;
      78             :     char *pszEmail;
      79             :     char *pszUserName;
      80             :     char *pszKey;
      81             :     char *pszApplication;
      82             :     char *pszLanguage;
      83             :     char *pszQueryTemplate;
      84             :     char *pszReverseQueryTemplate;
      85             :     bool bReadCache;
      86             :     bool bWriteCache;
      87             :     double dfDelayBetweenQueries;
      88             :     GDALDataset *poDS;
      89             : };
      90             : 
      91             : static CPLMutex *hOGRGeocodingMutex = nullptr;
      92             : static double dfLastQueryTimeStampOSMNominatim = 0.0;
      93             : static double dfLastQueryTimeStampMapQuestNominatim = 0.0;
      94             : 
      95             : static const char OSM_NOMINATIM_QUERY[] =
      96             :     "http://nominatim.openstreetmap.org/search?q=%s&format=xml&polygon_text=1";
      97             : static const char MAPQUEST_NOMINATIM_QUERY[] =
      98             :     "http://open.mapquestapi.com/nominatim/v1/search.php?q=%s&format=xml";
      99             : static const char YAHOO_QUERY[] = "http://where.yahooapis.com/geocode?q=%s";
     100             : static const char GEONAMES_QUERY[] =
     101             :     "http://api.geonames.org/search?q=%s&style=LONG";
     102             : static const char BING_QUERY[] =
     103             :     "http://dev.virtualearth.net/REST/v1/Locations?q=%s&o=xml";
     104             : 
     105             : static const char OSM_NOMINATIM_REVERSE_QUERY[] =
     106             :     "http://nominatim.openstreetmap.org/reverse?format=xml&lat={lat}&lon={lon}";
     107             : static const char MAPQUEST_NOMINATIM_REVERSE_QUERY[] =
     108             :     "http://open.mapquestapi.com/nominatim/v1/"
     109             :     "reverse.php?format=xml&lat={lat}&lon={lon}";
     110             : static const char YAHOO_REVERSE_QUERY[] =
     111             :     "http://where.yahooapis.com/geocode?q={lat},{lon}&gflags=R";
     112             : static const char GEONAMES_REVERSE_QUERY[] =
     113             :     "http://api.geonames.org/findNearby?lat={lat}&lng={lon}&style=LONG";
     114             : static const char BING_REVERSE_QUERY[] =
     115             :     "http://dev.virtualearth.net/REST/v1/Locations/"
     116             :     "{lat},{lon}?includeEntityTypes=countryRegion&o=xml";
     117             : 
     118             : static const char CACHE_LAYER_NAME[] = "ogr_geocode_cache";
     119             : static const char DEFAULT_CACHE_SQLITE[] = "ogr_geocode_cache.sqlite";
     120             : static const char DEFAULT_CACHE_CSV[] = "ogr_geocode_cache.csv";
     121             : 
     122             : static const char FIELD_URL[] = "url";
     123             : static const char FIELD_BLOB[] = "blob";
     124             : 
     125             : /************************************************************************/
     126             : /*                       OGRGeocodeGetParameter()                       */
     127             : /************************************************************************/
     128             : 
     129        1628 : static const char *OGRGeocodeGetParameter(char **papszOptions,
     130             :                                           const char *pszKey,
     131             :                                           const char *pszDefaultValue)
     132             : {
     133        1628 :     const char *pszRet = CSLFetchNameValue(papszOptions, pszKey);
     134        1628 :     if (pszRet != nullptr)
     135          36 :         return pszRet;
     136             : 
     137        1592 :     return CPLGetConfigOption(CPLSPrintf("OGR_GEOCODE_%s", pszKey),
     138        1592 :                               pszDefaultValue);
     139             : }
     140             : 
     141             : /************************************************************************/
     142             : /*                      OGRGeocodeHasStringValidFormat()                */
     143             : /************************************************************************/
     144             : 
     145             : // Checks that pszQueryTemplate has one and only one occurrence of %s in it.
     146         112 : static bool OGRGeocodeHasStringValidFormat(const char *pszQueryTemplate)
     147             : {
     148         112 :     const char *pszIter = pszQueryTemplate;
     149         112 :     bool bValidFormat = true;
     150         112 :     bool bFoundPctS = false;
     151        5244 :     while (*pszIter != '\0')
     152             :     {
     153        5132 :         if (*pszIter == '%')
     154             :         {
     155         112 :             if (pszIter[1] == '%')
     156             :             {
     157           0 :                 ++pszIter;
     158             :             }
     159         112 :             else if (pszIter[1] == 's')
     160             :             {
     161         112 :                 if (bFoundPctS)
     162             :                 {
     163           0 :                     bValidFormat = false;
     164           0 :                     break;
     165             :                 }
     166         112 :                 bFoundPctS = true;
     167             :             }
     168             :             else
     169             :             {
     170           0 :                 bValidFormat = false;
     171           0 :                 break;
     172             :             }
     173             :         }
     174        5132 :         ++pszIter;
     175             :     }
     176         112 :     if (!bFoundPctS)
     177           0 :         bValidFormat = false;
     178         112 :     return bValidFormat;
     179             : }
     180             : 
     181             : /************************************************************************/
     182             : /*                       OGRGeocodeCreateSession()                      */
     183             : /************************************************************************/
     184             : 
     185             : /* clang-format off */
     186             : /**
     187             :  * \brief Creates a session handle for geocoding requests.
     188             :  *
     189             :  * Available papszOptions values:
     190             :  * <ul>
     191             :  * <li> "CACHE_FILE" : Defaults to "ogr_geocode_cache.sqlite" (or otherwise
     192             :  *                    "ogr_geocode_cache.csv" if the SQLite driver isn't
     193             :  *                    available). Might be any CSV, SQLite or PostgreSQL
     194             :  *                    datasource.
     195             :  * <li> "READ_CACHE" : "TRUE" (default) or "FALSE"
     196             :  * <li> "WRITE_CACHE" : "TRUE" (default) or "FALSE"
     197             :  * <li> "SERVICE": <a href="http://wiki.openstreetmap.org/wiki/Nominatim">"OSM_NOMINATIM"</a>
     198             :  *      (default), <a href="http://open.mapquestapi.com/nominatim/">"MAPQUEST_NOMINATIM"</a>,
     199             :  *      <a href="http://developer.yahoo.com/geo/placefinder/">"YAHOO"</a>,
     200             :  *      <a href="http://www.geonames.org/export/geonames-search.html">"GEONAMES"</a>,
     201             :  *      <a href="http://msdn.microsoft.com/en-us/library/ff701714.aspx">"BING"</a> or
     202             :  *       other value.
     203             :  *      Note: "YAHOO" is no longer available as a free service.
     204             :  * <li> "EMAIL": used by OSM_NOMINATIM. Optional, but recommended.
     205             :  * <li> "USERNAME": used by GEONAMES. Compulsory in that case.
     206             :  * <li> "KEY": used by BING. Compulsory in that case.
     207             :  * <li> "APPLICATION": used to set the User-Agent MIME header. Defaults
     208             :  *       to GDAL/OGR version string.
     209             :  * <li> "LANGUAGE": used to set the Accept-Language MIME header. Preferred
     210             :  *      language order for showing search results.
     211             :  * <li> "DELAY": minimum delay, in second, between 2 consecutive queries.
     212             :  *       Defaults to 1.0.
     213             :  * <li> "QUERY_TEMPLATE": URL template for GET requests. Must contain one
     214             :  *       and only one occurrence of %%s in it. If not specified, for
     215             :  *       SERVICE=OSM_NOMINATIM, MAPQUEST_NOMINATIM, YAHOO, GEONAMES or BING,
     216             :  *       the URL template is hard-coded.
     217             :  * <li> "REVERSE_QUERY_TEMPLATE": URL template for GET requests for reverse
     218             :  *       geocoding. Must contain one and only one occurrence of {lon} and {lat}
     219             :  *       in it.  If not specified, for SERVICE=OSM_NOMINATIM,
     220             :  *       MAPQUEST_NOMINATIM, YAHOO, GEONAMES or BING, the URL template is
     221             :  *       hard-coded.
     222             :  * </ul>
     223             :  *
     224             :  * All the above options can also be set by defining the configuration option
     225             :  * of the same name, prefixed by OGR_GEOCODE_. For example "OGR_GEOCODE_SERVICE"
     226             :  * for the "SERVICE" option.
     227             :  *
     228             :  * @param papszOptions NULL, or a NULL-terminated list of string options.
     229             :  *
     230             :  * @return a handle that should be freed with OGRGeocodeDestroySession(), or
     231             :  *         NULL in case of failure.
     232             :  *
     233             :  */
     234             : /* clang-format on */
     235             : 
     236         112 : OGRGeocodingSessionH OGRGeocodeCreateSession(char **papszOptions)
     237             : {
     238             :     OGRGeocodingSessionH hSession = static_cast<OGRGeocodingSessionH>(
     239         112 :         CPLCalloc(1, sizeof(_OGRGeocodingSessionHS)));
     240             : 
     241         112 :     const char *pszCacheFilename = OGRGeocodeGetParameter(
     242             :         papszOptions, "CACHE_FILE", DEFAULT_CACHE_SQLITE);
     243         224 :     CPLString osExt = CPLGetExtensionSafe(pszCacheFilename);
     244         168 :     if (!(STARTS_WITH_CI(pszCacheFilename, "PG:") || EQUAL(osExt, "csv") ||
     245          56 :           EQUAL(osExt, "sqlite")))
     246             :     {
     247           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     248             :                  "Only .csv, .sqlite or PG: datasources are handled for now.");
     249           0 :         OGRGeocodeDestroySession(hSession);
     250           0 :         return nullptr;
     251             :     }
     252         112 :     hSession->pszCacheFilename = CPLStrdup(pszCacheFilename);
     253             : 
     254         112 :     hSession->bReadCache =
     255         112 :         CPLTestBool(OGRGeocodeGetParameter(papszOptions, "READ_CACHE", "TRUE"));
     256         112 :     hSession->bWriteCache = CPLTestBool(
     257             :         OGRGeocodeGetParameter(papszOptions, "WRITE_CACHE", "TRUE"));
     258             : 
     259             :     const char *pszGeocodingService =
     260         112 :         OGRGeocodeGetParameter(papszOptions, "SERVICE", "OSM_NOMINATIM");
     261         112 :     hSession->pszGeocodingService = CPLStrdup(pszGeocodingService);
     262             : 
     263             :     const char *pszEmail =
     264         112 :         OGRGeocodeGetParameter(papszOptions, "EMAIL", nullptr);
     265         112 :     hSession->pszEmail = pszEmail ? CPLStrdup(pszEmail) : nullptr;
     266             : 
     267             :     const char *pszUserName =
     268         112 :         OGRGeocodeGetParameter(papszOptions, "USERNAME", nullptr);
     269         112 :     hSession->pszUserName = pszUserName ? CPLStrdup(pszUserName) : nullptr;
     270             : 
     271         112 :     const char *pszKey = OGRGeocodeGetParameter(papszOptions, "KEY", nullptr);
     272         112 :     hSession->pszKey = pszKey ? CPLStrdup(pszKey) : nullptr;
     273             : 
     274         112 :     if (EQUAL(pszGeocodingService, "GEONAMES") && pszUserName == nullptr)
     275             :     {
     276           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     277             :                  "GEONAMES service requires USERNAME to be specified.");
     278           0 :         OGRGeocodeDestroySession(hSession);
     279           0 :         return nullptr;
     280             :     }
     281         112 :     else if (EQUAL(pszGeocodingService, "BING") && pszKey == nullptr)
     282             :     {
     283           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     284             :                  "BING service requires KEY to be specified.");
     285           0 :         OGRGeocodeDestroySession(hSession);
     286           0 :         return nullptr;
     287             :     }
     288             : 
     289         112 :     const char *pszApplication = OGRGeocodeGetParameter(
     290             :         papszOptions, "APPLICATION", GDALVersionInfo(""));
     291         112 :     hSession->pszApplication = CPLStrdup(pszApplication);
     292             : 
     293             :     const char *pszLanguage =
     294         112 :         OGRGeocodeGetParameter(papszOptions, "LANGUAGE", nullptr);
     295         112 :     hSession->pszLanguage = pszLanguage ? CPLStrdup(pszLanguage) : nullptr;
     296             : 
     297             :     const char *pszDelayBetweenQueries =
     298         112 :         OGRGeocodeGetParameter(papszOptions, "DELAY", "1.0");
     299             :     // coverity[tainted_data]
     300         112 :     hSession->dfDelayBetweenQueries = CPLAtofM(pszDelayBetweenQueries);
     301             : 
     302         112 :     const char *pszQueryTemplateDefault = nullptr;
     303         112 :     if (EQUAL(pszGeocodingService, "OSM_NOMINATIM"))
     304          28 :         pszQueryTemplateDefault = OSM_NOMINATIM_QUERY;
     305          84 :     else if (EQUAL(pszGeocodingService, "MAPQUEST_NOMINATIM"))
     306           0 :         pszQueryTemplateDefault = MAPQUEST_NOMINATIM_QUERY;
     307          84 :     else if (EQUAL(pszGeocodingService, "YAHOO"))
     308          28 :         pszQueryTemplateDefault = YAHOO_QUERY;
     309          56 :     else if (EQUAL(pszGeocodingService, "GEONAMES"))
     310          28 :         pszQueryTemplateDefault = GEONAMES_QUERY;
     311          28 :     else if (EQUAL(pszGeocodingService, "BING"))
     312          28 :         pszQueryTemplateDefault = BING_QUERY;
     313         112 :     const char *pszQueryTemplate = OGRGeocodeGetParameter(
     314             :         papszOptions, "QUERY_TEMPLATE", pszQueryTemplateDefault);
     315             : 
     316         224 :     if (pszQueryTemplate != nullptr &&
     317         112 :         !OGRGeocodeHasStringValidFormat(pszQueryTemplate))
     318             :     {
     319           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     320             :                  "QUERY_TEMPLATE value has an invalid format");
     321           0 :         OGRGeocodeDestroySession(hSession);
     322           0 :         return nullptr;
     323             :     }
     324             : 
     325         112 :     hSession->pszQueryTemplate =
     326         112 :         pszQueryTemplate ? CPLStrdup(pszQueryTemplate) : nullptr;
     327             : 
     328         112 :     const char *pszReverseQueryTemplateDefault = nullptr;
     329         112 :     if (EQUAL(pszGeocodingService, "OSM_NOMINATIM"))
     330          28 :         pszReverseQueryTemplateDefault = OSM_NOMINATIM_REVERSE_QUERY;
     331          84 :     else if (EQUAL(pszGeocodingService, "MAPQUEST_NOMINATIM"))
     332           0 :         pszReverseQueryTemplateDefault = MAPQUEST_NOMINATIM_REVERSE_QUERY;
     333          84 :     else if (EQUAL(pszGeocodingService, "YAHOO"))
     334          28 :         pszReverseQueryTemplateDefault = YAHOO_REVERSE_QUERY;
     335          56 :     else if (EQUAL(pszGeocodingService, "GEONAMES"))
     336          28 :         pszReverseQueryTemplateDefault = GEONAMES_REVERSE_QUERY;
     337          28 :     else if (EQUAL(pszGeocodingService, "BING"))
     338          28 :         pszReverseQueryTemplateDefault = BING_REVERSE_QUERY;
     339         112 :     const char *pszReverseQueryTemplate = OGRGeocodeGetParameter(
     340             :         papszOptions, "REVERSE_QUERY_TEMPLATE", pszReverseQueryTemplateDefault);
     341             : 
     342         112 :     if (pszReverseQueryTemplate != nullptr &&
     343         112 :         (strstr(pszReverseQueryTemplate, "{lat}") == nullptr ||
     344         112 :          strstr(pszReverseQueryTemplate, "{lon}") == nullptr))
     345             :     {
     346           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     347             :                  "REVERSE_QUERY_TEMPLATE value has an invalid format");
     348           0 :         OGRGeocodeDestroySession(hSession);
     349           0 :         return nullptr;
     350             :     }
     351             : 
     352         112 :     hSession->pszReverseQueryTemplate = (pszReverseQueryTemplate)
     353         112 :                                             ? CPLStrdup(pszReverseQueryTemplate)
     354             :                                             : nullptr;
     355             : 
     356         112 :     return hSession;
     357             : }
     358             : 
     359             : /************************************************************************/
     360             : /*                       OGRGeocodeDestroySession()                     */
     361             : /************************************************************************/
     362             : 
     363             : /**
     364             :  * \brief Destroys a session handle for geocoding requests.
     365             : 
     366             :  * @param hSession the handle to destroy.
     367             :  *
     368             :  */
     369        3526 : void OGRGeocodeDestroySession(OGRGeocodingSessionH hSession)
     370             : {
     371        3526 :     if (hSession == nullptr)
     372        3414 :         return;
     373         112 :     CPLFree(hSession->pszCacheFilename);
     374         112 :     CPLFree(hSession->pszGeocodingService);
     375         112 :     CPLFree(hSession->pszEmail);
     376         112 :     CPLFree(hSession->pszUserName);
     377         112 :     CPLFree(hSession->pszKey);
     378         112 :     CPLFree(hSession->pszApplication);
     379         112 :     CPLFree(hSession->pszLanguage);
     380         112 :     CPLFree(hSession->pszQueryTemplate);
     381         112 :     CPLFree(hSession->pszReverseQueryTemplate);
     382         112 :     if (hSession->poDS)
     383         112 :         delete hSession->poDS;
     384         112 :     CPLFree(hSession);
     385             : }
     386             : 
     387             : /************************************************************************/
     388             : /*                        OGRGeocodeGetCacheLayer()                     */
     389             : /************************************************************************/
     390             : 
     391         146 : static OGRLayer *OGRGeocodeGetCacheLayer(OGRGeocodingSessionH hSession,
     392             :                                          bool bCreateIfNecessary,
     393             :                                          int *pnIdxBlob)
     394             : {
     395         146 :     GDALDataset *poDS = hSession->poDS;
     396         292 :     CPLString osExt = CPLGetExtensionSafe(hSession->pszCacheFilename);
     397             : 
     398         146 :     if (poDS == nullptr)
     399             :     {
     400         128 :         if (GDALGetDriverCount() == 0)
     401           0 :             GDALAllRegister();
     402             : 
     403             :         const bool bHadValue =
     404         128 :             CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) != nullptr;
     405         128 :         std::string oOldVal(CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", ""));
     406             : 
     407         128 :         CPLSetThreadLocalConfigOption("OGR_SQLITE_SYNCHRONOUS", "OFF");
     408             : 
     409         128 :         poDS = GDALDataset::Open(hSession->pszCacheFilename,
     410             :                                  GDAL_OF_VECTOR | GDAL_OF_UPDATE, nullptr,
     411             :                                  nullptr, nullptr);
     412         128 :         if (poDS == nullptr &&
     413          32 :             EQUAL(hSession->pszCacheFilename, DEFAULT_CACHE_SQLITE))
     414             :         {
     415           0 :             poDS = GDALDataset::Open(DEFAULT_CACHE_CSV,
     416             :                                      GDAL_OF_VECTOR | GDAL_OF_UPDATE, nullptr,
     417             :                                      nullptr, nullptr);
     418           0 :             if (poDS != nullptr)
     419             :             {
     420           0 :                 CPLFree(hSession->pszCacheFilename);
     421           0 :                 hSession->pszCacheFilename = CPLStrdup(DEFAULT_CACHE_CSV);
     422           0 :                 CPLDebug("OGR", "Switch geocode cache file to %s",
     423             :                          hSession->pszCacheFilename);
     424           0 :                 osExt = "csv";
     425             :             }
     426             :         }
     427             : 
     428         128 :         if (bCreateIfNecessary && poDS == nullptr &&
     429          16 :             !STARTS_WITH_CI(hSession->pszCacheFilename, "PG:"))
     430             :         {
     431          16 :             auto poDriver = GetGDALDriverManager()->GetDriverByName(osExt);
     432          16 :             if (poDriver == nullptr &&
     433           0 :                 EQUAL(hSession->pszCacheFilename, DEFAULT_CACHE_SQLITE))
     434             :             {
     435           0 :                 CPLFree(hSession->pszCacheFilename);
     436           0 :                 hSession->pszCacheFilename = CPLStrdup(DEFAULT_CACHE_CSV);
     437           0 :                 CPLDebug("OGR", "Switch geocode cache file to %s",
     438             :                          hSession->pszCacheFilename);
     439           0 :                 osExt = "csv";
     440           0 :                 poDriver = GetGDALDriverManager()->GetDriverByName(osExt);
     441             :             }
     442          16 :             if (poDriver != nullptr)
     443             :             {
     444          16 :                 char **papszOptions = nullptr;
     445          16 :                 if (EQUAL(osExt, "SQLITE"))
     446             :                 {
     447             :                     papszOptions =
     448           8 :                         CSLAddNameValue(papszOptions, "METADATA", "FALSE");
     449             :                 }
     450             : 
     451          16 :                 poDS = poDriver->Create(hSession->pszCacheFilename, 0, 0, 0,
     452             :                                         GDT_Unknown, papszOptions);
     453             : 
     454          16 :                 if (poDS == nullptr &&
     455           0 :                     (EQUAL(osExt, "SQLITE") || EQUAL(osExt, "CSV")))
     456             :                 {
     457           0 :                     CPLFree(hSession->pszCacheFilename);
     458           0 :                     hSession->pszCacheFilename =
     459           0 :                         CPLStrdup(VSIMemGenerateHiddenFilename(CPLSPrintf(
     460             :                             "%s.%s", CACHE_LAYER_NAME, osExt.c_str())));
     461           0 :                     CPLDebug("OGR", "Switch geocode cache file to %s",
     462             :                              hSession->pszCacheFilename);
     463           0 :                     poDS = poDriver->Create(hSession->pszCacheFilename, 0, 0, 0,
     464             :                                             GDT_Unknown, papszOptions);
     465             :                 }
     466             : 
     467          16 :                 CSLDestroy(papszOptions);
     468             :             }
     469             :         }
     470             : 
     471         128 :         CPLSetThreadLocalConfigOption("OGR_SQLITE_SYNCHRONOUS",
     472           0 :                                       bHadValue ? oOldVal.c_str() : nullptr);
     473             : 
     474         128 :         if (poDS == nullptr)
     475          16 :             return nullptr;
     476             : 
     477         112 :         hSession->poDS = poDS;
     478             :     }
     479             : 
     480         130 :     CPLPushErrorHandler(CPLQuietErrorHandler);
     481         130 :     OGRLayer *poLayer = poDS->GetLayerByName(CACHE_LAYER_NAME);
     482         130 :     CPLPopErrorHandler();
     483             : 
     484         130 :     if (bCreateIfNecessary && poLayer == nullptr)
     485             :     {
     486          16 :         char **papszOptions = nullptr;
     487          16 :         if (EQUAL(osExt, "SQLITE"))
     488             :         {
     489             :             papszOptions =
     490           8 :                 CSLAddNameValue(papszOptions, "COMPRESS_COLUMNS", FIELD_BLOB);
     491             :         }
     492             :         poLayer =
     493          16 :             poDS->CreateLayer(CACHE_LAYER_NAME, nullptr, wkbNone, papszOptions);
     494          16 :         CSLDestroy(papszOptions);
     495             : 
     496          16 :         if (poLayer != nullptr)
     497             :         {
     498          16 :             OGRFieldDefn oFieldDefnURL(FIELD_URL, OFTString);
     499          16 :             OGRFieldDefn oFieldDefnBlob(FIELD_BLOB, OFTString);
     500          32 :             if (poLayer->CreateField(&oFieldDefnURL) != OGRERR_NONE ||
     501          16 :                 poLayer->CreateField(&oFieldDefnBlob) != OGRERR_NONE)
     502             :             {
     503           0 :                 return nullptr;
     504             :             }
     505          24 :             if (EQUAL(osExt, "SQLITE") ||
     506           8 :                 STARTS_WITH_CI(hSession->pszCacheFilename, "PG:"))
     507             :             {
     508           8 :                 const char *pszSQL = CPLSPrintf(
     509             :                     "CREATE INDEX idx_%s_%s ON %s(%s)", FIELD_URL,
     510           8 :                     poLayer->GetName(), poLayer->GetName(), FIELD_URL);
     511           8 :                 poDS->ExecuteSQL(pszSQL, nullptr, nullptr);
     512             :             }
     513             :         }
     514             :     }
     515             : 
     516         130 :     int nIdxBlob = -1;
     517         260 :     if (poLayer == nullptr ||
     518         260 :         poLayer->GetLayerDefn()->GetFieldIndex(FIELD_URL) < 0 ||
     519         130 :         (nIdxBlob = poLayer->GetLayerDefn()->GetFieldIndex(FIELD_BLOB)) < 0)
     520             :     {
     521           0 :         return nullptr;
     522             :     }
     523             : 
     524         130 :     if (pnIdxBlob)
     525         130 :         *pnIdxBlob = nIdxBlob;
     526             : 
     527         130 :     return poLayer;
     528             : }
     529             : 
     530             : /************************************************************************/
     531             : /*                        OGRGeocodeGetFromCache()                      */
     532             : /************************************************************************/
     533             : 
     534         112 : static char *OGRGeocodeGetFromCache(OGRGeocodingSessionH hSession,
     535             :                                     const char *pszURL)
     536             : {
     537         224 :     CPLMutexHolderD(&hOGRGeocodingMutex);
     538             : 
     539         112 :     int nIdxBlob = -1;
     540         112 :     OGRLayer *poLayer = OGRGeocodeGetCacheLayer(hSession, FALSE, &nIdxBlob);
     541         112 :     if (poLayer == nullptr)
     542          16 :         return nullptr;
     543             : 
     544          96 :     char *pszSQLEscapedURL = CPLEscapeString(pszURL, -1, CPLES_SQL);
     545          96 :     poLayer->SetAttributeFilter(
     546          96 :         CPLSPrintf("%s='%s'", FIELD_URL, pszSQLEscapedURL));
     547          96 :     CPLFree(pszSQLEscapedURL);
     548             : 
     549          96 :     char *pszRet = nullptr;
     550          96 :     OGRFeature *poFeature = poLayer->GetNextFeature();
     551          96 :     if (poFeature != nullptr)
     552             :     {
     553          78 :         if (poFeature->IsFieldSetAndNotNull(nIdxBlob))
     554          78 :             pszRet = CPLStrdup(poFeature->GetFieldAsString(nIdxBlob));
     555          78 :         OGRFeature::DestroyFeature(poFeature);
     556             :     }
     557             : 
     558          96 :     return pszRet;
     559             : }
     560             : 
     561             : /************************************************************************/
     562             : /*                        OGRGeocodePutIntoCache()                      */
     563             : /************************************************************************/
     564             : 
     565          34 : static bool OGRGeocodePutIntoCache(OGRGeocodingSessionH hSession,
     566             :                                    const char *pszURL, const char *pszContent)
     567             : {
     568          68 :     CPLMutexHolderD(&hOGRGeocodingMutex);
     569             : 
     570          34 :     int nIdxBlob = -1;
     571          34 :     OGRLayer *poLayer = OGRGeocodeGetCacheLayer(hSession, TRUE, &nIdxBlob);
     572          34 :     if (poLayer == nullptr)
     573           0 :         return false;
     574             : 
     575          34 :     OGRFeature *poFeature = new OGRFeature(poLayer->GetLayerDefn());
     576          34 :     poFeature->SetField(FIELD_URL, pszURL);
     577          34 :     poFeature->SetField(FIELD_BLOB, pszContent);
     578          34 :     const bool bRet = poLayer->CreateFeature(poFeature) == OGRERR_NONE;
     579          34 :     delete poFeature;
     580             : 
     581          34 :     return bRet;
     582             : }
     583             : 
     584             : /************************************************************************/
     585             : /*                        OGRGeocodeMakeRawLayer()                      */
     586             : /************************************************************************/
     587             : 
     588           0 : static OGRLayerH OGRGeocodeMakeRawLayer(const char *pszContent)
     589             : {
     590           0 :     OGRMemLayer *poLayer = new OGRMemLayer("result", nullptr, wkbNone);
     591           0 :     const OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
     592           0 :     OGRFieldDefn oFieldDefnRaw("raw", OFTString);
     593           0 :     poLayer->CreateField(&oFieldDefnRaw);
     594           0 :     OGRFeature *poFeature = new OGRFeature(poFDefn);
     595           0 :     poFeature->SetField("raw", pszContent);
     596           0 :     CPL_IGNORE_RET_VAL(poLayer->CreateFeature(poFeature));
     597           0 :     delete poFeature;
     598           0 :     return OGRLayer::ToHandle(poLayer);
     599             : }
     600             : 
     601             : /************************************************************************/
     602             : /*                  OGRGeocodeBuildLayerNominatim()                     */
     603             : /************************************************************************/
     604             : 
     605          40 : static OGRLayerH OGRGeocodeBuildLayerNominatim(CPLXMLNode *psSearchResults,
     606             :                                                const char * /* pszContent */,
     607             :                                                const bool bAddRawFeature)
     608             : {
     609          40 :     OGRMemLayer *poLayer = new OGRMemLayer("place", nullptr, wkbUnknown);
     610          40 :     const OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
     611             : 
     612          40 :     CPLXMLNode *psPlace = psSearchResults->psChild;
     613             :     // First iteration to add fields.
     614         104 :     while (psPlace != nullptr)
     615             :     {
     616          64 :         if (psPlace->eType == CXT_Element &&
     617          50 :             (strcmp(psPlace->pszValue, "place") == 0 ||  // Nominatim.
     618          38 :              strcmp(psPlace->pszValue, "geoname") == 0))
     619             :         {
     620          36 :             CPLXMLNode *psChild = psPlace->psChild;
     621         348 :             while (psChild != nullptr)
     622             :             {
     623         312 :                 const char *pszName = psChild->pszValue;
     624         660 :                 if ((psChild->eType == CXT_Element ||
     625          36 :                      psChild->eType == CXT_Attribute) &&
     626         660 :                     poFDefn->GetFieldIndex(pszName) < 0 &&
     627         312 :                     strcmp(pszName, "geotext") != 0)
     628             :                 {
     629         624 :                     OGRFieldDefn oFieldDefn(pszName, OFTString);
     630         312 :                     if (strcmp(pszName, "place_rank") == 0)
     631             :                     {
     632           0 :                         oFieldDefn.SetType(OFTInteger);
     633             :                     }
     634         312 :                     else if (strcmp(pszName, "lat") == 0)
     635             :                     {
     636          36 :                         oFieldDefn.SetType(OFTReal);
     637             :                     }
     638         276 :                     else if (strcmp(pszName, "lon") == 0 ||  // Nominatim.
     639         264 :                              strcmp(pszName, "lng") == 0)    // Geonames.
     640             :                     {
     641          36 :                         oFieldDefn.SetType(OFTReal);
     642             :                     }
     643         312 :                     poLayer->CreateField(&oFieldDefn);
     644             :                 }
     645         312 :                 psChild = psChild->psNext;
     646             :             }
     647             :         }
     648          64 :         psPlace = psPlace->psNext;
     649             :     }
     650             : 
     651          40 :     if (bAddRawFeature)
     652             :     {
     653          12 :         OGRFieldDefn oFieldDefnRaw("raw", OFTString);
     654           6 :         poLayer->CreateField(&oFieldDefnRaw);
     655             :     }
     656             : 
     657          40 :     psPlace = psSearchResults->psChild;
     658         104 :     while (psPlace != nullptr)
     659             :     {
     660          64 :         if (psPlace->eType == CXT_Element &&
     661          50 :             (strcmp(psPlace->pszValue, "place") == 0 ||   // Nominatim.
     662          38 :              strcmp(psPlace->pszValue, "geoname") == 0))  // Geonames.
     663             :         {
     664          36 :             bool bFoundLat = false;
     665          36 :             bool bFoundLon = false;
     666          36 :             double dfLat = 0.0;
     667          36 :             double dfLon = 0.0;
     668             : 
     669             :             // Iteration to fill the feature.
     670          36 :             OGRFeature *poFeature = new OGRFeature(poFDefn);
     671             : 
     672         348 :             for (CPLXMLNode *psChild = psPlace->psChild; psChild != nullptr;
     673         312 :                  psChild = psChild->psNext)
     674             :             {
     675         312 :                 const char *pszName = psChild->pszValue;
     676         312 :                 const char *pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
     677         312 :                 if (!(psChild->eType == CXT_Element ||
     678          36 :                       psChild->eType == CXT_Attribute))
     679             :                 {
     680             :                     // Do nothing.
     681           0 :                     continue;
     682             :                 }
     683         312 :                 const int nIdx = poFDefn->GetFieldIndex(pszName);
     684         312 :                 if (nIdx >= 0)
     685             :                 {
     686         312 :                     if (pszVal != nullptr)
     687             :                     {
     688         312 :                         poFeature->SetField(nIdx, pszVal);
     689         312 :                         if (strcmp(pszName, "lat") == 0)
     690             :                         {
     691          36 :                             bFoundLat = true;
     692          36 :                             dfLat = CPLAtofM(pszVal);
     693             :                         }
     694         276 :                         else if (strcmp(pszName, "lon") == 0 ||  // Nominatim.
     695         264 :                                  strcmp(pszName, "lng") == 0)    // Geonames.
     696             :                         {
     697          36 :                             bFoundLon = true;
     698          36 :                             dfLon = CPLAtofM(pszVal);
     699             :                         }
     700             :                     }
     701             :                 }
     702           0 :                 else if (strcmp(pszName, "geotext") == 0)
     703             :                 {
     704           0 :                     if (pszVal != nullptr)
     705             :                     {
     706           0 :                         OGRGeometry *poGeometry = nullptr;
     707           0 :                         OGRGeometryFactory::createFromWkt(pszVal, nullptr,
     708             :                                                           &poGeometry);
     709           0 :                         if (poGeometry)
     710           0 :                             poFeature->SetGeometryDirectly(poGeometry);
     711             :                     }
     712             :                 }
     713             :             }
     714             : 
     715          36 :             if (bAddRawFeature)
     716             :             {
     717           6 :                 CPLXMLNode *psOldNext = psPlace->psNext;
     718           6 :                 psPlace->psNext = nullptr;
     719           6 :                 char *pszXML = CPLSerializeXMLTree(psPlace);
     720           6 :                 psPlace->psNext = psOldNext;
     721             : 
     722           6 :                 poFeature->SetField("raw", pszXML);
     723           6 :                 CPLFree(pszXML);
     724             :             }
     725             : 
     726             :             // If we did not find an explicit geometry, build it from
     727             :             // the 'lon' and 'lat' attributes.
     728          36 :             if (poFeature->GetGeometryRef() == nullptr && bFoundLon &&
     729             :                 bFoundLat)
     730          36 :                 poFeature->SetGeometryDirectly(new OGRPoint(dfLon, dfLat));
     731             : 
     732          36 :             CPL_IGNORE_RET_VAL(poLayer->CreateFeature(poFeature));
     733          36 :             delete poFeature;
     734             :         }
     735          64 :         psPlace = psPlace->psNext;
     736             :     }
     737          40 :     return OGRLayer::ToHandle(poLayer);
     738             : }
     739             : 
     740             : /************************************************************************/
     741             : /*               OGRGeocodeReverseBuildLayerNominatim()                 */
     742             : /************************************************************************/
     743             : 
     744          12 : static OGRLayerH OGRGeocodeReverseBuildLayerNominatim(
     745             :     CPLXMLNode *psReverseGeocode, const char *pszContent, bool bAddRawFeature)
     746             : {
     747          12 :     CPLXMLNode *psResult = CPLGetXMLNode(psReverseGeocode, "result");
     748             :     CPLXMLNode *psAddressParts =
     749          12 :         CPLGetXMLNode(psReverseGeocode, "addressparts");
     750          12 :     if (psResult == nullptr || psAddressParts == nullptr)
     751             :     {
     752           0 :         return nullptr;
     753             :     }
     754             : 
     755          12 :     OGRMemLayer *poLayer = new OGRMemLayer("result", nullptr, wkbNone);
     756          12 :     const OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
     757             : 
     758          12 :     bool bFoundLat = false;
     759          12 :     bool bFoundLon = false;
     760          12 :     double dfLat = 0.0;
     761          12 :     double dfLon = 0.0;
     762             : 
     763             :     // First iteration to add fields.
     764          12 :     CPLXMLNode *psChild = psResult->psChild;
     765          96 :     while (psChild != nullptr)
     766             :     {
     767          84 :         const char *pszName = psChild->pszValue;
     768          84 :         const char *pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
     769         252 :         if ((psChild->eType == CXT_Element ||
     770         156 :              psChild->eType == CXT_Attribute) &&
     771          72 :             poFDefn->GetFieldIndex(pszName) < 0)
     772             :         {
     773         144 :             OGRFieldDefn oFieldDefn(pszName, OFTString);
     774          72 :             if (strcmp(pszName, "lat") == 0)
     775             :             {
     776          12 :                 if (pszVal != nullptr)
     777             :                 {
     778          12 :                     bFoundLat = true;
     779          12 :                     dfLat = CPLAtofM(pszVal);
     780             :                 }
     781          12 :                 oFieldDefn.SetType(OFTReal);
     782             :             }
     783          60 :             else if (strcmp(pszName, "lon") == 0)
     784             :             {
     785          12 :                 if (pszVal != nullptr)
     786             :                 {
     787          12 :                     bFoundLon = true;
     788          12 :                     dfLon = CPLAtofM(pszVal);
     789             :                 }
     790          12 :                 oFieldDefn.SetType(OFTReal);
     791             :             }
     792          72 :             poLayer->CreateField(&oFieldDefn);
     793             :         }
     794          84 :         psChild = psChild->psNext;
     795             :     }
     796             : 
     797             :     {
     798          24 :         OGRFieldDefn oFieldDefn("display_name", OFTString);
     799          12 :         poLayer->CreateField(&oFieldDefn);
     800             :     }
     801             : 
     802          12 :     psChild = psAddressParts->psChild;
     803         108 :     while (psChild != nullptr)
     804             :     {
     805          96 :         const char *pszName = psChild->pszValue;
     806         192 :         if ((psChild->eType == CXT_Element ||
     807         192 :              psChild->eType == CXT_Attribute) &&
     808          96 :             poFDefn->GetFieldIndex(pszName) < 0)
     809             :         {
     810         192 :             OGRFieldDefn oFieldDefn(pszName, OFTString);
     811          96 :             poLayer->CreateField(&oFieldDefn);
     812             :         }
     813          96 :         psChild = psChild->psNext;
     814             :     }
     815             : 
     816          12 :     if (bAddRawFeature)
     817             :     {
     818           4 :         OGRFieldDefn oFieldDefnRaw("raw", OFTString);
     819           2 :         poLayer->CreateField(&oFieldDefnRaw);
     820             :     }
     821             : 
     822             :     // Second iteration to fill the feature.
     823          12 :     OGRFeature *poFeature = new OGRFeature(poFDefn);
     824          12 :     psChild = psResult->psChild;
     825          96 :     while (psChild != nullptr)
     826             :     {
     827          84 :         int nIdx = 0;
     828          84 :         const char *pszName = psChild->pszValue;
     829          84 :         const char *pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
     830         252 :         if ((psChild->eType == CXT_Element ||
     831         156 :              psChild->eType == CXT_Attribute) &&
     832          72 :             (nIdx = poFDefn->GetFieldIndex(pszName)) >= 0)
     833             :         {
     834          72 :             if (pszVal != nullptr)
     835          72 :                 poFeature->SetField(nIdx, pszVal);
     836             :         }
     837          84 :         psChild = psChild->psNext;
     838             :     }
     839             : 
     840          12 :     const char *pszVal = CPLGetXMLValue(psResult, nullptr, nullptr);
     841          12 :     if (pszVal != nullptr)
     842          12 :         poFeature->SetField("display_name", pszVal);
     843             : 
     844          12 :     psChild = psAddressParts->psChild;
     845         108 :     while (psChild != nullptr)
     846             :     {
     847          96 :         int nIdx = 0;
     848          96 :         const char *pszName = psChild->pszValue;
     849          96 :         pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
     850         192 :         if ((psChild->eType == CXT_Element ||
     851         192 :              psChild->eType == CXT_Attribute) &&
     852          96 :             (nIdx = poFDefn->GetFieldIndex(pszName)) >= 0)
     853             :         {
     854          96 :             if (pszVal != nullptr)
     855          96 :                 poFeature->SetField(nIdx, pszVal);
     856             :         }
     857          96 :         psChild = psChild->psNext;
     858             :     }
     859             : 
     860          12 :     if (bAddRawFeature)
     861             :     {
     862           2 :         poFeature->SetField("raw", pszContent);
     863             :     }
     864             : 
     865             :     // If we did not find an explicit geometry, build it from
     866             :     // the 'lon' and 'lat' attributes.
     867          12 :     if (poFeature->GetGeometryRef() == nullptr && bFoundLon && bFoundLat)
     868          12 :         poFeature->SetGeometryDirectly(new OGRPoint(dfLon, dfLat));
     869             : 
     870          12 :     CPL_IGNORE_RET_VAL(poLayer->CreateFeature(poFeature));
     871          12 :     delete poFeature;
     872             : 
     873          12 :     return OGRLayer::ToHandle(poLayer);
     874             : }
     875             : 
     876             : /************************************************************************/
     877             : /*                   OGRGeocodeBuildLayerYahoo()                        */
     878             : /************************************************************************/
     879             : 
     880          26 : static OGRLayerH OGRGeocodeBuildLayerYahoo(CPLXMLNode *psResultSet,
     881             :                                            const char * /* pszContent */,
     882             :                                            bool bAddRawFeature)
     883             : {
     884          26 :     OGRMemLayer *poLayer = new OGRMemLayer("place", nullptr, wkbPoint);
     885          26 :     const OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
     886             : 
     887             :     // First iteration to add fields.
     888          26 :     CPLXMLNode *psPlace = psResultSet->psChild;
     889         258 :     while (psPlace != nullptr)
     890             :     {
     891         232 :         if (psPlace->eType == CXT_Element &&
     892         154 :             strcmp(psPlace->pszValue, "Result") == 0)
     893             :         {
     894          24 :             CPLXMLNode *psChild = psPlace->psChild;
     895         720 :             while (psChild != nullptr)
     896             :             {
     897         696 :                 const char *pszName = psChild->pszValue;
     898        1392 :                 if ((psChild->eType == CXT_Element ||
     899        1392 :                      psChild->eType == CXT_Attribute) &&
     900         696 :                     poFDefn->GetFieldIndex(pszName) < 0)
     901             :                 {
     902        1392 :                     OGRFieldDefn oFieldDefn(pszName, OFTString);
     903         696 :                     if (strcmp(pszName, "latitude") == 0)
     904             :                     {
     905          24 :                         oFieldDefn.SetType(OFTReal);
     906             :                     }
     907         672 :                     else if (strcmp(pszName, "longitude") == 0)
     908             :                     {
     909          24 :                         oFieldDefn.SetType(OFTReal);
     910             :                     }
     911         696 :                     poLayer->CreateField(&oFieldDefn);
     912             :                 }
     913         696 :                 psChild = psChild->psNext;
     914             :             }
     915             :         }
     916             : 
     917         232 :         psPlace = psPlace->psNext;
     918             :     }
     919             : 
     920          52 :     OGRFieldDefn oFieldDefnDisplayName("display_name", OFTString);
     921          26 :     poLayer->CreateField(&oFieldDefnDisplayName);
     922             : 
     923          26 :     if (bAddRawFeature)
     924             :     {
     925           8 :         OGRFieldDefn oFieldDefnRaw("raw", OFTString);
     926           4 :         poLayer->CreateField(&oFieldDefnRaw);
     927             :     }
     928             : 
     929          26 :     psPlace = psResultSet->psChild;
     930         258 :     while (psPlace != nullptr)
     931             :     {
     932         232 :         if (psPlace->eType == CXT_Element &&
     933         154 :             strcmp(psPlace->pszValue, "Result") == 0)
     934             :         {
     935          24 :             bool bFoundLat = false;
     936          24 :             bool bFoundLon = false;
     937          24 :             double dfLat = 0.0;
     938          24 :             double dfLon = 0.0;
     939             : 
     940             :             // Second iteration to fill the feature.
     941          24 :             OGRFeature *poFeature = new OGRFeature(poFDefn);
     942         720 :             for (CPLXMLNode *psChild = psPlace->psChild; psChild != nullptr;
     943         696 :                  psChild = psChild->psNext)
     944             :             {
     945         696 :                 const char *pszName = psChild->pszValue;
     946         696 :                 const char *pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
     947         696 :                 if (!(psChild->eType == CXT_Element ||
     948           0 :                       psChild->eType == CXT_Attribute))
     949             :                 {
     950             :                     // Do nothing.
     951           0 :                     continue;
     952             :                 }
     953         696 :                 const int nIdx = poFDefn->GetFieldIndex(pszName);
     954         696 :                 if (nIdx >= 0)
     955             :                 {
     956         696 :                     if (pszVal != nullptr)
     957             :                     {
     958         456 :                         poFeature->SetField(nIdx, pszVal);
     959         456 :                         if (strcmp(pszName, "latitude") == 0)
     960             :                         {
     961          24 :                             bFoundLat = true;
     962          24 :                             dfLat = CPLAtofM(pszVal);
     963             :                         }
     964         432 :                         else if (strcmp(pszName, "longitude") == 0)
     965             :                         {
     966          24 :                             bFoundLon = true;
     967          24 :                             dfLon = CPLAtofM(pszVal);
     968             :                         }
     969             :                     }
     970             :                 }
     971             :             }
     972             : 
     973          48 :             CPLString osDisplayName;
     974          24 :             for (int i = 1;; ++i)
     975             :             {
     976             :                 const int nIdx =
     977         120 :                     poFDefn->GetFieldIndex(CPLSPrintf("line%d", i));
     978         120 :                 if (nIdx < 0)
     979          24 :                     break;
     980          96 :                 if (poFeature->IsFieldSetAndNotNull(nIdx))
     981             :                 {
     982          60 :                     if (!osDisplayName.empty())
     983          36 :                         osDisplayName += ", ";
     984          60 :                     osDisplayName += poFeature->GetFieldAsString(nIdx);
     985             :                 }
     986          96 :             }
     987          24 :             poFeature->SetField("display_name", osDisplayName.c_str());
     988             : 
     989          24 :             if (bAddRawFeature)
     990             :             {
     991           4 :                 CPLXMLNode *psOldNext = psPlace->psNext;
     992           4 :                 psPlace->psNext = nullptr;
     993           4 :                 char *pszXML = CPLSerializeXMLTree(psPlace);
     994           4 :                 psPlace->psNext = psOldNext;
     995             : 
     996           4 :                 poFeature->SetField("raw", pszXML);
     997           4 :                 CPLFree(pszXML);
     998             :             }
     999             : 
    1000             :             // Build geometry from the 'lon' and 'lat' attributes.
    1001          24 :             if (bFoundLon && bFoundLat)
    1002          24 :                 poFeature->SetGeometryDirectly(new OGRPoint(dfLon, dfLat));
    1003             : 
    1004          24 :             CPL_IGNORE_RET_VAL(poLayer->CreateFeature(poFeature));
    1005          24 :             delete poFeature;
    1006             :         }
    1007         232 :         psPlace = psPlace->psNext;
    1008             :     }
    1009          52 :     return OGRLayer::ToHandle(poLayer);
    1010             : }
    1011             : 
    1012             : /************************************************************************/
    1013             : /*                   OGRGeocodeBuildLayerBing()                         */
    1014             : /************************************************************************/
    1015             : 
    1016          26 : static OGRLayerH OGRGeocodeBuildLayerBing(CPLXMLNode *psResponse,
    1017             :                                           const char * /* pszContent */,
    1018             :                                           bool bAddRawFeature)
    1019             : {
    1020             :     CPLXMLNode *psResources =
    1021          26 :         CPLGetXMLNode(psResponse, "ResourceSets.ResourceSet.Resources");
    1022          26 :     if (psResources == nullptr)
    1023           0 :         return nullptr;
    1024             : 
    1025          26 :     OGRMemLayer *poLayer = new OGRMemLayer("place", nullptr, wkbPoint);
    1026          26 :     const OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
    1027             : 
    1028             :     // First iteration to add fields.
    1029          26 :     CPLXMLNode *psPlace = psResources->psChild;
    1030          50 :     while (psPlace != nullptr)
    1031             :     {
    1032          24 :         if (psPlace->eType == CXT_Element &&
    1033          24 :             strcmp(psPlace->pszValue, "Location") == 0)
    1034             :         {
    1035          24 :             CPLXMLNode *psChild = psPlace->psChild;
    1036         144 :             while (psChild != nullptr)
    1037             :             {
    1038         120 :                 const char *pszName = psChild->pszValue;
    1039         240 :                 if ((psChild->eType == CXT_Element ||
    1040           0 :                      psChild->eType == CXT_Attribute) &&
    1041         120 :                     strcmp(pszName, "BoundingBox") != 0 &&
    1042         312 :                     strcmp(pszName, "GeocodePoint") != 0 &&
    1043          72 :                     poFDefn->GetFieldIndex(pszName) < 0)
    1044             :                 {
    1045          72 :                     if (psChild->psChild != nullptr &&
    1046          72 :                         psChild->psChild->eType == CXT_Element)
    1047             :                     {
    1048          48 :                         CPLXMLNode *psSubChild = psChild->psChild;
    1049         216 :                         while (psSubChild != nullptr)
    1050             :                         {
    1051         168 :                             pszName = psSubChild->pszValue;
    1052         336 :                             if ((psSubChild->eType == CXT_Element ||
    1053         336 :                                  psSubChild->eType == CXT_Attribute) &&
    1054         168 :                                 poFDefn->GetFieldIndex(pszName) < 0)
    1055             :                             {
    1056         336 :                                 OGRFieldDefn oFieldDefn(pszName, OFTString);
    1057         168 :                                 if (strcmp(pszName, "Latitude") == 0)
    1058             :                                 {
    1059          24 :                                     oFieldDefn.SetType(OFTReal);
    1060             :                                 }
    1061         144 :                                 else if (strcmp(pszName, "Longitude") == 0)
    1062             :                                 {
    1063          24 :                                     oFieldDefn.SetType(OFTReal);
    1064             :                                 }
    1065         168 :                                 poLayer->CreateField(&oFieldDefn);
    1066             :                             }
    1067         168 :                             psSubChild = psSubChild->psNext;
    1068          48 :                         }
    1069             :                     }
    1070             :                     else
    1071             :                     {
    1072          48 :                         OGRFieldDefn oFieldDefn(pszName, OFTString);
    1073          24 :                         poLayer->CreateField(&oFieldDefn);
    1074             :                     }
    1075             :                 }
    1076         120 :                 psChild = psChild->psNext;
    1077             :             }
    1078             :         }
    1079          24 :         psPlace = psPlace->psNext;
    1080             :     }
    1081             : 
    1082          26 :     if (bAddRawFeature)
    1083             :     {
    1084           8 :         OGRFieldDefn oFieldDefnRaw("raw", OFTString);
    1085           4 :         poLayer->CreateField(&oFieldDefnRaw);
    1086             :     }
    1087             : 
    1088             :     // Iteration to fill the feature.
    1089          26 :     psPlace = psResources->psChild;
    1090          50 :     while (psPlace != nullptr)
    1091             :     {
    1092          24 :         if (psPlace->eType == CXT_Element &&
    1093          24 :             strcmp(psPlace->pszValue, "Location") == 0)
    1094             :         {
    1095          24 :             bool bFoundLat = false;
    1096          24 :             bool bFoundLon = false;
    1097          24 :             double dfLat = 0.0;
    1098          24 :             double dfLon = 0.0;
    1099             : 
    1100          24 :             OGRFeature *poFeature = new OGRFeature(poFDefn);
    1101         144 :             for (CPLXMLNode *psChild = psPlace->psChild; psChild != nullptr;
    1102         120 :                  psChild = psChild->psNext)
    1103             :             {
    1104         120 :                 const char *pszName = psChild->pszValue;
    1105         120 :                 const char *pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
    1106         120 :                 if (!(psChild->eType == CXT_Element ||
    1107           0 :                       psChild->eType == CXT_Attribute))
    1108             :                 {
    1109             :                     // Do nothing.
    1110           0 :                     continue;
    1111             :                 }
    1112         120 :                 const int nIdx = poFDefn->GetFieldIndex(pszName);
    1113         120 :                 if (nIdx >= 0)
    1114             :                 {
    1115          24 :                     if (pszVal != nullptr)
    1116          24 :                         poFeature->SetField(nIdx, pszVal);
    1117             :                 }
    1118          96 :                 else if (strcmp(pszName, "BoundingBox") != 0 &&
    1119          72 :                          strcmp(pszName, "GeocodePoint") != 0 &&
    1120          48 :                          psChild->psChild != nullptr &&
    1121          48 :                          psChild->psChild->eType == CXT_Element)
    1122             :                 {
    1123          48 :                     for (CPLXMLNode *psSubChild = psChild->psChild;
    1124         216 :                          psSubChild != nullptr; psSubChild = psSubChild->psNext)
    1125             :                     {
    1126         168 :                         pszName = psSubChild->pszValue;
    1127         168 :                         pszVal = CPLGetXMLValue(psSubChild, nullptr, nullptr);
    1128         168 :                         if ((psSubChild->eType == CXT_Element ||
    1129           0 :                              psSubChild->eType == CXT_Attribute))
    1130             :                         {
    1131         168 :                             const int nIdx2 = poFDefn->GetFieldIndex(pszName);
    1132         168 :                             if (nIdx2 >= 0)
    1133             :                             {
    1134         168 :                                 if (pszVal != nullptr)
    1135             :                                 {
    1136         168 :                                     poFeature->SetField(nIdx2, pszVal);
    1137         168 :                                     if (strcmp(pszName, "Latitude") == 0)
    1138             :                                     {
    1139          24 :                                         bFoundLat = true;
    1140          24 :                                         dfLat = CPLAtofM(pszVal);
    1141             :                                     }
    1142         144 :                                     else if (strcmp(pszName, "Longitude") == 0)
    1143             :                                     {
    1144          24 :                                         bFoundLon = true;
    1145          24 :                                         dfLon = CPLAtofM(pszVal);
    1146             :                                     }
    1147             :                                 }
    1148             :                             }
    1149             :                         }
    1150             :                     }
    1151             :                 }
    1152             :             }
    1153             : 
    1154          24 :             if (bAddRawFeature)
    1155             :             {
    1156           4 :                 CPLXMLNode *psOldNext = psPlace->psNext;
    1157           4 :                 psPlace->psNext = nullptr;
    1158           4 :                 char *pszXML = CPLSerializeXMLTree(psPlace);
    1159           4 :                 psPlace->psNext = psOldNext;
    1160             : 
    1161           4 :                 poFeature->SetField("raw", pszXML);
    1162           4 :                 CPLFree(pszXML);
    1163             :             }
    1164             : 
    1165             :             // Build geometry from the 'lon' and 'lat' attributes.
    1166          24 :             if (bFoundLon && bFoundLat)
    1167          24 :                 poFeature->SetGeometryDirectly(new OGRPoint(dfLon, dfLat));
    1168             : 
    1169          24 :             CPL_IGNORE_RET_VAL(poLayer->CreateFeature(poFeature));
    1170          24 :             delete poFeature;
    1171             :         }
    1172          24 :         psPlace = psPlace->psNext;
    1173             :     }
    1174             : 
    1175          26 :     return OGRLayer::ToHandle(poLayer);
    1176             : }
    1177             : 
    1178             : /************************************************************************/
    1179             : /*                         OGRGeocodeBuildLayer()                       */
    1180             : /************************************************************************/
    1181             : 
    1182         112 : static OGRLayerH OGRGeocodeBuildLayer(const char *pszContent,
    1183             :                                       bool bAddRawFeature)
    1184             : {
    1185         112 :     OGRLayerH hLayer = nullptr;
    1186         112 :     CPLXMLNode *psRoot = CPLParseXMLString(pszContent);
    1187         112 :     if (psRoot != nullptr)
    1188             :     {
    1189         104 :         CPLXMLNode *psSearchResults = nullptr;
    1190         104 :         CPLXMLNode *psReverseGeocode = nullptr;
    1191         104 :         CPLXMLNode *psGeonames = nullptr;
    1192         104 :         CPLXMLNode *psResultSet = nullptr;
    1193         104 :         CPLXMLNode *psResponse = nullptr;
    1194         104 :         if ((psSearchResults = CPLSearchXMLNode(psRoot, "=searchresults")) !=
    1195             :             nullptr)
    1196          14 :             hLayer = OGRGeocodeBuildLayerNominatim(psSearchResults, pszContent,
    1197             :                                                    bAddRawFeature);
    1198          90 :         else if ((psReverseGeocode =
    1199          90 :                       CPLSearchXMLNode(psRoot, "=reversegeocode")) != nullptr)
    1200          12 :             hLayer = OGRGeocodeReverseBuildLayerNominatim(
    1201             :                 psReverseGeocode, pszContent, bAddRawFeature);
    1202          78 :         else if ((psGeonames = CPLSearchXMLNode(psRoot, "=geonames")) !=
    1203             :                  nullptr)
    1204          26 :             hLayer = OGRGeocodeBuildLayerNominatim(psGeonames, pszContent,
    1205             :                                                    bAddRawFeature);
    1206          52 :         else if ((psResultSet = CPLSearchXMLNode(psRoot, "=ResultSet")) !=
    1207             :                  nullptr)
    1208          26 :             hLayer = OGRGeocodeBuildLayerYahoo(psResultSet, pszContent,
    1209             :                                                bAddRawFeature);
    1210          26 :         else if ((psResponse = CPLSearchXMLNode(psRoot, "=Response")) !=
    1211             :                  nullptr)
    1212          26 :             hLayer = OGRGeocodeBuildLayerBing(psResponse, pszContent,
    1213             :                                               bAddRawFeature);
    1214         104 :         CPLDestroyXMLNode(psRoot);
    1215             :     }
    1216         112 :     if (hLayer == nullptr && bAddRawFeature)
    1217           0 :         hLayer = OGRGeocodeMakeRawLayer(pszContent);
    1218         112 :     return hLayer;
    1219             : }
    1220             : 
    1221             : /************************************************************************/
    1222             : /*                         OGRGeocodeCommon()                           */
    1223             : /************************************************************************/
    1224             : 
    1225         112 : static OGRLayerH OGRGeocodeCommon(OGRGeocodingSessionH hSession,
    1226             :                                   const std::string &osURLIn,
    1227             :                                   char **papszOptions)
    1228             : {
    1229         224 :     std::string osURL(osURLIn);
    1230             : 
    1231             :     // Only documented to work with OSM Nominatim.
    1232         112 :     if (hSession->pszLanguage != nullptr)
    1233             :     {
    1234           0 :         osURL += "&accept-language=";
    1235           0 :         osURL += hSession->pszLanguage;
    1236             :     }
    1237             : 
    1238             :     const char *pszExtraQueryParameters =
    1239         112 :         OGRGeocodeGetParameter(papszOptions, "EXTRA_QUERY_PARAMETERS", nullptr);
    1240         112 :     if (pszExtraQueryParameters != nullptr)
    1241             :     {
    1242           0 :         osURL += "&";
    1243           0 :         osURL += pszExtraQueryParameters;
    1244             :     }
    1245             : 
    1246         112 :     CPLString osURLWithEmail = osURL;
    1247         112 :     if (EQUAL(hSession->pszGeocodingService, "OSM_NOMINATIM") &&
    1248          28 :         hSession->pszEmail != nullptr)
    1249             :     {
    1250             :         char *const pszEscapedEmail =
    1251          28 :             CPLEscapeString(hSession->pszEmail, -1, CPLES_URL);
    1252          28 :         osURLWithEmail = osURL + "&email=" + pszEscapedEmail;
    1253          28 :         CPLFree(pszEscapedEmail);
    1254             :     }
    1255          84 :     else if (EQUAL(hSession->pszGeocodingService, "GEONAMES") &&
    1256          28 :              hSession->pszUserName != nullptr)
    1257             :     {
    1258             :         char *const pszEscaped =
    1259          28 :             CPLEscapeString(hSession->pszUserName, -1, CPLES_URL);
    1260          28 :         osURLWithEmail = osURL + "&username=" + pszEscaped;
    1261          28 :         CPLFree(pszEscaped);
    1262             :     }
    1263          56 :     else if (EQUAL(hSession->pszGeocodingService, "BING") &&
    1264          28 :              hSession->pszKey != nullptr)
    1265             :     {
    1266             :         char *const pszEscaped =
    1267          28 :             CPLEscapeString(hSession->pszKey, -1, CPLES_URL);
    1268          28 :         osURLWithEmail = osURL + "&key=" + pszEscaped;
    1269          28 :         CPLFree(pszEscaped);
    1270             :     }
    1271             : 
    1272             :     const bool bAddRawFeature =
    1273         112 :         CPLTestBool(OGRGeocodeGetParameter(papszOptions, "RAW_FEATURE", "NO"));
    1274             : 
    1275         112 :     OGRLayerH hLayer = nullptr;
    1276             : 
    1277         112 :     char *pszCachedResult = nullptr;
    1278         112 :     if (hSession->bReadCache)
    1279         112 :         pszCachedResult = OGRGeocodeGetFromCache(hSession, osURL.c_str());
    1280         112 :     if (pszCachedResult == nullptr)
    1281             :     {
    1282          34 :         double *pdfLastQueryTime = nullptr;
    1283          34 :         if (EQUAL(hSession->pszGeocodingService, "OSM_NOMINATIM"))
    1284          10 :             pdfLastQueryTime = &dfLastQueryTimeStampOSMNominatim;
    1285          24 :         else if (EQUAL(hSession->pszGeocodingService, "MAPQUEST_NOMINATIM"))
    1286           0 :             pdfLastQueryTime = &dfLastQueryTimeStampMapQuestNominatim;
    1287             : 
    1288          68 :         CPLString osHeaders = "User-Agent: ";
    1289          34 :         osHeaders += hSession->pszApplication;
    1290          34 :         if (hSession->pszLanguage != nullptr)
    1291             :         {
    1292           0 :             osHeaders += "\r\nAccept-Language: ";
    1293           0 :             osHeaders += hSession->pszLanguage;
    1294             :         }
    1295             :         char **papszHTTPOptions =
    1296          34 :             CSLAddNameValue(nullptr, "HEADERS", osHeaders.c_str());
    1297             : 
    1298          34 :         CPLHTTPResult *psResult = nullptr;
    1299          34 :         if (pdfLastQueryTime != nullptr)
    1300             :         {
    1301          10 :             CPLMutexHolderD(&hOGRGeocodingMutex);
    1302             :             struct timeval tv;
    1303             : 
    1304          10 :             gettimeofday(&tv, nullptr);
    1305          10 :             double dfCurrentTime = tv.tv_sec + tv.tv_usec / 1e6;
    1306          10 :             if (dfCurrentTime <
    1307          10 :                 *pdfLastQueryTime + hSession->dfDelayBetweenQueries)
    1308             :             {
    1309           8 :                 CPLSleep(*pdfLastQueryTime + hSession->dfDelayBetweenQueries -
    1310             :                          dfCurrentTime);
    1311             :             }
    1312             : 
    1313          10 :             psResult = CPLHTTPFetch(osURLWithEmail, papszHTTPOptions);
    1314             : 
    1315          10 :             gettimeofday(&tv, nullptr);
    1316          10 :             *pdfLastQueryTime = tv.tv_sec + tv.tv_usec / 1e6;
    1317             :         }
    1318             :         else
    1319             :         {
    1320          24 :             psResult = CPLHTTPFetch(osURLWithEmail, papszHTTPOptions);
    1321             :         }
    1322             : 
    1323          34 :         CSLDestroy(papszHTTPOptions);
    1324          34 :         papszHTTPOptions = nullptr;
    1325             : 
    1326          34 :         if (psResult == nullptr)
    1327             :         {
    1328           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Query '%s' failed",
    1329             :                      osURLWithEmail.c_str());
    1330             :         }
    1331             :         else
    1332             :         {
    1333          34 :             const char *pszResult =
    1334             :                 reinterpret_cast<const char *>(psResult->pabyData);
    1335          34 :             if (pszResult != nullptr)
    1336             :             {
    1337          34 :                 if (hSession->bWriteCache)
    1338             :                 {
    1339             :                     // coverity[tainted_data]
    1340          34 :                     OGRGeocodePutIntoCache(hSession, osURL.c_str(), pszResult);
    1341             :                 }
    1342          34 :                 hLayer = OGRGeocodeBuildLayer(pszResult, bAddRawFeature);
    1343             :             }
    1344          34 :             CPLHTTPDestroyResult(psResult);
    1345             :         }
    1346             :     }
    1347             :     else
    1348             :     {
    1349          78 :         hLayer = OGRGeocodeBuildLayer(pszCachedResult, bAddRawFeature);
    1350          78 :         CPLFree(pszCachedResult);
    1351             :     }
    1352             : 
    1353         224 :     return hLayer;
    1354             : }
    1355             : 
    1356             : /************************************************************************/
    1357             : /*                              OGRGeocode()                            */
    1358             : /************************************************************************/
    1359             : 
    1360             : /* clang-format off */
    1361             : /**
    1362             :  * \brief Runs a geocoding request.
    1363             :  *
    1364             :  * If the result is not found in cache, a GET request will be sent to resolve
    1365             :  * the query.
    1366             :  *
    1367             :  * Note: most online services have Term of Uses. You are kindly requested
    1368             :  * to read and follow them. For the OpenStreetMap Nominatim service, this
    1369             :  * implementation will make sure that no more than one request is sent by
    1370             :  * second, but there might be other restrictions that you must follow by other
    1371             :  * means.
    1372             :  *
    1373             :  * In case of success, the return of this function is a OGR layer that contain
    1374             :  * zero, one or several features matching the query. Note that the geometry of
    1375             :  * the features is not necessarily a point.  The returned layer must be freed
    1376             :  * with OGRGeocodeFreeResult().
    1377             :  *
    1378             :  * Note: this function is also available as the SQL
    1379             :  * <a href="ogr_sql_sqlite.html#ogr_sql_sqlite_ogr_geocode_function">ogr_geocode()</a>
    1380             :  * function of the SQL SQLite dialect.
    1381             :  *
    1382             :  * The list of recognized options is :
    1383             :  * <ul>
    1384             :  * <li>ADDRESSDETAILS=0 or 1: Include a breakdown of the address into elements
    1385             :  *     Defaults to 1. (Known to work with OSM and MapQuest Nominatim)
    1386             :  * <li>COUNTRYCODES=code1,code2,...codeN: Limit search results to a specific
    1387             :  *     country (or a list of countries). The codes must fellow ISO 3166-1, i.e.
    1388             :  *     gb for United Kingdom, de for Germany, etc.. (Known to work with OSM and
    1389             :  *     MapQuest Nominatim)
    1390             :  * <li>LIMIT=number: the number of records to return. Unlimited if not
    1391             :  *     specified.  (Known to work with OSM and MapQuest Nominatim)
    1392             :  * <li>RAW_FEATURE=YES: to specify that a 'raw' field must be added to the
    1393             :  *     returned feature with the raw XML content.
    1394             :  * <li>EXTRA_QUERY_PARAMETERS=params: additional parameters for the GET
    1395             :  *     request.
    1396             :  * </ul>
    1397             :  *
    1398             :  * @param hSession the geocoding session handle.
    1399             :  * @param pszQuery the string to geocode.
    1400             :  * @param papszStructuredQuery unused for now. Must be NULL.
    1401             :  * @param papszOptions a list of options or NULL.
    1402             :  *
    1403             :  * @return a OGR layer with the result(s), or NULL in case of error.
    1404             :  *         The returned layer must be freed with OGRGeocodeFreeResult().
    1405             :  *
    1406             :  */
    1407             : /* clang-format on */
    1408             : 
    1409          64 : OGRLayerH OGRGeocode(OGRGeocodingSessionH hSession, const char *pszQuery,
    1410             :                      char **papszStructuredQuery, char **papszOptions)
    1411             : {
    1412          64 :     VALIDATE_POINTER1(hSession, "OGRGeocode", nullptr);
    1413          64 :     if ((pszQuery == nullptr && papszStructuredQuery == nullptr) ||
    1414          64 :         (pszQuery != nullptr && papszStructuredQuery != nullptr))
    1415             :     {
    1416           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1417             :                  "Only one of pszQuery or papszStructuredQuery must be set.");
    1418           0 :         return nullptr;
    1419             :     }
    1420             : 
    1421          64 :     if (papszStructuredQuery != nullptr)
    1422             :     {
    1423           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1424             :                  "papszStructuredQuery not yet supported.");
    1425           0 :         return nullptr;
    1426             :     }
    1427             : 
    1428          64 :     if (hSession->pszQueryTemplate == nullptr)
    1429             :     {
    1430           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1431             :                  "QUERY_TEMPLATE parameter not defined");
    1432           0 :         return nullptr;
    1433             :     }
    1434             : 
    1435          64 :     constexpr const char *PCT_S = "%s";
    1436          64 :     const char *pszPctS = strstr(hSession->pszQueryTemplate, PCT_S);
    1437          64 :     if (!pszPctS)
    1438             :     {
    1439             :         // should not happen given OGRGeocodeHasStringValidFormat()
    1440           0 :         return nullptr;
    1441             :     }
    1442             : 
    1443          64 :     char *pszEscapedQuery = CPLEscapeString(pszQuery, -1, CPLES_URL);
    1444             : 
    1445         128 :     std::string osURL;
    1446          64 :     osURL.assign(hSession->pszQueryTemplate,
    1447          64 :                  pszPctS - hSession->pszQueryTemplate);
    1448          64 :     osURL += pszEscapedQuery;
    1449          64 :     osURL += (pszPctS + strlen(PCT_S));
    1450          64 :     CPLFree(pszEscapedQuery);
    1451             : 
    1452          64 :     if (EQUAL(hSession->pszGeocodingService, "OSM_NOMINATIM") ||
    1453          48 :         EQUAL(hSession->pszGeocodingService, "MAPQUEST_NOMINATIM"))
    1454             :     {
    1455             :         const char *pszAddressDetails =
    1456          16 :             OGRGeocodeGetParameter(papszOptions, "ADDRESSDETAILS", "1");
    1457          16 :         osURL += "&addressdetails=";
    1458          16 :         osURL += pszAddressDetails;
    1459             : 
    1460             :         const char *pszCountryCodes =
    1461          16 :             OGRGeocodeGetParameter(papszOptions, "COUNTRYCODES", nullptr);
    1462          16 :         if (pszCountryCodes != nullptr)
    1463             :         {
    1464           0 :             osURL += "&countrycodes=";
    1465           0 :             osURL += pszCountryCodes;
    1466             :         }
    1467             : 
    1468             :         const char *pszLimit =
    1469          16 :             OGRGeocodeGetParameter(papszOptions, "LIMIT", nullptr);
    1470          16 :         if (pszLimit != nullptr && *pszLimit != '\0')
    1471             :         {
    1472          16 :             osURL += "&limit=";
    1473          16 :             osURL += pszLimit;
    1474             :         }
    1475             :     }
    1476             : 
    1477             :     // coverity[tainted_data]
    1478          64 :     return OGRGeocodeCommon(hSession, osURL, papszOptions);
    1479             : }
    1480             : 
    1481             : /************************************************************************/
    1482             : /*                      OGRGeocodeReverseSubstitute()                   */
    1483             : /************************************************************************/
    1484             : 
    1485          48 : static CPLString OGRGeocodeReverseSubstitute(CPLString osURL, double dfLon,
    1486             :                                              double dfLat)
    1487             : {
    1488          48 :     size_t iPos = osURL.find("{lon}");
    1489          48 :     if (iPos != std::string::npos)
    1490             :     {
    1491          96 :         const CPLString osEnd(osURL.substr(iPos + 5));
    1492          48 :         osURL = osURL.substr(0, iPos);
    1493          48 :         osURL += CPLSPrintf("%.8f", dfLon);
    1494          48 :         osURL += osEnd;
    1495             :     }
    1496             : 
    1497          48 :     iPos = osURL.find("{lat}");
    1498          48 :     if (iPos != std::string::npos)
    1499             :     {
    1500          96 :         const CPLString osEnd(osURL.substr(iPos + 5));
    1501          48 :         osURL = osURL.substr(0, iPos);
    1502          48 :         osURL += CPLSPrintf("%.8f", dfLat);
    1503          48 :         osURL += osEnd;
    1504             :     }
    1505             : 
    1506          48 :     return osURL;
    1507             : }
    1508             : 
    1509             : /************************************************************************/
    1510             : /*                         OGRGeocodeReverse()                          */
    1511             : /************************************************************************/
    1512             : 
    1513             : /* clang-format off */
    1514             : /**
    1515             :  * \brief Runs a reverse geocoding request.
    1516             :  *
    1517             :  * If the result is not found in cache, a GET request will be sent to resolve
    1518             :  * the query.
    1519             :  *
    1520             :  * Note: most online services have Term of Uses. You are kindly requested
    1521             :  * to read and follow them. For the OpenStreetMap Nominatim service, this
    1522             :  * implementation will make sure that no more than one request is sent by
    1523             :  * second, but there might be other restrictions that you must follow by other
    1524             :  * means.
    1525             :  *
    1526             :  * In case of success, the return of this function is a OGR layer that contain
    1527             :  * zero, one or several features matching the query. The returned layer must be
    1528             :  * freed with OGRGeocodeFreeResult().
    1529             :  *
    1530             :  * Note: this function is also available as the SQL
    1531             :  * <a href="ogr_sql_sqlite.html#ogr_sql_sqlite_ogr_geocode_function">ogr_geocode_reverse()</a>
    1532             :  * function of the SQL SQLite dialect.
    1533             :  *
    1534             :  * The list of recognized options is :
    1535             :  * <ul>
    1536             :  * <li>ZOOM=a_level: to query a specific zoom level. Only understood by the OSM
    1537             :  *     Nominatim service.
    1538             :  * <li>RAW_FEATURE=YES: to specify that a 'raw' field must be added to the
    1539             :  *     returned feature with the raw XML content.
    1540             :  * <li>EXTRA_QUERY_PARAMETERS=params: additional parameters for the GET request
    1541             :  *     for reverse geocoding.
    1542             :  * </ul>
    1543             :  *
    1544             :  * @param hSession the geocoding session handle.
    1545             :  * @param dfLon the longitude.
    1546             :  * @param dfLat the latitude.
    1547             :  * @param papszOptions a list of options or NULL.
    1548             :  *
    1549             :  * @return a OGR layer with the result(s), or NULL in case of error.
    1550             :  *         The returned layer must be freed with OGRGeocodeFreeResult().
    1551             :  *
    1552             :  */
    1553             : /* clang-format on */
    1554             : 
    1555          48 : OGRLayerH OGRGeocodeReverse(OGRGeocodingSessionH hSession, double dfLon,
    1556             :                             double dfLat, char **papszOptions)
    1557             : {
    1558          48 :     VALIDATE_POINTER1(hSession, "OGRGeocodeReverse", nullptr);
    1559             : 
    1560          48 :     if (hSession->pszReverseQueryTemplate == nullptr)
    1561             :     {
    1562           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1563             :                  "REVERSE_QUERY_TEMPLATE parameter not defined");
    1564           0 :         return nullptr;
    1565             :     }
    1566             : 
    1567          96 :     CPLString osURL = hSession->pszReverseQueryTemplate;
    1568          48 :     osURL = OGRGeocodeReverseSubstitute(osURL, dfLon, dfLat);
    1569             : 
    1570          48 :     if (EQUAL(hSession->pszGeocodingService, "OSM_NOMINATIM"))
    1571             :     {
    1572             :         const char *pszZoomLevel =
    1573          12 :             OGRGeocodeGetParameter(papszOptions, "ZOOM", nullptr);
    1574          12 :         if (pszZoomLevel != nullptr)
    1575             :         {
    1576           4 :             osURL = osURL + "&zoom=" + pszZoomLevel;
    1577             :         }
    1578             :     }
    1579             : 
    1580             :     // coverity[tainted_data]
    1581          48 :     return OGRGeocodeCommon(hSession, osURL, papszOptions);
    1582             : }
    1583             : 
    1584             : /************************************************************************/
    1585             : /*                        OGRGeocodeFreeResult()                        */
    1586             : /************************************************************************/
    1587             : 
    1588             : /**
    1589             :  * \brief Destroys the result of a geocoding request.
    1590             :  *
    1591             :  * @param hLayer the layer returned by OGRGeocode() or OGRGeocodeReverse()
    1592             :  *               to destroy.
    1593             :  *
    1594             :  */
    1595         104 : void OGRGeocodeFreeResult(OGRLayerH hLayer)
    1596             : {
    1597         104 :     delete OGRLayer::FromHandle(hLayer);
    1598         104 : }

Generated by: LCOV version 1.14