Skip to content

Commit 5086929

Browse files
Add Leaflet.encoded plugin: Enable creating PolyLine and Polygon from encoded string (#1928)
* add plugin to visualize the polyline from an encoded string * correct import in user guide * Update polyline_encoded.py Added type information to for argument to `__init__` * rework encoded plugin to include PolygonFromEncoded * include doc and tests for PolygonFromEncoded * update doc to include the link of the algo * use path_options instead of parse_options * set path_options to an attribute --------- Co-authored-by: Hans Then <[email protected]>
1 parent 55a758a commit 5086929

File tree

6 files changed

+256
-0
lines changed

6 files changed

+256
-0
lines changed

docs/user_guide/plugins.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Plugins
2222
plugins/measure_control
2323
plugins/mouse_position
2424
plugins/pattern
25+
plugins/polygon_encoded
26+
plugins/polyline_encoded
2527
plugins/polyline_offset
2628
plugins/polyline_textpath
2729
plugins/realtime
@@ -80,6 +82,10 @@ Plugins
8082
- A control that displays geographic coordinates of the mouse pointer, as it is moved over the map.
8183
* - :doc:`Pattern <plugins/pattern>`
8284
- Add support for pattern fills on Paths.
85+
* - :doc:`Polygon Encoded <plugins/polygon_encoded>`
86+
- Draw a polygon directly from an encoded string.
87+
* - :doc:`Polyline Encoded <plugins/polyline_encoded>`
88+
- Draw a polyline directly from an encoded string.
8389
* - :doc:`Polyline Offset <plugins/polyline_offset>`
8490
- Shift relative pixel offset, without actually changing the actual latitude longitude values.
8591
* - :doc:`Polyline Textpath <plugins/polyline_textpath>`
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
```{code-cell} ipython3
2+
---
3+
nbsphinx: hidden
4+
---
5+
import folium
6+
from folium import plugins
7+
```
8+
9+
# PolygonFromEncoded
10+
11+
Create a Polygon directly from an encoded polyline string. To understand the encoding algorithm
12+
refer to [this](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) link.
13+
14+
```{code-cell} ipython3
15+
16+
m = folium.Map(location=[40, -80], zoom_start=5)
17+
18+
encoded = r"w`j~FpxivO}jz@qnnCd}~Bsa{@~f`C`lkH"
19+
plugins.PolygonFromEncoded(encoded=encoded).add_to(m)
20+
21+
m
22+
```
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
```{code-cell} ipython3
2+
---
3+
nbsphinx: hidden
4+
---
5+
import folium
6+
from folium import plugins
7+
```
8+
9+
# PolyLineFromEncoded
10+
11+
Create a PolyLine directly from an encoded polyline string. To understand the encoding algorithm
12+
refer to [this](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) link.
13+
14+
```{code-cell} ipython3
15+
16+
m = folium.Map(location=[40, -120], zoom_start=5)
17+
18+
encoded = r"_p~iF~cn~U_ulLn{vA_mqNvxq`@"
19+
plugins.PolyLineFromEncoded(encoded=encoded).add_to(m)
20+
21+
m
22+
```

folium/plugins/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from folium.plugins.boat_marker import BoatMarker
66
from folium.plugins.draw import Draw
77
from folium.plugins.dual_map import DualMap
8+
from folium.plugins.encoded import PolygonFromEncoded, PolyLineFromEncoded
89
from folium.plugins.fast_marker_cluster import FastMarkerCluster
910
from folium.plugins.feature_group_sub_group import FeatureGroupSubGroup
1011
from folium.plugins.float_image import FloatImage
@@ -55,6 +56,8 @@
5556
"MeasureControl",
5657
"MiniMap",
5758
"MousePosition",
59+
"PolygonFromEncoded",
60+
"PolyLineFromEncoded",
5861
"PolyLineTextPath",
5962
"PolyLineOffset",
6063
"Realtime",

folium/plugins/encoded.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from abc import ABC, abstractmethod
2+
3+
from jinja2 import Template
4+
5+
from folium.elements import JSCSSMixin
6+
from folium.features import MacroElement
7+
from folium.vector_layers import path_options
8+
9+
10+
class _BaseFromEncoded(JSCSSMixin, MacroElement, ABC):
11+
"""Base Interface to create folium objects from encoded strings.
12+
13+
Derived classes must define `_encoding_type` property which returns the string
14+
representation of the folium object to create from the encoded string.
15+
16+
Parameters
17+
----------
18+
encoded: str
19+
The raw encoded string from the Polyline Encoding Algorithm. See:
20+
https://developers.google.com/maps/documentation/utilities/polylinealgorithm
21+
**kwargs:
22+
Object options as accepted by leaflet.
23+
"""
24+
25+
_template = Template(
26+
"""
27+
{% macro script(this, kwargs) %}
28+
29+
var {{ this.get_name() }} = L.{{ this._encoding_type }}.fromEncoded(
30+
{{ this.encoded|tojson }},
31+
{{ this.options|tojson }}
32+
).addTo({{ this._parent.get_name() }});
33+
34+
{% endmacro %}
35+
"""
36+
)
37+
38+
default_js = [
39+
(
40+
"polyline-encoded",
41+
"https://cdn.jsdelivr.net/npm/[email protected]/Polyline.encoded.js",
42+
)
43+
]
44+
45+
def __init__(self, encoded: str):
46+
super().__init__()
47+
self.encoded = encoded
48+
49+
@property
50+
@abstractmethod
51+
def _encoding_type(self) -> str:
52+
"""An abstract getter to return the type of folium object to create."""
53+
raise NotImplementedError
54+
55+
56+
class PolyLineFromEncoded(_BaseFromEncoded):
57+
"""Create PolyLines directly from the encoded string.
58+
59+
Parameters
60+
----------
61+
encoded: str
62+
The raw encoded string from the Polyline Encoding Algorithm. See:
63+
https://developers.google.com/maps/documentation/utilities/polylinealgorithm
64+
**kwargs:
65+
Polyline options as accepted by leaflet. See:
66+
https://leafletjs.com/reference.html#polyline
67+
68+
Adapted from https://github.com/jieter/Leaflet.encoded
69+
70+
Examples
71+
--------
72+
>>> from folium import Map
73+
>>> from folium.plugins import PolyLineFromEncoded
74+
>>> m = Map()
75+
>>> encoded = r"_p~iF~cn~U_ulLn{vA_mqNvxq`@"
76+
>>> PolyLineFromEncoded(encoded=encoded, color="green").add_to(m)
77+
"""
78+
79+
def __init__(self, encoded: str, **kwargs):
80+
self._name = "PolyLineFromEncoded"
81+
super().__init__(encoded=encoded)
82+
self.options = path_options(line=True, **kwargs)
83+
84+
@property
85+
def _encoding_type(self) -> str:
86+
"""Return the name of folium object created from the encoded."""
87+
return "Polyline"
88+
89+
90+
class PolygonFromEncoded(_BaseFromEncoded):
91+
"""Create Polygons directly from the encoded string.
92+
93+
Parameters
94+
----------
95+
encoded: str
96+
The raw encoded string from the Polyline Encoding Algorithm. See:
97+
https://developers.google.com/maps/documentation/utilities/polylinealgorithm
98+
**kwargs:
99+
Polygon options as accepted by leaflet. See:
100+
https://leafletjs.com/reference.html#polygon
101+
102+
Adapted from https://github.com/jieter/Leaflet.encoded
103+
104+
Examples
105+
--------
106+
>>> from folium import Map
107+
>>> from folium.plugins import PolygonFromEncoded
108+
>>> m = Map()
109+
>>> encoded = r"w`j~FpxivO}jz@qnnCd}~Bsa{@~f`C`lkH"
110+
>>> PolygonFromEncoded(encoded=encoded).add_to(m)
111+
"""
112+
113+
def __init__(self, encoded: str, **kwargs):
114+
self._name = "PolygonFromEncoded"
115+
super().__init__(encoded)
116+
self.options = path_options(line=True, radius=None, **kwargs)
117+
118+
@property
119+
def _encoding_type(self) -> str:
120+
"""Return the name of folium object created from the encoded."""
121+
return "Polygon"

tests/plugins/test_encoded.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""Test PolyLineFromEncoded Plugin."""
2+
3+
from jinja2 import Template
4+
5+
from folium import Map
6+
from folium.plugins import PolygonFromEncoded, PolyLineFromEncoded
7+
from folium.utilities import normalize
8+
9+
10+
def test_polyline_from_encoded():
11+
"""Test `PolyLineFromEncoded` plugin.
12+
13+
The test ensures:
14+
- The original JS script is present in the HTML file.
15+
- The rendering from `PolyLineFromEncoded` and the original plugin gives the
16+
same output.
17+
"""
18+
19+
m = Map([35.0, -120.0], zoom_start=3)
20+
21+
encoded = r"_p~iF~cn~U_ulLn{vA_mqNvxq`@"
22+
kwargs = {"color": "green"}
23+
polyline = PolyLineFromEncoded(encoded=encoded, **kwargs)
24+
25+
polyline.add_to(m)
26+
27+
out = normalize(m._parent.render())
28+
29+
script = '<script src="https://cdn.jsdelivr.net/npm/[email protected]/Polyline.encoded.js"></script>'
30+
assert script in out
31+
32+
tmpl = Template(
33+
"""
34+
var {{this.get_name()}} = L.Polyline.fromEncoded(
35+
{{ this.encoded|tojson }},
36+
{{ this.options|tojson }}
37+
).addTo({{this._parent.get_name()}});
38+
"""
39+
)
40+
41+
expected_render = tmpl.render(this=polyline)
42+
actual_render = polyline._template.module.script(polyline)
43+
44+
assert normalize(expected_render) == normalize(actual_render)
45+
46+
47+
def test_polygon_from_encoded():
48+
"""Test `PolygonFromEncoded` plugin.
49+
50+
The test ensures:
51+
- The original JS script is present in the HTML file.
52+
- The rendering from `PolygonFromEncoded` and the original plugin gives the
53+
same output.
54+
"""
55+
56+
m = Map([40.0, -80.0], zoom_start=3)
57+
58+
encoded = r"w`j~FpxivO}jz@qnnCd}~Bsa{@~f`C`lkH"
59+
polygon = PolygonFromEncoded(encoded=encoded, kwargs={})
60+
61+
polygon.add_to(m)
62+
63+
out = normalize(m._parent.render())
64+
65+
script = '<script src="https://cdn.jsdelivr.net/npm/[email protected]/Polyline.encoded.js"></script>'
66+
assert script in out
67+
68+
tmpl = Template(
69+
"""
70+
var {{this.get_name()}} = L.Polygon.fromEncoded(
71+
{{ this.encoded|tojson }},
72+
{{ this.options|tojson }}
73+
)
74+
.addTo({{this._parent.get_name()}});
75+
"""
76+
)
77+
78+
expected_render = tmpl.render(this=polygon)
79+
80+
actual_render = polygon._template.module.script(polygon)
81+
82+
assert normalize(expected_render) == normalize(actual_render)

0 commit comments

Comments
 (0)