LCOV - code coverage report
Current view: top level - ogr - ogr_geocoding.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 618 709 87.2 %
Date: 2024-05-03 15:49:35 Functions: 17 18 94.4 %

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

Generated by: LCOV version 1.14