Skip to content

Commit 533af1d

Browse files
authored
Update gsm/nominatim geocoder implementation with user agent support and fix cache bug (#287)
* feat(geocoder): rewrite osm geocoder with new endpoint, user agent, and wp_remote_get * feat(geocoder): add accept-language based on gotcha tip in nominatim docs * fix(geocoder): throw exception on fail instead of polluting cache with false as an object * refactor(geocoder): don't reuse var in endpoint url * feat(geocoder): osm lazy cache self-healing * feat(plugin-option): add placeholder support to input text * feat(plugin-settings): add nominatim_contact_email support * feat(geocode): add leaflet_map_nominatim_contact_email filter * docs(readme): document setting and filter for the nominatim contact email sent in the geocoder user agent * fix(geocoder): only use site language otherwise the global cache can end up multilingual depending on the users language
1 parent bb330fb commit 533af1d

File tree

5 files changed

+129
-19
lines changed

5 files changed

+129
-19
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ However, you can also just give it an address, and the chosen geocoder (default:
110110
[leaflet-map address="Oslo, Norway"]
111111
```
112112

113+
When using the OpenStreetMap Nominatim geocoder, the plugin sends a contact email in the request user agent. By default this uses the site admin email, but you can override it in the plugin settings with `Nominatim Contact Email (optional)` or in code with the `leaflet_map_nominatim_contact_email` filter.
114+
115+
```php
116+
add_filter('leaflet_map_nominatim_contact_email', function ($email) {
117+
return 'maps@example.com';
118+
});
119+
```
120+
113121
#### [leaflet-map] Options:
114122

115123
| Option | Default |

class.geocoder.php

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,32 @@ public function __construct ($address) {
4444
/* retrieve cached geocoded location */
4545
$found_cache = get_option( $cached_address );
4646

47-
if ( $found_cache ) {
47+
if ( $this->is_valid_cached_location( $found_cache ) ) {
4848
$location = $found_cache;
4949
} else {
50+
if ( false !== $found_cache ) {
51+
$this->remove_cache_key( $cached_address );
52+
}
53+
5054
// try geocoding
5155
$geocoding_method = $geocoder . '_geocode';
5256

5357
try {
5458
$location = (Object) $this->$geocoding_method( $address );
5559

60+
if ( ! $this->is_valid_cached_location( $location ) ) {
61+
throw new Exception('Invalid geocoder response');
62+
}
63+
5664
/* add location */
5765
add_option($cached_address, $location);
5866

5967
/* add option key to locations for clean up purposes */
6068
$locations = get_option('leaflet_geocoded_locations', array());
61-
array_push($locations, $cached_address);
62-
update_option('leaflet_geocoded_locations', $locations);
69+
if ( ! in_array( $cached_address, $locations, true ) ) {
70+
array_push($locations, $cached_address);
71+
update_option('leaflet_geocoded_locations', $locations);
72+
}
6373
} catch (Exception $e) {
6474
// failed
6575
$location = $this->not_found;
@@ -83,6 +93,51 @@ public static function remove_caches () {
8393
delete_option('leaflet_geocoded_locations');
8494
}
8595

96+
/**
97+
* Determines whether a cached geocode entry is valid.
98+
*
99+
* @param mixed $location Cached location value from WordPress options.
100+
* @return bool
101+
*/
102+
private function is_valid_cached_location( $location ) {
103+
if ( ! is_object( $location ) ) {
104+
return false;
105+
}
106+
107+
if ( ! isset( $location->lat ) || ! isset( $location->lng ) ) {
108+
return false;
109+
}
110+
111+
return filter_var( $location->lat, FILTER_VALIDATE_FLOAT ) !== false
112+
&& filter_var( $location->lng, FILTER_VALIDATE_FLOAT ) !== false;
113+
}
114+
115+
/**
116+
* Removes a single geocode cache entry and its registry reference.
117+
*
118+
* @param string $cached_address Option key for the cached geocode.
119+
* @return void
120+
*/
121+
private function remove_cache_key( $cached_address ) {
122+
delete_option( $cached_address );
123+
124+
$addresses = get_option( 'leaflet_geocoded_locations', array() );
125+
$addresses = array_values(
126+
array_filter(
127+
$addresses,
128+
function ( $address ) use ( $cached_address ) {
129+
return $address !== $cached_address;
130+
}
131+
)
132+
);
133+
134+
if ( empty( $addresses ) ) {
135+
delete_option( 'leaflet_geocoded_locations' );
136+
} else {
137+
update_option( 'leaflet_geocoded_locations', $addresses );
138+
}
139+
}
140+
86141
/**
87142
* Used by geocoders to make requests via curl or file_get_contents
88143
*
@@ -158,24 +213,51 @@ private function google_geocode ( $address ) {
158213
/**
159214
* OpenStreetMap geocoder Nominatim (https://nominatim.openstreetmap.org/)
160215
*
161-
* @param string $address the urlencoded address to look up
162-
* @return varies object from API or null (failed)
216+
* @param string $address The URL-encoded address to look up.
217+
* @return object Object containing lat and lng properties.
218+
* @throws Exception When the request fails or no valid coordinates are returned.
163219
*/
220+
private function osm_geocode( $address ) {
221+
$request_url = sprintf(
222+
'https://nominatim.openstreetmap.org/search?format=jsonv2&limit=1&q=%s',
223+
$address
224+
);
164225

165-
private function osm_geocode ( $address ) {
166-
$geocode_url = 'https://nominatim.openstreetmap.org/?format=json&limit=1&q=';
167-
$geocode_url .= $address;
168-
$json = $this->get_url($geocode_url);
169-
$json = json_decode($json);
226+
$accept_language = str_replace( '_', '-', get_locale() );
227+
228+
$settings = Leaflet_Map_Plugin_Settings::init();
229+
$contact_email = $settings->get( 'nominatim_contact_email' );
170230

171-
if (isset($json[0]->lat) && isset($json[0]->lon)) {
172-
return (Object) array(
173-
'lat' => $json[0]->lat,
174-
'lng' => $json[0]->lon,
175-
);
176-
} else {
177-
return false;
231+
if ( empty( $contact_email ) ) {
232+
$contact_email = get_bloginfo( 'admin_email' );
178233
}
234+
235+
$contact_email = apply_filters( 'leaflet_map_nominatim_contact_email', $contact_email );
236+
237+
$agent = 'Nominatim query for ' . get_bloginfo( 'url' ) . '; contact ' . $contact_email;
238+
239+
$response = wp_remote_get(
240+
$request_url,
241+
array(
242+
'user-agent' => $agent,
243+
'headers' => array(
244+
'Accept-Language' => $accept_language,
245+
),
246+
)
247+
);
248+
249+
if ( ! is_wp_error( $response ) && isset( $response['body'] ) ) {
250+
$json = json_decode( $response['body'] );
251+
252+
if ( isset( $json[0]->lat ) && isset( $json[0]->lon ) ) {
253+
return (object) array(
254+
'lat' => $json[0]->lat,
255+
'lng' => $json[0]->lon,
256+
);
257+
}
258+
}
259+
260+
throw new Exception('No Address Found');
179261
}
180262

181263
/**
@@ -198,4 +280,4 @@ private function dawa_geocode ( $address ) {
198280
'lng' => $json[0]->adgangsadresse->adgangspunkt->koordinater[0]
199281
);
200282
}
201-
}
283+
}

class.plugin-option.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class Leaflet_Map_Plugin_Option
5555
public $min = 0;
5656
public $max = 0;
5757
public $step = 0;
58+
public $placeholder = '';
5859

5960
/**
6061
* Instantiate class
@@ -76,6 +77,7 @@ function __construct($details = array())
7677
'min' => FILTER_DEFAULT,
7778
'max' => FILTER_DEFAULT,
7879
'step' => FILTER_DEFAULT,
80+
'placeholder' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
7981
'options' => array(
8082
'filter' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
8183
'flags' => FILTER_FORCE_ARRAY
@@ -112,6 +114,7 @@ class="full-width"
112114
name="<?php echo $name; ?>"
113115
type="<?php echo $this->type; ?>"
114116
id="<?php echo $name; ?>"
117+
placeholder="<?php echo htmlspecialchars($this->placeholder); ?>"
115118
value="<?php echo htmlspecialchars($value); ?>"
116119
/>
117120
<?php
@@ -181,4 +184,4 @@ class="full-width">
181184
break;
182185
}
183186
}
184-
}
187+
}

class.plugin-settings.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,19 @@ private function __construct()
346346
),
347347
'helptext' => __('Select the Geocoding provider to use to retrieve addresses defined in shortcode.', 'leaflet-map')
348348
),
349+
'nominatim_contact_email' => array(
350+
'display_name'=>__('Nominatim Contact Email (optional)', 'leaflet-map'),
351+
'default' => '',
352+
'type' => 'text',
353+
'placeholder' => sprintf(
354+
__('defaults to admin email (%s)', 'leaflet-map'),
355+
get_bloginfo('admin_email')
356+
),
357+
'helptext' => __(
358+
'Optional contact email to send with OpenStreetMap Nominatim geocoding requests. If blank, the site admin email will be used.',
359+
'leaflet-map'
360+
),
361+
),
349362
'google_appkey' => array(
350363
'display_name'=>__('Google API Key (optional)', 'leaflet-map'),
351364
'default' => __('Supply a Google API Key', 'leaflet-map'),

readme.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ Lookup an address with:
2828

2929
`[leaflet-map address="chicago"]`
3030

31+
When using the OpenStreetMap Nominatim geocoder, the plugin sends a contact email in the request user agent. By default this uses the site admin email, but you can override it in the plugin settings or with the `leaflet_map_nominatim_contact_email` filter.
32+
33+
`add_filter('leaflet_map_nominatim_contact_email', function ($email) { return 'maps@example.com'; });`
34+
3135
Know the latitude and longitude of a location? Use them (and a zoom level) with:
3236

3337
`[leaflet-map lat=44.67 lng=-63.61 zoom=5]`

0 commit comments

Comments
 (0)