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,
Folium gives a folium. Marker() class for plotting markers on a map. Just pass the latitude and longitude of the location, mention the popup and tooltip and add it to the map.
We used the geopy library to get the latitude and longitude of the location. Then we used the "folium. map" method of the folium package for creating the base of Google Maps. In step 2, we used "folium.
The basic code for changing the map tiles used by folium is to first add a specified tile layer to the map (in this case “my_map”). You can also add a control for switching between layers with the “LayerControl()” method. # other mapping code (e.g. lines, markers etc.)
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)
If you are not bound to using folium, this kind of interactivity can be achieve using dash-leaflet rather easily,
import json
import dash
import dash_leaflet as dl
import dash_html_components as html
from dash.dependencies import Input, Output, ALL
# Setup markers.
locations = [[45.5012, -122.6655], [45.5132, -122.6708], [45.5275, -122.6692], [45.5318, -122.6745]]
markers = [dl.Marker(position=l, id=dict(id=i)) for i, l in enumerate(locations)]
# Create small example dash app showing a map and a div (for logging).
app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div([
dl.Map([dl.TileLayer()] + markers, center=[45.5236, -122.6750], zoom=14,
style={'width': '1000px', 'height': '500px'}),
html.Div(id="log")
])
# Callback for interactivity.
@app.callback(Output("log", "children"), Input(dict(id=ALL), "n_clicks"))
def log_position(_):
idx = json.loads(dash.callback_context.triggered[0]['prop_id'].split(".")[0])["id"]
location = locations[idx]
print(location) # print location to console
return location # print location to ui
if __name__ == '__main__':
app.run_server()
I wasn't sure what you meant by "copy", so I have demonstrated in the example how the location can be accessed both in the Python layer (printed to console) and in the ui layer (written to a div).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With