foliage 0.1.0

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 (68) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +9 -0
  4. data/app/assets/images/.keep +0 -0
  5. data/app/assets/images/map/marker/icon-2x.png +0 -0
  6. data/app/assets/images/map/marker/icon.png +0 -0
  7. data/app/assets/images/map/marker/icon.svg +67 -0
  8. data/app/assets/images/map/marker/shadow.png +0 -0
  9. data/app/assets/javascripts/core_ext.js.coffee +61 -0
  10. data/app/assets/javascripts/foliage.js.coffee +23 -0
  11. data/app/assets/javascripts/foliage/band.js.coffee +99 -0
  12. data/app/assets/javascripts/foliage/bubbles.js.coffee +77 -0
  13. data/app/assets/javascripts/foliage/categories.js.coffee +70 -0
  14. data/app/assets/javascripts/foliage/choropleth.js.coffee +51 -0
  15. data/app/assets/javascripts/foliage/color.js.coffee +39 -0
  16. data/app/assets/javascripts/foliage/gradient.js.coffee +72 -0
  17. data/app/assets/javascripts/foliage/heatmap.js.coffee +49 -0
  18. data/app/assets/javascripts/foliage/leaf.js.coffee +422 -0
  19. data/app/assets/javascripts/foliage/path.js.coffee +76 -0
  20. data/app/assets/javascripts/foliage/paths.js.coffee +131 -0
  21. data/app/assets/javascripts/foliage/point_group.js.coffee +83 -0
  22. data/app/assets/javascripts/foliage/points.js.coffee +79 -0
  23. data/app/assets/javascripts/foliage/simple.js.coffee +35 -0
  24. data/app/assets/javascripts/leaflet/geographic_util.js.coffee +23 -0
  25. data/app/assets/javascripts/leaflet/ghost_label.js.coffee +100 -0
  26. data/app/assets/javascripts/leaflet/ghost_label_cluster.js.coffee +192 -0
  27. data/app/assets/javascripts/leaflet/layers_scheduler.js.coffee +57 -0
  28. data/app/assets/javascripts/leaflet/reactive_measure.js.coffee +414 -0
  29. data/app/assets/stylesheets/all.scss +16 -0
  30. data/app/assets/stylesheets/application.css +15 -0
  31. data/app/assets/stylesheets/compass/reset.scss +3 -0
  32. data/app/assets/stylesheets/compass/reset/utilities.scss +142 -0
  33. data/app/assets/stylesheets/leaflet.scss +1093 -0
  34. data/app/assets/stylesheets/leaflet/label.scss +40 -0
  35. data/app/assets/stylesheets/leaflet/tooltip.scss +42 -0
  36. data/app/assets/stylesheets/mixins.scss +131 -0
  37. data/app/assets/stylesheets/reset.scss +89 -0
  38. data/app/assets/stylesheets/variables.scss +47 -0
  39. data/app/helpers/foliage_helper.rb +23 -0
  40. data/lib/foliage.rb +9 -0
  41. data/lib/foliage/leaf.rb +235 -0
  42. data/lib/foliage/rails.rb +2 -0
  43. data/lib/foliage/rails/engine.rb +7 -0
  44. data/lib/foliage/rails/integration.rb +8 -0
  45. data/lib/foliage/version.rb +3 -0
  46. data/vendor/assets/javascripts/.keep +0 -0
  47. data/vendor/assets/javascripts/autosize.js +211 -0
  48. data/vendor/assets/javascripts/geographiclib.js +3074 -0
  49. data/vendor/assets/javascripts/leaflet.js.erb +9175 -0
  50. data/vendor/assets/javascripts/leaflet/draw.js +3573 -0
  51. data/vendor/assets/javascripts/leaflet/easy-button.js +366 -0
  52. data/vendor/assets/javascripts/leaflet/fullscreen.js +162 -0
  53. data/vendor/assets/javascripts/leaflet/heatmap.js +142 -0
  54. data/vendor/assets/javascripts/leaflet/label.js +545 -0
  55. data/vendor/assets/javascripts/leaflet/measure.js +6966 -0
  56. data/vendor/assets/javascripts/leaflet/modal.js +364 -0
  57. data/vendor/assets/javascripts/leaflet/providers.js +479 -0
  58. data/vendor/assets/javascripts/rbush.js +621 -0
  59. data/vendor/assets/stylesheets/.keep +0 -0
  60. data/vendor/assets/stylesheets/bootstrap/mixins.scss +55 -0
  61. data/vendor/assets/stylesheets/bootstrap/variables.scss +10 -0
  62. data/vendor/assets/stylesheets/leaflet.scss +479 -0
  63. data/vendor/assets/stylesheets/leaflet/draw.scss +282 -0
  64. data/vendor/assets/stylesheets/leaflet/easy-button.scss +56 -0
  65. data/vendor/assets/stylesheets/leaflet/fullscreen.scss +2 -0
  66. data/vendor/assets/stylesheets/leaflet/measure.scss +168 -0
  67. data/vendor/assets/stylesheets/leaflet/modal.scss +85 -0
  68. metadata +171 -0
@@ -0,0 +1,192 @@
1
+ ###
2
+ # Provides clustering for GhostLabels
3
+
4
+ # options:
5
+ # type {string} ('number' || 'hidden') if number, display count of collapsed items inside a group. If hide, collapsed items are hidden.
6
+ # className {string} Overrides container class name. By default, it inherits from layer
7
+ # innerClassName {string} Set a custom inner class name
8
+ # margin: {number (as px)} Wrap labels in a margin box, considering as the clustering limits. Default: 0
9
+ #
10
+ #
11
+ # Thanks to LayerGroup.collision for inspiration https://github.com/MazeMap/Leaflet.LayerGroup.Collision
12
+ # Thanks to RBush for awesome lib https://github.com/mourner/rbush
13
+ #
14
+ ###
15
+
16
+ L.GhostLabelCluster = L.LayerGroup.extend
17
+ __initialize: L.LayerGroup::initialize
18
+ __addLayer: L.LayerGroup::addLayer
19
+ __removeLayer: L.LayerGroup::removeLayer
20
+ __clearLayers: L.LayerGroup::clearLayers
21
+
22
+ initialize: (options) ->
23
+ L.setOptions @, options
24
+ @_originalLayers = []
25
+ @_clusterIndex = []
26
+ @_visibleLayers = {}
27
+ @_rbush = []
28
+ @_cachedRelativeBoxes = []
29
+ @_margin = 0
30
+ @__initialize.call @, options
31
+ @_margin = options.margin or 0
32
+ @_rbush = null
33
+ return
34
+
35
+ addLayer: (layer) ->
36
+ @_originalLayers.push layer unless @_originalLayers.indexOf(layer) != -1
37
+ if @_map
38
+ @__addClusteredLayer layer
39
+ return
40
+
41
+ bind: (layer, parent) ->
42
+ @addLayer layer
43
+
44
+ if @_originalLayers.indexOf(layer) != -1
45
+ # To be updated when feature name change
46
+ parent.bindGhostLabel layer
47
+ parent.on 'remove', @removeLayer, @
48
+
49
+
50
+ removeLayer: (e) ->
51
+ layer = e.target.label
52
+ @_rbush.remove @_cachedRelativeBoxes[layer._leaflet_id]
53
+ delete @_cachedRelativeBoxes[layer._leaflet_id]
54
+
55
+ i = @_originalLayers.indexOf(layer)
56
+ if i != -1
57
+ @_originalLayers.splice i, 1
58
+
59
+ delete @_visibleLayers[layer._leaflet_id]
60
+ @__removeLayer.call @, layer
61
+
62
+ return
63
+
64
+ clearLayers: ->
65
+ @_rbush = rbush()
66
+ _clusterIndex: []
67
+ @_originalLayers = []
68
+ @_visibleLayers = {}
69
+ @_cachedRelativeBoxes = []
70
+ @__clearLayers.call this
71
+ return
72
+
73
+ onAdd: (map) ->
74
+ unless @_map
75
+ @_map = map
76
+ @refresh()
77
+ map.on 'zoomend', @refresh, this
78
+ return
79
+
80
+ onRemove: (map) ->
81
+ map.off 'zoomend', @refresh, this
82
+ return
83
+
84
+ __addClusteredLayer: (layer) ->
85
+ className = if @options.className? then @options.className else layer.options.className
86
+ innerClass = if @options.innerClassName? then @options.innerClassName else ''
87
+
88
+ bush = @_rbush
89
+
90
+ box = @_cachedRelativeBoxes[layer._leaflet_id]
91
+ visible = false
92
+ if !box
93
+ # Add the layer to the map so it's instantiated on the DOM,
94
+ # in order to fetch its position and size.
95
+ @__addLayer.call @, layer
96
+ visible = true
97
+
98
+ box = @_getContainerBox(layer._container)
99
+
100
+ @_cachedRelativeBoxes[layer._leaflet_id] = box
101
+
102
+ box = @_positionBox(@_map.latLngToLayerPoint(layer.getLatLng()), box)
103
+
104
+ # Search collisions from absolute position
105
+ collidedItems = bush.search(box)
106
+
107
+ # Add reference to layer to track collided layers. Take advantage of rbush properties
108
+ box.push id: layer._leaflet_id
109
+
110
+ if collidedItems.length is 0
111
+ if !visible
112
+ @__addLayer.call @, layer
113
+ @_visibleLayers[layer._leaflet_id] = layer
114
+ bush.load [box]
115
+ else
116
+ @__removeLayer.call @, layer
117
+ # Layers which collided
118
+ latLngBounds = new L.LatLngBounds
119
+ latLngBounds.extend layer.getLatLng()
120
+
121
+ idsToCollapse = []
122
+ for item in collidedItems
123
+ otherLayer = @getLayer(item[4].id)
124
+
125
+ if otherLayer
126
+ bounds = otherLayer.getLatLng()
127
+ @__removeLayer.call @, otherLayer
128
+ idsToCollapse.push otherLayer._leaflet_id
129
+ else
130
+ collapsedLayer = @_visibleLayers[@_clusterIndex[item[4].id]]
131
+ bounds = collapsedLayer.getLatLng() unless collapsedLayer is undefined
132
+
133
+ latLngBounds.extend bounds unless bounds is undefined
134
+
135
+
136
+ collapsedLayer ||= new L.GhostLabel(className: className)
137
+
138
+ collapsedLayer.setLatLng latLngBounds.getCenter()
139
+
140
+
141
+ # add to this layer group
142
+ @__addLayer.call @, collapsedLayer
143
+
144
+ @_visibleLayers[collapsedLayer._leaflet_id] = collapsedLayer
145
+
146
+
147
+ # clustering track
148
+ @_clusterIndex[layer._leaflet_id] = collapsedLayer._leaflet_id
149
+ for id in idsToCollapse
150
+ @_clusterIndex[id] = collapsedLayer._leaflet_id
151
+
152
+ if @options.type is 'number'
153
+ count = @_clusterIndex.filter((i) -> i == collapsedLayer._leaflet_id).length
154
+ collapsedLayer.setContent("<span class='#{innerClass}'>#{count}</span>")
155
+
156
+ collapsedLayer.setLatLng latLngBounds.getCenter()
157
+
158
+
159
+ return
160
+
161
+ _getContainerBox: (el) ->
162
+ styles = window.getComputedStyle(el)
163
+ [
164
+ parseInt(styles.marginLeft)
165
+ parseInt(styles.marginTop)
166
+ parseInt(styles.marginLeft) + parseInt(styles.width)
167
+ parseInt(styles.marginTop) + parseInt(styles.height)
168
+ ]
169
+
170
+ _positionBox: (offset, box) ->
171
+ [
172
+ box[0] + offset.x - (@_margin)
173
+ box[1] + offset.y - (@_margin)
174
+ box[2] + offset.x + @_margin
175
+ box[3] + offset.y + @_margin
176
+ ]
177
+
178
+ refresh: ->
179
+ for id, layer of @_visibleLayers
180
+ @__removeLayer.call @, layer
181
+ delete @_visibleLayers[id]
182
+
183
+ @_rbush = rbush()
184
+
185
+ for layer in @_originalLayers
186
+ @__addClusteredLayer layer
187
+ return
188
+
189
+
190
+ L.ghostLabelCluster = (options) ->
191
+ new (L.GhostLabelCluster)(options or {})
192
+
@@ -0,0 +1,57 @@
1
+ # The scheduler allows to render a layerGroup at a specified level (compared to others).
2
+ # It "schedules" the layerGroup i.e. it replays layerGroup's rendering to the right time.
3
+ L.LayersScheduler = L.Class.extend
4
+
5
+ # flow is an array of layerGroup ids
6
+ initialize: (flow = [], options = {}) ->
7
+ L.Util.setOptions @, options
8
+ @flow = flow
9
+
10
+
11
+ addTo: (map) ->
12
+ @_map = map
13
+
14
+ insert: (id, options = {}) ->
15
+ if options.back then @flow.unshift(id) else @flow.push(id)
16
+ @flow
17
+
18
+ # Note: if layer not found in the flow, we consider to let it in top.
19
+ schedule: (layerGroup = undefined) ->
20
+
21
+ if layerGroup and layerGroup._leaflet_id
22
+ index = @flow.indexOf(layerGroup._leaflet_id)
23
+
24
+ # unless layerGroup to schedule is already the last element
25
+ unless index == -1 or index == @flow.length - 1
26
+ # we need to find the next element to place it before
27
+ nextSibling = @_map._layers[@flow[index + 1]]
28
+
29
+ # to do that, we need to iterate until finding geometry layer and its first svg path
30
+ if nextSibling and nextSibling.getLayers().length
31
+ # as a ILayer (including feature layer)
32
+ objectLayer = nextSibling
33
+
34
+ loop
35
+ break unless (objectLayer and Object.keys(objectLayer._layers || []).length) or not objectLayer._latlngs
36
+ objectLayer = objectLayer.getLayers()[0]
37
+
38
+ layerGroup.bringBefore objectLayer._container if objectLayer._container
39
+
40
+ else
41
+ #redraw all ?
42
+
43
+ # factory
44
+ L.layersScheduler = (flow = [], options = {}) ->
45
+ new L.LayersScheduler(flow, options)
46
+
47
+
48
+ L.LayerGroup.include
49
+ bringBefore: (node) ->
50
+ @invoke 'bringBefore', node
51
+
52
+ L.Path.include
53
+ bringBefore: (node) ->
54
+ root = @_map._pathRoot
55
+ path = @_container
56
+ root.insertBefore path, node
57
+
@@ -0,0 +1,414 @@
1
+ # Extends featureGroup to return measure. Works with inherited classes (L.MultiPolygon, L.MultiPolyline)
2
+ L.FeatureGroup.include
3
+ getMeasure: () ->
4
+
5
+ measure =
6
+ perimeter: 0
7
+ area: 0
8
+
9
+ this.eachLayer (layer) ->
10
+ m = layer.getMeasure()
11
+ measure.perimeter += m.perimeter
12
+ measure.area += m.area
13
+
14
+ measure
15
+
16
+
17
+ L.Polygon.include
18
+ ###
19
+ # Get centroid of the polygon in square meters
20
+ # Portage from leaflet1.0.0-rc1: https://github.com/Leaflet/Leaflet/blob/master/src/layer/vector/Polygon.js
21
+ # @return {number} polygon centroid
22
+ ###
23
+ __getCenter: ->
24
+ @__project()
25
+ points = @_rings[0]
26
+ len = points.length
27
+ if !len
28
+ return null
29
+ # polygon centroid algorithm; only uses the first ring if there are multiple
30
+ area = x = y = 0
31
+ i = 0
32
+ j = len - 1
33
+ while i < len
34
+ p1 = points[i]
35
+ p2 = points[j]
36
+ f = p1.y * p2.x - (p2.y * p1.x)
37
+ x += (p1.x + p2.x) * f
38
+ y += (p1.y + p2.y) * f
39
+ area += f * 3
40
+ j = i++
41
+ if area == 0
42
+ # Polygon is so small that all points are on same pixel.
43
+ center = points[0]
44
+ else
45
+ center = [
46
+ x / area
47
+ y / area
48
+ ]
49
+ @_map.layerPointToLatLng center
50
+
51
+ L.Polyline.include
52
+ ###
53
+ # Return LatLngs as array of [lat, lng] pair.
54
+ # @return {Array} [[lat,lng], [lat,lng]]
55
+ ###
56
+ getLatLngsAsArray: ->
57
+ arr = []
58
+ for latlng in @_latlngs
59
+ arr.push [latlng.lat, latlng.lng]
60
+ arr
61
+
62
+ ###
63
+ # Get center of the polyline in meters
64
+ # Portage from leaflet1.0.0-rc1: https://github.com/Leaflet/Leaflet/blob/master/src/layer/vector/Polyline.js
65
+ # @return {number} polyline center
66
+ ###
67
+ __getCenter: ->
68
+ @__project()
69
+ i = undefined
70
+ halfDist = undefined
71
+ segDist = undefined
72
+ dist = undefined
73
+ p1 = undefined
74
+ p2 = undefined
75
+ ratio = undefined
76
+ points = @_rings[0]
77
+ len = points.length
78
+ if !len
79
+ return null
80
+ # polyline centroid algorithm; only uses the first ring if there are multiple
81
+ i = 0
82
+ halfDist = 0
83
+ while i < len - 1
84
+ halfDist += points[i].distanceTo(points[i + 1]) / 2
85
+ i++
86
+ # The line is so small in the current view that all points are on the same pixel.
87
+ if halfDist == 0
88
+ return @_map.layerPointToLatLng(points[0])
89
+ i = 0
90
+ dist = 0
91
+ while i < len - 1
92
+ p1 = points[i]
93
+ p2 = points[i + 1]
94
+ segDist = p1.distanceTo(p2)
95
+ dist += segDist
96
+ if dist > halfDist
97
+ ratio = (dist - halfDist) / segDist
98
+ return @_map.layerPointToLatLng([
99
+ p2.x - (ratio * (p2.x - (p1.x)))
100
+ p2.y - (ratio * (p2.y - (p1.y)))
101
+ ])
102
+ i++
103
+ return
104
+
105
+ __project: ->
106
+ pxBounds = new (L.Bounds)
107
+ @_rings = []
108
+ @__projectLatlngs @_latlngs, @_rings, pxBounds
109
+ return
110
+
111
+ # recursively turns latlngs into a set of rings with projected coordinates
112
+ __projectLatlngs: (latlngs, result, projectedBounds) ->
113
+ flat = latlngs[0] instanceof L.LatLng
114
+ len = latlngs.length
115
+ i = undefined
116
+ ring = undefined
117
+ if flat
118
+ ring = []
119
+ i = 0
120
+ while i < len
121
+ ring[i] = @_map.latLngToLayerPoint(latlngs[i])
122
+ projectedBounds.extend ring[i]
123
+ i++
124
+ result.push ring
125
+ else
126
+ i = 0
127
+ while i < len
128
+ @__projectLatlngs latlngs[i], result, projectedBounds
129
+ i++
130
+ return
131
+
132
+ getMeasure: () ->
133
+ g = new L.GeographicUtil.Polygon @getLatLngsAsArray()
134
+
135
+ measure =
136
+ perimeter: g.perimeter()
137
+ area: g.area()
138
+
139
+ measure
140
+
141
+
142
+ L.Draw.Polyline.include
143
+ __addHooks: L.Draw.Polyline.prototype.addHooks
144
+ __removeHooks: L.Draw.Polyline.prototype.removeHooks
145
+ __vertexChanged: L.Draw.Polyline.prototype._vertexChanged
146
+
147
+ _vertexChanged: () ->
148
+ @__vertexChanged.apply this, arguments
149
+ @_tooltip.hide()
150
+
151
+
152
+ __onMouseMove: (e) ->
153
+ @_tooltip.hide() if @_tooltip?
154
+ return unless @_markers.length > 0
155
+ newPos = @_map.mouseEventToLayerPoint(e.originalEvent)
156
+ mouseLatLng = @_map.layerPointToLatLng(newPos)
157
+
158
+ latLngArray = []
159
+ for latLng in @_poly.getLatLngs()
160
+ latLngArray.push latLng
161
+ latLngArray.push mouseLatLng
162
+
163
+ # draw a polyline
164
+ if @_markers.length == 1
165
+ clone = L.polyline latLngArray
166
+
167
+ # draw a polygon
168
+ if @_markers.length >= 2
169
+ clone = L.polygon latLngArray
170
+
171
+ clone._map = @_map
172
+ center = clone.__getCenter()
173
+
174
+ g = new L.GeographicUtil.Polygon clone.getLatLngsAsArray()
175
+
176
+ measure =
177
+ perimeter: g.perimeter()
178
+ area: g.area()
179
+
180
+ e.target.reactiveMeasureControl.updateContent measure, {selection: true}
181
+
182
+
183
+
184
+ addHooks: () ->
185
+ @__addHooks.apply this, arguments
186
+ @_map.on 'mousemove', @__onMouseMove, this
187
+ return
188
+
189
+ removeHooks: () ->
190
+ if @_map.reactiveMeasureControl
191
+ @_map.off 'mousemove'
192
+ @__removeHooks.apply this, arguments
193
+ return
194
+
195
+ L.Edit.Poly.include
196
+ __addHooks: L.Edit.Poly.prototype.addHooks
197
+ __removeHooks: L.Edit.Poly.prototype.removeHooks
198
+
199
+ __onHandlerDrag: (e) ->
200
+ center = @_poly.__getCenter()
201
+
202
+ g = new L.GeographicUtil.Polygon @_poly.getLatLngsAsArray()
203
+
204
+ measure =
205
+ perimeter: g.perimeter()
206
+ area: g.area()
207
+
208
+ L.extend(L.Draw.Polyline.prototype.options, target: e.marker.getLatLng())
209
+
210
+ e.marker._map.reactiveMeasureControl.updateContent measure, {selection: true}
211
+
212
+
213
+ addHooks: () ->
214
+ @__addHooks.apply this, arguments
215
+ this._poly.on 'editdrag', @__onHandlerDrag, this
216
+
217
+ removeHooks: () ->
218
+
219
+ g = new L.GeographicUtil.Polygon @_poly.getLatLngsAsArray()
220
+
221
+ measure =
222
+ perimeter: g.perimeter()
223
+ area: g.area()
224
+
225
+ @._poly._map.reactiveMeasureControl.updateContent measure, {selection: false} if @._poly._map?
226
+
227
+ if L.EditToolbar.reactiveMeasure
228
+ this._poly.off 'editdrag'
229
+
230
+ @__removeHooks.apply this, arguments
231
+
232
+ L.Edit.PolyVerticesEdit.include
233
+ __onTouchMove: L.Edit.PolyVerticesEdit::_onTouchMove
234
+ __removeMarker: L.Edit.PolyVerticesEdit::_removeMarker
235
+
236
+ _onMarkerDrag: (e) ->
237
+ marker = e.target
238
+ L.extend marker._origLatLng, marker._latlng
239
+ if marker._middleLeft
240
+ marker._middleLeft.setLatLng @_getMiddleLatLng(marker._prev, marker)
241
+ if marker._middleRight
242
+ marker._middleRight.setLatLng @_getMiddleLatLng(marker, marker._next)
243
+ @_poly.redraw()
244
+ # Overrides to track mouse position
245
+ @_poly.fire 'editdrag', marker: e.target
246
+ return
247
+
248
+ _onTouchMove: (e) ->
249
+ @__onTouchMove.apply @, arguments
250
+ @_poly.fire 'editdrag'
251
+
252
+ _removeMarker: (marker) ->
253
+ @__removeMarker.apply @, arguments
254
+ @_poly.fire 'editdrag', marker: marker
255
+
256
+
257
+ L.LatLng.prototype.toArray = ->
258
+ [@lat, @lng]
259
+
260
+ L.Tooltip.include
261
+ __initialize: L.Tooltip.prototype.initialize
262
+ __dispose: L.Tooltip.prototype.dispose
263
+
264
+ initialize: (map,options = {}) ->
265
+ @__initialize.apply this, arguments
266
+
267
+ dispose: ->
268
+ @_map.off 'mouseover'
269
+ @__dispose.apply this, arguments
270
+
271
+ __updateTooltipMeasure: (latLng, measure = {}, options = {}) ->
272
+ labelText =
273
+ text: ''
274
+ #TODO: use L.drawLocal to i18n tooltip
275
+ if measure['perimeter']
276
+ labelText['text'] += "<span class='leaflet-draw-tooltip-measure perimeter'>#{L.GeometryUtil.readableDistance(measure.perimeter, !!options.metric, !!options.feet)}</span>"
277
+
278
+ if measure['area']
279
+ labelText['text'] += "<span class='leaflet-draw-tooltip-measure area'>#{L.GeometryUtil.readableArea(measure.area, !!options.metric)}</span>"
280
+
281
+ if latLng
282
+ @updateContent labelText
283
+ @__updatePosition latLng, options
284
+
285
+ return
286
+
287
+ __updatePosition: (latlng, options = {}) ->
288
+ pos = @_map.latLngToLayerPoint(latlng)
289
+ labelWidth = @_container.offsetWidth
290
+
291
+ map_width = @_map.getContainer().offsetWidth
292
+ L.DomUtil.removeClass(@_container, 'leaflet-draw-tooltip-left')
293
+
294
+ if @_container
295
+ @_container.style.visibility = 'inherit'
296
+ container = @_map.layerPointToContainerPoint pos
297
+ styles = window.getComputedStyle(@_container)
298
+
299
+ container_width = @_container.offsetWidth + parseInt(styles.paddingLeft) + parseInt(styles.paddingRight) + parseInt(styles.marginLeft) + parseInt(styles.marginRight)
300
+
301
+
302
+ if (container.x < 0 || container.x > (map_width - container_width) || container.y < @_container.offsetHeight)
303
+ pos = pos.add(L.point(-container_width, 0))
304
+ L.DomUtil.addClass(@_container, 'leaflet-draw-tooltip-left')
305
+
306
+ L.DomUtil.setPosition(@_container, pos)
307
+
308
+ hide: ->
309
+ @_container.style.visibility = 'hidden'
310
+
311
+ L.EditToolbar.Edit.include
312
+ _onMouseMove: (e) ->
313
+ return
314
+
315
+ L.EditToolbar.Delete.include
316
+ _onMouseMove: (e) ->
317
+ return
318
+
319
+ ###
320
+ #Add Configuration options
321
+ ###
322
+
323
+ L.DrawToolbar.include
324
+ __initialize: L.DrawToolbar.prototype.initialize
325
+
326
+ initialize: (options) ->
327
+ @__initialize.apply this, arguments
328
+ return
329
+
330
+ L.EditToolbar.include
331
+ __initialize: L.EditToolbar.prototype.initialize
332
+
333
+ initialize: () ->
334
+ @__initialize.apply this, arguments
335
+ return
336
+
337
+
338
+ ###
339
+ # Leaflet.Draw Patches
340
+ ###
341
+ L.EditToolbar.Edit.include
342
+ __removeHooks: L.EditToolbar.Edit::removeHooks
343
+ __revertLayer: L.EditToolbar.Edit::_revertLayer
344
+
345
+ # Patch missing event
346
+ removeHooks: ->
347
+ @__removeHooks.apply @, arguments
348
+ if @_map
349
+ @_map.off 'draw:editvertex', @_updateTooltip, @
350
+
351
+ # Patch handlers not reverted on cancel edit. See https://github.com/Leaflet/Leaflet.draw/issues/532
352
+ _revertLayer: (layer) ->
353
+ id = L.Util.stamp layer
354
+ @__revertLayer.apply @, arguments
355
+ layer.editing.latlngs = this._uneditedLayerProps[id].latlngs
356
+ layer.editing._poly._latlngs = this._uneditedLayerProps[id].latlngs
357
+ layer.editing._verticesHandlers[0]._latlngs = this._uneditedLayerProps[id].latlngs
358
+
359
+ _editStyle: ->
360
+ # missing method declaration in Leaflet.Draw
361
+ return
362
+
363
+ L.EditToolbar.include
364
+ # Patch _activeMode is null
365
+ _save: ->
366
+ handler = this._activeMode.handler
367
+ handler.save()
368
+ handler.disable()
369
+
370
+ L.ReactiveMeasureControl = L.Control.extend
371
+ options:
372
+ position: 'bottomright'
373
+ metric: true
374
+ feet: false
375
+ measure:
376
+ perimeter: 0
377
+ area: 0
378
+
379
+ initialize: (layers, options = {}) ->
380
+ L.Util.setOptions @, options
381
+ # Be sure to reset
382
+ @options.measure.perimeter = 0
383
+ @options.measure.area = 0
384
+
385
+ if layers.getLayers().length > 0
386
+ layers.eachLayer (layer) =>
387
+ if typeof layer.getMeasure is 'function'
388
+ m = layer.getMeasure()
389
+ @options.measure.perimeter += m.perimeter
390
+ @options.measure.area += m.area
391
+
392
+ onAdd: (map) ->
393
+ @_container = L.DomUtil.create('div', "reactive-measure-control #{map._leaflet_id}")
394
+ map.reactiveMeasureControl = @
395
+
396
+ if map and @_container
397
+ @updateContent(@options.measure)
398
+ @_container
399
+
400
+ updateContent: (measure = {}, options = {}) ->
401
+ text = ''
402
+ if measure['perimeter']
403
+ text += "<span class='leaflet-draw-tooltip-measure perimeter'>#{L.GeometryUtil.readableDistance(measure.perimeter, !!@options.metric, !!options.feet)}</span>"
404
+ if measure['area']
405
+ text += "<span class='leaflet-draw-tooltip-measure area'>#{L.GeometryUtil.readableArea(measure.area, !!@options.metric)}</span>"
406
+
407
+ if options.selection? && options.selection is true
408
+ L.DomUtil.addClass @_container, 'selection'
409
+ else
410
+ L.DomUtil.removeClass @_container, 'selection'
411
+
412
+ @_container.innerHTML = text
413
+
414
+ return