diff --git a/folium/plugins/draw.py b/folium/plugins/draw.py
index 2de7733bbd..648d553e77 100644
--- a/folium/plugins/draw.py
+++ b/folium/plugins/draw.py
@@ -3,6 +3,25 @@
from folium.elements import JSCSSMixin
from folium.template import Template
+# Each corner maps to the pair of CSS edge offsets that pins the export button
+# there. "topright" reproduces the historical hard-coded placement exactly.
+_EXPORT_POSITION_CSS = {
+ "topright": "top: 90px;\n right: 10px;",
+ "topleft": "top: 90px;\n left: 10px;",
+ "bottomright": "bottom: 20px;\n right: 10px;",
+ "bottomleft": "bottom: 20px;\n left: 10px;",
+}
+
+
+def _export_position_to_css(export_position):
+ try:
+ return _EXPORT_POSITION_CSS[export_position]
+ except KeyError:
+ raise ValueError(
+ "export_position must be one of "
+ f"{sorted(_EXPORT_POSITION_CSS)}, not {export_position!r}"
+ ) from None
+
class Draw(JSCSSMixin, MacroElement):
'''
@@ -20,6 +39,10 @@ class Draw(JSCSSMixin, MacroElement):
position : {'topleft', 'toprigth', 'bottomleft', 'bottomright'}
Position of control.
See https://leafletjs.com/reference.html#control
+ export_position : {'topright', 'topleft', 'bottomright', 'bottomleft'}
+ Corner of the map to place the export button in, when ``export``
+ is True. Defaults to 'topright'. Use this to keep the button clear
+ of other controls such as a LayerControl.
show_geometry_on_click : bool, default True
When True, opens an alert with the geometry description on click.
draw_options : dict, optional
@@ -60,7 +83,7 @@ class Draw(JSCSSMixin, MacroElement):
Export
@@ -156,6 +178,7 @@ def __init__(
feature_group=None,
filename="data.geojson",
position="topleft",
+ export_position="topright",
show_geometry_on_click=True,
draw_options=None,
edit_options=None,
@@ -167,6 +190,7 @@ def __init__(
self.feature_group = feature_group
self.filename = filename
self.position = position
+ self.export_position_css = _export_position_to_css(export_position)
self.show_geometry_on_click = show_geometry_on_click
self.draw_options = draw_options or {}
self.edit_options = edit_options or {}
diff --git a/tests/plugins/test_draw.py b/tests/plugins/test_draw.py
index 16889b1aea..507474de2a 100644
--- a/tests/plugins/test_draw.py
+++ b/tests/plugins/test_draw.py
@@ -5,6 +5,8 @@
import re
+import pytest
+
import folium
from folium import plugins
from folium.template import Template
@@ -68,3 +70,42 @@ def test_two_draw_controls_get_unique_export_ids():
handler = out[start : out.index("}", start) + 1]
assert f"drawnItems_{draw.get_name()}.toGeoJSON()" in handler
assert filename in handler
+
+
+def test_draw_export_position_default():
+ """The default export button keeps its historical top-right placement."""
+ m = folium.Map([45.0, 3.0], zoom_start=4)
+ draw = plugins.Draw(export=True)
+ m.add_child(draw)
+
+ out = normalize(m._parent.render())
+
+ block = out[out.index(f"#export_{draw.get_name()}") :]
+ block = block[: block.index("}")]
+ assert "position: absolute;" in block
+ assert "top: 90px;" in block
+ assert "right: 10px;" in block
+
+
+def test_draw_export_position_corners():
+ expected = {
+ "topright": ("top: 90px;", "right: 10px;"),
+ "topleft": ("top: 90px;", "left: 10px;"),
+ "bottomright": ("bottom: 20px;", "right: 10px;"),
+ "bottomleft": ("bottom: 20px;", "left: 10px;"),
+ }
+ for position, edges in expected.items():
+ m = folium.Map([45.0, 3.0], zoom_start=4)
+ draw = plugins.Draw(export=True, export_position=position)
+ m.add_child(draw)
+
+ out = normalize(m._parent.render())
+ block = out[out.index(f"#export_{draw.get_name()}") :]
+ block = block[: block.index("}")]
+ for edge in edges:
+ assert edge in block, f"{position}: {edge!r} missing from {block!r}"
+
+
+def test_draw_export_position_invalid():
+ with pytest.raises(ValueError, match="export_position must be one of"):
+ plugins.Draw(export=True, export_position="middle")