Skip to content

Refactor marker cluster #704

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 5 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
214 changes: 199 additions & 15 deletions examples/1000_MarkerCluster.ipynb

Large diffs are not rendered by default.

130 changes: 0 additions & 130 deletions examples/FastMarkerCluster.ipynb

This file was deleted.

117 changes: 32 additions & 85 deletions examples/Features.ipynb

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions folium/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from folium._version import get_versions

from folium.features import (
Circle, CircleMarker, ClickForMarker, CustomIcon, DivIcon, GeoJson, LatLngPopup,
MarkerCluster, PolyLine, RegularPolygonMarker, TopoJson, Vega, VegaLite,
Circle, CircleMarker, ClickForMarker, CustomIcon, DivIcon, GeoJson,
LatLngPopup, PolyLine, RegularPolygonMarker, TopoJson, Vega, VegaLite,
WmsTileLayer,
)

Expand Down
70 changes: 1 addition & 69 deletions folium/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from branca.element import (CssLink, Element, Figure, JavascriptLink, MacroElement) # noqa
from branca.utilities import (_locations_tolist, _parse_size, image_to_url, iter_points, none_max, none_min) # noqa

from folium.map import FeatureGroup, Icon, Layer, Marker
from folium.map import FeatureGroup, Icon, Layer, Popup, Marker

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

F401 'folium.map.Popup' imported but unused

from folium.utilities import _parse_path, _parse_wms

from jinja2 import Template
Expand Down Expand Up @@ -744,74 +744,6 @@ def _get_self_bounds(self):
]


class MarkerCluster(Layer):
"""
Creates a MarkerCluster element to append into a map with
Map.add_child.

Parameters
----------
name : string, default None
The name of the Layer, as it will appear in LayerControls
overlay : bool, default False
Adds the layer as an optional overlay (True) or the base layer (False).
control : bool, default True
Whether the Layer will be included in LayerControls
icon_create_function : string, default None
Override the default behaviour, making possible to customize markers colors and sizes

Example
-------
>>> icon_create_function = '''
... function (cluster) {
... var childCount = cluster.getChildCount();
... var c = ' marker-cluster-small';
... return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>',
className: 'marker-cluster' + c,
iconSize: new L.Point(40, 40) });
... }
... '''
"""
def __init__(self, name=None, overlay=True, control=True, icon_create_function=None):
super(MarkerCluster, self).__init__(name=name, overlay=overlay,
control=control)
self._name = 'MarkerCluster'
self._icon_create_function = icon_create_function.strip()
self._template = Template(u"""
{% macro script(this, kwargs) %}
var {{this.get_name()}} = L.markerClusterGroup({
{% if this._icon_create_function %}
iconCreateFunction: {{this._icon_create_function}}
{% endif %}
});
{{this._parent.get_name()}}.addLayer({{this.get_name()}});
{% endmacro %}
""")

def render(self, **kwargs):
"""Renders the HTML representation of the element."""
super(MarkerCluster, self).render()

figure = self.get_root()
assert isinstance(figure, Figure), ('You cannot render this Element '
'if it is not in a Figure.')
figure.header.add_child(
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/leaflet.markercluster-src.js'), # noqa
name='marker_cluster_src')

figure.header.add_child(
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/leaflet.markercluster.js'), # noqa
name='marker_cluster')

figure.header.add_child(
CssLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/MarkerCluster.css'), # noqa
name='marker_cluster_css')

figure.header.add_child(
CssLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/MarkerCluster.Default.css'), # noqa
name='marker_cluster_default_css')


class DivIcon(MacroElement):
"""
Represents a lightweight icon for markers that uses a simple `div`
Expand Down
6 changes: 0 additions & 6 deletions folium/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@
'https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js'),
('awesome_markers',
'https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js'), # noqa
('marker_cluster',
'https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/leaflet.markercluster.js'), # noqa
]

_default_css = [
Expand All @@ -52,10 +50,6 @@
'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css'), # noqa
('awesome_markers_css',
'https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css'), # noqa
('marker_cluster_default_css',
'https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/MarkerCluster.Default.css'), # noqa
('marker_cluster_css',
'https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/MarkerCluster.css'), # noqa
('awesome_rotate_css',
'https://rawgit.com/python-visualization/folium/master/folium/templates/leaflet.awesome.rotate.css'), # noqa
]
Expand Down
80 changes: 48 additions & 32 deletions folium/plugins/marker_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,70 @@

from __future__ import (absolute_import, division, print_function)

from branca.element import CssLink, Figure, JavascriptLink, MacroElement
from branca.element import CssLink, Figure, JavascriptLink

from folium.map import Icon, Marker, Popup
from folium.map import Icon, Layer, Marker, Popup

from jinja2 import Template


class MarkerCluster(MacroElement):
class MarkerCluster(Layer):
"""
Creates a MarkerCluster plugin to append into a map with
Map.add_child.
Provides Beautiful Animated Marker Clustering functionality for maps.

Parameters
----------
locations: list of list or array of shape (n,2).
name : string, default None
The name of the Layer, as it will appear in LayerControls
overlay : bool, default False
Adds the layer as an optional overlay (True) or the base layer (False).
control : bool, default True
Whether the Layer will be included in LayerControls
icon_create_function : string, default None
Override the default behaviour, making possible to customize
markers colors and sizes.

locations: list of list or array of shape (n, 2).
Data points of the form [[lat, lng]].

popups: list of length n.
Popup for each marker.

icons: list of length n.
Icon for each marker.

"""
def __init__(self, locations, popups=None, icons=None):
super(MarkerCluster, self).__init__()
self._name = 'MarkerCluster'
Example
-------
>>> icon_create_function = '''
... function(cluster) {
... return L.divIcon({html: '<b>' + cluster.getChildCount() + '</b>',
... className: 'marker-cluster marker-cluster-small',
... iconSize: new L.Point(20, 20)});
}'''

if popups is None:
popups = [None]*len(locations)
if icons is None:
icons = [None]*len(locations)

for location, popup, icon in zip(locations, popups, icons):
if popup is None or isinstance(popup, Popup):
p = popup
else:
p = Popup(popup)
if icon is None or isinstance(icon, Icon):
i = icon
else:
i = Icon(icon)
self.add_child(Marker(location, popup=p, icon=i))
"""
def __init__(self, locations=None, popups=None, icons=None, name=None,
overlay=True, control=True, icon_create_function=None):
super(MarkerCluster, self).__init__(name=name, overlay=overlay, control=control) # noqa

if locations is not None:
if popups is None:
popups = [None]*len(locations)
if icons is None:
icons = [None]*len(locations)
for location, popup, icon in zip(locations, popups, icons):
p = popup if popup is None or isinstance(popup, Popup) else Popup(popup) # noqa
i = icon if icon is None or isinstance(icon, Icon) else Icon(icon) # noqa
self.add_child(Marker(location, popup=p, icon=i))

self._name = 'MarkerCluster'
self._icon_create_function = icon_create_function.strip() if icon_create_function else '' # noqa
self._template = Template(u"""
{% macro script(this, kwargs) %}
var {{this.get_name()}} = L.markerClusterGroup();
{{this._parent.get_name()}}.addLayer({{this.get_name()}});
var {{this.get_name()}} = L.markerClusterGroup({
{% if this._icon_create_function %}
iconCreateFunction: {{this._icon_create_function}}
{% endif %}
});
{{this._parent.get_name()}}.addLayer({{this.get_name()}});
{% endmacro %}
""")

Expand All @@ -61,13 +77,13 @@ def render(self, **kwargs):
'if it is not in a Figure.')

figure.header.add_child(
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/leaflet.markercluster.js'), # noqa
JavascriptLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.1.0/leaflet.markercluster.js'), # noqa
name='markerclusterjs')

figure.header.add_child(
CssLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/MarkerCluster.css'), # noqa
CssLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.1.0/MarkerCluster.css'), # noqa
name='markerclustercss')

figure.header.add_child(
CssLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/MarkerCluster.Default.css'), # noqa
CssLink('https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.1.0/MarkerCluster.Default.css'), # noqa
name='markerclusterdefaultcss')
3 changes: 0 additions & 3 deletions folium/templates/fol_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/leaflet.markercluster.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/MarkerCluster.Default.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.0.0/MarkerCluster.css"/>
<link rel="stylesheet" href="https://rawgit.com/python-visualization/folium/master/folium/templates/leaflet.awesome.rotate.css"/>

<style>
Expand Down
10 changes: 3 additions & 7 deletions tests/plugins/test_fast_marker_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,9 @@ def test_fast_marker_cluster():
out = m._parent.render()

# We verify that imports
assert ('<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.'
'markercluster/1.0.0/leaflet.markercluster.js"></script>') in out
assert ('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/'
'libs/leaflet.markercluster/1.0.0/MarkerCluster.css" />') in out
assert ('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/'
'libs/leaflet.markercluster/1.0.0/MarkerCluster.Default.css" />'
) in out
assert '<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.1.0/leaflet.markercluster.js"></script>' in out # noqa
assert '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.1.0/MarkerCluster.css" />' in out # noqa
assert '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.1.0/MarkerCluster.Default.css" />' in out # noqa

# Verify the script part is okay.
tmpl = Template("""
Expand Down
16 changes: 8 additions & 8 deletions tests/plugins/test_marker_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ def test_marker_cluster():
out = m._parent.render()

# We verify that imports
assert ('<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.'
'markercluster/1.0.0/leaflet.markercluster.js"></script>') in out
assert ('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/'
'libs/leaflet.markercluster/1.0.0/MarkerCluster.css" />') in out
assert ('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/'
'libs/leaflet.markercluster/1.0.0/MarkerCluster.Default.css" />'
) in out
assert '<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.1.0/leaflet.markercluster.js"></script>' in out # noqa
assert '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.1.0/MarkerCluster.css" />' in out # noqa
assert '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.1.0/MarkerCluster.Default.css" />' in out # noqa

# Verify the script part is okay.
tmpl = Template("""
var {{this.get_name()}} = L.markerClusterGroup();
var {{this.get_name()}} = L.markerClusterGroup({
{% if this._icon_create_function %}
iconCreateFunction: {{this._icon_create_function}}
{% endif %}
});
{{this._parent.get_name()}}.addLayer({{this.get_name()}});

{% for marker in this._children.values() %}
Expand Down