-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgeocoder.module
357 lines (324 loc) · 10.5 KB
/
geocoder.module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
<?php
/**
* @file
* Module file for geocoder module.
*/
include_once('geocoder.widget.inc');
include_once('geocoder.services.inc');
define('GEOCODER_GOOGLE_AUTH_NONE', 1);
define('GEOCODER_GOOGLE_AUTH_KEY', 2);
define('GEOCODER_GOOGLE_AUTH_WORK', 3);
/**
* The Geocoder API call.
*
* Given a handler and data, geocode the data into a geometry object using the handler.
*
* @param string $handler
* The geocoder handler to use. Call geocoder_handler_info() to get a list
*
* @param mixed $data
* Data to be passed into the handler for geocoding. For example a address string.
*
* @param array $options
* Additional options to pass to the handler. Exact key / values to pass depend on the handler.
*
* @param int $cache_type
* DEPRECATED. All results are cached persistently.
*
* @param bool $cache_reset
* (optional) Ignore potentially matched cache record, and live fetch. Defaults to FALSE.
*
* @return Geometry
* Returns a geoPHP geometry object. Generally a Point.
* See https://backdropcms.org/project/geoPHP and https://github.com/phayes/geoPHP/wiki/API-Reference
*
* @example:
* geocoder('google','4925 Gair Ave, Terrace, BC');
* geocoder('google','New York City',array('geometry_type' => 'bounds'));
*/
function geocoder($handler, $data, $options = array(), $cache_type = 'DEPRECATED', $cache_reset = FALSE) {
$processor = geocoder_get_handler($handler);
if (!$processor) {
return NULL;
}
// Attempt to retrieve from persistent cache.
$geometry = $cache_reset ? NULL : geocoder_cache_get($handler, $data, $options);
// No cache record, so fetch live.
if ($geometry === NULL) {
try {
$geometry = call_user_func($processor['callback'], $data, $options);
}
catch (Exception $e) {
watchdog_exception('geocoder', $e);
return NULL;
}
// Always save result into persistent cache.
geocoder_cache_set($geometry, $handler, $data, $options);
}
if (!$geometry && config_get('geocoder.settings', 'geocoder_log_empty_results')) {
watchdog('geocoder', t('No results for geocoding @data', array('@data' => $data)));
}
return $geometry;
}
/**
* Implements hook_menu().
*/
function geocoder_menu() {
// Admin settings for the site.
$items['admin/config/content/geocoder'] = array(
'title' => 'Geocoder settings',
'description' => 'Configuration for API keys and other global settings.',
'page callback' => 'backdrop_get_form',
'page arguments' => array('geocoder_admin_settings'),
'file' => 'geocoder.admin.inc',
'access arguments' => array('administer site configuration'),
'type' => MENU_NORMAL_ITEM, // optional
);
$items['geocoder/service/%'] = array(
'title' => 'AJAX / AJAJ geocoding service',
'description' => 'Provides basic callback for geocoding using JavaScript',
'page callback' => 'geocoder_service_callback',
'page arguments' => array(2),
'type' => MENU_CALLBACK,
'access arguments' => array(arg(2)),
'access callback' => 'geocoder_service_check_perms',
);
return $items;
}
/**
* Loads each plugin file and returns them in an array by name (without .inc).
*/
function geocoder_get_plugins($type) {
$plugins = &backdrop_static(__FUNCTION__);
if (!isset($plugins)) {
$plugins = array();
$pattern = BACKDROP_ROOT . '/' . backdrop_get_path('module', 'geocoder') . '/' . 'plugins/' . $type . '/*.inc';
foreach (glob($pattern) as $filename) {
$plugin = NULL;
include_once($filename);
if (isset($plugin)) {
$id = basename($filename, '.inc');
$plugins[$id] = $plugin;
$plugins[$id]['name'] = $id;
}
}
}
return $plugins;
}
/**
* Geocoder Handler Information
*
* Return a list of all handlers that might geocode something for you.
* Optionally you may pass a field-type and get back a list of
* handlers that are compatible with that field.
*/
function geocoder_handler_info($field_type = NULL) {
static $handlers;
if (!$handlers) {
$handlers = geocoder_get_plugins('geocoder_handler');
}
if ($field_type) {
$field_handlers = $handlers;
foreach ($field_handlers as $i => $handler) {
if (!isset($handler['field_types']) || !in_array($field_type, $handler['field_types'])) {
unset($field_handlers[$i]);
}
}
return $field_handlers;
}
return $handlers;
}
/**
* Fetch geocoder handler
*
* Given a name, fetch the full handler definition
*/
function geocoder_get_handler($handler_name) {
$handlers = geocoder_handler_info();
if (isset($handlers[$handler_name])) {
return $handlers[$handler_name];
}
else return FALSE;
}
/**
* Get supported field types
*
* Get a list of supported field types along with the processors that support them
*/
function geocoder_supported_field_types() {
$supported = array();
$processors = geocoder_handler_info();
foreach ($processors as $processor) {
if (isset($processor['field_types'])) {
foreach ($processor['field_types'] as $field_type) {
$supported[$field_type][] = $processor['name'];
}
}
}
return $supported;
}
// These functions have to do with providing AJAX / AHAH
// service functionality so that users can make use of
// geocoder interactively via JavaScript.
/**
* Implements hook_permission().
*
* We define permissions for accessing geocoder over AJAX / services.
* There is one global permission which gives access to everything,
* and one permission per handler. The site-administrator can therefore
* fine tune which handlers are accessible. Note that to use AJAX with
* geocoder these permissions need to be set.
*/
function geocoder_permission() {
$handler_info = geocoder_handler_info();
$perms = array(
'geocoder_service_all_handlers' => array(
'title' => t('Can use all Geocoder handlers through AJAX / service'),
),
);
foreach ($handler_info as $name => $handler) {
$perms['geocoder_service_handler_' . $name] = array(
'title' => t('Can geocode using @handler through AJAX / service', array('@handler' => $handler['title'])),
);
}
return $perms;
}
/**
* Geocoder service check permissions
*
* Given a handler, check to see if the user has
* permission to execute it via AJAX / services
*/
function geocoder_service_check_perms($handler) {
return (user_access('geocoder_service_all_handlers') || user_access('geocoder_service_handler_' . $handler));
}
/**
* Page callback for AJAX / Geocoder service
*
* This service can be accessed at /geocoder/service/<handler>
* and takes the query-parameter "data". Optionally a "output"
* parameter may also be passed. "output" corresponds to
* geoPHP output formats.
*
* Some examples:
* /geocoder/service/google?data=4925 Gair Ave, Terrace, BC
* /geocoder/service/wkt?data=POINT(10 10)
* /geocoder/service/yahoo?data=94112&output=wkt
*/
function geocoder_service_callback($handler) {
if (!isset($_GET['data'])) {
throw new Exception(t('No data parameter found'));
exit();
}
$format = isset($_GET['output']) ? $_GET['output'] : 'json';
module_load_include('inc', 'geofield', 'libraries/geophp/geoPHP');
geocoder_service_check_request($handler, $format);
$geom = geocoder($handler, $_GET['data']);
header('Content-Type: ' . geocoder_service_get_content_type($format));
print $geom->out($format);
exit();
}
/**
* Get Content-Type for an output format
*
* Given an output format, this helper function passes
* a compatible HTTP content-type to be placed in the
* header.
*/
function geocoder_service_get_content_type($format) {
$types = array(
'json' => 'application/json',
'kml' => 'application/xml',
'georss' => 'application/xml',
'gpx' => 'application/xml',
'wkt' => 'text/plain',
'wkb' => 'text/plain',
'google_geocode' => 'text/plain',
);
return $types[$format];
}
/**
* Geocoder Service Check Request
*
* Check to make sure the request to the service is valid. This
* checks to make sure the handler and the format exists, and
* also checks permission
*/
function geocoder_service_check_request($handler, $format, $check_ac = TRUE) {
if (!geocoder_get_handler($handler)) {
backdrop_set_message(t('Could not find handler @handler', array('@handler' => $handler)), 'error');
backdrop_not_found();
exit();
}
if (($format && $format != 'default') && !in_array($format, array_keys(geoPHP::getAdapterMap()))) {
throw new Exception(t('Could not find output-format @format', array('@format' => $format)), 'error');
exit();
}
if (!geocoder_service_check_perms($handler)) {
backdrop_access_denied();
exit();
}
}
/**
* Create a unified cache id from relevant cache data.
*/
function _geocoder_cache_cid($data) {
ksort($data);
return sha1(serialize($data));
}
/**
* Retrieve a cached geocoded location.
*
* @param string $handler
* The handler used to geocode this data.
* @param mixed $data
* The data used to fetch live geo data.
* @param array $options
* Handler-specific options that have effect on the result.
*
* @return Geometry
* A Geometry object, FALSE (no result), or NULL (no cache).
*/
function geocoder_cache_get($handler, $data, $options) {
$data = compact('handler', 'data', 'options');
$cid = _geocoder_cache_cid($data);
module_load_include('inc', 'geofield', 'libraries/geophp/geoPHP');
if ($cache = cache_get($cid, 'cache_geocoder')) {
return $cache->data['geometry'];
}
}
/**
* Cache a geocoded result.
*
* @param mixed $geometry
* A Geometry object, or FALSE (no result).
* @param string $handler
* The handler used to geocode this data.
* @param mixed $data
* The data used to fetch live geo data.
* @param array $options
* Handler-specific options that have effect on the result.
*/
function geocoder_cache_set($geometry, $handler, $data, $options) {
$config = config('geocoder.settings');
// Don't cache no-results, to live geocode the same data again next time.
if (!$geometry && !$config->get('geocoder_cache_empty_results')) {
return;
}
// Construct the cache id from result-relevant parameters.
$data = compact('handler', 'data', 'options');
$cid = _geocoder_cache_cid($data);
// Cache result-relevant parameters together with the actual result, so cache records can be traced.
$data['geometry'] = $geometry ? $geometry : FALSE;
cache_set($cid, $data, 'cache_geocoder', $config->get('geocoder_cache_ttl'));
}
/**
* Implements hook_config_info().
*/
function geocoder_config_info() {
$prefixes['geocoder.settings'] = array(
'label' => t('Geocoder settings'),
'group' => t('Configuration'),
);
return $prefixes;
}