LCOV - code coverage report
Current view: top level - ogr - ogr_geocoding.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 613 704 87.1 %
Date: 2026-01-12 05:48:10 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        3599 : void OGRGeocodeDestroySession(OGRGeocodingSessionH hSession)
     370             : {
     371        3599 :     if (hSession == nullptr)
     372        3487 :         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 :     auto poFeature = std::make_unique<OGRFeature>(poFDefn);
     595           0 :     poFeature->SetField("raw", pszContent);
     596           0 :     CPL_IGNORE_RET_VAL(poLayer->CreateFeature(std::move(poFeature)));
     597           0 :     return OGRLayer::ToHandle(poLayer);
     598             : }
     599             : 
     600             : /************************************************************************/
     601             : /*                  OGRGeocodeBuildLayerNominatim()                     */
     602             : /************************************************************************/
     603             : 
     604          40 : static OGRLayerH OGRGeocodeBuildLayerNominatim(CPLXMLNode *psSearchResults,
     605             :                                                const char * /* pszContent */,
     606             :                                                const bool bAddRawFeature)
     607             : {
     608          40 :     OGRMemLayer *poLayer = new OGRMemLayer("place", nullptr, wkbUnknown);
     609          40 :     const OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
     610             : 
     611          40 :     CPLXMLNode *psPlace = psSearchResults->psChild;
     612             :     // First iteration to add fields.
     613         104 :     while (psPlace != nullptr)
     614             :     {
     615          64 :         if (psPlace->eType == CXT_Element &&
     616          50 :             (strcmp(psPlace->pszValue, "place") == 0 ||  // Nominatim.
     617          38 :              strcmp(psPlace->pszValue, "geoname") == 0))
     618             :         {
     619          36 :             CPLXMLNode *psChild = psPlace->psChild;
     620         348 :             while (psChild != nullptr)
     621             :             {
     622         312 :                 const char *pszName = psChild->pszValue;
     623         660 :                 if ((psChild->eType == CXT_Element ||
     624          36 :                      psChild->eType == CXT_Attribute) &&
     625         660 :                     poFDefn->GetFieldIndex(pszName) < 0 &&
     626         312 :                     strcmp(pszName, "geotext") != 0)
     627             :                 {
     628         624 :                     OGRFieldDefn oFieldDefn(pszName, OFTString);
     629         312 :                     if (strcmp(pszName, "place_rank") == 0)
     630             :                     {
     631           0 :                         oFieldDefn.SetType(OFTInteger);
     632             :                     }
     633         312 :                     else if (strcmp(pszName, "lat") == 0)
     634             :                     {
     635          36 :                         oFieldDefn.SetType(OFTReal);
     636             :                     }
     637         276 :                     else if (strcmp(pszName, "lon") == 0 ||  // Nominatim.
     638         264 :                              strcmp(pszName, "lng") == 0)    // Geonames.
     639             :                     {
     640          36 :                         oFieldDefn.SetType(OFTReal);
     641             :                     }
     642         312 :                     poLayer->CreateField(&oFieldDefn);
     643             :                 }
     644         312 :                 psChild = psChild->psNext;
     645             :             }
     646             :         }
     647          64 :         psPlace = psPlace->psNext;
     648             :     }
     649             : 
     650          40 :     if (bAddRawFeature)
     651             :     {
     652          12 :         OGRFieldDefn oFieldDefnRaw("raw", OFTString);
     653           6 :         poLayer->CreateField(&oFieldDefnRaw);
     654             :     }
     655             : 
     656          40 :     psPlace = psSearchResults->psChild;
     657         104 :     while (psPlace != nullptr)
     658             :     {
     659          64 :         if (psPlace->eType == CXT_Element &&
     660          50 :             (strcmp(psPlace->pszValue, "place") == 0 ||   // Nominatim.
     661          38 :              strcmp(psPlace->pszValue, "geoname") == 0))  // Geonames.
     662             :         {
     663          36 :             bool bFoundLat = false;
     664          36 :             bool bFoundLon = false;
     665          36 :             double dfLat = 0.0;
     666          36 :             double dfLon = 0.0;
     667             : 
     668             :             // Iteration to fill the feature.
     669          36 :             auto poFeature = std::make_unique<OGRFeature>(poFDefn);
     670             : 
     671         348 :             for (CPLXMLNode *psChild = psPlace->psChild; psChild != nullptr;
     672         312 :                  psChild = psChild->psNext)
     673             :             {
     674         312 :                 const char *pszName = psChild->pszValue;
     675         312 :                 const char *pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
     676         312 :                 if (!(psChild->eType == CXT_Element ||
     677          36 :                       psChild->eType == CXT_Attribute))
     678             :                 {
     679             :                     // Do nothing.
     680           0 :                     continue;
     681             :                 }
     682         312 :                 const int nIdx = poFDefn->GetFieldIndex(pszName);
     683         312 :                 if (nIdx >= 0)
     684             :                 {
     685         312 :                     if (pszVal != nullptr)
     686             :                     {
     687         312 :                         poFeature->SetField(nIdx, pszVal);
     688         312 :                         if (strcmp(pszName, "lat") == 0)
     689             :                         {
     690          36 :                             bFoundLat = true;
     691          36 :                             dfLat = CPLAtofM(pszVal);
     692             :                         }
     693         276 :                         else if (strcmp(pszName, "lon") == 0 ||  // Nominatim.
     694         264 :                                  strcmp(pszName, "lng") == 0)    // Geonames.
     695             :                         {
     696          36 :                             bFoundLon = true;
     697          36 :                             dfLon = CPLAtofM(pszVal);
     698             :                         }
     699             :                     }
     700             :                 }
     701           0 :                 else if (strcmp(pszName, "geotext") == 0)
     702             :                 {
     703           0 :                     if (pszVal != nullptr)
     704             :                     {
     705           0 :                         OGRGeometry *poGeometry = nullptr;
     706           0 :                         OGRGeometryFactory::createFromWkt(pszVal, nullptr,
     707             :                                                           &poGeometry);
     708           0 :                         if (poGeometry)
     709           0 :                             poFeature->SetGeometryDirectly(poGeometry);
     710             :                     }
     711             :                 }
     712             :             }
     713             : 
     714          36 :             if (bAddRawFeature)
     715             :             {
     716           6 :                 CPLXMLNode *psOldNext = psPlace->psNext;
     717           6 :                 psPlace->psNext = nullptr;
     718           6 :                 char *pszXML = CPLSerializeXMLTree(psPlace);
     719           6 :                 psPlace->psNext = psOldNext;
     720             : 
     721           6 :                 poFeature->SetField("raw", pszXML);
     722           6 :                 CPLFree(pszXML);
     723             :             }
     724             : 
     725             :             // If we did not find an explicit geometry, build it from
     726             :             // the 'lon' and 'lat' attributes.
     727          36 :             if (poFeature->GetGeometryRef() == nullptr && bFoundLon &&
     728             :                 bFoundLat)
     729          36 :                 poFeature->SetGeometryDirectly(new OGRPoint(dfLon, dfLat));
     730             : 
     731          36 :             CPL_IGNORE_RET_VAL(poLayer->CreateFeature(std::move(poFeature)));
     732             :         }
     733          64 :         psPlace = psPlace->psNext;
     734             :     }
     735          80 :     return OGRLayer::ToHandle(poLayer);
     736             : }
     737             : 
     738             : /************************************************************************/
     739             : /*               OGRGeocodeReverseBuildLayerNominatim()                 */
     740             : /************************************************************************/
     741             : 
     742          12 : static OGRLayerH OGRGeocodeReverseBuildLayerNominatim(
     743             :     CPLXMLNode *psReverseGeocode, const char *pszContent, bool bAddRawFeature)
     744             : {
     745          12 :     CPLXMLNode *psResult = CPLGetXMLNode(psReverseGeocode, "result");
     746             :     CPLXMLNode *psAddressParts =
     747          12 :         CPLGetXMLNode(psReverseGeocode, "addressparts");
     748          12 :     if (psResult == nullptr || psAddressParts == nullptr)
     749             :     {
     750           0 :         return nullptr;
     751             :     }
     752             : 
     753          12 :     OGRMemLayer *poLayer = new OGRMemLayer("result", nullptr, wkbNone);
     754          12 :     const OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
     755             : 
     756          12 :     bool bFoundLat = false;
     757          12 :     bool bFoundLon = false;
     758          12 :     double dfLat = 0.0;
     759          12 :     double dfLon = 0.0;
     760             : 
     761             :     // First iteration to add fields.
     762          12 :     CPLXMLNode *psChild = psResult->psChild;
     763          96 :     while (psChild != nullptr)
     764             :     {
     765          84 :         const char *pszName = psChild->pszValue;
     766          84 :         const char *pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
     767         252 :         if ((psChild->eType == CXT_Element ||
     768         156 :              psChild->eType == CXT_Attribute) &&
     769          72 :             poFDefn->GetFieldIndex(pszName) < 0)
     770             :         {
     771         144 :             OGRFieldDefn oFieldDefn(pszName, OFTString);
     772          72 :             if (strcmp(pszName, "lat") == 0)
     773             :             {
     774          12 :                 if (pszVal != nullptr)
     775             :                 {
     776          12 :                     bFoundLat = true;
     777          12 :                     dfLat = CPLAtofM(pszVal);
     778             :                 }
     779          12 :                 oFieldDefn.SetType(OFTReal);
     780             :             }
     781          60 :             else if (strcmp(pszName, "lon") == 0)
     782             :             {
     783          12 :                 if (pszVal != nullptr)
     784             :                 {
     785          12 :                     bFoundLon = true;
     786          12 :                     dfLon = CPLAtofM(pszVal);
     787             :                 }
     788          12 :                 oFieldDefn.SetType(OFTReal);
     789             :             }
     790          72 :             poLayer->CreateField(&oFieldDefn);
     791             :         }
     792          84 :         psChild = psChild->psNext;
     793             :     }
     794             : 
     795             :     {
     796          24 :         OGRFieldDefn oFieldDefn("display_name", OFTString);
     797          12 :         poLayer->CreateField(&oFieldDefn);
     798             :     }
     799             : 
     800          12 :     psChild = psAddressParts->psChild;
     801         108 :     while (psChild != nullptr)
     802             :     {
     803          96 :         const char *pszName = psChild->pszValue;
     804         192 :         if ((psChild->eType == CXT_Element ||
     805         192 :              psChild->eType == CXT_Attribute) &&
     806          96 :             poFDefn->GetFieldIndex(pszName) < 0)
     807             :         {
     808         192 :             OGRFieldDefn oFieldDefn(pszName, OFTString);
     809          96 :             poLayer->CreateField(&oFieldDefn);
     810             :         }
     811          96 :         psChild = psChild->psNext;
     812             :     }
     813             : 
     814          12 :     if (bAddRawFeature)
     815             :     {
     816           4 :         OGRFieldDefn oFieldDefnRaw("raw", OFTString);
     817           2 :         poLayer->CreateField(&oFieldDefnRaw);
     818             :     }
     819             : 
     820             :     // Second iteration to fill the feature.
     821          24 :     auto poFeature = std::make_unique<OGRFeature>(poFDefn);
     822          12 :     psChild = psResult->psChild;
     823          96 :     while (psChild != nullptr)
     824             :     {
     825          84 :         int nIdx = 0;
     826          84 :         const char *pszName = psChild->pszValue;
     827          84 :         const char *pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
     828         252 :         if ((psChild->eType == CXT_Element ||
     829         156 :              psChild->eType == CXT_Attribute) &&
     830          72 :             (nIdx = poFDefn->GetFieldIndex(pszName)) >= 0)
     831             :         {
     832          72 :             if (pszVal != nullptr)
     833          72 :                 poFeature->SetField(nIdx, pszVal);
     834             :         }
     835          84 :         psChild = psChild->psNext;
     836             :     }
     837             : 
     838          12 :     const char *pszVal = CPLGetXMLValue(psResult, nullptr, nullptr);
     839          12 :     if (pszVal != nullptr)
     840          12 :         poFeature->SetField("display_name", pszVal);
     841             : 
     842          12 :     psChild = psAddressParts->psChild;
     843         108 :     while (psChild != nullptr)
     844             :     {
     845          96 :         int nIdx = 0;
     846          96 :         const char *pszName = psChild->pszValue;
     847          96 :         pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
     848         192 :         if ((psChild->eType == CXT_Element ||
     849         192 :              psChild->eType == CXT_Attribute) &&
     850          96 :             (nIdx = poFDefn->GetFieldIndex(pszName)) >= 0)
     851             :         {
     852          96 :             if (pszVal != nullptr)
     853          96 :                 poFeature->SetField(nIdx, pszVal);
     854             :         }
     855          96 :         psChild = psChild->psNext;
     856             :     }
     857             : 
     858          12 :     if (bAddRawFeature)
     859             :     {
     860           2 :         poFeature->SetField("raw", pszContent);
     861             :     }
     862             : 
     863             :     // If we did not find an explicit geometry, build it from
     864             :     // the 'lon' and 'lat' attributes.
     865          12 :     if (poFeature->GetGeometryRef() == nullptr && bFoundLon && bFoundLat)
     866          12 :         poFeature->SetGeometryDirectly(new OGRPoint(dfLon, dfLat));
     867             : 
     868          12 :     CPL_IGNORE_RET_VAL(poLayer->CreateFeature(std::move(poFeature)));
     869             : 
     870          12 :     return OGRLayer::ToHandle(poLayer);
     871             : }
     872             : 
     873             : /************************************************************************/
     874             : /*                   OGRGeocodeBuildLayerYahoo()                        */
     875             : /************************************************************************/
     876             : 
     877          26 : static OGRLayerH OGRGeocodeBuildLayerYahoo(CPLXMLNode *psResultSet,
     878             :                                            const char * /* pszContent */,
     879             :                                            bool bAddRawFeature)
     880             : {
     881          26 :     OGRMemLayer *poLayer = new OGRMemLayer("place", nullptr, wkbPoint);
     882          26 :     const OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
     883             : 
     884             :     // First iteration to add fields.
     885          26 :     CPLXMLNode *psPlace = psResultSet->psChild;
     886         258 :     while (psPlace != nullptr)
     887             :     {
     888         232 :         if (psPlace->eType == CXT_Element &&
     889         154 :             strcmp(psPlace->pszValue, "Result") == 0)
     890             :         {
     891          24 :             CPLXMLNode *psChild = psPlace->psChild;
     892         720 :             while (psChild != nullptr)
     893             :             {
     894         696 :                 const char *pszName = psChild->pszValue;
     895        1392 :                 if ((psChild->eType == CXT_Element ||
     896        1392 :                      psChild->eType == CXT_Attribute) &&
     897         696 :                     poFDefn->GetFieldIndex(pszName) < 0)
     898             :                 {
     899        1392 :                     OGRFieldDefn oFieldDefn(pszName, OFTString);
     900         696 :                     if (strcmp(pszName, "latitude") == 0)
     901             :                     {
     902          24 :                         oFieldDefn.SetType(OFTReal);
     903             :                     }
     904         672 :                     else if (strcmp(pszName, "longitude") == 0)
     905             :                     {
     906          24 :                         oFieldDefn.SetType(OFTReal);
     907             :                     }
     908         696 :                     poLayer->CreateField(&oFieldDefn);
     909             :                 }
     910         696 :                 psChild = psChild->psNext;
     911             :             }
     912             :         }
     913             : 
     914         232 :         psPlace = psPlace->psNext;
     915             :     }
     916             : 
     917          52 :     OGRFieldDefn oFieldDefnDisplayName("display_name", OFTString);
     918          26 :     poLayer->CreateField(&oFieldDefnDisplayName);
     919             : 
     920          26 :     if (bAddRawFeature)
     921             :     {
     922           8 :         OGRFieldDefn oFieldDefnRaw("raw", OFTString);
     923           4 :         poLayer->CreateField(&oFieldDefnRaw);
     924             :     }
     925             : 
     926          26 :     psPlace = psResultSet->psChild;
     927         258 :     while (psPlace != nullptr)
     928             :     {
     929         232 :         if (psPlace->eType == CXT_Element &&
     930         154 :             strcmp(psPlace->pszValue, "Result") == 0)
     931             :         {
     932          24 :             bool bFoundLat = false;
     933          24 :             bool bFoundLon = false;
     934          24 :             double dfLat = 0.0;
     935          24 :             double dfLon = 0.0;
     936             : 
     937             :             // Second iteration to fill the feature.
     938          48 :             auto poFeature = std::make_unique<OGRFeature>(poFDefn);
     939         720 :             for (CPLXMLNode *psChild = psPlace->psChild; psChild != nullptr;
     940         696 :                  psChild = psChild->psNext)
     941             :             {
     942         696 :                 const char *pszName = psChild->pszValue;
     943         696 :                 const char *pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
     944         696 :                 if (!(psChild->eType == CXT_Element ||
     945           0 :                       psChild->eType == CXT_Attribute))
     946             :                 {
     947             :                     // Do nothing.
     948           0 :                     continue;
     949             :                 }
     950         696 :                 const int nIdx = poFDefn->GetFieldIndex(pszName);
     951         696 :                 if (nIdx >= 0)
     952             :                 {
     953         696 :                     if (pszVal != nullptr)
     954             :                     {
     955         456 :                         poFeature->SetField(nIdx, pszVal);
     956         456 :                         if (strcmp(pszName, "latitude") == 0)
     957             :                         {
     958          24 :                             bFoundLat = true;
     959          24 :                             dfLat = CPLAtofM(pszVal);
     960             :                         }
     961         432 :                         else if (strcmp(pszName, "longitude") == 0)
     962             :                         {
     963          24 :                             bFoundLon = true;
     964          24 :                             dfLon = CPLAtofM(pszVal);
     965             :                         }
     966             :                     }
     967             :                 }
     968             :             }
     969             : 
     970          24 :             CPLString osDisplayName;
     971          24 :             for (int i = 1;; ++i)
     972             :             {
     973             :                 const int nIdx =
     974         120 :                     poFDefn->GetFieldIndex(CPLSPrintf("line%d", i));
     975         120 :                 if (nIdx < 0)
     976          24 :                     break;
     977          96 :                 if (poFeature->IsFieldSetAndNotNull(nIdx))
     978             :                 {
     979          60 :                     if (!osDisplayName.empty())
     980          36 :                         osDisplayName += ", ";
     981          60 :                     osDisplayName += poFeature->GetFieldAsString(nIdx);
     982             :                 }
     983          96 :             }
     984          24 :             poFeature->SetField("display_name", osDisplayName.c_str());
     985             : 
     986          24 :             if (bAddRawFeature)
     987             :             {
     988           4 :                 CPLXMLNode *psOldNext = psPlace->psNext;
     989           4 :                 psPlace->psNext = nullptr;
     990           4 :                 char *pszXML = CPLSerializeXMLTree(psPlace);
     991           4 :                 psPlace->psNext = psOldNext;
     992             : 
     993           4 :                 poFeature->SetField("raw", pszXML);
     994           4 :                 CPLFree(pszXML);
     995             :             }
     996             : 
     997             :             // Build geometry from the 'lon' and 'lat' attributes.
     998          24 :             if (bFoundLon && bFoundLat)
     999          24 :                 poFeature->SetGeometryDirectly(new OGRPoint(dfLon, dfLat));
    1000             : 
    1001          24 :             CPL_IGNORE_RET_VAL(poLayer->CreateFeature(std::move(poFeature)));
    1002             :         }
    1003         232 :         psPlace = psPlace->psNext;
    1004             :     }
    1005          52 :     return OGRLayer::ToHandle(poLayer);
    1006             : }
    1007             : 
    1008             : /************************************************************************/
    1009             : /*                   OGRGeocodeBuildLayerBing()                         */
    1010             : /************************************************************************/
    1011             : 
    1012          26 : static OGRLayerH OGRGeocodeBuildLayerBing(CPLXMLNode *psResponse,
    1013             :                                           const char * /* pszContent */,
    1014             :                                           bool bAddRawFeature)
    1015             : {
    1016             :     CPLXMLNode *psResources =
    1017          26 :         CPLGetXMLNode(psResponse, "ResourceSets.ResourceSet.Resources");
    1018          26 :     if (psResources == nullptr)
    1019           0 :         return nullptr;
    1020             : 
    1021          26 :     OGRMemLayer *poLayer = new OGRMemLayer("place", nullptr, wkbPoint);
    1022          26 :     const OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();
    1023             : 
    1024             :     // First iteration to add fields.
    1025          26 :     CPLXMLNode *psPlace = psResources->psChild;
    1026          50 :     while (psPlace != nullptr)
    1027             :     {
    1028          24 :         if (psPlace->eType == CXT_Element &&
    1029          24 :             strcmp(psPlace->pszValue, "Location") == 0)
    1030             :         {
    1031          24 :             CPLXMLNode *psChild = psPlace->psChild;
    1032         144 :             while (psChild != nullptr)
    1033             :             {
    1034         120 :                 const char *pszName = psChild->pszValue;
    1035         240 :                 if ((psChild->eType == CXT_Element ||
    1036           0 :                      psChild->eType == CXT_Attribute) &&
    1037         120 :                     strcmp(pszName, "BoundingBox") != 0 &&
    1038         312 :                     strcmp(pszName, "GeocodePoint") != 0 &&
    1039          72 :                     poFDefn->GetFieldIndex(pszName) < 0)
    1040             :                 {
    1041          72 :                     if (psChild->psChild != nullptr &&
    1042          72 :                         psChild->psChild->eType == CXT_Element)
    1043             :                     {
    1044          48 :                         CPLXMLNode *psSubChild = psChild->psChild;
    1045         216 :                         while (psSubChild != nullptr)
    1046             :                         {
    1047         168 :                             pszName = psSubChild->pszValue;
    1048         336 :                             if ((psSubChild->eType == CXT_Element ||
    1049         336 :                                  psSubChild->eType == CXT_Attribute) &&
    1050         168 :                                 poFDefn->GetFieldIndex(pszName) < 0)
    1051             :                             {
    1052         336 :                                 OGRFieldDefn oFieldDefn(pszName, OFTString);
    1053         168 :                                 if (strcmp(pszName, "Latitude") == 0)
    1054             :                                 {
    1055          24 :                                     oFieldDefn.SetType(OFTReal);
    1056             :                                 }
    1057         144 :                                 else if (strcmp(pszName, "Longitude") == 0)
    1058             :                                 {
    1059          24 :                                     oFieldDefn.SetType(OFTReal);
    1060             :                                 }
    1061         168 :                                 poLayer->CreateField(&oFieldDefn);
    1062             :                             }
    1063         168 :                             psSubChild = psSubChild->psNext;
    1064          48 :                         }
    1065             :                     }
    1066             :                     else
    1067             :                     {
    1068          48 :                         OGRFieldDefn oFieldDefn(pszName, OFTString);
    1069          24 :                         poLayer->CreateField(&oFieldDefn);
    1070             :                     }
    1071             :                 }
    1072         120 :                 psChild = psChild->psNext;
    1073             :             }
    1074             :         }
    1075          24 :         psPlace = psPlace->psNext;
    1076             :     }
    1077             : 
    1078          26 :     if (bAddRawFeature)
    1079             :     {
    1080           8 :         OGRFieldDefn oFieldDefnRaw("raw", OFTString);
    1081           4 :         poLayer->CreateField(&oFieldDefnRaw);
    1082             :     }
    1083             : 
    1084             :     // Iteration to fill the feature.
    1085          26 :     psPlace = psResources->psChild;
    1086          50 :     while (psPlace != nullptr)
    1087             :     {
    1088          24 :         if (psPlace->eType == CXT_Element &&
    1089          24 :             strcmp(psPlace->pszValue, "Location") == 0)
    1090             :         {
    1091          24 :             bool bFoundLat = false;
    1092          24 :             bool bFoundLon = false;
    1093          24 :             double dfLat = 0.0;
    1094          24 :             double dfLon = 0.0;
    1095             : 
    1096          24 :             auto poFeature = std::make_unique<OGRFeature>(poFDefn);
    1097         144 :             for (CPLXMLNode *psChild = psPlace->psChild; psChild != nullptr;
    1098         120 :                  psChild = psChild->psNext)
    1099             :             {
    1100         120 :                 const char *pszName = psChild->pszValue;
    1101         120 :                 const char *pszVal = CPLGetXMLValue(psChild, nullptr, nullptr);
    1102         120 :                 if (!(psChild->eType == CXT_Element ||
    1103           0 :                       psChild->eType == CXT_Attribute))
    1104             :                 {
    1105             :                     // Do nothing.
    1106           0 :                     continue;
    1107             :                 }
    1108         120 :                 const int nIdx = poFDefn->GetFieldIndex(pszName);
    1109         120 :                 if (nIdx >= 0)
    1110             :                 {
    1111          24 :                     if (pszVal != nullptr)
    1112          24 :                         poFeature->SetField(nIdx, pszVal);
    1113             :                 }
    1114          96 :                 else if (strcmp(pszName, "BoundingBox") != 0 &&
    1115          72 :                          strcmp(pszName, "GeocodePoint") != 0 &&
    1116          48 :                          psChild->psChild != nullptr &&
    1117          48 :                          psChild->psChild->eType == CXT_Element)
    1118             :                 {
    1119          48 :                     for (CPLXMLNode *psSubChild = psChild->psChild;
    1120         216 :                          psSubChild != nullptr; psSubChild = psSubChild->psNext)
    1121             :                     {
    1122         168 :                         pszName = psSubChild->pszValue;
    1123         168 :                         pszVal = CPLGetXMLValue(psSubChild, nullptr, nullptr);
    1124         168 :                         if ((psSubChild->eType == CXT_Element ||
    1125           0 :                              psSubChild->eType == CXT_Attribute))
    1126             :                         {
    1127         168 :                             const int nIdx2 = poFDefn->GetFieldIndex(pszName);
    1128         168 :                             if (nIdx2 >= 0)
    1129             :                             {
    1130         168 :                                 if (pszVal != nullptr)
    1131             :                                 {
    1132         168 :                                     poFeature->SetField(nIdx2, pszVal);
    1133         168 :                                     if (strcmp(pszName, "Latitude") == 0)
    1134             :                                     {
    1135          24 :                                         bFoundLat = true;
    1136          24 :                                         dfLat = CPLAtofM(pszVal);
    1137             :                                     }
    1138         144 :                                     else if (strcmp(pszName, "Longitude") == 0)
    1139             :                                     {
    1140          24 :                                         bFoundLon = true;
    1141          24 :                                         dfLon = CPLAtofM(pszVal);
    1142             :                                     }
    1143             :                                 }
    1144             :                             }
    1145             :                         }
    1146             :                     }
    1147             :                 }
    1148             :             }
    1149             : 
    1150          24 :             if (bAddRawFeature)
    1151             :             {
    1152           4 :                 CPLXMLNode *psOldNext = psPlace->psNext;
    1153           4 :                 psPlace->psNext = nullptr;
    1154           4 :                 char *pszXML = CPLSerializeXMLTree(psPlace);
    1155           4 :                 psPlace->psNext = psOldNext;
    1156             : 
    1157           4 :                 poFeature->SetField("raw", pszXML);
    1158           4 :                 CPLFree(pszXML);
    1159             :             }
    1160             : 
    1161             :             // Build geometry from the 'lon' and 'lat' attributes.
    1162          24 :             if (bFoundLon && bFoundLat)
    1163          24 :                 poFeature->SetGeometryDirectly(new OGRPoint(dfLon, dfLat));
    1164             : 
    1165          24 :             CPL_IGNORE_RET_VAL(poLayer->CreateFeature(std::move(poFeature)));
    1166             :         }
    1167          24 :         psPlace = psPlace->psNext;
    1168             :     }
    1169             : 
    1170          26 :     return OGRLayer::ToHandle(poLayer);
    1171             : }
    1172             : 
    1173             : /************************************************************************/
    1174             : /*                         OGRGeocodeBuildLayer()                       */
    1175             : /************************************************************************/
    1176             : 
    1177         112 : static OGRLayerH OGRGeocodeBuildLayer(const char *pszContent,
    1178             :                                       bool bAddRawFeature)
    1179             : {
    1180         112 :     OGRLayerH hLayer = nullptr;
    1181         112 :     CPLXMLNode *psRoot = CPLParseXMLString(pszContent);
    1182         112 :     if (psRoot != nullptr)
    1183             :     {
    1184         104 :         CPLXMLNode *psSearchResults = nullptr;
    1185         104 :         CPLXMLNode *psReverseGeocode = nullptr;
    1186         104 :         CPLXMLNode *psGeonames = nullptr;
    1187         104 :         CPLXMLNode *psResultSet = nullptr;
    1188         104 :         CPLXMLNode *psResponse = nullptr;
    1189         104 :         if ((psSearchResults = CPLSearchXMLNode(psRoot, "=searchresults")) !=
    1190             :             nullptr)
    1191          14 :             hLayer = OGRGeocodeBuildLayerNominatim(psSearchResults, pszContent,
    1192             :                                                    bAddRawFeature);
    1193          90 :         else if ((psReverseGeocode =
    1194          90 :                       CPLSearchXMLNode(psRoot, "=reversegeocode")) != nullptr)
    1195          12 :             hLayer = OGRGeocodeReverseBuildLayerNominatim(
    1196             :                 psReverseGeocode, pszContent, bAddRawFeature);
    1197          78 :         else if ((psGeonames = CPLSearchXMLNode(psRoot, "=geonames")) !=
    1198             :                  nullptr)
    1199          26 :             hLayer = OGRGeocodeBuildLayerNominatim(psGeonames, pszContent,
    1200             :                                                    bAddRawFeature);
    1201          52 :         else if ((psResultSet = CPLSearchXMLNode(psRoot, "=ResultSet")) !=
    1202             :                  nullptr)
    1203          26 :             hLayer = OGRGeocodeBuildLayerYahoo(psResultSet, pszContent,
    1204             :                                                bAddRawFeature);
    1205          26 :         else if ((psResponse = CPLSearchXMLNode(psRoot, "=Response")) !=
    1206             :                  nullptr)
    1207          26 :             hLayer = OGRGeocodeBuildLayerBing(psResponse, pszContent,
    1208             :                                               bAddRawFeature);
    1209         104 :         CPLDestroyXMLNode(psRoot);
    1210             :     }
    1211         112 :     if (hLayer == nullptr && bAddRawFeature)
    1212           0 :         hLayer = OGRGeocodeMakeRawLayer(pszContent);
    1213         112 :     return hLayer;
    1214             : }
    1215             : 
    1216             : /************************************************************************/
    1217             : /*                         OGRGeocodeCommon()                           */
    1218             : /************************************************************************/
    1219             : 
    1220         112 : static OGRLayerH OGRGeocodeCommon(OGRGeocodingSessionH hSession,
    1221             :                                   const std::string &osURLIn,
    1222             :                                   char **papszOptions)
    1223             : {
    1224         224 :     std::string osURL(osURLIn);
    1225             : 
    1226             :     // Only documented to work with OSM Nominatim.
    1227         112 :     if (hSession->pszLanguage != nullptr)
    1228             :     {
    1229           0 :         osURL += "&accept-language=";
    1230           0 :         osURL += hSession->pszLanguage;
    1231             :     }
    1232             : 
    1233             :     const char *pszExtraQueryParameters =
    1234         112 :         OGRGeocodeGetParameter(papszOptions, "EXTRA_QUERY_PARAMETERS", nullptr);
    1235         112 :     if (pszExtraQueryParameters != nullptr)
    1236             :     {
    1237           0 :         osURL += "&";
    1238           0 :         osURL += pszExtraQueryParameters;
    1239             :     }
    1240             : 
    1241         112 :     CPLString osURLWithEmail = osURL;
    1242         112 :     if (EQUAL(hSession->pszGeocodingService, "OSM_NOMINATIM") &&
    1243          28 :         hSession->pszEmail != nullptr)
    1244             :     {
    1245             :         char *const pszEscapedEmail =
    1246          28 :             CPLEscapeString(hSession->pszEmail, -1, CPLES_URL);
    1247          28 :         osURLWithEmail = osURL + "&email=" + pszEscapedEmail;
    1248          28 :         CPLFree(pszEscapedEmail);
    1249             :     }
    1250          84 :     else if (EQUAL(hSession->pszGeocodingService, "GEONAMES") &&
    1251          28 :              hSession->pszUserName != nullptr)
    1252             :     {
    1253             :         char *const pszEscaped =
    1254          28 :             CPLEscapeString(hSession->pszUserName, -1, CPLES_URL);
    1255          28 :         osURLWithEmail = osURL + "&username=" + pszEscaped;
    1256          28 :         CPLFree(pszEscaped);
    1257             :     }
    1258          56 :     else if (EQUAL(hSession->pszGeocodingService, "BING") &&
    1259          28 :              hSession->pszKey != nullptr)
    1260             :     {
    1261             :         char *const pszEscaped =
    1262          28 :             CPLEscapeString(hSession->pszKey, -1, CPLES_URL);
    1263          28 :         osURLWithEmail = osURL + "&key=" + pszEscaped;
    1264          28 :         CPLFree(pszEscaped);
    1265             :     }
    1266             : 
    1267             :     const bool bAddRawFeature =
    1268         112 :         CPLTestBool(OGRGeocodeGetParameter(papszOptions, "RAW_FEATURE", "NO"));
    1269             : 
    1270         112 :     OGRLayerH hLayer = nullptr;
    1271             : 
    1272         112 :     char *pszCachedResult = nullptr;
    1273         112 :     if (hSession->bReadCache)
    1274         112 :         pszCachedResult = OGRGeocodeGetFromCache(hSession, osURL.c_str());
    1275         112 :     if (pszCachedResult == nullptr)
    1276             :     {
    1277          34 :         double *pdfLastQueryTime = nullptr;
    1278          34 :         if (EQUAL(hSession->pszGeocodingService, "OSM_NOMINATIM"))
    1279          10 :             pdfLastQueryTime = &dfLastQueryTimeStampOSMNominatim;
    1280          24 :         else if (EQUAL(hSession->pszGeocodingService, "MAPQUEST_NOMINATIM"))
    1281           0 :             pdfLastQueryTime = &dfLastQueryTimeStampMapQuestNominatim;
    1282             : 
    1283          68 :         CPLString osHeaders = "User-Agent: ";
    1284          34 :         osHeaders += hSession->pszApplication;
    1285          34 :         if (hSession->pszLanguage != nullptr)
    1286             :         {
    1287           0 :             osHeaders += "\r\nAccept-Language: ";
    1288           0 :             osHeaders += hSession->pszLanguage;
    1289             :         }
    1290             :         char **papszHTTPOptions =
    1291          34 :             CSLAddNameValue(nullptr, "HEADERS", osHeaders.c_str());
    1292             : 
    1293          34 :         CPLHTTPResult *psResult = nullptr;
    1294          34 :         if (pdfLastQueryTime != nullptr)
    1295             :         {
    1296          10 :             CPLMutexHolderD(&hOGRGeocodingMutex);
    1297             :             struct timeval tv;
    1298             : 
    1299          10 :             gettimeofday(&tv, nullptr);
    1300          10 :             double dfCurrentTime = tv.tv_sec + tv.tv_usec / 1e6;
    1301          10 :             if (dfCurrentTime <
    1302          10 :                 *pdfLastQueryTime + hSession->dfDelayBetweenQueries)
    1303             :             {
    1304           8 :                 CPLSleep(*pdfLastQueryTime + hSession->dfDelayBetweenQueries -
    1305             :                          dfCurrentTime);
    1306             :             }
    1307             : 
    1308          10 :             psResult = CPLHTTPFetch(osURLWithEmail, papszHTTPOptions);
    1309             : 
    1310          10 :             gettimeofday(&tv, nullptr);
    1311          10 :             *pdfLastQueryTime = tv.tv_sec + tv.tv_usec / 1e6;
    1312             :         }
    1313             :         else
    1314             :         {
    1315          24 :             psResult = CPLHTTPFetch(osURLWithEmail, papszHTTPOptions);
    1316             :         }
    1317             : 
    1318          34 :         CSLDestroy(papszHTTPOptions);
    1319          34 :         papszHTTPOptions = nullptr;
    1320             : 
    1321          34 :         if (psResult == nullptr)
    1322             :         {
    1323           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Query '%s' failed",
    1324             :                      osURLWithEmail.c_str());
    1325             :         }
    1326             :         else
    1327             :         {
    1328          34 :             const char *pszResult =
    1329             :                 reinterpret_cast<const char *>(psResult->pabyData);
    1330          34 :             if (pszResult != nullptr)
    1331             :             {
    1332          34 :                 if (hSession->bWriteCache)
    1333             :                 {
    1334             :                     // coverity[tainted_data]
    1335          34 :                     OGRGeocodePutIntoCache(hSession, osURL.c_str(), pszResult);
    1336             :                 }
    1337          34 :                 hLayer = OGRGeocodeBuildLayer(pszResult, bAddRawFeature);
    1338             :             }
    1339          34 :             CPLHTTPDestroyResult(psResult);
    1340             :         }
    1341             :     }
    1342             :     else
    1343             :     {
    1344          78 :         hLayer = OGRGeocodeBuildLayer(pszCachedResult, bAddRawFeature);
    1345          78 :         CPLFree(pszCachedResult);
    1346             :     }
    1347             : 
    1348         224 :     return hLayer;
    1349             : }
    1350             : 
    1351             : /************************************************************************/
    1352             : /*                              OGRGeocode()                            */
    1353             : /************************************************************************/
    1354             : 
    1355             : /* clang-format off */
    1356             : /**
    1357             :  * \brief Runs a geocoding request.
    1358             :  *
    1359             :  * If the result is not found in cache, a GET request will be sent to resolve
    1360             :  * the query.
    1361             :  *
    1362             :  * Note: most online services have Term of Uses. You are kindly requested
    1363             :  * to read and follow them. For the OpenStreetMap Nominatim service, this
    1364             :  * implementation will make sure that no more than one request is sent by
    1365             :  * second, but there might be other restrictions that you must follow by other
    1366             :  * means.
    1367             :  *
    1368             :  * In case of success, the return of this function is a OGR layer that contain
    1369             :  * zero, one or several features matching the query. Note that the geometry of
    1370             :  * the features is not necessarily a point.  The returned layer must be freed
    1371             :  * with OGRGeocodeFreeResult().
    1372             :  *
    1373             :  * Note: this function is also available as the SQL
    1374             :  * <a href="ogr_sql_sqlite.html#ogr_sql_sqlite_ogr_geocode_function">ogr_geocode()</a>
    1375             :  * function of the SQL SQLite dialect.
    1376             :  *
    1377             :  * The list of recognized options is :
    1378             :  * <ul>
    1379             :  * <li>ADDRESSDETAILS=0 or 1: Include a breakdown of the address into elements
    1380             :  *     Defaults to 1. (Known to work with OSM and MapQuest Nominatim)
    1381             :  * <li>COUNTRYCODES=code1,code2,...codeN: Limit search results to a specific
    1382             :  *     country (or a list of countries). The codes must fellow ISO 3166-1, i.e.
    1383             :  *     gb for United Kingdom, de for Germany, etc.. (Known to work with OSM and
    1384             :  *     MapQuest Nominatim)
    1385             :  * <li>LIMIT=number: the number of records to return. Unlimited if not
    1386             :  *     specified.  (Known to work with OSM and MapQuest Nominatim)
    1387             :  * <li>RAW_FEATURE=YES: to specify that a 'raw' field must be added to the
    1388             :  *     returned feature with the raw XML content.
    1389             :  * <li>EXTRA_QUERY_PARAMETERS=params: additional parameters for the GET
    1390             :  *     request.
    1391             :  * </ul>
    1392             :  *
    1393             :  * @param hSession the geocoding session handle.
    1394             :  * @param pszQuery the string to geocode.
    1395             :  * @param papszStructuredQuery unused for now. Must be NULL.
    1396             :  * @param papszOptions a list of options or NULL.
    1397             :  *
    1398             :  * @return a OGR layer with the result(s), or NULL in case of error.
    1399             :  *         The returned layer must be freed with OGRGeocodeFreeResult().
    1400             :  *
    1401             :  */
    1402             : /* clang-format on */
    1403             : 
    1404          64 : OGRLayerH OGRGeocode(OGRGeocodingSessionH hSession, const char *pszQuery,
    1405             :                      char **papszStructuredQuery, char **papszOptions)
    1406             : {
    1407          64 :     VALIDATE_POINTER1(hSession, "OGRGeocode", nullptr);
    1408          64 :     if ((pszQuery == nullptr && papszStructuredQuery == nullptr) ||
    1409          64 :         (pszQuery != nullptr && papszStructuredQuery != nullptr))
    1410             :     {
    1411           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1412             :                  "Only one of pszQuery or papszStructuredQuery must be set.");
    1413           0 :         return nullptr;
    1414             :     }
    1415             : 
    1416          64 :     if (papszStructuredQuery != nullptr)
    1417             :     {
    1418           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1419             :                  "papszStructuredQuery not yet supported.");
    1420           0 :         return nullptr;
    1421             :     }
    1422             : 
    1423          64 :     if (hSession->pszQueryTemplate == nullptr)
    1424             :     {
    1425           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1426             :                  "QUERY_TEMPLATE parameter not defined");
    1427           0 :         return nullptr;
    1428             :     }
    1429             : 
    1430          64 :     constexpr const char *PCT_S = "%s";
    1431          64 :     const char *pszPctS = strstr(hSession->pszQueryTemplate, PCT_S);
    1432          64 :     if (!pszPctS)
    1433             :     {
    1434             :         // should not happen given OGRGeocodeHasStringValidFormat()
    1435           0 :         return nullptr;
    1436             :     }
    1437             : 
    1438          64 :     char *pszEscapedQuery = CPLEscapeString(pszQuery, -1, CPLES_URL);
    1439             : 
    1440         128 :     std::string osURL;
    1441          64 :     osURL.assign(hSession->pszQueryTemplate,
    1442          64 :                  pszPctS - hSession->pszQueryTemplate);
    1443          64 :     osURL += pszEscapedQuery;
    1444          64 :     osURL += (pszPctS + strlen(PCT_S));
    1445          64 :     CPLFree(pszEscapedQuery);
    1446             : 
    1447          64 :     if (EQUAL(hSession->pszGeocodingService, "OSM_NOMINATIM") ||
    1448          48 :         EQUAL(hSession->pszGeocodingService, "MAPQUEST_NOMINATIM"))
    1449             :     {
    1450             :         const char *pszAddressDetails =
    1451          16 :             OGRGeocodeGetParameter(papszOptions, "ADDRESSDETAILS", "1");
    1452          16 :         osURL += "&addressdetails=";
    1453          16 :         osURL += pszAddressDetails;
    1454             : 
    1455             :         const char *pszCountryCodes =
    1456          16 :             OGRGeocodeGetParameter(papszOptions, "COUNTRYCODES", nullptr);
    1457          16 :         if (pszCountryCodes != nullptr)
    1458             :         {
    1459           0 :             osURL += "&countrycodes=";
    1460           0 :             osURL += pszCountryCodes;
    1461             :         }
    1462             : 
    1463             :         const char *pszLimit =
    1464          16 :             OGRGeocodeGetParameter(papszOptions, "LIMIT", nullptr);
    1465          16 :         if (pszLimit != nullptr && *pszLimit != '\0')
    1466             :         {
    1467          16 :             osURL += "&limit=";
    1468          16 :             osURL += pszLimit;
    1469             :         }
    1470             :     }
    1471             : 
    1472             :     // coverity[tainted_data]
    1473          64 :     return OGRGeocodeCommon(hSession, osURL, papszOptions);
    1474             : }
    1475             : 
    1476             : /************************************************************************/
    1477             : /*                      OGRGeocodeReverseSubstitute()                   */
    1478             : /************************************************************************/
    1479             : 
    1480          48 : static CPLString OGRGeocodeReverseSubstitute(CPLString osURL, double dfLon,
    1481             :                                              double dfLat)
    1482             : {
    1483          48 :     size_t iPos = osURL.find("{lon}");
    1484          48 :     if (iPos != std::string::npos)
    1485             :     {
    1486          96 :         const CPLString osEnd(osURL.substr(iPos + 5));
    1487          48 :         osURL = osURL.substr(0, iPos);
    1488          48 :         osURL += CPLSPrintf("%.8f", dfLon);
    1489          48 :         osURL += osEnd;
    1490             :     }
    1491             : 
    1492          48 :     iPos = osURL.find("{lat}");
    1493          48 :     if (iPos != std::string::npos)
    1494             :     {
    1495          96 :         const CPLString osEnd(osURL.substr(iPos + 5));
    1496          48 :         osURL = osURL.substr(0, iPos);
    1497          48 :         osURL += CPLSPrintf("%.8f", dfLat);
    1498          48 :         osURL += osEnd;
    1499             :     }
    1500             : 
    1501          48 :     return osURL;
    1502             : }
    1503             : 
    1504             : /************************************************************************/
    1505             : /*                         OGRGeocodeReverse()                          */
    1506             : /************************************************************************/
    1507             : 
    1508             : /* clang-format off */
    1509             : /**
    1510             :  * \brief Runs a reverse geocoding request.
    1511             :  *
    1512             :  * If the result is not found in cache, a GET request will be sent to resolve
    1513             :  * the query.
    1514             :  *
    1515             :  * Note: most online services have Term of Uses. You are kindly requested
    1516             :  * to read and follow them. For the OpenStreetMap Nominatim service, this
    1517             :  * implementation will make sure that no more than one request is sent by
    1518             :  * second, but there might be other restrictions that you must follow by other
    1519             :  * means.
    1520             :  *
    1521             :  * In case of success, the return of this function is a OGR layer that contain
    1522             :  * zero, one or several features matching the query. The returned layer must be
    1523             :  * freed with OGRGeocodeFreeResult().
    1524             :  *
    1525             :  * Note: this function is also available as the SQL
    1526             :  * <a href="ogr_sql_sqlite.html#ogr_sql_sqlite_ogr_geocode_function">ogr_geocode_reverse()</a>
    1527             :  * function of the SQL SQLite dialect.
    1528             :  *
    1529             :  * The list of recognized options is :
    1530             :  * <ul>
    1531             :  * <li>ZOOM=a_level: to query a specific zoom level. Only understood by the OSM
    1532             :  *     Nominatim service.
    1533             :  * <li>RAW_FEATURE=YES: to specify that a 'raw' field must be added to the
    1534             :  *     returned feature with the raw XML content.
    1535             :  * <li>EXTRA_QUERY_PARAMETERS=params: additional parameters for the GET request
    1536             :  *     for reverse geocoding.
    1537             :  * </ul>
    1538             :  *
    1539             :  * @param hSession the geocoding session handle.
    1540             :  * @param dfLon the longitude.
    1541             :  * @param dfLat the latitude.
    1542             :  * @param papszOptions a list of options or NULL.
    1543             :  *
    1544             :  * @return a OGR layer with the result(s), or NULL in case of error.
    1545             :  *         The returned layer must be freed with OGRGeocodeFreeResult().
    1546             :  *
    1547             :  */
    1548             : /* clang-format on */
    1549             : 
    1550          48 : OGRLayerH OGRGeocodeReverse(OGRGeocodingSessionH hSession, double dfLon,
    1551             :                             double dfLat, char **papszOptions)
    1552             : {
    1553          48 :     VALIDATE_POINTER1(hSession, "OGRGeocodeReverse", nullptr);
    1554             : 
    1555          48 :     if (hSession->pszReverseQueryTemplate == nullptr)
    1556             :     {
    1557           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1558             :                  "REVERSE_QUERY_TEMPLATE parameter not defined");
    1559           0 :         return nullptr;
    1560             :     }
    1561             : 
    1562          96 :     CPLString osURL = hSession->pszReverseQueryTemplate;
    1563          48 :     osURL = OGRGeocodeReverseSubstitute(osURL, dfLon, dfLat);
    1564             : 
    1565          48 :     if (EQUAL(hSession->pszGeocodingService, "OSM_NOMINATIM"))
    1566             :     {
    1567             :         const char *pszZoomLevel =
    1568          12 :             OGRGeocodeGetParameter(papszOptions, "ZOOM", nullptr);
    1569          12 :         if (pszZoomLevel != nullptr)
    1570             :         {
    1571           4 :             osURL = osURL + "&zoom=" + pszZoomLevel;
    1572             :         }
    1573             :     }
    1574             : 
    1575             :     // coverity[tainted_data]
    1576          48 :     return OGRGeocodeCommon(hSession, osURL, papszOptions);
    1577             : }
    1578             : 
    1579             : /************************************************************************/
    1580             : /*                        OGRGeocodeFreeResult()                        */
    1581             : /************************************************************************/
    1582             : 
    1583             : /**
    1584             :  * \brief Destroys the result of a geocoding request.
    1585             :  *
    1586             :  * @param hLayer the layer returned by OGRGeocode() or OGRGeocodeReverse()
    1587             :  *               to destroy.
    1588             :  *
    1589             :  */
    1590         104 : void OGRGeocodeFreeResult(OGRLayerH hLayer)
    1591             : {
    1592         104 :     delete OGRLayer::FromHandle(hLayer);
    1593         104 : }

Generated by: LCOV version 1.14