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