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