maps: add option to clusterize markers (#21048)
This commit is contained in:
parent
4ae2ac1ad5
commit
ce7386ffd6
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('maps', '0004_map_initial_state'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='map',
|
||||||
|
name='group_markers',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Group markers in clusters'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -200,6 +200,7 @@ class Map(CellBase):
|
||||||
choices=ZOOM_LEVELS, default='0')
|
choices=ZOOM_LEVELS, default='0')
|
||||||
max_zoom = models.CharField(_('Maximal zoom level'), max_length=2,
|
max_zoom = models.CharField(_('Maximal zoom level'), max_length=2,
|
||||||
choices=ZOOM_LEVELS, default=19)
|
choices=ZOOM_LEVELS, default=19)
|
||||||
|
group_markers = models.BooleanField(_('Group markers in clusters'), default=False)
|
||||||
layers = models.ManyToManyField(MapLayer, verbose_name=_('Layers'), blank=True)
|
layers = models.ManyToManyField(MapLayer, verbose_name=_('Layers'), blank=True)
|
||||||
|
|
||||||
template_name = 'maps/map_cell.html'
|
template_name = 'maps/map_cell.html'
|
||||||
|
@ -208,7 +209,7 @@ class Map(CellBase):
|
||||||
verbose_name = _('Map')
|
verbose_name = _('Map')
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
js = ('xstatic/leaflet.js', 'js/combo.map.js')
|
js = ('xstatic/leaflet.js', 'js/combo.map.js', 'xstatic/leaflet.markercluster.js')
|
||||||
css = {'all': ('xstatic/leaflet.css', 'css/combo.map.css')}
|
css = {'all': ('xstatic/leaflet.css', 'css/combo.map.css')}
|
||||||
|
|
||||||
def get_default_position(self):
|
def get_default_position(self):
|
||||||
|
@ -216,7 +217,7 @@ class Map(CellBase):
|
||||||
|
|
||||||
def get_default_form_class(self):
|
def get_default_form_class(self):
|
||||||
fields = ('title', 'initial_state', 'initial_zoom', 'min_zoom',
|
fields = ('title', 'initial_state', 'initial_zoom', 'min_zoom',
|
||||||
'max_zoom', 'layers')
|
'max_zoom', 'group_markers', 'layers')
|
||||||
widgets = {'layers': forms.widgets.CheckboxSelectMultiple}
|
widgets = {'layers': forms.widgets.CheckboxSelectMultiple}
|
||||||
return forms.models.modelform_factory(self.__class__, fields=fields,
|
return forms.models.modelform_factory(self.__class__, fields=fields,
|
||||||
widgets=widgets)
|
widgets=widgets)
|
||||||
|
@ -245,4 +246,5 @@ class Map(CellBase):
|
||||||
ctx['tile_urltemplate'] = settings.COMBO_MAP_TILE_URLTEMPLATE
|
ctx['tile_urltemplate'] = settings.COMBO_MAP_TILE_URLTEMPLATE
|
||||||
ctx['map_attribution'] = settings.COMBO_MAP_ATTRIBUTION
|
ctx['map_attribution'] = settings.COMBO_MAP_ATTRIBUTION
|
||||||
ctx['max_bounds'] = settings.COMBO_MAP_MAX_BOUNDS
|
ctx['max_bounds'] = settings.COMBO_MAP_MAX_BOUNDS
|
||||||
|
ctx['group_markers'] = self.group_markers
|
||||||
return ctx
|
return ctx
|
||||||
|
|
|
@ -44,7 +44,7 @@ div.combo-cell-map.leaflet-container {
|
||||||
|
|
||||||
/* leaflet styles */
|
/* leaflet styles */
|
||||||
|
|
||||||
div.leaflet-marker-icon {
|
div.leaflet-marker-icon.leaflet-div-icon {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
@ -135,3 +135,52 @@ ul#id_icon {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* leaflet markercluster styles */
|
||||||
|
|
||||||
|
.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
|
||||||
|
transition: transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-cluster-spider-leg {
|
||||||
|
/* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
|
||||||
|
transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-cluster {
|
||||||
|
background-clip: padding-box;
|
||||||
|
border-radius: 30px;
|
||||||
|
div {
|
||||||
|
display: block;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
&-small {
|
||||||
|
background: #b5e28c;
|
||||||
|
div {
|
||||||
|
font-size: 200%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-medium {
|
||||||
|
background: #f1d357;
|
||||||
|
div {
|
||||||
|
font-size: 200%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-large {
|
||||||
|
background: #fd9c73;
|
||||||
|
div {
|
||||||
|
font-size: 150%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,30 @@ $(function() {
|
||||||
});
|
});
|
||||||
if (map.geo_json) map.geo_json.remove();
|
if (map.geo_json) map.geo_json.remove();
|
||||||
map.geo_json = geo_json;
|
map.geo_json = geo_json;
|
||||||
geo_json.addTo(map);
|
if ($map_widget.data('group-markers')) {
|
||||||
|
var markers = L.markerClusterGroup({showCoverageOnHover: false,
|
||||||
|
zoomToBoundsOnClick: true,
|
||||||
|
removeOutsideVisibleBounds: true,
|
||||||
|
iconCreateFunction: function (cluster) {
|
||||||
|
var icon_size = 60;
|
||||||
|
var childCount = cluster.getChildCount();
|
||||||
|
var icon_html = '<div><span>' + childCount + '</span></div>';
|
||||||
|
var c = ' marker-cluster-';
|
||||||
|
if (childCount < 10) {
|
||||||
|
c += 'small';
|
||||||
|
} else if (childCount < 100) {
|
||||||
|
c += 'medium';
|
||||||
|
} else {
|
||||||
|
c += 'large';
|
||||||
|
}
|
||||||
|
return new L.DivIcon({html: icon_html, className: 'marker-cluster' + c, iconSize: new L.Point(icon_size, icon_size)});
|
||||||
|
}});
|
||||||
|
markers.addLayer(geo_json);
|
||||||
|
map.addLayer(markers);
|
||||||
|
map.clustered_markers = markers;
|
||||||
|
} else {
|
||||||
|
geo_json.addTo(map);
|
||||||
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(geo_json);
|
callback(geo_json);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
data-max-zoom="{{ max_zoom }}" data-init-lat="{{ init_lat }}"
|
data-max-zoom="{{ max_zoom }}" data-init-lat="{{ init_lat }}"
|
||||||
data-init-lng="{{ init_lng }}" data-geojson-url="{{ geojson_url }}"
|
data-init-lng="{{ init_lng }}" data-geojson-url="{{ geojson_url }}"
|
||||||
data-tile-urltemplate="{{ tile_urltemplate}}" data-map-attribution="{{ map_attribution}}"
|
data-tile-urltemplate="{{ tile_urltemplate}}" data-map-attribution="{{ map_attribution}}"
|
||||||
|
{% if group_markers %}data-group-markers="1"{% endif %}
|
||||||
{% if max_bounds.corner1.lat %}
|
{% if max_bounds.corner1.lat %}
|
||||||
data-max-bounds-lat1="{{ max_bounds.corner1.lat }}"
|
data-max-bounds-lat1="{{ max_bounds.corner1.lat }}"
|
||||||
data-max-bounds-lng1="{{ max_bounds.corner1.lng }}"
|
data-max-bounds-lng1="{{ max_bounds.corner1.lng }}"
|
||||||
|
|
|
@ -82,6 +82,7 @@ INSTALLED_APPS = (
|
||||||
'xstatic.pkg.leaflet',
|
'xstatic.pkg.leaflet',
|
||||||
'xstatic.pkg.opensans',
|
'xstatic.pkg.opensans',
|
||||||
'xstatic.pkg.roboto_fontface',
|
'xstatic.pkg.roboto_fontface',
|
||||||
|
'xstatic.pkg.leaflet_markercluster',
|
||||||
)
|
)
|
||||||
|
|
||||||
INSTALLED_APPS = plugins.register_plugins_apps(INSTALLED_APPS)
|
INSTALLED_APPS = plugins.register_plugins_apps(INSTALLED_APPS)
|
||||||
|
|
|
@ -17,6 +17,7 @@ Depends: ${misc:Depends}, ${python:Depends},
|
||||||
python-xstatic-chartnew-js,
|
python-xstatic-chartnew-js,
|
||||||
python-xstatic-josefinsans,
|
python-xstatic-josefinsans,
|
||||||
python-xstatic-leaflet,
|
python-xstatic-leaflet,
|
||||||
|
python-xstatic-leaflet-markercluster,
|
||||||
python-xstatic-opensans,
|
python-xstatic-opensans,
|
||||||
python-xstatic-roboto-fontface,
|
python-xstatic-roboto-fontface,
|
||||||
python-eopayment (>= 1.9),
|
python-eopayment (>= 1.9),
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -142,6 +142,7 @@ setup(
|
||||||
'requests',
|
'requests',
|
||||||
'XStatic-ChartNew.js',
|
'XStatic-ChartNew.js',
|
||||||
'XStatic-Leaflet',
|
'XStatic-Leaflet',
|
||||||
|
'XStatic-Leaflet-MarkerCluster',
|
||||||
'XStatic_JosefinSans',
|
'XStatic_JosefinSans',
|
||||||
'XStatic_OpenSans',
|
'XStatic_OpenSans',
|
||||||
'XStatic_roboto-fontface',
|
'XStatic_roboto-fontface',
|
||||||
|
|
|
@ -94,12 +94,18 @@ def test_cell_rendering(layer):
|
||||||
assert 'data-init-lat="48.83369263315934"' in rendered
|
assert 'data-init-lat="48.83369263315934"' in rendered
|
||||||
assert 'data-init-lng="2.3233688436448574"' in rendered
|
assert 'data-init-lng="2.3233688436448574"' in rendered
|
||||||
assert 'data-geojson-url="/ajax/mapcell/geojson/1/"' in rendered
|
assert 'data-geojson-url="/ajax/mapcell/geojson/1/"' in rendered
|
||||||
|
assert 'data-group-markers="1"' not in rendered
|
||||||
resp = client.get('/test_map_cell/')
|
resp = client.get('/test_map_cell/')
|
||||||
assert 'xstatic/leaflet.js' in resp.content
|
assert 'xstatic/leaflet.js' in resp.content
|
||||||
assert 'js/combo.map.js' in resp.content
|
assert 'js/combo.map.js' in resp.content
|
||||||
assert 'xstatic/leaflet.css' in resp.content
|
assert 'xstatic/leaflet.css' in resp.content
|
||||||
assert 'css/combo.map.css' in resp.content
|
assert 'css/combo.map.css' in resp.content
|
||||||
|
|
||||||
|
cell.group_markers = True
|
||||||
|
cell.save()
|
||||||
|
rendered = cell.render(context)
|
||||||
|
assert 'data-group-markers="1"' in rendered
|
||||||
|
|
||||||
|
|
||||||
def test_get_geojson_on_non_public_page(layer):
|
def test_get_geojson_on_non_public_page(layer):
|
||||||
page = Page(title='xxx', slug='new', template_name='standard',
|
page = Page(title='xxx', slug='new', template_name='standard',
|
||||||
|
|
Loading…
Reference in New Issue