foliage 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|