class GeoMap:
def update(self, location_center:np.array, locations: np.array):
self.map = folium.Map(location_center, zoom_start=10)
for i in range(locations.shape[0]):
location = tuple(locations[i, j] for j in range(locations.shape[1]))
folium.Marker(
location=location,
).add_to(self.map)
formatter = "function(num) {return L.Util.formatNum(num, 3) + ' º ';};"
plugins.MousePosition(
position="topright",
separator=" | ",
empty_string="NaN",
lng_first=True,
num_digits=20,
prefix="Coordinates:",
lat_formatter=formatter,
lng_formatter=formatter,
).add_to(self.map)
def display(self):
display(self.map)
But, I would like to enable the user to copy a marker's location on a folium Map by clicking on it. I suppose that there may be a way to get the location of a marker using a on_click event (in Python). But, I did not find any example on the web.
I am using Python, but if you have a solution that works well using Python and some Javascript it will be fine too.
Any help would be really appreciated !
Thanks,
Since you're working with markers, you can add a popup to each marker. The popup will open when you click on the marker icon. popup
can take an html
string as input so you can use this to insert a copy button or something similar.
Subsequently, you will need to add a javascript
copy function to the folium
html output. This can be done with MacroElement
.
Implementing this would result in the following basic example:
import folium
import jinja2
location_center = [45.5236, -122.6750]
locations = [[45.5012, -122.6655],[45.5132, -122.6708],[45.5275, -122.6692],[45.5318, -122.6745]]
m = folium.Map(location_center, zoom_start=13)
for location in locations:
folium.Marker(
location=location,
popup = f'<input type="text" value="{location[0]}, {location[1]}" id="myInput"><button onclick="myFunction()">Copy location</button>'
).add_to(m)
el = folium.MacroElement().add_to(m)
el._template = jinja2.Template("""
{% macro script(this, kwargs) %}
function myFunction() {
/* Get the text field */
var copyText = document.getElementById("myInput");
/* Select the text field */
copyText.select();
copyText.setSelectionRange(0, 99999); /* For mobile devices */
/* Copy the text inside the text field */
document.execCommand("copy");
}
{% endmacro %}
""")
display(m)
In case you wish to copy the latitude and longitude directly when clicking the marker: this is also possible, but requires monkey patching the Marker
's jinja
template to add a click event. Monkey patching is needed because the template for the markers is hard coded in folium
.
Additionally, the function that will be triggered on the click can be defined with MacroElement
:
import folium
import jinja2
from jinja2 import Template
from folium.map import Marker
tmpldata = """<!-- monkey patched Marker template -->
{% macro script(this, kwargs) %}
var {{ this.get_name() }} = L.marker(
{{ this.location|tojson }},
{{ this.options|tojson }}
).addTo({{ this._parent.get_name() }}).on('click', onClick);
{% endmacro %}
"""
Marker._mytemplate = Template(tmpldata)
def myMarkerInit(self, *args, **kwargs):
self.__init_orig__(*args, **kwargs)
self._template = self._mytemplate
Marker.__init_orig__ = Marker.__init__
Marker.__init__ = myMarkerInit
location_center = [45.5236, -122.6750]
locations = [[45.5012, -122.6655],[45.5132, -122.6708],[45.5275, -122.6692],[45.5318, -122.6745]]
m = folium.Map(location_center, zoom_start=13)
for location in locations: #range(locations.shape[0]):
folium.Marker(
location=location,
popup = f'<p id="latlon">{location[0]}, {location[1]}</p>'
).add_to(m)
el = folium.MacroElement().add_to(m)
el._template = jinja2.Template("""
{% macro script(this, kwargs) %}
function copy(text) {
var input = document.createElement('textarea');
input.innerHTML = text;
document.body.appendChild(input);
input.select();
var result = document.execCommand('copy');
document.body.removeChild(input);
return result;
};
function getInnerText( sel ) {
var txt = '';
$( sel ).contents().each(function() {
var children = $(this).children();
txt += ' ' + this.nodeType === 3 ? this.nodeValue : children.length ? getInnerText( this ) : $(this).text();
});
return txt;
};
function onClick(e) {
var popup = e.target.getPopup();
var content = popup.getContent();
text = getInnerText(content);
copy(text);
};
{% endmacro %}
""")
display(m)
In case of working with draggable markers by setting draggable=True
in the Marker object, copying the hardcoded coordinates from the popup makes no sense. In that case you'd better retrieve the latest coordinates from the Marker object and update the popup accordingly:
import folium
import jinja2
from jinja2 import Template
from folium.map import Marker
tmpldata = """<!-- monkey patched Marker template -->
{% macro script(this, kwargs) %}
var {{ this.get_name() }} = L.marker(
{{ this.location|tojson }},
{{ this.options|tojson }}
).addTo({{ this._parent.get_name() }}).on('click', onClick);
{% endmacro %}
"""
Marker._mytemplate = Template(tmpldata)
def myMarkerInit(self, *args, **kwargs):
self.__init_orig__(*args, **kwargs)
self._template = self._mytemplate
Marker.__init_orig__ = Marker.__init__
Marker.__init__ = myMarkerInit
location_center = [45.5236, -122.6750]
locations = [[45.5012, -122.6655],[45.5132, -122.6708],[45.5275, -122.6692],[45.5318, -122.6745]]
m = folium.Map(location_center, zoom_start=13)
for location in locations: #range(locations.shape[0]):
folium.Marker(
location=location,
popup = f'<p id="latlon">{location[0]}, {location[1]}</p>',
draggable=True
).add_to(m)
el = folium.MacroElement().add_to(m)
el._template = jinja2.Template("""
{% macro script(this, kwargs) %}
function copy(text) {
var input = document.createElement('textarea');
input.innerHTML = text;
document.body.appendChild(input);
input.select();
var result = document.execCommand('copy');
document.body.removeChild(input);
return result;
};
function onClick(e) {
var lat = e.latlng.lat;
var lng = e.latlng.lng;
var newContent = '<p id="latlon">' + lat + ', ' + lng + '</p>';
e.target.setPopupContent(newContent);
copy(lat + ', ' + lng);
};
{% endmacro %}
""")
display(m)