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.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +9 -0
- data/app/assets/images/.keep +0 -0
- data/app/assets/images/map/marker/icon-2x.png +0 -0
- data/app/assets/images/map/marker/icon.png +0 -0
- data/app/assets/images/map/marker/icon.svg +67 -0
- data/app/assets/images/map/marker/shadow.png +0 -0
- data/app/assets/javascripts/core_ext.js.coffee +61 -0
- data/app/assets/javascripts/foliage.js.coffee +23 -0
- data/app/assets/javascripts/foliage/band.js.coffee +99 -0
- data/app/assets/javascripts/foliage/bubbles.js.coffee +77 -0
- data/app/assets/javascripts/foliage/categories.js.coffee +70 -0
- data/app/assets/javascripts/foliage/choropleth.js.coffee +51 -0
- data/app/assets/javascripts/foliage/color.js.coffee +39 -0
- data/app/assets/javascripts/foliage/gradient.js.coffee +72 -0
- data/app/assets/javascripts/foliage/heatmap.js.coffee +49 -0
- data/app/assets/javascripts/foliage/leaf.js.coffee +422 -0
- data/app/assets/javascripts/foliage/path.js.coffee +76 -0
- data/app/assets/javascripts/foliage/paths.js.coffee +131 -0
- data/app/assets/javascripts/foliage/point_group.js.coffee +83 -0
- data/app/assets/javascripts/foliage/points.js.coffee +79 -0
- data/app/assets/javascripts/foliage/simple.js.coffee +35 -0
- data/app/assets/javascripts/leaflet/geographic_util.js.coffee +23 -0
- data/app/assets/javascripts/leaflet/ghost_label.js.coffee +100 -0
- data/app/assets/javascripts/leaflet/ghost_label_cluster.js.coffee +192 -0
- data/app/assets/javascripts/leaflet/layers_scheduler.js.coffee +57 -0
- data/app/assets/javascripts/leaflet/reactive_measure.js.coffee +414 -0
- data/app/assets/stylesheets/all.scss +16 -0
- data/app/assets/stylesheets/application.css +15 -0
- data/app/assets/stylesheets/compass/reset.scss +3 -0
- data/app/assets/stylesheets/compass/reset/utilities.scss +142 -0
- data/app/assets/stylesheets/leaflet.scss +1093 -0
- data/app/assets/stylesheets/leaflet/label.scss +40 -0
- data/app/assets/stylesheets/leaflet/tooltip.scss +42 -0
- data/app/assets/stylesheets/mixins.scss +131 -0
- data/app/assets/stylesheets/reset.scss +89 -0
- data/app/assets/stylesheets/variables.scss +47 -0
- data/app/helpers/foliage_helper.rb +23 -0
- data/lib/foliage.rb +9 -0
- data/lib/foliage/leaf.rb +235 -0
- data/lib/foliage/rails.rb +2 -0
- data/lib/foliage/rails/engine.rb +7 -0
- data/lib/foliage/rails/integration.rb +8 -0
- data/lib/foliage/version.rb +3 -0
- data/vendor/assets/javascripts/.keep +0 -0
- data/vendor/assets/javascripts/autosize.js +211 -0
- data/vendor/assets/javascripts/geographiclib.js +3074 -0
- data/vendor/assets/javascripts/leaflet.js.erb +9175 -0
- data/vendor/assets/javascripts/leaflet/draw.js +3573 -0
- data/vendor/assets/javascripts/leaflet/easy-button.js +366 -0
- data/vendor/assets/javascripts/leaflet/fullscreen.js +162 -0
- data/vendor/assets/javascripts/leaflet/heatmap.js +142 -0
- data/vendor/assets/javascripts/leaflet/label.js +545 -0
- data/vendor/assets/javascripts/leaflet/measure.js +6966 -0
- data/vendor/assets/javascripts/leaflet/modal.js +364 -0
- data/vendor/assets/javascripts/leaflet/providers.js +479 -0
- data/vendor/assets/javascripts/rbush.js +621 -0
- data/vendor/assets/stylesheets/.keep +0 -0
- data/vendor/assets/stylesheets/bootstrap/mixins.scss +55 -0
- data/vendor/assets/stylesheets/bootstrap/variables.scss +10 -0
- data/vendor/assets/stylesheets/leaflet.scss +479 -0
- data/vendor/assets/stylesheets/leaflet/draw.scss +282 -0
- data/vendor/assets/stylesheets/leaflet/easy-button.scss +56 -0
- data/vendor/assets/stylesheets/leaflet/fullscreen.scss +2 -0
- data/vendor/assets/stylesheets/leaflet/measure.scss +168 -0
- data/vendor/assets/stylesheets/leaflet/modal.scss +85 -0
- metadata +171 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
# Add sprockets directives below:
|
2
|
+
#= require foliage/gradient
|
3
|
+
|
4
|
+
class Foliage.Choropleth extends Foliage.Gradient
|
5
|
+
|
6
|
+
constructor: (@layer, @data, options = {}) ->
|
7
|
+
super @data, @layer.reference, options
|
8
|
+
|
9
|
+
if this.valid()
|
10
|
+
|
11
|
+
# Compute colors
|
12
|
+
start = new Foliage.Color(options.startColor)
|
13
|
+
stop = new Foliage.Color(options.stopColor)
|
14
|
+
for grade in @grades
|
15
|
+
level = grade.index / (@levelNumber - 1.0)
|
16
|
+
grade.fillColor = Foliage.Color.toString
|
17
|
+
red: start.red + (Math.round(stop.red - start.red) * level)
|
18
|
+
green: start.green + (Math.round(stop.green - start.green) * level)
|
19
|
+
blue: start.blue + (Math.round(stop.blue - start.blue) * level)
|
20
|
+
console.log "Colors computed"
|
21
|
+
else
|
22
|
+
console.warn "Invalid choropleth for #{@layer.reference}"
|
23
|
+
console.warn @data
|
24
|
+
|
25
|
+
# Build layer as wanted
|
26
|
+
buildLayerGroup: (widget, globalStyle = {}) ->
|
27
|
+
group = []
|
28
|
+
for zone in @data
|
29
|
+
zoneStyle =
|
30
|
+
fillColor: this.gradeFor(zone[@layer.reference]).fillColor
|
31
|
+
zoneLayer = new L.GeoJSON(zone.shape, $.extend(true, {}, globalStyle, zoneStyle))
|
32
|
+
widget._bindPopup(zoneLayer, zone)
|
33
|
+
group.push(zoneLayer)
|
34
|
+
group
|
35
|
+
|
36
|
+
# Build HTML legend for given choropleth computed layer
|
37
|
+
buildLegend: () ->
|
38
|
+
html = "<div class='leaflet-legend-item' id='legend-#{@layer.name}'>"
|
39
|
+
html += "<h3>#{@layer.label}"+ if @layer.unit then " (#{@layer.unit})" else "" +"</h3>"
|
40
|
+
html += "<div class='leaflet-legend-body leaflet-choropleth-scale'>"
|
41
|
+
html += "<span class='min-value'>#{@grades[0].minLabel}</span>"
|
42
|
+
html += "<span class='max-value'>#{@grades[@levelNumber - 1].maxLabel}</span>"
|
43
|
+
html += "<span class='leaflet-choropleth-grades'>"
|
44
|
+
for grade in @grades
|
45
|
+
html += "<i class='leaflet-choropleth-grade' style='width: #{100 / @levelNumber}%; background-color: #{grade.fillColor}' title='#{grade.minLabel} ~ #{grade.maxLabel}'></i>"
|
46
|
+
html += "</span>"
|
47
|
+
html += "</div>"
|
48
|
+
html += "</div>"
|
49
|
+
return html
|
50
|
+
|
51
|
+
Foliage.registerLayerType "choropleth", Foliage.Choropleth
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Add sprockets directives below:
|
2
|
+
|
3
|
+
class Foliage.Color
|
4
|
+
constructor: (color) ->
|
5
|
+
@red = parseInt(color.slice(1, 3), 16)
|
6
|
+
@green = parseInt(color.slice(3, 5), 16)
|
7
|
+
@blue = parseInt(color.slice(5, 7), 16)
|
8
|
+
|
9
|
+
toString: () ->
|
10
|
+
Foliage.Color.toString(this)
|
11
|
+
|
12
|
+
|
13
|
+
Foliage.Color.toString = (color) ->
|
14
|
+
return "##{Foliage.Color.toHexCanal(color.red)}#{Foliage.Color.toHexCanal(color.green)}#{Foliage.Color.toHexCanal(color.blue)}"
|
15
|
+
|
16
|
+
Foliage.Color.toHexCanal = (integer) ->
|
17
|
+
hex = Math.round(integer).toString(16)
|
18
|
+
if integer <= 0
|
19
|
+
return "00"
|
20
|
+
else if integer < 16
|
21
|
+
return "0" + hex
|
22
|
+
else if integer > 255
|
23
|
+
return "FF"
|
24
|
+
else
|
25
|
+
return hex
|
26
|
+
|
27
|
+
Foliage.Color.parse = (color) ->
|
28
|
+
value =
|
29
|
+
red: parseInt(color.slice(1, 3), 16)
|
30
|
+
green: parseInt(color.slice(3, 5), 16)
|
31
|
+
blue: parseInt(color.slice(5, 7), 16)
|
32
|
+
return value
|
33
|
+
|
34
|
+
Foliage.Color.random = () ->
|
35
|
+
value =
|
36
|
+
red: 16 * Math.round(16*Math.random())
|
37
|
+
green: 16 * Math.round(16*Math.random())
|
38
|
+
blue: 16 * Math.round(16*Math.random())
|
39
|
+
return Foliage.Color.toString value
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Add sprockets directives below:
|
2
|
+
#
|
3
|
+
|
4
|
+
# Computes grades for a given
|
5
|
+
class Foliage.Gradient
|
6
|
+
|
7
|
+
constructor: (data, property, options = {}) ->
|
8
|
+
options.round ?= 5
|
9
|
+
options.levelNumber ?= 7
|
10
|
+
|
11
|
+
# Default values
|
12
|
+
@round ?= options.round
|
13
|
+
@levelNumber ?= options.levelNumber
|
14
|
+
|
15
|
+
# Find min and max values
|
16
|
+
@grades = []
|
17
|
+
if data[0]
|
18
|
+
@maxValue = data[0][property]
|
19
|
+
@minValue = data[0][property]
|
20
|
+
for zone in data
|
21
|
+
if zone[property] > @maxValue
|
22
|
+
@maxValue = zone[property]
|
23
|
+
if zone[property] < @minValue
|
24
|
+
@minValue = zone[property]
|
25
|
+
if @maxValue == @minValue
|
26
|
+
console.warn "Force max value to be different of min value"
|
27
|
+
@maxValue += 1
|
28
|
+
else
|
29
|
+
console.warn "Sets default min and max without data"
|
30
|
+
@maxValue = 10
|
31
|
+
@minValue = 0
|
32
|
+
console.log "Exact min (#{@minValue}) and max (#{@maxValue}) computed"
|
33
|
+
|
34
|
+
# Simplify values
|
35
|
+
maxMagnitude = Math.magnitude(@maxValue)
|
36
|
+
minMagnitude = Math.magnitude(@minValue)
|
37
|
+
ref = minMagnitude
|
38
|
+
if maxMagnitude.power > minMagnitude.power
|
39
|
+
ref = maxMagnitude
|
40
|
+
@power = ref.power
|
41
|
+
mag = ref.magnitude
|
42
|
+
mag = mag / 10 if mag >= 100
|
43
|
+
@maxValue = Math.ceil2(@maxValue, mag * @round)
|
44
|
+
@minValue = Math.floor2(@minValue, mag * @round)
|
45
|
+
@length = @maxValue - @minValue
|
46
|
+
|
47
|
+
if @length > 0
|
48
|
+
if @levelNumber > @length and @length > 2
|
49
|
+
@levelNumber = @length
|
50
|
+
console.log "Min (#{@minValue}) and max (#{@maxValue}) computed"
|
51
|
+
|
52
|
+
# Compute grades
|
53
|
+
for g in [1..@levelNumber]
|
54
|
+
grade =
|
55
|
+
index: (g-1)
|
56
|
+
min: @minValue + (g-1) * @length / @levelNumber
|
57
|
+
max: @minValue + g * @length / @levelNumber
|
58
|
+
grade.minLabel = Math.humanize(grade.min, @power)
|
59
|
+
grade.maxLabel = Math.humanize(grade.max, @power)
|
60
|
+
@grades.push grade
|
61
|
+
console.log "Grades computed"
|
62
|
+
|
63
|
+
# Returns the grade for the given value
|
64
|
+
gradeFor: (value) ->
|
65
|
+
level = Math.round(@levelNumber * (value - @minValue) / @length)
|
66
|
+
level = @levelNumber - 1 if level >= @levelNumber
|
67
|
+
level = 0 if level < 0 or isNaN(level)
|
68
|
+
return @grades[level]
|
69
|
+
|
70
|
+
# Returns if the gradient is valid
|
71
|
+
valid: () ->
|
72
|
+
@length > 0
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Foliage.Heatmap
|
2
|
+
|
3
|
+
constructor: (@layer, @data, options = {}) ->
|
4
|
+
@items = []
|
5
|
+
property = @layer.reference
|
6
|
+
console.log property
|
7
|
+
console.log @data
|
8
|
+
for zone in @data
|
9
|
+
coords = zone.shape.coordinates
|
10
|
+
@items.push [coords[1], coords[0], ]
|
11
|
+
|
12
|
+
if this.valid()
|
13
|
+
console.log "Heatmap computed"
|
14
|
+
else
|
15
|
+
console.warn "Invalid heatmap points"
|
16
|
+
|
17
|
+
# Build layer as wanted
|
18
|
+
buildLayerGroup: (widget, globalStyle = {}) ->
|
19
|
+
group = []
|
20
|
+
overlay = new L.TileLayer.WebGLHeatMap
|
21
|
+
size: 12
|
22
|
+
opacity: 0.8
|
23
|
+
autoresize: true
|
24
|
+
overlay.setData(@items)
|
25
|
+
|
26
|
+
group.push(overlay)
|
27
|
+
group
|
28
|
+
|
29
|
+
# Build HTML legend for given categories computed layer
|
30
|
+
buildLegend: () ->
|
31
|
+
html = "<div class='leaflet-legend-item' id='legend-#{@layer.name}'>"
|
32
|
+
html += "<h3>#{@layer.label}</h3>"
|
33
|
+
html += "<div class='leaflet-legend-body leaflet-categories-scale'>"
|
34
|
+
# html += "<span class='leaflet-categories-items'>"
|
35
|
+
# for name, item of @items
|
36
|
+
# html += "<span class='leaflet-categories-item'>"
|
37
|
+
# html += "<i class='leaflet-categories-sample' style='background-color: #{item.fillColor};'></i>"
|
38
|
+
# html += " #{item.name}"
|
39
|
+
# html += "</span>"
|
40
|
+
# html += "</span>"
|
41
|
+
html += "</div>"
|
42
|
+
html += "</div>"
|
43
|
+
return html
|
44
|
+
|
45
|
+
# Check if categories are valid
|
46
|
+
valid: () ->
|
47
|
+
@items.length > 0
|
48
|
+
|
49
|
+
Foliage.registerLayerType "heatmap", Foliage.Heatmap
|
@@ -0,0 +1,422 @@
|
|
1
|
+
# Add sprockets directives below:
|
2
|
+
#= require core_ext
|
3
|
+
#= require foliage/color
|
4
|
+
#= require_self
|
5
|
+
#= require foliage/band
|
6
|
+
#= require foliage/bubbles
|
7
|
+
#= require foliage/categories
|
8
|
+
#= require foliage/choropleth
|
9
|
+
#= require foliage/heatmap
|
10
|
+
#= require foliage/path
|
11
|
+
#= require foliage/paths
|
12
|
+
#= require foliage/points
|
13
|
+
#= require foliage/point_group
|
14
|
+
#= require foliage/simple
|
15
|
+
|
16
|
+
((F, $) ->
|
17
|
+
"use strict"
|
18
|
+
|
19
|
+
F.layer = (layer, data, options) ->
|
20
|
+
@layerTypes ?= {}
|
21
|
+
if type = @layerTypes[layer.type]
|
22
|
+
return new type(layer, data, options)
|
23
|
+
else
|
24
|
+
console.warn "Invalid layer type: #{layer.type}"
|
25
|
+
return null
|
26
|
+
|
27
|
+
F.registerLayerType = (name, klass) ->
|
28
|
+
@layerTypes ?= {}
|
29
|
+
@layerTypes[name] = klass
|
30
|
+
|
31
|
+
# Generated colors in ruby
|
32
|
+
# $ p = Proc.new{[37 * rand(7), 255].min.to_i.to_s(16).rjust(2, "0")}
|
33
|
+
# $ (1..300).collect{|x| "##{p[]}#{p[]}#{p[]}"}.uniq
|
34
|
+
F.colors = ["#00de00", "#6f006f", "#4ade94", "#004ab9", "#de6f4a", "#b9b925", "#00b994", "#25946f", "#de00b9", "#94006f", "#de6f94", "#252594", "#dede94", "#4a2594", "#940000", "#deb9de", "#00b9b9", "#00de94", "#25254a", "#6fde6f", "#4a0094", "#256f4a", "#6f4a25", "#4a4a00", "#b9006f", "#4a6f25", "#6f946f", "#009425", "#6f4ade", "#2525de", "#b9946f", "#b9b994", "#b9de94", "#de256f", "#b900b9", "#4a4a6f", "#4a2525", "#006fde", "#940025", "#250094", "#b900de", "#4ab9b9", "#00004a", "#6f6fde", "#256fde", "#b92594", "#6f944a", "#6f6f25", "#4ab9de", "#de2525", "#2525b9", "#944a94", "#b94a94", "#946f94", "#b94a6f", "#000094", "#4a6f6f", "#006f00", "#946f4a", "#00256f", "#6f4a6f", "#de6fb9", "#6fdeb9", "#de6f00", "#94b94a", "#94b994", "#6f6fb9", "#b925de", "#de2594", "#dede25", "#6f4a94", "#946f6f", "#de25de", "#b92525", "#6fde94", "#254a25", "#4adeb9", "#00deb9", "#b9b9b9", "#6f4a4a", "#256f25", "#25deb9", "#6f25de", "#94b925", "#b9254a", "#4ade25", "#4a006f", "#25006f", "#94de00", "#6fb925", "#259425", "#6f9425", "#944a00", "#25b9b9", "#25de4a", "#00254a", "#94254a", "#4a6f94", "#002500", "#6fdede", "#deb925", "#b9b9de", "#4a4a94", "#004a4a", "#25b994", "#6f6f00", "#b92500", "#b925b9", "#940094", "#2594de", "#4ade4a", "#949400", "#256f6f", "#de00de", "#6fde25", "#4a6fde", "#4a4ab9", "#deb96f", "#6f0025", "#00b925", "#0000b9", "#254a94", "#4a25b9", "#b9004a", "#b9de00", "#6f254a", "#6f2500", "#94b96f", "#25de00", "#b99425", "#b90025", "#0094b9", "#4ab925", "#4ab96f", "#6fde00", "#b9b96f", "#94b9b9", "#de4a6f", "#4a2500", "#de0000", "#4a4a4a", "#259494", "#9400b9", "#b9deb9", "#254a00", "#0000de", "#dede4a", "#94dede", "#94de25", "#4a9494", "#4a94de", "#6fb9b9", "#dede00", "#b9256f", "#de9494", "#009494", "#006f4a", "#94944a", "#4ab900", "#6f6f4a", "#b99494", "#6f004a", "#4a256f", "#00b9de", "#b99400", "#00b96f", "#deb9b9", "#4a6f00", "#000025", "#00006f", "#00de4a", "#b96f94", "#6fb9de", "#946fde", "#deb900", "#004ade", "#254ab9", "#25de6f", "#94deb9", "#b994de", "#004a25", "#94256f", "#250025", "#6f6f6f", "#4a944a", "#4a25de", "#00b94a", "#4a4a25", "#9400de", "#94004a", "#4a94b9", "#94de94", "#6f256f", "#6fb900", "#b9944a", "#de94de", "#944a25", "#6f2594"]
|
35
|
+
|
36
|
+
$.widget "ui.leaf",
|
37
|
+
options:
|
38
|
+
box:
|
39
|
+
height: 400
|
40
|
+
width: null
|
41
|
+
backgrounds: {}
|
42
|
+
overlays: {}
|
43
|
+
controls: {}
|
44
|
+
series: {}
|
45
|
+
controlDefaults:
|
46
|
+
fullscreen:
|
47
|
+
position: 'topleft'
|
48
|
+
title: "Fullscreen"
|
49
|
+
geocoder:
|
50
|
+
collapsed: true,
|
51
|
+
position: 'topright',
|
52
|
+
text: 'Locate',
|
53
|
+
bounds: null,
|
54
|
+
email: null
|
55
|
+
layerSelector:
|
56
|
+
collapsed: true,
|
57
|
+
position: 'topright',
|
58
|
+
autoZIndex: true
|
59
|
+
scale:
|
60
|
+
position: 'bottomleft'
|
61
|
+
maxWidth: 200
|
62
|
+
metric: true
|
63
|
+
imperial: false
|
64
|
+
updateWhenIdle: false
|
65
|
+
zoom:
|
66
|
+
position: 'topleft'
|
67
|
+
zoomInText: ''
|
68
|
+
zoomInTitle: "Zoom in"
|
69
|
+
zoomOutText: ''
|
70
|
+
zoomOutTitle: "Zoom out"
|
71
|
+
layers: {}
|
72
|
+
layerDefaults:
|
73
|
+
band:
|
74
|
+
stroke: true
|
75
|
+
color: "#333333"
|
76
|
+
weight: 1
|
77
|
+
opacity: 1
|
78
|
+
fill: true
|
79
|
+
fillColor: "blue"
|
80
|
+
fillOpacity: 1
|
81
|
+
round: 5
|
82
|
+
startColor: '#FFFFFF'
|
83
|
+
stopColor: '#910000'
|
84
|
+
levelNumber: 7
|
85
|
+
bubbles:
|
86
|
+
stroke: true
|
87
|
+
color: "#333333"
|
88
|
+
weight: 1
|
89
|
+
opacity: 1
|
90
|
+
fill: true
|
91
|
+
fillColor: "orange"
|
92
|
+
fillOpacity: 1
|
93
|
+
categories:
|
94
|
+
stroke: true
|
95
|
+
color: "#333333"
|
96
|
+
weight: 1
|
97
|
+
opacity: 1
|
98
|
+
fill: true
|
99
|
+
fillOpacity: 1
|
100
|
+
choropleth:
|
101
|
+
stroke: true
|
102
|
+
color: "#333333"
|
103
|
+
weight: 1
|
104
|
+
opacity: 1
|
105
|
+
fill: true
|
106
|
+
fillColor: "blue"
|
107
|
+
fillOpacity: 1
|
108
|
+
round: 5
|
109
|
+
startColor: '#FFFFFF'
|
110
|
+
stopColor: '#910000'
|
111
|
+
levelNumber: 7
|
112
|
+
simple:
|
113
|
+
stroke: true
|
114
|
+
color: "#333333"
|
115
|
+
weight: 1
|
116
|
+
opacity: 1
|
117
|
+
fill: true
|
118
|
+
fillColor: "green"
|
119
|
+
fillOpacity: 1
|
120
|
+
paths:
|
121
|
+
stroke: true
|
122
|
+
color: "#333333"
|
123
|
+
weight: 2
|
124
|
+
opacity: 0.2
|
125
|
+
fill: true
|
126
|
+
fillOpacity: 1
|
127
|
+
path:
|
128
|
+
stroke: true
|
129
|
+
color: "#333333"
|
130
|
+
weight: 3
|
131
|
+
opacity: 1
|
132
|
+
fill: true
|
133
|
+
fillOpacity: 1
|
134
|
+
fillColor: "#333333"
|
135
|
+
radius: 3
|
136
|
+
points:
|
137
|
+
stroke: true
|
138
|
+
color: "#333333"
|
139
|
+
weight: 2
|
140
|
+
opacity: 1
|
141
|
+
fill: true
|
142
|
+
fillOpacity: 1
|
143
|
+
radius: 5
|
144
|
+
point_group:
|
145
|
+
stroke: true
|
146
|
+
color: "#333333"
|
147
|
+
weight: 2
|
148
|
+
opacity: 1
|
149
|
+
fill: true
|
150
|
+
fillOpacity: 1
|
151
|
+
radius: 5
|
152
|
+
map:
|
153
|
+
maxZoom: 25
|
154
|
+
minZoom:2
|
155
|
+
scrollWheelZoom: false
|
156
|
+
zoomControl: false
|
157
|
+
attributionControl: true
|
158
|
+
setDefaultBackground: false
|
159
|
+
dragging: true
|
160
|
+
touchZoom: true
|
161
|
+
doubleClickZoom: true
|
162
|
+
boxZoom: true
|
163
|
+
tap: true
|
164
|
+
view:
|
165
|
+
center:[]
|
166
|
+
zoom : 13
|
167
|
+
colors: F.colors
|
168
|
+
|
169
|
+
_create: ->
|
170
|
+
$.extend(true, @options, @element.data("leaf"))
|
171
|
+
@mapElement = $("<div>", class: "map").insertAfter(@element)
|
172
|
+
@map = L.map(@mapElement[0], @options.map)
|
173
|
+
@layers = []
|
174
|
+
|
175
|
+
if @options.map.setDefaultBackground
|
176
|
+
opts = {}
|
177
|
+
opts['attribution'] = @options.backgrounds.attribution if @options.backgrounds.attribution?
|
178
|
+
opts['minZoom'] = @options.backgrounds.minZoom if @options.backgrounds.minZoom?
|
179
|
+
opts['maxZoom'] = @options.backgrounds.maxZoom if @options.backgrounds.maxZoom?
|
180
|
+
opts['subdomains'] = @options.backgrounds.subdomains if @options.backgrounds.subdomains?
|
181
|
+
opts['tms'] = true if @options.backgrounds.tms
|
182
|
+
|
183
|
+
backgroundLayer = L.tileLayer(@options.backgrounds.url, opts)
|
184
|
+
backgroundLayer.addTo @map
|
185
|
+
@ghostLabelCluster = L.ghostLabelCluster(type: 'number', innerClassName: 'leaflet-ghost-label-collapsed')
|
186
|
+
@ghostLabelCluster.addTo @map
|
187
|
+
|
188
|
+
@layersScheduler = L.layersScheduler()
|
189
|
+
@layersScheduler.addTo @map
|
190
|
+
|
191
|
+
this._resize()
|
192
|
+
this._refreshView()
|
193
|
+
this._refreshControls()
|
194
|
+
|
195
|
+
_destroy: ->
|
196
|
+
@mapElement.remove()
|
197
|
+
|
198
|
+
zoom: (zoom) ->
|
199
|
+
return @map.getZoom() unless zoom?
|
200
|
+
@options.view.zoom = zoom
|
201
|
+
this._refreshZoom()
|
202
|
+
|
203
|
+
height: (height) ->
|
204
|
+
return @options.box.height unless height?
|
205
|
+
@options.view.box.height = height
|
206
|
+
this._resize()
|
207
|
+
|
208
|
+
rebuild: ->
|
209
|
+
this._destroy()
|
210
|
+
this._create()
|
211
|
+
|
212
|
+
layrs: ->
|
213
|
+
return @layers
|
214
|
+
|
215
|
+
mappo: ->
|
216
|
+
return @map
|
217
|
+
|
218
|
+
mappoElement: ->
|
219
|
+
return @mapElement
|
220
|
+
|
221
|
+
_resize: ->
|
222
|
+
if @options.box?
|
223
|
+
if @options.box.height?
|
224
|
+
@mapElement.height @options.box.height
|
225
|
+
if @options.box.width?
|
226
|
+
@mapElement.width @options.box.width
|
227
|
+
this._trigger "resize"
|
228
|
+
|
229
|
+
# Retuns data from a serie found with the given name
|
230
|
+
_getSerieData: (name) ->
|
231
|
+
if @options.series[name]?
|
232
|
+
return @options.series[name]
|
233
|
+
else
|
234
|
+
console.error "Cannot find serie #{name}"
|
235
|
+
alert "Cannot find serie #{name}"
|
236
|
+
|
237
|
+
# Displays all given controls
|
238
|
+
_refreshControls: ->
|
239
|
+
console.log "Refresh controls...", @options.controls
|
240
|
+
unless @options and @options.controls?
|
241
|
+
console.log "No controls..."
|
242
|
+
return false
|
243
|
+
widget = this
|
244
|
+
for name, options of @options.controls
|
245
|
+
console.log "Add control #{name}..."
|
246
|
+
if options isnt false
|
247
|
+
functionName = "_add#{name.camelize()}Control"
|
248
|
+
if $.isFunction widget[functionName]
|
249
|
+
options = {} if options is true
|
250
|
+
widget[functionName].call(widget, options)
|
251
|
+
else
|
252
|
+
console.log "Unknown control: #{name}"
|
253
|
+
|
254
|
+
_addFullscreenControl: (options) ->
|
255
|
+
options = $.extend true, {}, @options.controlDefaults.fullscreen, options
|
256
|
+
control = new L.Control.FullScreen options
|
257
|
+
@map.addControl control
|
258
|
+
@map.on "enterFullscreen", (e) =>
|
259
|
+
@map.scrollWheelZoom.enable();
|
260
|
+
|
261
|
+
@map.on "exitFullscreen", (e) =>
|
262
|
+
@map.scrollWheelZoom.disable();
|
263
|
+
|
264
|
+
|
265
|
+
_addGeocoderControl: (options) ->
|
266
|
+
options = $.extend true, {}, @options.controlDefaults.geocoder, options
|
267
|
+
control = new L.Control.OSMGeocoder options
|
268
|
+
@map.addControl control
|
269
|
+
|
270
|
+
_addLayerSelectorControl: (options) ->
|
271
|
+
baseLayers = {}
|
272
|
+
overlays = {}
|
273
|
+
|
274
|
+
if @options.backgrounds.length > 0
|
275
|
+
for layer, index in @options.backgrounds
|
276
|
+
opts = {}
|
277
|
+
opts['attribution'] = layer.attribution if layer.attribution?
|
278
|
+
opts['minZoom'] = layer.minZoom if layer.minZoom?
|
279
|
+
opts['maxZoom'] = layer.maxZoom if layer.maxZoom?
|
280
|
+
opts['subdomains'] = layer.subdomains if layer.subdomains?
|
281
|
+
opts['tms'] = true if layer.tms
|
282
|
+
|
283
|
+
backgroundLayer = L.tileLayer(layer.url, opts)
|
284
|
+
baseLayers[layer.name] = backgroundLayer
|
285
|
+
@map.addLayer(backgroundLayer) if layer.byDefault
|
286
|
+
else
|
287
|
+
# no backgrounds, set defaults
|
288
|
+
backgrounds = ['OpenStreetMap.HOT',"OpenStreetMap.Mapnik", "Thunderforest.Landscape", "Esri.WorldImagery"]
|
289
|
+
|
290
|
+
baseLayers = {}
|
291
|
+
for layer, index in backgrounds
|
292
|
+
backgroundLayer = L.tileLayer.provider(layer)
|
293
|
+
baseLayers[layer] = backgroundLayer
|
294
|
+
@map.addLayer(backgroundLayer) if index == 0
|
295
|
+
|
296
|
+
|
297
|
+
for layer in @options.overlays
|
298
|
+
overlayLayer = L.tileLayer.provider(layer.provider_name)
|
299
|
+
overlays[layer.name] = overlayLayer
|
300
|
+
|
301
|
+
legendControl = new L.control(position: "bottomright")
|
302
|
+
legendControl.onAdd = (map) ->
|
303
|
+
L.DomUtil.create('div', 'leaflet-legend-control')
|
304
|
+
@map.addControl legendControl
|
305
|
+
|
306
|
+
$('.leaflet-legend-control').on 'click', () ->
|
307
|
+
$(this).find('#legend-activity').toggleClass('minified')
|
308
|
+
|
309
|
+
|
310
|
+
for layer in @options.layers
|
311
|
+
if console.group isnt undefined
|
312
|
+
console.group "Add layer #{layer.name} (#{layer.type})..."
|
313
|
+
else
|
314
|
+
console.log "Add layer #{layer.name}..."
|
315
|
+
options = {} if options is true
|
316
|
+
|
317
|
+
data = this._getSerieData(layer.serie)
|
318
|
+
options = $.extend true, {}, @options.layerDefaults[layer.type], layer, parent: this
|
319
|
+
renderedLayer = F.layer(layer, data, options)
|
320
|
+
if renderedLayer and renderedLayer.valid()
|
321
|
+
# Build layer group
|
322
|
+
layerGroup = renderedLayer.buildLayerGroup(this, options)
|
323
|
+
console.log("#{layer.name} layer rendered", layerGroup)
|
324
|
+
# Add layer overlay
|
325
|
+
overlayLayer = L.layerGroup(layerGroup)
|
326
|
+
overlayLayer.name = layer.name
|
327
|
+
@layers.push layer
|
328
|
+
layer.overlay = overlays[layer.label] = overlayLayer
|
329
|
+
@map.addLayer(overlayLayer)
|
330
|
+
@layersScheduler.insert overlayLayer._leaflet_id
|
331
|
+
console.log("#{layer.name} layer added")
|
332
|
+
try
|
333
|
+
group = new L.featureGroup(layerGroup)
|
334
|
+
bounds = group.getBounds()
|
335
|
+
@map.fitBounds(bounds)
|
336
|
+
if bounds.getNorthEast().equals bounds.getSouthWest()
|
337
|
+
@map.setZoom 18
|
338
|
+
# Add legend
|
339
|
+
legend = legendControl.getContainer()
|
340
|
+
legend.innerHTML += renderedLayer.buildLegend()
|
341
|
+
else
|
342
|
+
console.warn "Cannot add layer #{layer.type}"
|
343
|
+
|
344
|
+
console.groupEnd() if console.groupEnd isnt undefined
|
345
|
+
|
346
|
+
@map.on "overlayadd", (event) =>
|
347
|
+
@layersScheduler.schedule event.layer
|
348
|
+
console.log "Add legend control..."
|
349
|
+
legend = $(legendControl.getContainer())
|
350
|
+
legend.children("#legend-#{event.layer.name}").show()
|
351
|
+
legend.children(".first").removeClass("first")
|
352
|
+
legend.children(":visible:first").addClass("first")
|
353
|
+
legend.removeClass("empty")
|
354
|
+
return
|
355
|
+
|
356
|
+
@map.on "overlayremove", (event) ->
|
357
|
+
console.log "Remove legend control..."
|
358
|
+
legend = $(legendControl.getContainer())
|
359
|
+
legend.children("#legend-#{event.layer.name}").hide()
|
360
|
+
legend.children(".first").removeClass("first")
|
361
|
+
legend.children(":visible:first").addClass("first")
|
362
|
+
legend.addClass("empty") if legend.children(":visible").length <= 0
|
363
|
+
return
|
364
|
+
|
365
|
+
control = new L.Control.Layers(baseLayers, overlays, @options.controlDefaults.layerSelector)
|
366
|
+
@map.addControl control
|
367
|
+
|
368
|
+
_addScaleControl: (options) ->
|
369
|
+
options = $.extend true, {}, @options.controlDefaults.scale, options
|
370
|
+
control = new L.Control.Scale options
|
371
|
+
@map.addControl control
|
372
|
+
|
373
|
+
_addZoomControl: (options) ->
|
374
|
+
options = $.extend true, {}, @options.controlDefaults.zoom, options
|
375
|
+
control = new L.Control.Zoom options
|
376
|
+
@map.addControl control
|
377
|
+
|
378
|
+
|
379
|
+
# Build a popup from given parameters. For now it only uses popup attribute of
|
380
|
+
# a zone. After it will use a global template for all zone by default which can be
|
381
|
+
# overriden with a local popup
|
382
|
+
_bindPopup: (layer, zone) ->
|
383
|
+
return unless zone.popup?
|
384
|
+
popup = ""
|
385
|
+
for block in zone.popup
|
386
|
+
popup += "<div class='popup-#{block.type}'>"
|
387
|
+
if block.label?
|
388
|
+
popup += "<span class='popup-block-label'>#{block.label}</span>"
|
389
|
+
if block.content?
|
390
|
+
popup += "<span class='popup-block-content'>#{block.content}</span>"
|
391
|
+
else if block.value?
|
392
|
+
popup += "<span class='popup-block-value'>#{block.value}</span>"
|
393
|
+
popup += "</div>"
|
394
|
+
layer.bindPopup(popup)
|
395
|
+
return layer
|
396
|
+
|
397
|
+
|
398
|
+
_refreshView: (view) ->
|
399
|
+
this._setDefaultView()
|
400
|
+
if @options.view.center.length > 0
|
401
|
+
@map.setView(@options.view.center, @options.view.zoom)
|
402
|
+
|
403
|
+
_setDefaultView: ->
|
404
|
+
@map.fitWorld()
|
405
|
+
@map.setZoom 6
|
406
|
+
|
407
|
+
_refreshZoom: ->
|
408
|
+
if @options.view.zoom?
|
409
|
+
@map.setZoom(@options.view.zoom)
|
410
|
+
|
411
|
+
$.loadLeaves = ->
|
412
|
+
$("*[data-leaf]").each ->
|
413
|
+
$(this).leaf()
|
414
|
+
return
|
415
|
+
|
416
|
+
# Needed to easily write setTimeout in CoffeeScript
|
417
|
+
delay = (time, method) -> setTimeout method, time
|
418
|
+
|
419
|
+
$(document).ready $.loadLeaves
|
420
|
+
$(document).on "page:load cocoon:after-insert cell:load", $.loadFoliages
|
421
|
+
|
422
|
+
) Foliage, jQuery
|