foliplus 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
foliplus/Fullscreen.py ADDED
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ from ._cdn import LEAFLET_FULLSCREEN
4
+ from ._typing import Position
5
+ from .base import BaseControl
6
+ from .locale import LocaleConfig
7
+
8
+
9
+ class Fullscreen(BaseControl):
10
+ """Fullscreen control that hides other map components when entering fullscreen.
11
+
12
+ When toggling fullscreen, other controls (HeatmapControl, LayerControl, ScaleControl
13
+ , MapSearch, MeasureControl, etc.), inside ``.leaflet-control-container`` are
14
+ automatically hidden/shown for a cleaner view.
15
+
16
+ Parameters
17
+ ----------
18
+ position : str, default "bottomright"
19
+ One of ``"topleft"``, ``"topright"``, ``"bottomleft"``, ``"bottomright"``.
20
+
21
+ hide_self : bool, default True
22
+ Whether to hide the fullscreen button itself after entering fullscreen.
23
+ Users can exit via the ``Esc`` key.
24
+
25
+ locale : str or LocaleConfig, optional
26
+ Language code (``"en"``, ``"zh"``) or a :class:`LocaleConfig` instance.
27
+ Defaults to auto-detection, falling back to English.
28
+
29
+ Examples
30
+ --------
31
+ >>> import folium
32
+ >>> from foliplus import Fullscreen
33
+ >>> m = folium.Map()
34
+ >>> Fullscreen().add_to(m)
35
+ """
36
+
37
+ default_js = [
38
+ (
39
+ "Control.Fullscreen.js",
40
+ f"https://cdn.jsdelivr.net/npm/leaflet.fullscreen@{LEAFLET_FULLSCREEN}/Control.FullScreen.min.js",
41
+ )
42
+ ]
43
+ default_css = [
44
+ (
45
+ "Control.FullScreen.css",
46
+ f"https://cdn.jsdelivr.net/npm/leaflet.fullscreen@{LEAFLET_FULLSCREEN}/Control.FullScreen.css",
47
+ )
48
+ ]
49
+
50
+ def __init__(
51
+ self,
52
+ position: Position = "bottomright",
53
+ hide_self: bool = True,
54
+ locale: str | LocaleConfig | None = None,
55
+ ):
56
+ super().__init__(position=position, locale=locale)
57
+ self.hide_self = hide_self
58
+ self._template = self._get_template(js_file="Fullscreen.js")
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ from ._cdn import CHROMA_JS, H3_JS, SIMPLE_STATISTICS
6
+ from ._typing import Position
7
+ from .base import BaseControl
8
+ from .locale import LocaleConfig
9
+
10
+
11
+ class HeatmapControl(BaseControl):
12
+ """H3 hexbin aggregation heatmap control.
13
+
14
+ Auto-discovers point layers (`Marker` / `CircleMarker` / `GeoJSON` Point) and
15
+ aggregates them into H3 hexagons in real-time via h3-js. Resolution auto-adjusts
16
+ with zoom.
17
+
18
+ Parameters
19
+ ----------
20
+ position : str, default "topleft"
21
+ One of ``"topleft"``, ``"topright"``, ``"bottomleft"``, ``"bottomright"``.
22
+
23
+ color_scheme : str, default "Greens"
24
+ Default color scheme name. Supports chroma.js / ColorBrewer palettes: ``Blues``,
25
+ ``Greens``, ``Reds``, ``Oranges``, ``Purples``, ``YlOrRd``, ``Viridis``.
26
+
27
+ method : Literal["jenks", "quantile", "equal", "heads"], default "jenks"
28
+ Default classification method.
29
+
30
+ n_classes : int, default 6
31
+ Number of classification classes, range 2-9.
32
+
33
+ agg : Literal["count", "sum", "avg", "min", "max"], default "count"
34
+ Default aggregation method.
35
+
36
+ schemes : list[str], optional
37
+ List of available color scheme names. Can include custom hex values like
38
+ ``["#f00", "#0f0", "#00f"]``.
39
+
40
+ style : dict, optional
41
+ Grid style overrides. Supported keys:
42
+ - ``border_weight`` (float, default 1.5): border width
43
+ - ``border_color`` (str, default "#333333"): border color
44
+ - ``fill_opacity`` (float, default 0.7): fill opacity
45
+ - ``border_opacity`` (float, default 0.9): border opacity
46
+ - ``label_show`` (bool, default True): show aggregated value at hex center
47
+ - ``label_size`` (int, default 11): label font size
48
+ - ``label_color`` (str, default "#fff"): label color
49
+ - ``label_format`` (str, default "auto"): number format —
50
+ ``"auto"`` (10K/1K suffix), ``"int"``, ``"comma"`` (thousands separator)
51
+
52
+ locale : str or LocaleConfig, optional
53
+ Language code (``"en"``, ``"zh"``) or a :class:`LocaleConfig` instance.
54
+ Defaults to auto-detection, falling back to English.
55
+
56
+ Examples
57
+ --------
58
+ >>> import folium
59
+ >>> from foliplus import HeatmapControl
60
+ >>> m = folium.Map()
61
+ >>> HeatmapControl().add_to(m)
62
+ """
63
+
64
+ default_js = [
65
+ ("h3-js", f"https://cdn.jsdelivr.net/npm/h3-js@{H3_JS}/dist/h3-js.umd.js"),
66
+ (
67
+ "simple-statistics",
68
+ f"https://cdn.jsdelivr.net/npm/simple-statistics@{SIMPLE_STATISTICS}/dist/simple-statistics.min.js",
69
+ ),
70
+ (
71
+ "chroma-js",
72
+ f"https://cdn.jsdelivr.net/npm/chroma-js@{CHROMA_JS}/chroma.min.js",
73
+ ),
74
+ ]
75
+
76
+ def __init__(
77
+ self,
78
+ position: Position = "topleft",
79
+ color_scheme: str = "Greens",
80
+ method: Literal["jenks", "quantile", "equal", "heads"] = "jenks",
81
+ n_classes: int = 6,
82
+ agg: Literal["count", "sum", "avg", "min", "max"] = "count",
83
+ schemes: list[str] | None = None,
84
+ style: dict | None = None,
85
+ locale: str | LocaleConfig | None = None,
86
+ ):
87
+ super().__init__(position=position, locale=locale)
88
+ self._template = self._get_template(
89
+ js_file="HeatmapControl.js", css_file="HeatmapControl.css", use_panel=True
90
+ )
91
+ self.color_scheme = color_scheme
92
+ self.method = method
93
+ self.n_classes = n_classes
94
+ self.agg = agg
95
+ self.schemes = schemes or [
96
+ "Blues",
97
+ "Greens",
98
+ "Reds",
99
+ "Oranges",
100
+ "Purples",
101
+ "YlOrRd",
102
+ "Viridis",
103
+ ]
104
+ self.style = {
105
+ "border_weight": 1.5,
106
+ "border_color": "#333333",
107
+ "fill_opacity": 0.7,
108
+ "border_opacity": 0.9,
109
+ "label_show": True,
110
+ "label_size": 11,
111
+ "label_color": "#fff",
112
+ "label_format": "auto",
113
+ } | (style or {})
114
+
115
+ # CDN versions for JS dynamic loader
116
+ self._h3_version = H3_JS
117
+ self._ss_version = SIMPLE_STATISTICS
118
+ self._chroma_version = CHROMA_JS
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import OrderedDict
4
+
5
+ from folium.map import Layer
6
+
7
+ from ._typing import Position
8
+ from .base import BaseControl
9
+ from .locale import LocaleConfig
10
+
11
+
12
+ class LayerControl(BaseControl):
13
+ """Layer control with geometry-type icons, drag-and-drop order, and a collapsible
14
+ panel.
15
+
16
+ Replaces Folium's default layer control with:
17
+ - 📐 Geometry-type icons for quick layer identification.
18
+ - 🔀 Drag-and-drop reordering, synced to Leaflet render order.
19
+ - ✅ Multi-select checkboxes with z-index stacking.
20
+ - 🎨 Color picker to replace base maps with a solid background color.
21
+ - 📂 Collapsible panel consistent with other foliplus controls.
22
+
23
+ Parameters
24
+ ----------
25
+ position : str, default "topleft"
26
+ One of ``"topleft"``, ``"topright"``, ``"bottomleft"``, ``"bottomright"``.
27
+
28
+ locale : str or LocaleConfig, optional
29
+ Language code (``"en"``, ``"zh"``) or a :class:`LocaleConfig` instance.
30
+ Defaults to auto-detection, falling back to English.
31
+
32
+ Notes
33
+ -----
34
+ Layer identification relies on ``map._layers`` and the ``window`` global variable at
35
+ runtime. The initial layer list is collected during rendering by traversing the
36
+ parent map's children.
37
+
38
+ Examples
39
+ --------
40
+ >>> import folium
41
+ >>> from foliplus import LayerControl
42
+ >>> m = folium.Map()
43
+ >>> LayerControl().add_to(m)
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ position: Position = "topleft",
49
+ locale: str | LocaleConfig | None = None,
50
+ ):
51
+ super().__init__(position=position, locale=locale)
52
+ self.base_layers: OrderedDict[str, str] = OrderedDict()
53
+ self.overlays: OrderedDict[str, str] = OrderedDict()
54
+ self._template = self._get_template(
55
+ js_file="LayerControl.js", css_file="LayerControl.css", use_panel=True
56
+ )
57
+
58
+ def render(self, **kwargs):
59
+ """Collect layers from the parent map before rendering.
60
+
61
+ Traverses the parent map's ``_children`` to find ``Layer`` instances, then
62
+ populates ``self.base_layers`` and ``self.overlays`` according to each layer's
63
+ ``overlay`` flag.
64
+ """
65
+ self.base_layers.clear()
66
+ self.overlays.clear()
67
+ for item in self._parent._children.values():
68
+ if not isinstance(item, Layer) or not item.control:
69
+ continue
70
+
71
+ key = item.layer_name
72
+ if not item.overlay:
73
+ self.base_layers[key] = item.get_name()
74
+ else:
75
+ self.overlays[key] = item.get_name()
76
+
77
+ super().render(**kwargs)
foliplus/MapSearch.py ADDED
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from ._typing import Position
4
+ from .base import BaseControl
5
+ from .locale import LocaleConfig
6
+
7
+
8
+ class MapSearch(BaseControl):
9
+ """Map search control with coordinate and address lookup modes.
10
+
11
+ Adds a collapsible search box to the map supporting two modes:
12
+
13
+ - 📍 **Coordinate search** (default): enter latitude/longitude to fly to and place a
14
+ marker.
15
+ - 🌐 **Address search**: enter a keyword and geocode via Nominatim.
16
+
17
+ Switch between modes via the tool button inside the expanded panel.
18
+
19
+ Parameters
20
+ ----------
21
+ zoom : int, default 15
22
+ Zoom level after coordinate search. Typically 1-18.
23
+
24
+ position : str, default "topleft"
25
+ One of ``"topleft"``, ``"topright"``, ``"bottomleft"``, ``"bottomright"``.
26
+
27
+ locale : str or LocaleConfig, optional
28
+ Language code (``"en"``, ``"zh"``) or a :class:`LocaleConfig` instance.
29
+ Defaults to auto-detection, falling back to English.
30
+
31
+ Examples
32
+ --------
33
+ >>> import folium
34
+ >>> from foliplus import MapSearch
35
+ >>> m = folium.Map()
36
+ >>> MapSearch().add_to(m)
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ zoom: int = 15,
42
+ position: Position = "topleft",
43
+ locale: str | LocaleConfig | None = None,
44
+ ):
45
+ super().__init__(position=position, locale=locale)
46
+ self.zoom = zoom
47
+ self._template = self._get_template(
48
+ js_file="MapSearch.js", css_file="MapSearch.css"
49
+ )
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from ._cdn import GCOORD
4
+ from ._typing import Position
5
+ from .base import BaseControl
6
+ from .locale import LocaleConfig
7
+
8
+
9
+ class MeasureControl(BaseControl):
10
+ """Measurement tools control with locate, distance, and circle drawing modes.
11
+
12
+ Click the icon to expand the toolbar, then click a button to activate a mode:
13
+
14
+ - 📍 **Locate**: click to place a marker showing coordinates and reverse-geocoded
15
+ address. Click the popup or the × on the marker to delete it.
16
+ - 📏 **Distance**: click to draw a polyline. Segment and total distances update
17
+ in real-time. Double-click / right-click / click the last point to finish.
18
+ - ⭕ **Circle**: first click sets the center; move the mouse to set the radius;
19
+ second click confirms.
20
+ - 🗑️ **Clear**: remove all measurement layers at once.
21
+
22
+ After drawing a line or circle: click the object to toggle labels and × buttons;
23
+ click empty map space to hide × buttons.
24
+
25
+ Parameters
26
+ ----------
27
+ position : str, default "bottomright"
28
+ One of ``"topleft"``, ``"topright"``, ``"bottomleft"``, ``"bottomright"``.
29
+
30
+ locale : str or LocaleConfig, optional
31
+ Language code (``"en"``, ``"zh"``) or a :class:`LocaleConfig` instance.
32
+ Defaults to auto-detection, falling back to English.
33
+
34
+ Examples
35
+ --------
36
+ >>> import folium
37
+ >>> from foliplus import MeasureControl
38
+ >>> m = folium.Map()
39
+ >>> MeasureControl().add_to(m)
40
+ """
41
+
42
+ default_js = [
43
+ (
44
+ "gcoord",
45
+ f"https://cdn.jsdelivr.net/npm/gcoord@{GCOORD}/dist/gcoord.global.prod.js",
46
+ ),
47
+ ]
48
+
49
+ def __init__(
50
+ self,
51
+ position: Position = "bottomright",
52
+ locale: str | LocaleConfig | None = None,
53
+ ):
54
+ super().__init__(position=position, locale=locale)
55
+ self._template = self._get_template(
56
+ js_file="MeasureControl.js", css_file="MeasureControl.css"
57
+ )
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from ._typing import Position
4
+ from .base import BaseControl
5
+ from .locale import LocaleConfig
6
+
7
+
8
+ class ScaleControl(BaseControl):
9
+ """Scale control with metric units and optional zoom level display.
10
+
11
+ Adds a scale bar to the map with an optional current zoom level label.
12
+
13
+ Parameters
14
+ ----------
15
+ position : str, default "bottomleft"
16
+ One of ``"topleft"``, ``"topright"``, ``"bottomleft"``, ``"bottomright"``.
17
+
18
+ metric : bool, default True
19
+ Whether to show metric units (meters / kilometers).
20
+
21
+ show_zoom : bool, default True
22
+ Whether to show the current map zoom level.
23
+
24
+ locale : str or LocaleConfig, optional
25
+ Language code (``"en"``, ``"zh"``) or a :class:`LocaleConfig` instance.
26
+ Defaults to auto-detection, falling back to English.
27
+
28
+ Examples
29
+ --------
30
+ >>> import folium
31
+ >>> from foliplus import ScaleControl
32
+ >>> m = folium.Map()
33
+ >>> ScaleControl().add_to(m)
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ position: Position = "bottomleft",
39
+ metric: bool = True,
40
+ show_zoom: bool = True,
41
+ locale: str | LocaleConfig | None = None,
42
+ ):
43
+ super().__init__(position=position, locale=locale)
44
+ self.metric = metric
45
+ self.show_zoom = show_zoom
46
+ self._template = self._get_template(
47
+ js_file="ScaleControl.js", css_file="ScaleControl.css"
48
+ )
foliplus/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ from .Fullscreen import Fullscreen
2
+ from .HeatmapControl import HeatmapControl
3
+ from .LayerControl import LayerControl
4
+ from .MapSearch import MapSearch
5
+ from .MeasureControl import MeasureControl
6
+ from .ScaleControl import ScaleControl
7
+
8
+ __all__ = [
9
+ "Fullscreen",
10
+ "HeatmapControl",
11
+ "LayerControl",
12
+ "MapSearch",
13
+ "MeasureControl",
14
+ "ScaleControl",
15
+ ]
16
+
17
+ __version__ = "0.1.0"
foliplus/_cdn.py ADDED
@@ -0,0 +1,16 @@
1
+ """Centralized CDN version management.
2
+
3
+ All CDN dependency versions are defined here. Bump a version in one place to update it
4
+ across all controls.
5
+ """
6
+
7
+ # Fullscreen
8
+ LEAFLET_FULLSCREEN = "3"
9
+
10
+ # HeatmapControl
11
+ H3_JS = "4"
12
+ SIMPLE_STATISTICS = "7"
13
+ CHROMA_JS = "2"
14
+
15
+ # MeasureControl
16
+ GCOORD = "1"
foliplus/_typing.py ADDED
@@ -0,0 +1,3 @@
1
+ from typing import Literal
2
+
3
+ Position = Literal["topleft", "topright", "bottomleft", "bottomright"]
foliplus/base.py ADDED
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from textwrap import dedent
5
+
6
+ from folium import MacroElement
7
+ from folium.elements import JSCSSMixin
8
+ from jinja2 import Template
9
+
10
+ from ._cdn import GCOORD
11
+ from ._typing import Position
12
+ from .locale import LocaleConfig, resolve_locale
13
+
14
+ src_dir = Path(__file__).parent
15
+ js_dir = src_dir / "js"
16
+ css_dir = src_dir / "css"
17
+
18
+
19
+ class BaseControl(JSCSSMixin, MacroElement):
20
+ """Base class for all foliplus controls.
21
+
22
+ Handles resource loading (CSS/JS), template injection, and localization. All
23
+ foliplus components (Fullscreen, HeatmapControl, LayerControl, etc.) inherit from
24
+ this class.
25
+
26
+ Parameters
27
+ ----------
28
+ position : str, default "topleft"
29
+ One of ``"topleft"``, ``"topright"``, ``"bottomleft"``, ``"bottomright"``.
30
+
31
+ locale : str or LocaleConfig, optional
32
+ Language code (``"en"``, ``"zh"``) or a :class:`LocaleConfig` instance.
33
+ Defaults to auto-detection, falling back to English.
34
+ """
35
+
36
+ _common = css_dir.joinpath("common.css").read_text(encoding="utf-8")
37
+ _panel = css_dir.joinpath("panel.css").read_text(encoding="utf-8")
38
+ _runtime = js_dir.joinpath("runtime.js").read_text(encoding="utf-8")
39
+
40
+ def __init__(
41
+ self,
42
+ position: Position = "topleft",
43
+ locale: str | LocaleConfig | None = None,
44
+ ):
45
+ super().__init__()
46
+ self._name = self.__class__.__name__
47
+ self.position = position
48
+ self.locale = resolve_locale(locale)
49
+ self._gcoord_version = GCOORD
50
+
51
+ def _get_js(self, filename: str) -> str:
52
+ return js_dir.joinpath(filename).read_text(encoding="utf-8")
53
+
54
+ def _get_css(self, filename: str) -> str:
55
+ return css_dir.joinpath(filename).read_text(encoding="utf-8")
56
+
57
+ def _get_template(
58
+ self,
59
+ *,
60
+ js_file: str | None = None,
61
+ css_file: str | None = None,
62
+ use_panel: bool = False,
63
+ ) -> Template:
64
+ """Build a Jinja2 template with shared CSS/JS + component assets.
65
+
66
+ Injects ``common.css``, ``panel.css`` (if ``use_panel=True``), ``runtime.js``,
67
+ and the specified component JS/CSS files into a Jinja2 macro pair
68
+ (``html`` / ``script``) for folium's rendering pipeline.
69
+
70
+ Parameters
71
+ ----------
72
+ js_file : str, optional
73
+ Component JS filename (e.g. ``"LayerControl.js"``).
74
+ css_file : str, optional
75
+ Component CSS filename (e.g. ``"LayerControl.css"``).
76
+ use_panel : bool, default False
77
+ Whether to include shared panel CSS.
78
+
79
+ Returns
80
+ -------
81
+ Template
82
+ A Jinja2 ``Template`` instance ready for folium rendering.
83
+ """
84
+ js_runtime = self._get_js(js_file) if js_file else ""
85
+ css_common = self._get_css(css_file) if css_file else ""
86
+ css_panel = self._panel if use_panel else ""
87
+ locale_table = self.locale.get_js_table()
88
+
89
+ return Template(
90
+ dedent(f"""\
91
+ {{% macro html(this, kwargs) %}}
92
+ <style>
93
+ {self._common}
94
+ {css_panel}
95
+ {css_common}
96
+ </style>
97
+ {{% endmacro %}}
98
+
99
+ {{% macro script(this, kwargs) %}}
100
+ var _LOCALE = window._LOCALE || {locale_table};
101
+ {self._runtime}
102
+ {js_runtime}
103
+ {{% endmacro %}}""")
104
+ )