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

Generated by: LCOV version 1.14