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