-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Add layer control tree plugin #1895
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
Conengmo
merged 3 commits into
python-visualization:main
from
hansthen:layer_control_tree
Apr 4, 2024
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| ```{code-cell} ipython3 | ||
| --- | ||
| nbsphinx: hidden | ||
| --- | ||
| import folium | ||
| import folium.plugins | ||
| ``` | ||
|
|
||
| # TreeLayerControl | ||
| Create a Layer Control allowing a tree structure for the layers. | ||
|
|
||
| See https://github.com/jjimenezshaw/Leaflet.Control.Layers.Tree for more | ||
| information. | ||
|
|
||
| ## Simple example | ||
|
|
||
| ```{code-cell} ipython3 | ||
| import folium | ||
| from folium.plugins.treelayercontrol import TreeLayerControl | ||
| from folium.features import Marker | ||
|
|
||
| m = folium.Map(location=[46.603354, 1.8883335], zoom_start=5) | ||
| osm = folium.TileLayer("openstreetmap").add_to(m) | ||
|
|
||
| overlay_tree = { | ||
| "label": "Points of Interest", | ||
| "select_all_checkbox": "Un/select all", | ||
| "children": [ | ||
| { | ||
| "label": "Europe", | ||
| "select_all_checkbox": True, | ||
| "children": [ | ||
| { | ||
| "label": "France", | ||
| "select_all_checkbox": True, | ||
| "children": [ | ||
| { "label": "Tour Eiffel", "layer": Marker([48.8582441, 2.2944775]).add_to(m) }, | ||
| { "label": "Notre Dame", "layer": Marker([48.8529540, 2.3498726]).add_to(m) }, | ||
| { "label": "Louvre", "layer": Marker([48.8605847, 2.3376267]).add_to(m) }, | ||
| ] | ||
| }, { | ||
| "label": "Germany", | ||
| "select_all_checkbox": True, | ||
| "children": [ | ||
| { "label": "Branderburger Tor", "layer": Marker([52.5162542, 13.3776805]).add_to(m)}, | ||
| { "label": "Kölner Dom", "layer": Marker([50.9413240, 6.9581201]).add_to(m)}, | ||
| ] | ||
| }, {"label": "Spain", | ||
| "select_all_checkbox": "De/seleccionar todo", | ||
| "children": [ | ||
| { "label": "Palacio Real", "layer": Marker([40.4184145, -3.7137051]).add_to(m)}, | ||
| { "label": "La Alhambra", "layer": Marker([37.1767829, -3.5892795]).add_to(m)}, | ||
| ] | ||
| } | ||
| ] | ||
| }, { | ||
| "label": "Asia", | ||
| "select_all_checkbox": True, | ||
| "children": [ | ||
| { | ||
| "label": "Jordan", | ||
| "select_all_checkbox": True, | ||
| "children": [ | ||
| { "label": "Petra", "layer": Marker([30.3292215, 35.4432464]).add_to(m) }, | ||
| { "label": "Wadi Rum", "layer": Marker([29.6233486, 35.4390656]).add_to(m) } | ||
| ] | ||
| }, { | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| control = TreeLayerControl(overlay_tree=overlay_tree).add_to(m) | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| from typing import Union | ||
|
|
||
| from branca.element import MacroElement | ||
|
|
||
| from folium.elements import JSCSSMixin | ||
| from folium.template import Template | ||
| from folium.utilities import parse_options | ||
|
|
||
|
|
||
| class TreeLayerControl(JSCSSMixin, MacroElement): | ||
| """ | ||
| Create a Layer Control allowing a tree structure for the layers. | ||
| See https://github.com/jjimenezshaw/Leaflet.Control.Layers.Tree for more | ||
| information. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| base_tree : dict | ||
| A dictionary defining the base layers. | ||
| Valid elements are | ||
|
|
||
| children: list | ||
| Array of child nodes for this node. Each node is a dict that has the same valid elements as base_tree. | ||
| label: str | ||
| Text displayed in the tree for this node. It may contain HTML code. | ||
| layer: Layer | ||
| The layer itself. This needs to be added to the map. | ||
| name: str | ||
| Text displayed in the toggle when control is minimized. | ||
| If not present, label is used. It makes sense only when | ||
| namedToggle is true, and with base layers. | ||
| radioGroup: str, default '' | ||
| Text to identify different radio button groups. | ||
| It is used in the name attribute in the radio button. | ||
| It is used only in the overlays layers (ignored in the base | ||
| layers), allowing you to have radio buttons instead of checkboxes. | ||
| See that radio groups cannot be unselected, so create a 'fake' | ||
| layer (like L.layersGroup([])) if you want to disable it. | ||
| Default '' (that means checkbox). | ||
| collapsed: bool, default False | ||
| Indicate whether this tree node should be collapsed initially, | ||
| useful for opening large trees partially based on user input or | ||
| context. | ||
| selectAllCheckbox: bool or str | ||
| Displays a checkbox to select/unselect all overlays in the | ||
| sub-tree. In case of being a <str>, that text will be the title | ||
| (tooltip). When any overlay in the sub-tree is clicked, the | ||
| checkbox goes into indeterminate state (a dash in the box). | ||
| overlay_tree: dict | ||
| Similar to baseTree, but for overlays. | ||
| closed_symbol: str, default '+', | ||
| Symbol displayed on a closed node (that you can click to open). | ||
| opened_symbol: str, default '-', | ||
| Symbol displayed on an opened node (that you can click to close). | ||
| space_symbol: str, default ' ', | ||
| Symbol between the closed or opened symbol, and the text. | ||
| selector_back: bool, default False, | ||
| Flag to indicate if the selector (+ or −) is after the text. | ||
| named_toggle: bool, default False, | ||
| Flag to replace the toggle image (box with the layers image) with the | ||
| 'name' of the selected base layer. If the name field is not present in | ||
| the tree for this layer, label is used. See that you can show a | ||
| different name when control is collapsed than the one that appears | ||
| in the tree when it is expanded. | ||
| collapse_all: str, default '', | ||
| Text for an entry in control that collapses the tree (baselayers or | ||
| overlays). If empty, no entry is created. | ||
| expand_all: str, default '', | ||
| Text for an entry in control that expands the tree. If empty, no entry | ||
| is created | ||
| label_is_selector: str, default 'both', | ||
| Controls if a label or only the checkbox/radiobutton can toggle layers. | ||
| If set to `both`, `overlay` or `base` those labels can be clicked | ||
| on to toggle the layer. | ||
| **kwargs | ||
| Additional (possibly inherited) options. See | ||
| https://leafletjs.com/reference.html#control-layers | ||
|
|
||
| Examples | ||
| -------- | ||
| >>> import folium | ||
| >>> from folium.plugins.treelayercontrol import TreeLayerControl | ||
| >>> from folium.features import Marker | ||
|
|
||
| >>> m = folium.Map(location=[46.603354, 1.8883335], zoom_start=5) | ||
|
|
||
| >>> marker = Marker([48.8582441, 2.2944775]).add_to(m) | ||
|
|
||
| >>> overlay_tree = { | ||
| ... "label": "Points of Interest", | ||
| ... "selectAllCheckbox": "Un/select all", | ||
| ... "children": [ | ||
| ... { | ||
| ... "label": "Europe", | ||
| ... "selectAllCheckbox": True, | ||
| ... "children": [ | ||
| ... { | ||
| ... "label": "France", | ||
| ... "selectAllCheckbox": True, | ||
| ... "children": [ | ||
| ... {"label": "Tour Eiffel", "layer": marker}, | ||
| ... ], | ||
| ... } | ||
| ... ], | ||
| ... } | ||
| ... ], | ||
| ... } | ||
|
|
||
| >>> control = TreeLayerControl(overlay_tree=overlay_tree).add_to(m) | ||
| """ | ||
|
|
||
| default_js = [ | ||
| ( | ||
| "L.Control.Layers.Tree.min.js", | ||
| "https://cdn.jsdelivr.net/npm/[email protected]/L.Control.Layers.Tree.min.js", # noqa | ||
| ), | ||
| ] | ||
| default_css = [ | ||
| ( | ||
| "L.Control.Layers.Tree.min.css", | ||
| "https://cdn.jsdelivr.net/npm/[email protected]/L.Control.Layers.Tree.min.css", # noqa | ||
| ) | ||
| ] | ||
|
|
||
| _template = Template( | ||
| """ | ||
| {% macro script(this,kwargs) %} | ||
| L.control.layers.tree( | ||
| {{this.base_tree|tojavascript}}, | ||
| {{this.overlay_tree|tojavascript}}, | ||
| {{this.options|tojson}} | ||
| ).addTo({{this._parent.get_name()}}); | ||
| {% endmacro %} | ||
| """ | ||
| ) | ||
|
|
||
| def __init__( | ||
| self, | ||
| base_tree: Union[dict, list, None] = None, | ||
| overlay_tree: Union[dict, list, None] = None, | ||
| closed_symbol: str = "+", | ||
| opened_symbol: str = "-", | ||
| space_symbol: str = " ", | ||
| selector_back: bool = False, | ||
| named_toggle: bool = False, | ||
| collapse_all: str = "", | ||
| expand_all: str = "", | ||
| label_is_selector: str = "both", | ||
| **kwargs | ||
| ): | ||
| super().__init__() | ||
| self._name = "TreeLayerControl" | ||
| kwargs["closed_symbol"] = closed_symbol | ||
| kwargs["openened_symbol"] = opened_symbol | ||
| kwargs["space_symbol"] = space_symbol | ||
| kwargs["selector_back"] = selector_back | ||
| kwargs["named_toggle"] = named_toggle | ||
| kwargs["collapse_all"] = collapse_all | ||
| kwargs["expand_all"] = expand_all | ||
| kwargs["label_is_selector"] = label_is_selector | ||
| self.options = parse_options(**kwargs) | ||
| self.base_tree = base_tree | ||
| self.overlay_tree = overlay_tree |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import json | ||
| from typing import Union | ||
|
|
||
| import jinja2 | ||
| from branca.element import Element | ||
|
|
||
| from folium.utilities import JsCode, TypeJsonValue, camelize | ||
|
|
||
|
|
||
| def tojavascript(obj: Union[str, JsCode, dict, list, Element]) -> str: | ||
| if isinstance(obj, JsCode): | ||
| return obj.js_code | ||
| elif isinstance(obj, Element): | ||
| return obj.get_name() | ||
| elif isinstance(obj, dict): | ||
| out = ["{\n"] | ||
| for key, value in obj.items(): | ||
| out.append(f' "{camelize(key)}": ') | ||
| out.append(tojavascript(value)) | ||
| out.append(",\n") | ||
| out.append("}") | ||
| return "".join(out) | ||
| elif isinstance(obj, list): | ||
| out = ["[\n"] | ||
| for value in obj: | ||
| out.append(tojavascript(value)) | ||
| out.append(",\n") | ||
| out.append("]") | ||
| return "".join(out) | ||
| else: | ||
| return _to_escaped_json(obj) | ||
|
|
||
|
|
||
| def _to_escaped_json(obj: TypeJsonValue) -> str: | ||
| return ( | ||
| json.dumps(obj) | ||
| .replace("<", "\\u003c") | ||
| .replace(">", "\\u003e") | ||
| .replace("&", "\\u0026") | ||
| .replace("'", "\\u0027") | ||
| ) | ||
|
|
||
|
|
||
| class Environment(jinja2.Environment): | ||
| def __init__(self, *args, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
| self.filters["tojavascript"] = tojavascript | ||
|
|
||
|
|
||
| class Template(jinja2.Template): | ||
hansthen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| environment_class = Environment | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.