geo-activity-playground 0.31.0__py3-none-any.whl → 0.34.1__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.
Files changed (55) hide show
  1. geo_activity_playground/__main__.py +1 -1
  2. geo_activity_playground/core/activities.py +1 -1
  3. geo_activity_playground/core/config.py +5 -1
  4. geo_activity_playground/core/heatmap.py +61 -15
  5. geo_activity_playground/core/tiles.py +8 -5
  6. geo_activity_playground/explorer/video.py +2 -1
  7. geo_activity_playground/importers/directory.py +6 -2
  8. geo_activity_playground/importers/strava_checkout.py +8 -2
  9. geo_activity_playground/webui/activity/blueprint.py +7 -0
  10. geo_activity_playground/webui/activity/controller.py +127 -43
  11. geo_activity_playground/webui/activity/templates/activity/day.html.j2 +6 -2
  12. geo_activity_playground/webui/activity/templates/activity/lines.html.j2 +1 -1
  13. geo_activity_playground/webui/activity/templates/activity/name.html.j2 +3 -2
  14. geo_activity_playground/webui/activity/templates/activity/show.html.j2 +2 -2
  15. geo_activity_playground/webui/app.py +15 -3
  16. geo_activity_playground/webui/entry_controller.py +1 -1
  17. geo_activity_playground/webui/equipment/controller.py +1 -1
  18. geo_activity_playground/webui/explorer/templates/explorer/index.html.j2 +20 -44
  19. geo_activity_playground/webui/heatmap/blueprint.py +5 -2
  20. geo_activity_playground/webui/heatmap/heatmap_controller.py +14 -4
  21. geo_activity_playground/webui/heatmap/templates/heatmap/index.html.j2 +1 -1
  22. geo_activity_playground/webui/settings/blueprint.py +44 -33
  23. geo_activity_playground/webui/settings/templates/settings/color-schemes.html.j2 +11 -2
  24. geo_activity_playground/webui/settings/templates/settings/privacy-zones.html.j2 +1 -1
  25. geo_activity_playground/webui/square_planner/templates/square_planner/index.html.j2 +1 -1
  26. geo_activity_playground/webui/static/Leaflet.fullscreen.min.js +1 -0
  27. geo_activity_playground/webui/static/MarkerCluster.Default.css +60 -0
  28. geo_activity_playground/webui/static/MarkerCluster.css +14 -0
  29. geo_activity_playground/webui/static/bootstrap.min.css +6 -0
  30. geo_activity_playground/webui/static/fullscreen.png +0 -0
  31. geo_activity_playground/webui/static/fullscreen@2x.png +0 -0
  32. geo_activity_playground/webui/static/leaflet.css +661 -0
  33. geo_activity_playground/webui/static/leaflet.fullscreen.css +40 -0
  34. geo_activity_playground/webui/static/leaflet.js +6 -0
  35. geo_activity_playground/webui/static/leaflet.markercluster.js +3 -0
  36. geo_activity_playground/webui/static/table-sort.min.js +8 -0
  37. geo_activity_playground/webui/static/vega-embed@6 +7 -0
  38. geo_activity_playground/webui/static/vega-lite@4 +2 -0
  39. geo_activity_playground/webui/static/vega@5 +2 -0
  40. geo_activity_playground/webui/summary/controller.py +20 -25
  41. geo_activity_playground/webui/summary/templates/summary/index.html.j2 +18 -5
  42. geo_activity_playground/webui/templates/home.html.j2 +4 -3
  43. geo_activity_playground/webui/templates/page.html.j2 +14 -16
  44. geo_activity_playground/webui/tile/blueprint.py +3 -2
  45. geo_activity_playground/webui/tile/controller.py +7 -3
  46. geo_activity_playground/webui/{upload/controller.py → upload_blueprint.py} +41 -35
  47. {geo_activity_playground-0.31.0.dist-info → geo_activity_playground-0.34.1.dist-info}/METADATA +5 -6
  48. {geo_activity_playground-0.31.0.dist-info → geo_activity_playground-0.34.1.dist-info}/RECORD +53 -41
  49. geo_activity_playground/webui/upload/__init__.py +0 -0
  50. geo_activity_playground/webui/upload/blueprint.py +0 -44
  51. /geo_activity_playground/webui/{upload/templates → templates}/upload/index.html.j2 +0 -0
  52. /geo_activity_playground/webui/{upload/templates → templates}/upload/reload.html.j2 +0 -0
  53. {geo_activity_playground-0.31.0.dist-info → geo_activity_playground-0.34.1.dist-info}/LICENSE +0 -0
  54. {geo_activity_playground-0.31.0.dist-info → geo_activity_playground-0.34.1.dist-info}/WHEEL +0 -0
  55. {geo_activity_playground-0.31.0.dist-info → geo_activity_playground-0.34.1.dist-info}/entry_points.txt +0 -0
@@ -73,7 +73,7 @@
73
73
  })
74
74
  L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
75
75
  maxZoom: 19,
76
- attribution: '&copy; <a href=" http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
76
+ attribution: '{{ map_tile_attribution|safe }}'
77
77
  }).addTo(map)
78
78
  let explorer_layer_cluster_color = L.geoJSON(explorer_geojson, {
79
79
  style: function (feature) {
@@ -87,58 +87,34 @@
87
87
  let explorer_layer_first_age_color = L.geoJSON(explorer_geojson, {
88
88
  style: function (feature) {
89
89
  return {
90
- color: "#440154", fillColor: feature.properties.first_age_color,
91
- weight: 0.5
90
+ color: " #440154", fillColor: feature.properties.first_age_color, weight: 0.5
92
91
  }
93
92
  },
94
93
  onEachFeature: onEachFeature
95
- })
96
- let explorer_layer_last_age_color = L.geoJSON(explorer_geojson, {
97
- style: function (feature) {
98
- return {
99
- color: "#440154", fillColor: feature.properties.last_age_color, weight:
100
- 0.5
101
- }
102
- },
103
- onEachFeature: onEachFeature
104
- })
105
-
106
- let bbox = {{ center.bbox| safe }}
107
- if (bbox) {
108
- map.fitBounds(L.geoJSON(bbox).getBounds())
109
- }
110
-
111
- let explorer_square_layer = L.geoJSON(square_geojson,
112
- {
113
- style: function (feature) {
94
+ }); let explorer_layer_last_age_color = L.geoJSON(explorer_geojson, {
95
+ style:
96
+ function (feature) {
114
97
  return {
115
- color: "blue", fill: false, weight: 2
98
+ color: "#440154", fillColor: feature.properties.last_age_color, weight:
99
+ 0.5
116
100
  }
117
- }
118
- }
119
- ).addTo(map)
120
-
121
- active_layer = explorer_layer_cluster_color
122
-
123
- function changeColor(method) {
101
+ }, onEachFeature: onEachFeature
102
+ }); let bbox = {{ center.bbox| safe }}; if (bbox) {
103
+ map.fitBounds(L.geoJSON(bbox).getBounds())
104
+ }; let explorer_square_layer = L.geoJSON(square_geojson, {
105
+ style: function (feature) { return { color: "blue", fill: false, weight: 2 } }
106
+ }).addTo(map)
107
+ active_layer = explorer_layer_cluster_color; function changeColor(method) {
124
108
  map.removeLayer(active_layer)
125
- if (method == "cluster") {
126
- active_layer = explorer_layer_cluster_color
127
- } else if (method == "first") {
109
+ if (method == "cluster") { active_layer = explorer_layer_cluster_color } else if (method == "first") {
128
110
  active_layer = explorer_layer_first_age_color
129
111
  } else if (method == "last") {
130
112
  active_layer = explorer_layer_last_age_color
131
- }
132
- map.addLayer(active_layer)
133
- }
134
-
135
- function downloadAs(suffix) {
136
- bounds = map.getBounds()
137
- zoom = "{{ zoom }}"
138
- window.location.href =
139
- `/explorer/${zoom}/${bounds.getNorth()}/${bounds.getEast()}/${bounds.getSouth()}/${bounds.getWest()}/${suffix}`
140
- }
141
- </script>
113
+ } map.addLayer(active_layer)
114
+ } function downloadAs(suffix) {
115
+ bounds = map.getBounds(); zoom = "{{ zoom }}"
116
+ window.location.href = `/explorer/${zoom}/${bounds.getNorth()}/${bounds.getEast()}/${bounds.getSouth()}/${bounds.getWest()}/${suffix}`
117
+ } </script>
142
118
  </div>
143
119
  </div>
144
120
 
@@ -6,12 +6,15 @@ from flask import Response
6
6
  from ...core.activities import ActivityRepository
7
7
  from ...explorer.tile_visits import TileVisitAccessor
8
8
  from .heatmap_controller import HeatmapController
9
+ from geo_activity_playground.core.config import Config
9
10
 
10
11
 
11
12
  def make_heatmap_blueprint(
12
- repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
13
+ repository: ActivityRepository,
14
+ tile_visit_accessor: TileVisitAccessor,
15
+ config: Config,
13
16
  ) -> Blueprint:
14
- heatmap_controller = HeatmapController(repository, tile_visit_accessor)
17
+ heatmap_controller = HeatmapController(repository, tile_visit_accessor, config)
15
18
  blueprint = Blueprint("heatmap", __name__, template_folder="templates")
16
19
 
17
20
  @blueprint.route("/")
@@ -8,6 +8,7 @@ from PIL import Image
8
8
  from PIL import ImageDraw
9
9
 
10
10
  from geo_activity_playground.core.activities import ActivityRepository
11
+ from geo_activity_playground.core.config import Config
11
12
  from geo_activity_playground.core.heatmap import convert_to_grayscale
12
13
  from geo_activity_playground.core.heatmap import GeoBounds
13
14
  from geo_activity_playground.core.heatmap import get_sensible_zoom_level
@@ -29,10 +30,14 @@ OSM_TILE_SIZE = 256 # OSM tile size in pixel
29
30
 
30
31
  class HeatmapController:
31
32
  def __init__(
32
- self, repository: ActivityRepository, tile_visit_accessor: TileVisitAccessor
33
+ self,
34
+ repository: ActivityRepository,
35
+ tile_visit_accessor: TileVisitAccessor,
36
+ config: Config,
33
37
  ) -> None:
34
38
  self._repository = repository
35
39
  self._tile_visit_accessor = tile_visit_accessor
40
+ self._config = config
36
41
 
37
42
  self.tile_histories = self._tile_visit_accessor.tile_state["tile_history"]
38
43
  self.tile_evolution_states = self._tile_visit_accessor.tile_state[
@@ -140,11 +145,11 @@ class HeatmapController:
140
145
  tile_counts = np.sqrt(tile_counts) / 5
141
146
  tile_counts[tile_counts > 1.0] = 1.0
142
147
 
143
- cmap = pl.get_cmap("hot")
148
+ cmap = pl.get_cmap(self._config.color_scheme_for_heatmap)
144
149
  data_color = cmap(tile_counts)
145
150
  data_color[data_color == cmap(0.0)] = 0.0 # remove background color
146
151
 
147
- map_tile = np.array(get_tile(z, x, y)) / 255
152
+ map_tile = np.array(get_tile(z, x, y, self._config.map_tile_url)) / 255
148
153
  map_tile = convert_to_grayscale(map_tile)
149
154
  map_tile = 1.0 - map_tile # invert colors
150
155
  for c in range(3):
@@ -168,7 +173,12 @@ class HeatmapController:
168
173
  background = np.zeros((*pixel_bounds.shape, 3))
169
174
  for x in range(tile_bounds.x_tile_min, tile_bounds.x_tile_max):
170
175
  for y in range(tile_bounds.y_tile_min, tile_bounds.y_tile_max):
171
- tile = np.array(get_tile(tile_bounds.zoom, x, y)) / 255
176
+ tile = (
177
+ np.array(
178
+ get_tile(tile_bounds.zoom, x, y, self._config.map_tile_url)
179
+ )
180
+ / 255
181
+ )
172
182
 
173
183
  i = y - tile_bounds.y_tile_min
174
184
  j = x - tile_bounds.x_tile_min
@@ -35,7 +35,7 @@
35
35
  });
36
36
  L.tileLayer('/heatmap/tile/{z}/{x}/{y}/{{ kinds_str }}.png', {
37
37
  maxZoom: 19,
38
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
38
+ attribution: '{{ map_tile_attribution|safe }}'
39
39
  }).addTo(map)
40
40
 
41
41
  let bbox = {{ center.bbox| safe }}
@@ -15,6 +15,43 @@ from geo_activity_playground.webui.authenticator import needs_authentication
15
15
  from geo_activity_playground.webui.settings.controller import SettingsController
16
16
 
17
17
 
18
+ VEGA_COLOR_SCHEMES_CONTINUOUS = [
19
+ "lightgreyred",
20
+ "lightgreyteal",
21
+ "lightmulti",
22
+ "lightorange",
23
+ "lighttealblue",
24
+ "blues",
25
+ "tealblues",
26
+ "teals",
27
+ "greens",
28
+ "browns",
29
+ "oranges",
30
+ "reds",
31
+ "purples",
32
+ "warmgreys",
33
+ "greys",
34
+ ]
35
+
36
+ MATPLOTLIB_COLOR_SCHEMES_CONTINUOUS = [
37
+ "afmhot",
38
+ "bone",
39
+ "cividis",
40
+ "copper",
41
+ "gist_gray",
42
+ "gist_heat",
43
+ "gnuplot2",
44
+ "gray",
45
+ "Greys_r",
46
+ "hot",
47
+ "inferno",
48
+ "magma",
49
+ "pink",
50
+ "plasma",
51
+ "viridis",
52
+ ]
53
+
54
+
18
55
  def int_or_none(s: str) -> Optional[int]:
19
56
  if s:
20
57
  try:
@@ -56,44 +93,16 @@ def make_settings_blueprint(
56
93
  config_accessor().color_scheme_for_kind = request.form[
57
94
  "color_scheme_for_kind"
58
95
  ]
96
+ config_accessor().color_scheme_for_heatmap = request.form[
97
+ "color_scheme_for_heatmap"
98
+ ]
59
99
  config_accessor.save()
60
100
  flash("Updated color schemes.", category="success")
101
+
61
102
  return render_template(
62
103
  "settings/color-schemes.html.j2",
63
104
  color_scheme_for_counts=config_accessor().color_scheme_for_counts,
64
- color_scheme_for_counts_avail=[
65
- "viridis",
66
- "magma",
67
- "inferno",
68
- "plasma",
69
- "cividis",
70
- "turbo",
71
- "bluegreen",
72
- "bluepurple",
73
- "goldgreen",
74
- "goldorange",
75
- "goldred",
76
- "greenblue",
77
- "orangered",
78
- "purplebluegreen",
79
- "purpleblue",
80
- "purplered",
81
- "redpurple",
82
- "yellowgreenblue",
83
- "yellowgreen",
84
- "yelloworangebrown",
85
- "yelloworangered",
86
- "darkblue",
87
- "darkgold",
88
- "darkgreen",
89
- "darkmulti",
90
- "darkred",
91
- "lightgreyred",
92
- "lightgreyteal",
93
- "lightmulti",
94
- "lightorange",
95
- "lighttealblue",
96
- ],
105
+ color_scheme_for_counts_avail=VEGA_COLOR_SCHEMES_CONTINUOUS,
97
106
  color_scheme_for_kind=config_accessor().color_scheme_for_kind,
98
107
  color_scheme_for_kind_avail=[
99
108
  "accent",
@@ -111,6 +120,8 @@ def make_settings_blueprint(
111
120
  "tableau10",
112
121
  "tableau20",
113
122
  ],
123
+ color_scheme_for_heatmap=config_accessor().color_scheme_for_heatmap,
124
+ color_scheme_for_heatmap_avail=MATPLOTLIB_COLOR_SCHEMES_CONTINUOUS,
114
125
  )
115
126
 
116
127
  @blueprint.route("/equipment-offsets", methods=["GET", "POST"])
@@ -18,8 +18,17 @@
18
18
  </div>
19
19
 
20
20
  <div class="mb-3">
21
- <label class="form-label">Color scheme for heatmaps</label>
22
- <select class="form-select" aria-label="Color scheme for heatmaps" name="color_scheme_for_counts">
21
+ <label class="form-label">Color scheme for interactive heatmap</label>
22
+ <select class="form-select" aria-label="Color scheme for heatmaps" name="color_scheme_for_heatmap">
23
+ {% for cs in color_scheme_for_heatmap_avail %}
24
+ <option {% if cs==color_scheme_for_heatmap %} selected {% endif %}>{{ cs }}</option>
25
+ {% endfor %}
26
+ </select>
27
+ </div>
28
+
29
+ <div class="mb-3">
30
+ <label class="form-label">Color scheme for count plots</label>
31
+ <select class="form-select" aria-label="Color scheme for count plots" name="color_scheme_for_counts">
23
32
  {% for cs in color_scheme_for_counts_avail %}
24
33
  <option {% if cs==color_scheme_for_counts %} selected {% endif %}>{{ cs }}</option>
25
34
  {% endfor %}
@@ -20,7 +20,7 @@
20
20
  })
21
21
  L.tileLayer('/tile/color/{z}/{x}/{y}.png', {
22
22
  maxZoom: 19,
23
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
23
+ attribution: '{{ map_tile_attribution|safe }}'
24
24
  }).addTo(map)
25
25
 
26
26
  let geojson_layer = L.geoJSON(geojson).addTo(map)
@@ -106,7 +106,7 @@
106
106
 
107
107
  L.tileLayer('/tile/grayscale/{z}/{x}/{y}.png', {
108
108
  maxZoom: 19,
109
- attribution: '&copy; <a href=" http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
109
+ attribution: '{{ map_tile_attribution|safe }}'
110
110
  }).addTo(map)
111
111
 
112
112
  let explorer_geojson = {{ explored_geojson| safe }}
@@ -0,0 +1 @@
1
+ L.Control.Fullscreen=L.Control.extend({options:{position:"topleft",title:{"false":"View Fullscreen","true":"Exit Fullscreen"}},onAdd:function(map){var container=L.DomUtil.create("div","leaflet-control-fullscreen leaflet-bar leaflet-control");this.link=L.DomUtil.create("a","leaflet-control-fullscreen-button leaflet-bar-part",container);this.link.href="#";this._map=map;this._map.on("fullscreenchange",this._toggleTitle,this);this._toggleTitle();L.DomEvent.on(this.link,"click",this._click,this);return container},_click:function(e){L.DomEvent.stopPropagation(e);L.DomEvent.preventDefault(e);this._map.toggleFullscreen(this.options)},_toggleTitle:function(){this.link.title=this.options.title[this._map.isFullscreen()]}});L.Map.include({isFullscreen:function(){return this._isFullscreen||false},toggleFullscreen:function(options){var container=this.getContainer();if(this.isFullscreen()){if(options&&options.pseudoFullscreen){this._disablePseudoFullscreen(container)}else if(document.exitFullscreen){document.exitFullscreen()}else if(document.mozCancelFullScreen){document.mozCancelFullScreen()}else if(document.webkitCancelFullScreen){document.webkitCancelFullScreen()}else if(document.msExitFullscreen){document.msExitFullscreen()}else{this._disablePseudoFullscreen(container)}}else{if(options&&options.pseudoFullscreen){this._enablePseudoFullscreen(container)}else if(container.requestFullscreen){container.requestFullscreen()}else if(container.mozRequestFullScreen){container.mozRequestFullScreen()}else if(container.webkitRequestFullscreen){container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}else if(container.msRequestFullscreen){container.msRequestFullscreen()}else{this._enablePseudoFullscreen(container)}}},_enablePseudoFullscreen:function(container){L.DomUtil.addClass(container,"leaflet-pseudo-fullscreen");this._setFullscreen(true);this.invalidateSize();this.fire("fullscreenchange")},_disablePseudoFullscreen:function(container){L.DomUtil.removeClass(container,"leaflet-pseudo-fullscreen");this._setFullscreen(false);this.invalidateSize();this.fire("fullscreenchange")},_setFullscreen:function(fullscreen){this._isFullscreen=fullscreen;var container=this.getContainer();if(fullscreen){L.DomUtil.addClass(container,"leaflet-fullscreen-on")}else{L.DomUtil.removeClass(container,"leaflet-fullscreen-on")}},_onFullscreenChange:function(e){var fullscreenElement=document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement;if(fullscreenElement===this.getContainer()&&!this._isFullscreen){this._setFullscreen(true);this.fire("fullscreenchange")}else if(fullscreenElement!==this.getContainer()&&this._isFullscreen){this._setFullscreen(false);this.fire("fullscreenchange")}}});L.Map.mergeOptions({fullscreenControl:false});L.Map.addInitHook(function(){if(this.options.fullscreenControl){this.fullscreenControl=new L.Control.Fullscreen(this.options.fullscreenControl);this.addControl(this.fullscreenControl)}var fullscreenchange;if("onfullscreenchange"in document){fullscreenchange="fullscreenchange"}else if("onmozfullscreenchange"in document){fullscreenchange="mozfullscreenchange"}else if("onwebkitfullscreenchange"in document){fullscreenchange="webkitfullscreenchange"}else if("onmsfullscreenchange"in document){fullscreenchange="MSFullscreenChange"}if(fullscreenchange){var onFullscreenChange=L.bind(this._onFullscreenChange,this);this.whenReady(function(){L.DomEvent.on(document,fullscreenchange,onFullscreenChange)});this.on("unload",function(){L.DomEvent.off(document,fullscreenchange,onFullscreenChange)})}});L.control.fullscreen=function(options){return new L.Control.Fullscreen(options)};
@@ -0,0 +1,60 @@
1
+ .marker-cluster-small {
2
+ background-color: rgba(181, 226, 140, 0.6);
3
+ }
4
+ .marker-cluster-small div {
5
+ background-color: rgba(110, 204, 57, 0.6);
6
+ }
7
+
8
+ .marker-cluster-medium {
9
+ background-color: rgba(241, 211, 87, 0.6);
10
+ }
11
+ .marker-cluster-medium div {
12
+ background-color: rgba(240, 194, 12, 0.6);
13
+ }
14
+
15
+ .marker-cluster-large {
16
+ background-color: rgba(253, 156, 115, 0.6);
17
+ }
18
+ .marker-cluster-large div {
19
+ background-color: rgba(241, 128, 23, 0.6);
20
+ }
21
+
22
+ /* IE 6-8 fallback colors */
23
+ .leaflet-oldie .marker-cluster-small {
24
+ background-color: rgb(181, 226, 140);
25
+ }
26
+ .leaflet-oldie .marker-cluster-small div {
27
+ background-color: rgb(110, 204, 57);
28
+ }
29
+
30
+ .leaflet-oldie .marker-cluster-medium {
31
+ background-color: rgb(241, 211, 87);
32
+ }
33
+ .leaflet-oldie .marker-cluster-medium div {
34
+ background-color: rgb(240, 194, 12);
35
+ }
36
+
37
+ .leaflet-oldie .marker-cluster-large {
38
+ background-color: rgb(253, 156, 115);
39
+ }
40
+ .leaflet-oldie .marker-cluster-large div {
41
+ background-color: rgb(241, 128, 23);
42
+ }
43
+
44
+ .marker-cluster {
45
+ background-clip: padding-box;
46
+ border-radius: 20px;
47
+ }
48
+ .marker-cluster div {
49
+ width: 30px;
50
+ height: 30px;
51
+ margin-left: 5px;
52
+ margin-top: 5px;
53
+
54
+ text-align: center;
55
+ border-radius: 15px;
56
+ font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
57
+ }
58
+ .marker-cluster span {
59
+ line-height: 30px;
60
+ }
@@ -0,0 +1,14 @@
1
+ .leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
2
+ -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
3
+ -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
4
+ -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
5
+ transition: transform 0.3s ease-out, opacity 0.3s ease-in;
6
+ }
7
+
8
+ .leaflet-cluster-spider-leg {
9
+ /* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
10
+ -webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
11
+ -moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
12
+ -o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
13
+ transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
14
+ }