Skip to content

Commit

Permalink
expanding Geocoder plugin to support other built-in providers support…
Browse files Browse the repository at this point in the history
…ed by leaflet-control-geocoder

also allows for user-defined geocode providers
  • Loading branch information
prusswan committed Dec 24, 2023
1 parent 2586125 commit ac95f40
Showing 1 changed file with 119 additions and 2 deletions.
121 changes: 119 additions & 2 deletions folium/plugins/geocoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ class Geocoder(JSCSSMixin, MacroElement):
Choose from 'topleft', 'topright', 'bottomleft' or 'bottomright'.
add_marker: bool, default True
If True, adds a marker on the found location.
geocodeProvider: str, default 'nominatim'
see https://github.com/perliedman/leaflet-control-geocoder/tree/2.4.0/src/geocoders for other built-in providers
geocodeProviderOptions: dict, default None
For use with specific providers that may require api keys or other parameters
serviceUrl: str, default None
For use with user-defined geocode provider. If used, geocodeFunction must also be specified, geocodeProvider param will be ignored.
geocodeFunction: str, default None
geocode function of user-defined geocode provider. see https://github.com/perliedman/leaflet-control-geocoder/blob/1.13.0/src/geocoders/nominatim.js for a reference implementation
resultsHandlerFunction: str, default None
For use with user-defined geocode provider, to format geocoding responses into the format expected by leaflet-control-geocoder. Each result should have 'name','center' (L.latLng),'bbox' (L.latLngBounds) properties. see https://github.com/perliedman/leaflet-control-geocoder/blob/1.13.0/src/geocoders/photon.js (_decodeFeatures) for a reference implementation
For all options see https://github.com/perliedman/leaflet-control-geocoder
Expand All @@ -27,8 +37,103 @@ class Geocoder(JSCSSMixin, MacroElement):
_template = Template(
"""
{% macro script(this, kwargs) %}
var geocoderOpts_{{ this.get_name() }} = {{ this.options|tojson }};
if ('geocodeFunction' in geocoderOpts_{{ this.get_name() }}) {
eval("geocoderOpts_{{ this.get_name() }}['geocodeFunction'] = " + geocoderOpts_{{ this.get_name() }}['geocodeFunction']);
}
if ('resultsHandlerFunction' in geocoderOpts_{{ this.get_name() }}) {
eval("geocoderOpts_{{ this.get_name() }}['resultsHandlerFunction'] = " + geocoderOpts_{{ this.get_name() }}['resultsHandlerFunction']);
}
var customGeocoderTemplate_{{ this.get_name() }} = {
class: L.Class.extend({
options: {
serviceUrl: geocoderOpts_{{ this.get_name() }}['serviceUrl'],
geocodingQueryParams: geocoderOpts_{{ this.get_name() }}['geocodingQueryParams'] || {},
reverseQueryParams: geocoderOpts_{{ this.get_name() }}['reverseQueryParams'] || {},
},
initialize: function(options) {
L.Util.setOptions(this, options || {});
},
geocode: geocoderOpts_{{ this.get_name() }}['geocodeFunction'],
suggest: function(query,cb,context) {
return this.geocode(query, cb, context);
},
// placeholder: not used by folium.plugin.Geocoder
reverse: function(location, scale, cb, context) {
var params = L.extend(
{
//defaultParam: defaultParamValue
},
this.options.reverseQueryParams
);
this.getJSON(
this.options.serviceUrl + 'reverse',
params,
L.bind(function(data) {
cb.call(context, this._resultsHandler(data));
}, this)
);
},
_resultsHandler: function(data) {
if ('resultsHandlerFunction' in geocoderOpts_{{ this.get_name() }}) {
return geocoderOpts_{{ this.get_name() }}['resultsHandlerFunction'](data);
}
return data;
},
getJSON: function(url, params, callback) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState !== 4){
return;
}
if (xmlHttp.status !== 200 && xmlHttp.status !== 304){
callback('');
return;
}
callback(JSON.parse(xmlHttp.response));
};
xmlHttp.open('GET', url + L.Util.getParamString(params), true);
xmlHttp.setRequestHeader('Accept', 'application/json');
xmlHttp.send(null);
}
}),
factory: function(options) {
return new L.Control.Geocoder.Custom_{{ this.get_name() }}(options);
},
}
L.Util.extend(L.Control.Geocoder, {
Custom_{{ this.get_name() }}: customGeocoderTemplate_{{ this.get_name() }}.class,
custom_{{ this.get_name() }}: customGeocoderTemplate_{{ this.get_name() }}.factory
});
// use lowercase for geocoder name
var geocoderName_{{ this.get_name() }};
if ('serviceUrl' in geocoderOpts_{{ this.get_name() }}) {
geocoderName_{{ this.get_name() }} = 'custom_{{ this.get_name() }}';
}
else {
geocoderName_{{ this.get_name() }} = geocoderOpts_{{ this.get_name() }}["geocodeProvider"];
}
var customGeocoder_{{ this.get_name() }} = L.Control.Geocoder[ geocoderName_{{ this.get_name() }} ](
geocoderOpts_{{ this.get_name() }}['geocodeProviderOptions']
);
geocoderOpts_{{ this.get_name() }}["geocoder"] = customGeocoder_{{ this.get_name() }};
L.Control.geocoder(
{{ this.options|tojson }}
geocoderOpts_{{ this.get_name() }}
).on('markgeocode', function(e) {
{{ this._parent.get_name() }}.setView(e.geocode.center, 11);
}).addTo({{ this._parent.get_name() }});
Expand All @@ -50,12 +155,24 @@ class Geocoder(JSCSSMixin, MacroElement):
)
]

def __init__(self, collapsed=False, position="topright", add_marker=True, **kwargs):
def __init__(self, collapsed=False, position="topright", add_marker=True,
geocodeProvider="nominatim",
geocodeProviderOptions=None,
serviceUrl=None,
geocodeFunction=None,
resultsHandlerFunction=None,
**kwargs):
super().__init__()
self._name = "Geocoder"
self.options = parse_options(
collapsed=collapsed,
position=position,
defaultMarkGeocode=add_marker,
geocodeProvider=geocodeProvider,
serviceUrl=serviceUrl,
geocodeFunction=geocodeFunction,
resultsHandlerFunction=resultsHandlerFunction,
**kwargs
)
if geocodeProviderOptions is not None:
self.options['geocodeProviderOptions'] = geocodeProviderOptions

0 comments on commit ac95f40

Please sign in to comment.