Skip to content

Add Leaflet.encoded plugin: Enable creating PolyLine and Polygon from encoded string #1928

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/user_guide/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Plugins
plugins/measure_control
plugins/mouse_position
plugins/pattern
plugins/polygon_encoded
plugins/polyline_encoded
plugins/polyline_offset
plugins/polyline_textpath
plugins/realtime
Expand Down Expand Up @@ -80,6 +82,10 @@ Plugins
- A control that displays geographic coordinates of the mouse pointer, as it is moved over the map.
* - :doc:`Pattern <plugins/pattern>`
- Add support for pattern fills on Paths.
* - :doc:`Polygon Encoded <plugins/polygon_encoded>`
- Draw a polygon directly from an encoded string.
* - :doc:`Polyline Encoded <plugins/polyline_encoded>`
- Draw a polyline directly from an encoded string.
* - :doc:`Polyline Offset <plugins/polyline_offset>`
- Shift relative pixel offset, without actually changing the actual latitude longitude values.
* - :doc:`Polyline Textpath <plugins/polyline_textpath>`
Expand Down
22 changes: 22 additions & 0 deletions docs/user_guide/plugins/polygon_encoded.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
```{code-cell} ipython3
---
nbsphinx: hidden
---
import folium
from folium import plugins
```

# PolygonFromEncoded

Create a Polygon directly from an encoded polyline string. To understand the encoding algorithm
refer to [this](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) link.

```{code-cell} ipython3

m = folium.Map(location=[40, -80], zoom_start=5)

encoded = r"w`j~FpxivO}jz@qnnCd}~Bsa{@~f`C`lkH"
plugins.PolygonFromEncoded(encoded=encoded).add_to(m)

m
```
22 changes: 22 additions & 0 deletions docs/user_guide/plugins/polyline_encoded.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
```{code-cell} ipython3
---
nbsphinx: hidden
---
import folium
from folium import plugins
```

# PolyLineFromEncoded

Create a PolyLine directly from an encoded polyline string. To understand the encoding algorithm
refer to [this](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) link.

```{code-cell} ipython3

m = folium.Map(location=[40, -120], zoom_start=5)

encoded = r"_p~iF~cn~U_ulLn{vA_mqNvxq`@"
plugins.PolyLineFromEncoded(encoded=encoded).add_to(m)

m
```
3 changes: 3 additions & 0 deletions folium/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from folium.plugins.boat_marker import BoatMarker
from folium.plugins.draw import Draw
from folium.plugins.dual_map import DualMap
from folium.plugins.encoded import PolygonFromEncoded, PolyLineFromEncoded
from folium.plugins.fast_marker_cluster import FastMarkerCluster
from folium.plugins.feature_group_sub_group import FeatureGroupSubGroup
from folium.plugins.float_image import FloatImage
Expand Down Expand Up @@ -55,6 +56,8 @@
"MeasureControl",
"MiniMap",
"MousePosition",
"PolygonFromEncoded",
"PolyLineFromEncoded",
"PolyLineTextPath",
"PolyLineOffset",
"Realtime",
Expand Down
121 changes: 121 additions & 0 deletions folium/plugins/encoded.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from abc import ABC, abstractmethod

from jinja2 import Template

from folium.elements import JSCSSMixin
from folium.features import MacroElement
from folium.vector_layers import path_options


class _BaseFromEncoded(JSCSSMixin, MacroElement, ABC):
"""Base Interface to create folium objects from encoded strings.

Derived classes must define `_encoding_type` property which returns the string
representation of the folium object to create from the encoded string.

Parameters
----------
encoded: str
The raw encoded string from the Polyline Encoding Algorithm. See:
https://developers.google.com/maps/documentation/utilities/polylinealgorithm
**kwargs:
Object options as accepted by leaflet.
"""

_template = Template(
"""
{% macro script(this, kwargs) %}

var {{ this.get_name() }} = L.{{ this._encoding_type }}.fromEncoded(
{{ this.encoded|tojson }},
{{ this.options|tojson }}
).addTo({{ this._parent.get_name() }});

{% endmacro %}
"""
)

default_js = [
(
"polyline-encoded",
"https://cdn.jsdelivr.net/npm/[email protected]/Polyline.encoded.js",
)
]

def __init__(self, encoded: str):
super().__init__()
self.encoded = encoded

@property
@abstractmethod
def _encoding_type(self) -> str:
"""An abstract getter to return the type of folium object to create."""
raise NotImplementedError


class PolyLineFromEncoded(_BaseFromEncoded):
"""Create PolyLines directly from the encoded string.

Parameters
----------
encoded: str
The raw encoded string from the Polyline Encoding Algorithm. See:
https://developers.google.com/maps/documentation/utilities/polylinealgorithm
**kwargs:
Polyline options as accepted by leaflet. See:
https://leafletjs.com/reference.html#polyline

Adapted from https://github.com/jieter/Leaflet.encoded

Examples
--------
>>> from folium import Map
>>> from folium.plugins import PolyLineFromEncoded
>>> m = Map()
>>> encoded = r"_p~iF~cn~U_ulLn{vA_mqNvxq`@"
>>> PolyLineFromEncoded(encoded=encoded, color="green").add_to(m)
"""

def __init__(self, encoded: str, **kwargs):
self._name = "PolyLineFromEncoded"
super().__init__(encoded=encoded)
self.options = path_options(line=True, **kwargs)

@property
def _encoding_type(self) -> str:
"""Return the name of folium object created from the encoded."""
return "Polyline"


class PolygonFromEncoded(_BaseFromEncoded):
"""Create Polygons directly from the encoded string.

Parameters
----------
encoded: str
The raw encoded string from the Polyline Encoding Algorithm. See:
https://developers.google.com/maps/documentation/utilities/polylinealgorithm
**kwargs:
Polygon options as accepted by leaflet. See:
https://leafletjs.com/reference.html#polygon

Adapted from https://github.com/jieter/Leaflet.encoded

Examples
--------
>>> from folium import Map
>>> from folium.plugins import PolygonFromEncoded
>>> m = Map()
>>> encoded = r"w`j~FpxivO}jz@qnnCd}~Bsa{@~f`C`lkH"
>>> PolygonFromEncoded(encoded=encoded).add_to(m)
"""

def __init__(self, encoded: str, **kwargs):
self._name = "PolygonFromEncoded"
super().__init__(encoded)
self.options = path_options(line=True, radius=None, **kwargs)

@property
def _encoding_type(self) -> str:
"""Return the name of folium object created from the encoded."""
return "Polygon"
82 changes: 82 additions & 0 deletions tests/plugins/test_encoded.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Test PolyLineFromEncoded Plugin."""

from jinja2 import Template

from folium import Map
from folium.plugins import PolygonFromEncoded, PolyLineFromEncoded
from folium.utilities import normalize


def test_polyline_from_encoded():
"""Test `PolyLineFromEncoded` plugin.

The test ensures:
- The original JS script is present in the HTML file.
- The rendering from `PolyLineFromEncoded` and the original plugin gives the
same output.
"""

m = Map([35.0, -120.0], zoom_start=3)

encoded = r"_p~iF~cn~U_ulLn{vA_mqNvxq`@"
kwargs = {"color": "green"}
polyline = PolyLineFromEncoded(encoded=encoded, **kwargs)

polyline.add_to(m)

out = normalize(m._parent.render())

script = '<script src="https://cdn.jsdelivr.net/npm/[email protected]/Polyline.encoded.js"></script>'
assert script in out

tmpl = Template(
"""
var {{this.get_name()}} = L.Polyline.fromEncoded(
{{ this.encoded|tojson }},
{{ this.options|tojson }}
).addTo({{this._parent.get_name()}});
"""
)

expected_render = tmpl.render(this=polyline)
actual_render = polyline._template.module.script(polyline)

assert normalize(expected_render) == normalize(actual_render)


def test_polygon_from_encoded():
"""Test `PolygonFromEncoded` plugin.

The test ensures:
- The original JS script is present in the HTML file.
- The rendering from `PolygonFromEncoded` and the original plugin gives the
same output.
"""

m = Map([40.0, -80.0], zoom_start=3)

encoded = r"w`j~FpxivO}jz@qnnCd}~Bsa{@~f`C`lkH"
polygon = PolygonFromEncoded(encoded=encoded, kwargs={})

polygon.add_to(m)

out = normalize(m._parent.render())

script = '<script src="https://cdn.jsdelivr.net/npm/[email protected]/Polyline.encoded.js"></script>'
assert script in out

tmpl = Template(
"""
var {{this.get_name()}} = L.Polygon.fromEncoded(
{{ this.encoded|tojson }},
{{ this.options|tojson }}
)
.addTo({{this._parent.get_name()}});
"""
)

expected_render = tmpl.render(this=polygon)

actual_render = polygon._template.module.script(polygon)

assert normalize(expected_render) == normalize(actual_render)