Skip to content

Commit 07617b3

Browse files
author
bkaba
committed
add coordinate_input plugin
1 parent f0c6464 commit 07617b3

2 files changed

Lines changed: 168 additions & 0 deletions

File tree

folium/plugins/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from folium.plugins.antpath import AntPath
44
from folium.plugins.beautify_icon import BeautifyIcon
55
from folium.plugins.boat_marker import BoatMarker
6+
from folium.plugins.coordinate_input import CoordinateInput
67
from folium.plugins.draw import Draw
78
from folium.plugins.dual_map import DualMap
89
from folium.plugins.encoded import PolygonFromEncoded, PolyLineFromEncoded
@@ -49,6 +50,7 @@
4950
"BeautifyIcon",
5051
"BoatMarker",
5152
"CirclePattern",
53+
"CoordinateInput",
5254
"Draw",
5355
"DualMap",
5456
"FastMarkerCluster",

folium/plugins/coordinate_input.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
from branca.element import MacroElement
2+
from folium.template import Template
3+
4+
5+
class CoordinateInput(MacroElement):
6+
"""
7+
Add input form to the map for entering coordinates and placing markers at those locations.
8+
Supports marker removal by double-clicking.
9+
10+
Parameters
11+
----------
12+
position : str, default 'topleft'
13+
Corner of the map where the input form will be placed.
14+
Options: 'topleft', 'topright', 'bottomleft', 'bottomright'
15+
placeholder : str, default 'Latitude, Longitude'
16+
Placeholder text for the coordinate input field (e.g., "40.7128, -74.0060")
17+
button_text : str, default 'Add Marker'
18+
Text displayed on the submit button
19+
popup_text : str, default None
20+
Text to display in marker popups. Use ${lat} and ${lng} for coordinates.
21+
If None, will show 'Lat: {lat}, Lon: {lng}'
22+
show_instructions : bool, default True
23+
Whether to show instructions for removing markers
24+
25+
Examples
26+
--------
27+
>>> m = folium.Map([45.5, -122.3], zoom_start=13)
28+
>>> CoordinateInput().add_to(m)
29+
>>> m.save('map.html')
30+
31+
>>> # With custom settings
32+
>>> CoordinateInput(
33+
... position='topright',
34+
... placeholder='Enter coordinates',
35+
... button_text='Place Marker',
36+
... popup_text='<b>Coordinates:</b><br>Lat: ${lat}<br>Lon: ${lng}'
37+
... ).add_to(m)
38+
39+
To remove a marker, double-click on it.
40+
"""
41+
42+
_template = Template(
43+
r"""
44+
{% macro script(this, kwargs) %}
45+
(function() {
46+
var {{ this.get_name() }}_control = L.control({position: '{{ this.position }}'});
47+
48+
{{ this.get_name() }}_control.onAdd = function (map) {
49+
var div = L.DomUtil.create('div', 'leaflet-control leaflet-bar');
50+
div.id = '{{ this.get_name() }}_container';
51+
div.style.backgroundColor = 'white';
52+
div.style.padding = '10px';
53+
div.style.borderRadius = '5px';
54+
div.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
55+
56+
var instructionsHtml = '{% if this.show_instructions %}<div style="margin-bottom: 8px; font-size: 10px; color: #666; max-width: 150px;"><em>Double-click marker to remove</em></div>{% endif %}';
57+
58+
div.innerHTML = '<div style="font-family: Arial, sans-serif; font-size: 12px; width: 150px;">' +
59+
'<div style="margin-bottom: 8px; font-weight: bold;">Add Marker</div>' +
60+
instructionsHtml +
61+
'<input type="text" id="{{ this.get_name() }}_coords" placeholder="{{ this.placeholder }}" style="width: 100%; padding: 5px; margin-bottom: 5px; box-sizing: border-box;">' +
62+
'<button id="{{ this.get_name() }}_btn" style="width: 100%; background-color: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 12px;">{{ this.button_text }}</button>' +
63+
'<button id="{{ this.get_name() }}_clear_btn" style="width: 100%; margin-top: 5px; background-color: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 12px;">Clear All</button>' +
64+
'</div>';
65+
66+
L.DomEvent.disableClickPropagation(div);
67+
68+
return div;
69+
};
70+
71+
{{ this.get_name() }}_control.addTo({{ this._parent.get_name() }});
72+
73+
setTimeout(function() {
74+
var btnElement = document.getElementById('{{ this.get_name() }}_btn');
75+
var clearBtnElement = document.getElementById('{{ this.get_name() }}_clear_btn');
76+
var coordsElement = document.getElementById('{{ this.get_name() }}_coords');
77+
var map = {{ this._parent.get_name() }};
78+
var markerGroup = L.featureGroup();
79+
markerGroup.addTo(map);
80+
81+
if (btnElement && coordsElement) {
82+
var addMarker = function() {
83+
var input = coordsElement.value.trim();
84+
var parts = input.split(',');
85+
86+
if (parts.length !== 2) {
87+
alert('Please enter coordinates in format: latitude, longitude');
88+
return;
89+
}
90+
91+
var lat = parseFloat(parts[0].trim());
92+
var lng = parseFloat(parts[1].trim());
93+
94+
if (isNaN(lat) || isNaN(lng)) {
95+
alert('Please enter valid numbers for coordinates');
96+
return;
97+
}
98+
99+
if (lat < -90 || lat > 90) {
100+
alert('Latitude must be between -90 and 90');
101+
return;
102+
}
103+
104+
if (lng < -180 || lng > 180) {
105+
alert('Longitude must be between -180 and 180');
106+
return;
107+
}
108+
109+
var marker = L.marker([lat, lng]);
110+
111+
var popupText = '{{ this.popup_text }}';
112+
if (popupText === '') {
113+
popupText = 'Lat: ' + lat.toFixed(4) + '<br>Lon: ' + lng.toFixed(4);
114+
} else {
115+
popupText = popupText.replace(/\$\{lat\}/g, lat.toFixed(4));
116+
popupText = popupText.replace(/\$\{lng\}/g, lng.toFixed(4));
117+
}
118+
marker.bindPopup(popupText);
119+
120+
// Add double-click to remove marker
121+
marker.on('dblclick', function() {
122+
markerGroup.removeLayer(marker);
123+
});
124+
125+
markerGroup.addLayer(marker);
126+
coordsElement.value = '';
127+
marker.openPopup();
128+
};
129+
130+
btnElement.addEventListener('click', addMarker);
131+
132+
coordsElement.addEventListener('keypress', function(e) {
133+
if (e.key === 'Enter') {
134+
addMarker();
135+
}
136+
});
137+
138+
clearBtnElement.addEventListener('click', function() {
139+
if (confirm('Are you sure you want to remove all markers?')) {
140+
markerGroup.clearLayers();
141+
coordsElement.value = '';
142+
}
143+
});
144+
}
145+
}, 100);
146+
})();
147+
{% endmacro %}
148+
"""
149+
)
150+
151+
def __init__(
152+
self,
153+
position: str = "topleft",
154+
placeholder: str = "Latitude, Longitude",
155+
button_text: str = "Add Marker",
156+
popup_text: str = "",
157+
show_instructions: bool = True,
158+
):
159+
super().__init__()
160+
self._name = "CoordinateInput"
161+
162+
self.position = position
163+
self.placeholder = placeholder
164+
self.button_text = button_text
165+
self.popup_text = popup_text if popup_text else "Lat: ${lat}<br>Lon: ${lng}"
166+
self.show_instructions = show_instructions

0 commit comments

Comments
 (0)