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

Generated by: LCOV version 1.14