Coverage Report

Created: 2025-12-31 08:30

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