glug 0.0.3 → 0.0.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 021d7f8ef27d817a09761e33a6768f58f8d94848
4
- data.tar.gz: ae6f46a63cc86c955683f64f44118e50efc28845
2
+ SHA256:
3
+ metadata.gz: 8426da28bdb893a064adf7e2e79a72791af6520b115185c41766f03749697b1c
4
+ data.tar.gz: f03ae85175a83109aa75db59225cc7086e28651814d826cc163c61889408e724
5
5
  SHA512:
6
- metadata.gz: '039967c1194fabd0c105c3f4eb2e328ffb4589d7fda079037d109338aac9b09ebee55db91ba9b4a5421f45aab7aaa20aa7acc51e4868a79860925be964bb0ce7'
7
- data.tar.gz: 89e4ec1f4a0dd64cd3186f4e911e24088c9104623df83483b180c7987fe21fe76f7084dfaadf71783bf70a2d2b9c4239976f3e5b69b49d1f2b343081bcc3b48e
6
+ metadata.gz: e232e3273af876c58257603c9c0245ffd6350aa8a75f136f2da556128c28f2384d86925db66feee5f76b5e1373b4d90b5fdd0ec0d827b6eafb8f58d635655a89
7
+ data.tar.gz: b152ed4020b693e72998fa9b7ee1277e99dc8601e58a133a2aa03ee58d2d75bb27acf01ff1a8ad245cf5e6a14bb617589d4b267f7f988a35cfc6aed30f3bee96
@@ -0,0 +1,122 @@
1
+ module Glug # :nodoc:
2
+
3
+ # ----- Subscriptable
4
+ # allows us to create conditions with syntax
5
+ # any[(highway=='primary'),(highway=='trunk')]
6
+
7
+ class Subscriptable
8
+ def initialize(type)
9
+ @type=type
10
+ end
11
+ def [](*arguments)
12
+ Condition.new.from_list(@type, arguments)
13
+ end
14
+ end
15
+
16
+ # ----- Condition
17
+ # represents a GL filter of the form [operator, key, value] (etc.)
18
+ # can be merged with other conditions via the & and | operators
19
+
20
+ class Condition
21
+ attr_accessor :values, :operator
22
+
23
+ # GL operators we can't use verbatim (mostly Ruby reserved words)
24
+ SUBSTITUTIONS = {
25
+ string_format: "format",
26
+ is_in: "in",
27
+ case_when: "case",
28
+ _!: "!",
29
+ subtract: "-", divide: "/", pow: "^", # so we can write 'subtract(100,height)'
30
+ feature_id: "id" # Glug already uses 'id'
31
+ }
32
+
33
+ # GL operators that make sense to use as suffixed dot methods
34
+ DOT_METHODS = [
35
+ :array, :boolean, :string_format, :image, :number, :number_format, :object, :string,
36
+ :to_boolean, :to_color, :to_number, :to_string, :typeof,
37
+ :length, :slice, :match,
38
+ :downcase, :upcase, :is_supported_script, :to_rgba,
39
+ :abs, :acos, :asin, :atan, :ceil, :cos, :floor, :ln, :log10, :log2, :round, :sin, :sqrt, :tan
40
+ ]
41
+
42
+ def is(*args); Condition.new.from_key(:==, self, args) end
43
+ def ==(*args); Condition.new.from_key(:==, self, args) end
44
+ def !=(*args); Condition.new.from_key(:!=, self, args) end
45
+ def <(*args); Condition.new.from_key(:< , self, args) end
46
+ def >(*args); Condition.new.from_key(:> , self, args) end
47
+ def <=(*args); Condition.new.from_key(:<=, self, args) end
48
+ def >=(*args); Condition.new.from_key(:>=, self, args) end
49
+ def %(*args); Condition.new.from_key(:% , self, args) end
50
+ def +(*args); Condition.new.from_key(:+ , self, args) end
51
+ def -(*args); Condition.new.from_key(:- , self, args) end
52
+ def *(*args); Condition.new.from_key(:* , self, args) end
53
+ def /(*args); Condition.new.from_key(:/ , self, args) end
54
+ def **(*args); Condition.new.from_key(:^ , self, args) end
55
+ def in(*args); Condition.new.from_key(:in, self, [[:literal,args.flatten]]) end
56
+ def [](*args); Condition.new.from_key(:at, args[0], [self]) end
57
+
58
+ def initialize
59
+ @values=[]
60
+ end
61
+ def from_key(operator, key, list)
62
+ @operator = SUBSTITUTIONS[operator] || operator.to_s.gsub('_','-')
63
+ @values = [key].concat(list)
64
+ self
65
+ end
66
+ def from_list(operator, list)
67
+ @operator = SUBSTITUTIONS[operator] || operator.to_s.gsub('_','-')
68
+ @values = list
69
+ self
70
+ end
71
+
72
+ def &(cond); merge(:all,cond) end
73
+ def |(cond); merge(:any,cond) end
74
+ def merge(op,cond)
75
+ if cond.nil?
76
+ self
77
+ elsif @operator==op
78
+ Condition.new.from_list(op, @values + [cond])
79
+ elsif cond.operator==op
80
+ Condition.new.from_list(op, [self] + cond.values)
81
+ else
82
+ Condition.new.from_list(op, [self, cond])
83
+ end
84
+ end
85
+ def <<(cond); @values << cond.encode; self end
86
+
87
+ # Support dot access for most methods
88
+ def method_missing(method_sym, *args)
89
+ if DOT_METHODS.include?(method_sym)
90
+ Condition.new.from_key(method_sym, self, args)
91
+ else
92
+ super
93
+ end
94
+ end
95
+
96
+ # Encode into an array for GL JSON (recursive)
97
+ def encode
98
+ transform_underscores
99
+ [@operator.to_s, *@values.map { |v| v.is_a?(Condition) ? v.encode : v } ]
100
+ end
101
+ def to_json(opts)
102
+ encode.to_json(opts)
103
+ end
104
+ def to_s
105
+ "<Condition #{@operator} #{@values}>"
106
+ end
107
+
108
+ # Transform nested { font_scale: 0.8 } to { "font-scale"=>0.8 }
109
+ def transform_underscores
110
+ @values.map! do |v|
111
+ if v.is_a?(Hash)
112
+ new_hash = {}
113
+ v.each { |hk,hv| new_hash[hk.is_a?(Symbol) ? hk.to_s.gsub('_','-') : hk] = hv }
114
+ new_hash
115
+ else
116
+ v
117
+ end
118
+ end
119
+ end
120
+
121
+ end # class Condition
122
+ end # module Glug
@@ -0,0 +1,9 @@
1
+ require 'chroma'
2
+ class Integer
3
+ def chroma_hex(op,p)
4
+ ("#"+to_s(16).rjust(6,'0')).paint.send(op,p).to_hex
5
+ end
6
+ def chroma(op,p)
7
+ chroma_hex(op,p).gsub('#','0x').to_i(16)
8
+ end
9
+ end
data/lib/glug/layer.rb ADDED
@@ -0,0 +1,261 @@
1
+ module Glug # :nodoc:
2
+
3
+ # ----- Layer
4
+ # a layer in a GL style
5
+ # this is where most of the hard work happens, including 'method_missing' and 'on' calls to provide the grammar
6
+
7
+ class Layer
8
+
9
+ # GL properties (as distinct from OSM keys)
10
+ LAYOUT = [ :visibility,
11
+ :line_cap, :line_join, :line_miter_limit, :line_round_limit,
12
+ :symbol_placement, :symbol_spacing, :symbol_avoid_edges, :symbol_z_order,
13
+ :icon_allow_overlap, :icon_ignore_placement, :icon_optional, :icon_rotation_alignment, :icon_size,
14
+ :icon_image, :icon_rotate, :icon_padding, :icon_keep_upright, :icon_offset,
15
+ :icon_text_fit, :icon_text_fit_padding, :icon_anchor, :icon_pitch_alignment,
16
+ :text_rotation_alignment, :text_field, :text_font, :text_size, :text_max_width, :text_line_height,
17
+ :text_letter_spacing, :text_justify, :text_anchor, :text_max_angle, :text_rotate, :text_padding,
18
+ :text_keep_upright, :text_transform, :text_offset, :text_allow_overlap, :text_ignore_placement, :text_optional,
19
+ :text_pitch_alignment ]
20
+ PAINT = [ :background_color, :background_pattern, :background_opacity,
21
+ :fill_antialias, :fill_opacity, :fill_color, :fill_outline_color, :fill_translate, :fill_translate_anchor, :fill_pattern,
22
+ :line_opacity, :line_color, :line_translate, :line_translate_anchor, :line_width, :line_gap_width, :line_offset,
23
+ :line_blur, :line_dasharray, :line_pattern, :line_gradient,
24
+ :icon_opacity, :icon_color, :icon_halo_color, :icon_halo_width, :icon_halo_blur, :icon_translate, :icon_translate_anchor,
25
+ :text_opacity, :text_color, :text_halo_color, :text_halo_width, :text_halo_blur, :text_translate, :text_translate_anchor,
26
+ :raster_opacity, :raster_hue_rotate, :raster_brightness_min, :raster_brightness_max, :raster_saturation, :raster_contrast, :raster_resampling, :raster_fade_duration,
27
+ :circle_radius, :circle_color, :circle_blur, :circle_opacity, :circle_translate, :circle_translate_anchor,
28
+ :circle_pitch_scale, :circle_pitch_alignment, :circle_stroke_width, :circle_stroke_color, :circle_stroke_opacity,
29
+ :fill_extrusion_opacity, :fill_extrusion_color, :fill_extrusion_translate, :fill_extrusion_translate_anchor,
30
+ :fill_extrusion_pattern, :fill_extrusion_height, :fill_extrusion_base, :fill_extrusion_vertical_gradient,
31
+ :heatmap_radius, :heatmap_weight, :heatmap_intensity, :heatmap_color, :heatmap_opacity,
32
+ :hillshade_illumination_direction, :hillshade_illumination_anchor, :hillshade_exaggeration,
33
+ :hillshade_shadow_color, :hillshade_highlight_color, :hillshade_accent_color ]
34
+ TOP_LEVEL = [ :metadata, :zoom, :interactive ]
35
+ HIDDEN = [ :ref, :source, :source_layer, :id, :type, :filter, :layout, :paint ] # top level, not settable by commands
36
+ EXPRESSIONS=[ :array, :boolean, :collator, :string_format, :image, :literal, :number,
37
+ :number_format, :object, :string, :to_boolean, :to_color, :to_number, :to_string,
38
+ :typeof, :accumulated, :feature_state, :geometry_type, :feature_id,
39
+ :line_progress, :properties, :at, :get, :has, :is_in, :index_of,
40
+ :length, :slice,
41
+ :all, :any, :case_when, :coalesce, :match, :within,
42
+ :interpolate, :interpolate_hcl, :interpolate_lab, :step,
43
+ :let, :var, :concat, :downcase, :upcase,
44
+ :is_supported_script, :resolved_locale,
45
+ :rgb, :rgba, :to_rgba, :abs, :acos, :asin, :atan, :ceil, :cos, :distance,
46
+ :e, :floor, :ln, :ln2, :log10, :log2, :max, :min, :pi, :round, :sin, :sqrt, :tan,
47
+ :distance_from_center, :pitch, :zoom, :heatmap_density,
48
+ :subtract, :divide, :pow, :_! ]
49
+
50
+ # Shared properties that can be recalled by using a 'ref'
51
+ REF_PROPERTIES = ['type', 'source', 'source-layer', 'minzoom', 'maxzoom', 'filter', 'layout']
52
+
53
+ attr_accessor :kv # key-value pairs for layout, paint, and top level
54
+ attr_accessor :condition # filter condition
55
+ attr_accessor :stylesheet # parent stylesheet object
56
+
57
+ def initialize(stylesheet, args={})
58
+ @stylesheet = stylesheet
59
+ @condition = args[:condition]
60
+ @kv = args[:kv] || {}
61
+ @kv[:id] = args[:id]
62
+ if args[:zoom] then @kv[:zoom]=args[:zoom] end
63
+
64
+ @type = nil # auto-detected layer type
65
+ @write = true # write this layer out, or has it been suppressed?
66
+ @cascade_cond = nil # are we currently evaluating a cascade directive?
67
+ @cascades = args[:cascades] || [] # cascade list to apply to all subsequent layers
68
+ @uncascaded = nil # condition to add to non-cascaded layers
69
+
70
+ @kv[:source] ||= stylesheet.sources.find {|k,v| v[:default] }[0]
71
+ @kv[:source_layer] ||= args[:id]
72
+ @child_num = 0 # incremented sublayer suffix
73
+ end
74
+
75
+ # Handle all missing 'method' calls
76
+ # If we can match it to a GL property, it's an assignment:
77
+ # otherwise it's an OSM key
78
+ def method_missing(method_sym, *arguments)
79
+ if EXPRESSIONS.include?(method_sym)
80
+ return Condition.new.from_list(method_sym, arguments)
81
+ elsif LAYOUT.include?(method_sym) || PAINT.include?(method_sym) || TOP_LEVEL.include?(method_sym)
82
+ v = arguments.length==1 ? arguments[0] : arguments
83
+ if v.is_a?(Proc) then v=v.call(@kv[method_sym]) end
84
+ if @cascade_cond.nil?
85
+ @kv[method_sym] = v
86
+ else
87
+ _add_cascade_condition(method_sym, v)
88
+ end
89
+ else
90
+ return Condition.new.from_list("get", [method_sym])
91
+ end
92
+ end
93
+
94
+ # Convenience so we can write literal(1,2,3) rather than literal([1,2,3])
95
+ def literal(*args)
96
+ if args.length==1 && args[0].is_a?(Hash)
97
+ # Hashes - literal(frog: 1, bill: 2)
98
+ Condition.new.from_list(:literal, [args[0]])
99
+ else
100
+ # Arrays - literal(1,2,3)
101
+ Condition.new.from_list(:literal, [args])
102
+ end
103
+ end
104
+
105
+ # Return a current value from @kv
106
+ # This allows us to do: line_width current_value(:line_width)/2.0
107
+ def current_value(key)
108
+ @kv[key]
109
+ end
110
+
111
+ # Add a sublayer with an additional filter
112
+ def on(*args, &block)
113
+ @child_num+=1
114
+ r = Layer.new(@stylesheet,
115
+ :id => "#{@kv[:id]}__#{@child_num}".to_sym,
116
+ :kv => @kv.dup, :cascades => @cascades.dup)
117
+
118
+ # Set zoom level
119
+ if args[0].is_a?(Range) || args[0].is_a?(Integer)
120
+ r.kv[:zoom] = args.shift
121
+ end
122
+
123
+ # Set condition
124
+ sub_cond = nil
125
+ if args.empty?
126
+ sub_cond = @condition # just inherit parent layer's condition
127
+ else
128
+ sub_cond = (args.length==1) ? args[0] : Condition.new.from_list(:any,args)
129
+ sub_cond = nilsafe_merge(sub_cond, @condition)
130
+ end
131
+ r._set_filter(nilsafe_merge(sub_cond, @uncascaded))
132
+ r.instance_eval(&block)
133
+ @stylesheet._add_layer(r)
134
+
135
+ # Create cascaded layers
136
+ child_chr='a'
137
+ @cascades.each do |c|
138
+ c_cond, c_kv = c
139
+ l = Layer.new(@stylesheet, :id=>"#{r.kv[:id]}__#{child_chr}", :kv=>r.kv.dup)
140
+ l._set_filter(nilsafe_merge(sub_cond, c_cond))
141
+ l.kv.merge!(c_kv)
142
+ @stylesheet._add_layer(l)
143
+ child_chr.next!
144
+ end
145
+ end
146
+
147
+ # Nil-safe merge
148
+ def nilsafe_merge(a,b)
149
+ a.nil? ? b : (a & b)
150
+ end
151
+
152
+ # Add a cascading condition
153
+ def cascade(*args, &block)
154
+ cond = (args.length==1) ? args[0] : Condition.new.from_list(:any,args)
155
+ @cascade_cond = cond
156
+ self.instance_eval(&block)
157
+ @cascade_cond = nil
158
+ end
159
+ def _add_cascade_condition(k, v)
160
+ if @cascades.length>0 && @cascades[-1][0].to_s==@cascade_cond.to_s
161
+ @cascades[-1][1][k]=v
162
+ else
163
+ @cascades << [@cascade_cond, { k=>v }]
164
+ end
165
+ end
166
+ def uncascaded(*args)
167
+ cond = case args.length
168
+ when 0; nil
169
+ when 1; args[0]
170
+ else; Condition.new.from_list(:any,args)
171
+ end
172
+ @uncascaded = cond
173
+ end
174
+
175
+ # Setters for @condition (making sure we copy when inheriting)
176
+ def filter(*args)
177
+ _set_filter(args.length==1 ? args[0] : Condition.new.from_list(:any,args))
178
+ end
179
+ def _set_filter(condition)
180
+ @condition = condition.nil? ? nil : condition.dup
181
+ end
182
+
183
+ # Set layer name
184
+ def id(name)
185
+ @kv[:id] = name
186
+ end
187
+
188
+ # Suppress output of this layer
189
+ def suppress; @write = false end
190
+ def write?; @write end
191
+
192
+ # Square-bracket filters (any[...], all[...])
193
+ def any ; return Subscriptable.new(:any ) end
194
+ def all ; return Subscriptable.new(:all ) end
195
+
196
+ # Deduce 'type' attribute from style attributes
197
+ def set_type_from(s)
198
+ return unless s.include?('-')
199
+ t = (s=~/^fill-extrusion/ ? "fill-extrusion" : s.split('-')[0]).to_sym
200
+ if t==:icon || t==:text then t=:symbol end
201
+ if @type && @type!=t then raise "Attribute #{s} conflicts with deduced type #{@type} in layer #{@kv[:id]}" end
202
+ @type=t
203
+ end
204
+
205
+ # Create a GL-format hash from a layer definition
206
+ def to_hash
207
+ hash = { :layout=> {}, :paint => {} }
208
+
209
+ # Assign key/values to correct place
210
+ @kv.each do |k,v|
211
+ s = k.to_s.gsub('_','-')
212
+ if s.include?('-color') && v.is_a?(Integer) then v = "#%06x" % v end
213
+ if v.respond_to?(:encode) then v=v.encode end
214
+
215
+ if LAYOUT.include?(k)
216
+ hash[:layout][s]=v
217
+ set_type_from s
218
+ elsif PAINT.include?(k)
219
+ hash[:paint][s]=v
220
+ set_type_from s
221
+ elsif TOP_LEVEL.include?(k) || HIDDEN.include?(k)
222
+ hash[s]=v
223
+ else raise "#{s} isn't a recognised layer attribute"
224
+ end
225
+ end
226
+
227
+ hash['type'] = @type
228
+ if @condition then hash['filter'] = @condition.encode end
229
+
230
+ # Convert zoom level
231
+ if (v=hash['zoom'])
232
+ hash['minzoom'] = v.is_a?(Range) ? v.first : v
233
+ hash['maxzoom'] = v.is_a?(Range) ? v.last : v
234
+ hash.delete('zoom')
235
+ end
236
+
237
+ # See if we can reuse an earlier layer's properties
238
+ mk = ref_key(hash)
239
+ if stylesheet.refs[mk]
240
+ REF_PROPERTIES.each { |k| hash.delete(k) }
241
+ hash['ref'] = stylesheet.refs[mk]
242
+ else
243
+ stylesheet.refs[mk] = hash['id']
244
+ end
245
+
246
+ if hash[:layout].empty? && hash[:paint].empty?
247
+ nil
248
+ else
249
+ hash.delete(:layout) if hash[:layout].empty?
250
+ hash.delete(:paint) if hash[:paint].empty?
251
+ hash
252
+ end
253
+ end
254
+
255
+ # Key to identify matching layer properties (slow but...)
256
+ def ref_key(hash)
257
+ (REF_PROPERTIES.collect { |k| hash[k] } ).to_json
258
+ end
259
+
260
+ end # class Layer
261
+ end # module Glug
@@ -0,0 +1,50 @@
1
+ module Glug # :nodoc:
2
+
3
+ # ----- Stylesheet
4
+ # the main document object
5
+
6
+ class Stylesheet
7
+ attr_accessor :sources, :kv, :refs
8
+
9
+ def initialize(&block)
10
+ @sources = {}
11
+ @kv = {}
12
+ @layers = []
13
+ @refs = {}
14
+ instance_eval(&block)
15
+ end
16
+
17
+ # Set a property, e.g. 'bearing 29'
18
+ def method_missing(method_sym, *arguments)
19
+ @kv[method_sym] = arguments[0]
20
+ end
21
+
22
+ # Add a source
23
+ def source(source_name, opts={})
24
+ @sources[source_name] = opts
25
+ end
26
+
27
+ # Add a layer
28
+ # creates a new Layer object using the block supplied
29
+ def layer(id, opts={}, &block)
30
+ r = Layer.new(self, :id=>id, :kv=>opts)
31
+ @layers << r
32
+ r.instance_eval(&block)
33
+ end
34
+
35
+ # Assemble into GL JSON format
36
+ def to_hash
37
+ out = @kv.dup
38
+ out['sources'] = @sources.dup
39
+ out['sources'].each { |k,v| v.delete(:default); out['sources'][k] = v }
40
+ out['layers'] = @layers.select { |r| r.write? }.collect { |r| r.to_hash }.compact
41
+ out
42
+ end
43
+ def to_json(*args); JSON.neat_generate(to_hash) end
44
+
45
+ # Setter for Layer to add sublayers
46
+ def _add_layer(layer)
47
+ @layers << layer
48
+ end
49
+ end
50
+ end
data/lib/glug.rb CHANGED
@@ -1,328 +1,10 @@
1
1
  require 'json'
2
2
  require 'neatjson'
3
3
 
4
- module Glug # :nodoc:
5
-
6
- # ----- Subscriptable
7
- # allows us to create conditions with syntax
8
- # any[(highway=='primary'),(highway=='trunk')]
9
-
10
- class Subscriptable
11
- def initialize(type)
12
- @type=type
13
- end
14
- def [](*arguments)
15
- Condition.new.from_list(@type, arguments)
16
- end
17
- end
18
-
19
- # ----- Condition
20
- # represents a Mapbox GL filter of the form [operator, key, value] (etc.)
21
- # can be merged with other conditions via the & and | operators
22
-
23
- class Condition
24
- attr_accessor :values, :operator
25
- def initialize
26
- @values=[]
27
- end
28
- def from_key(operator, key, list)
29
- @operator = operator
30
- @values = [key].concat(list)
31
- self
32
- end
33
- def from_list(operator, list)
34
- @operator = operator
35
- @values = list
36
- self
37
- end
38
- def &(cond); merge(:all,cond) end
39
- def |(cond); merge(:any,cond) end
40
- def merge(op,cond)
41
- if cond.nil?
42
- self
43
- elsif @operator==op
44
- Condition.new.from_list(op, @values + [cond])
45
- # @values << cond; self
46
- elsif cond.operator==op
47
- Condition.new.from_list(op, [self] + cond.values)
48
- # cond.values << self; cond
49
- else
50
- Condition.new.from_list(op, [self, cond])
51
- end
52
- end
53
- # Encode into an array for Mapbox GL JSON (recursive)
54
- def encode
55
- [@operator.to_s, *@values.map { |v| v.is_a?(Condition) ? v.encode : v } ]
56
- end
57
- def to_s; "<Condition #{@operator} #{@values}>" end
58
- end
59
-
60
- # ----- Stylesheet
61
- # the main document object
62
-
63
- class Stylesheet
64
- attr_accessor :sources, :kv, :refs
65
-
66
- def initialize(&block)
67
- @sources = {}
68
- @kv = {}
69
- @layers = []
70
- @refs = {}
71
- instance_eval(&block)
72
- end
73
-
74
- # Set a property, e.g. 'bearing 29'
75
- def method_missing(method_sym, *arguments)
76
- @kv[method_sym] = arguments[0]
77
- end
78
-
79
- # Add a source
80
- def source(source_name, opts={})
81
- @sources[source_name] = opts
82
- end
83
-
84
- # Add a layer
85
- # creates a new Layer object using the block supplied
86
- def layer(id, opts={}, &block)
87
- r = Layer.new(self, :id=>id, :kv=>opts)
88
- @layers << r
89
- r.instance_eval(&block)
90
- end
91
-
92
- # Assemble into Mapbox GL JSON format
93
- def to_hash
94
- out = @kv.dup
95
- out['sources'] = @sources
96
- out['layers'] = @layers.select { |r| r.write? }.collect { |r| r.to_hash }.compact
97
- out
98
- end
99
- def to_json(*args); JSON.neat_generate(to_hash) end
100
-
101
- # Setter for Layer to add sublayers
102
- def _add_layer(layer)
103
- @layers << layer
104
- end
105
- end
106
-
107
- # ----- OSMKey
108
- # enables us to write "population<30000" and have it magically converted into a Condition
109
-
110
- class OSMKey
111
- def initialize(k)
112
- @k=k
113
- end
114
- def is(*args); Condition.new.from_key(:==,@k,args) end
115
- def ==(*args); Condition.new.from_key(:==,@k,args) end
116
- def !=(*args); Condition.new.from_key(:!=,@k,args) end
117
- def <(*args); Condition.new.from_key(:< ,@k,args) end
118
- def >(*args); Condition.new.from_key(:> ,@k,args) end
119
- def <=(*args); Condition.new.from_key(:<=,@k,args) end
120
- def >=(*args); Condition.new.from_key(:>=,@k,args) end
121
- def in(*args); Condition.new.from_key(:in,@k,args) end
122
- def not_in(*args); Condition.new.from_key('!in',@k,args) end
123
- end
124
-
125
- # ----- Layer
126
- # a layer in an Mapbox GL style
127
- # this is where most of the hard work happens, including 'method_missing' and 'on'
128
- # calls to provide the grammar
129
-
130
- class Layer
131
-
132
- # Mapbox GL properties (as distinct from OSM keys)
133
- LAYOUT = [ :visibility, :line_cap, :line_join, :line_miter_limit, :line_round_limit, :symbol_placement, :symbol_spacing, :symbol_avoid_edges, :icon_allow_overlap, :icon_ignore_placement, :icon_optional, :icon_rotation_alignment, :icon_size, :icon_image, :icon_rotate, :icon_padding, :icon_keep_upright, :icon_offset, :text_rotation_alignment, :text_field, :text_font, :text_size, :text_max_width, :text_line_height, :text_letter_spacing, :text_justify, :text_anchor, :text_max_angle, :text_rotate, :text_padding, :text_keep_upright, :text_transform, :text_offset, :text_allow_overlap, :text_ignore_placement, :text_optional ]
134
- PAINT = [ :background_color, :background_pattern, :background_opacity, :fill_antialias, :fill_opacity, :fill_color, :fill_outline_color, :fill_translate, :fill_translate_anchor, :fill_pattern, :line_opacity, :line_color, :line_translate, :line_translate_anchor, :line_width, :line_gap_width, :line_blur, :line_dasharray, :line_pattern, :icon_opacity, :icon_color, :icon_halo_color, :icon_halo_width, :icon_halo_blur, :icon_translate, :icon_translate_anchor, :text_opacity, :text_color, :text_halo_color, :text_halo_width, :text_halo_blur, :text_translate, :text_translate_anchor, :raster_opacity, :raster_hue_rotate, :raster_brightness_min, :raster_brightness_max, :raster_saturation, :raster_contrast, :raster_fade_duration, :circle_radius, :circle_color, :circle_blur, :circle_opacity, :circle_translate, :circle_translate_anchor ]
135
- TOP_LEVEL = [ :metadata, :zoom, :interactive ]
136
- HIDDEN = [ :ref, :source, :source_layer, :id, :type, :filter, :layout, :paint ] # top level, not settable by commands
137
-
138
- # Shared properties that can be recalled by using a 'ref'
139
- REF_PROPERTIES = ['type', 'source', 'source-layer', 'minzoom', 'maxzoom', 'filter', 'layout']
140
-
141
- attr_accessor :kv # key-value pairs for layout, paint, and top level
142
- attr_accessor :condition # filter condition
143
- attr_accessor :stylesheet # parent stylesheet object
144
-
145
- def initialize(stylesheet, args={})
146
- @stylesheet = stylesheet
147
- @condition = args[:condition]
148
- @kv = args[:kv] || {}
149
- @kv[:id] = args[:id]
150
- if args[:zoom] then @kv[:zoom]=args[:zoom] end
151
-
152
- @type = nil # auto-detected layer type
153
- @write = true # write this layer out, or has it been suppressed?
154
- @cascade_cond = nil # are we currently evaluating a cascade directive?
155
- @cascades = args[:cascades] || [] # cascade list to apply to all subsequent layers
156
- @uncascaded = nil # condition to add to non-cascaded layers
157
-
158
- @kv[:source] ||= stylesheet.sources.find {|k,v| v[:default] }[0]
159
- @kv[:source_layer] ||= args[:id]
160
- @child_num = 0 # incremented sublayer suffix
161
- end
162
-
163
- # Handle all missing 'method' calls
164
- # If we can match it to a Mapbox GL property, it's an assignment:
165
- # otherwise it's an OSM key
166
- def method_missing(method_sym, *arguments)
167
- if LAYOUT.include?(method_sym) || PAINT.include?(method_sym) || TOP_LEVEL.include?(method_sym)
168
- v = arguments.length==1 ? arguments[0] : arguments
169
- if v.is_a?(Proc) then v=v.call(@kv[method_sym]) end
170
- if @cascade_cond.nil?
171
- @kv[method_sym] = v
172
- else
173
- _add_cascade_condition(method_sym, v)
174
- end
175
- else
176
- return OSMKey.new(method_sym.to_s)
177
- end
178
- end
179
-
180
- # Add a sublayer with an additional filter
181
- def on(*args, &block)
182
- @child_num+=1
183
- r = Layer.new(@stylesheet,
184
- :id => "#{@kv[:id]}__#{@child_num}".to_sym,
185
- :kv => @kv.dup, :cascades => @cascades.dup)
186
-
187
- # Set zoom level
188
- if args[0].is_a?(Range) || args[0].is_a?(Fixnum)
189
- r.kv[:zoom] = args.shift
190
- end
191
-
192
- # Set condition
193
- sub_cond = nil
194
- if args.empty?
195
- sub_cond = @condition # just inherit parent layer's condition
196
- else
197
- sub_cond = (args.length==1) ? args[0] : Condition.new.from_list(:any,args)
198
- sub_cond = nilsafe_merge(sub_cond, @condition)
199
- end
200
- r._set_filter(nilsafe_merge(sub_cond, @uncascaded))
201
- r.instance_eval(&block)
202
- @stylesheet._add_layer(r)
203
-
204
- # Create cascaded layers
205
- child_chr='a'
206
- @cascades.each do |c|
207
- c_cond, c_kv = c
208
- l = Layer.new(@stylesheet, :id=>"#{r.kv[:id]}__#{child_chr}", :kv=>r.kv.dup)
209
- l._set_filter(nilsafe_merge(sub_cond, c_cond))
210
- l.kv.merge!(c_kv)
211
- @stylesheet._add_layer(l)
212
- child_chr.next!
213
- end
214
- end
215
-
216
- # Short-form key constructor - for reserved words
217
- def tag(k)
218
- OSMKey.new(k)
219
- end
220
-
221
- # Nil-safe merge
222
- def nilsafe_merge(a,b)
223
- a.nil? ? b : (a & b)
224
- end
225
-
226
- # Add a cascading condition
227
- def cascade(*args, &block)
228
- cond = (args.length==1) ? args[0] : Condition.new.from_list(:any,args)
229
- @cascade_cond = cond
230
- self.instance_eval(&block)
231
- @cascade_cond = nil
232
- end
233
- def _add_cascade_condition(k, v)
234
- if @cascades.length>0 && @cascades[-1][0].to_s==@cascade_cond.to_s
235
- @cascades[-1][1][k]=v
236
- else
237
- @cascades << [@cascade_cond, { k=>v }]
238
- end
239
- end
240
- def uncascaded(*args)
241
- cond = case args.length
242
- when 0; nil
243
- when 1; args[0]
244
- else; Condition.new.from_list(:any,args)
245
- end
246
- @uncascaded = cond
247
- end
248
-
249
- # Setters for @condition (making sure we copy when inheriting)
250
- def filter(*args)
251
- _set_filter(args.length==1 ? args[0] : Condition.new.from_list(:any,args))
252
- end
253
- def _set_filter(condition)
254
- @condition = condition.nil? ? nil : condition.dup
255
- end
256
-
257
- # Set layer name
258
- def id(name)
259
- @kv[:id] = name
260
- end
261
-
262
- # Suppress output of this layer
263
- def suppress; @write = false end
264
- def write?; @write end
265
-
266
- # Square-bracket filters (any[...], all[...], none[...])
267
- def any ; return Subscriptable.new(:any ) end
268
- def all ; return Subscriptable.new(:all ) end
269
- def none; return Subscriptable.new(:none) end
270
-
271
- # Deduce 'type' attribute from style attributes
272
- def set_type_from(s)
273
- return unless s.include?('-')
274
- t = s.split('-')[0].to_sym
275
- if t==:icon || t==:text then t=:symbol end
276
- if @type && @type!=t then raise "Attribute #{s} conflicts with deduced type #{@type} in layer #{@kv[:id]}" end
277
- @type=t
278
- end
279
-
280
- # Create a Mapbox GL-format hash from a layer definition
281
- def to_hash
282
- hash = { :layout=> {}, :paint => {} }
283
-
284
- # Assign key/values to correct place
285
- @kv.each do |k,v|
286
- s = k.to_s.gsub('_','-')
287
- if s.include?('-color') && v.is_a?(Fixnum) then v = "#%06x" % v end
288
-
289
- if LAYOUT.include?(k)
290
- hash[:layout][s]=v
291
- set_type_from s
292
- elsif PAINT.include?(k)
293
- hash[:paint][s]=v
294
- set_type_from s
295
- elsif TOP_LEVEL.include?(k) || HIDDEN.include?(k)
296
- hash[s]=v
297
- else raise "#{s} isn't a recognised layer attribute"
298
- end
299
- end
300
-
301
- hash['type'] = @type
302
- if @condition then hash['filter'] = @condition.encode end
303
-
304
- # Convert zoom level
305
- if (v=hash['zoom'])
306
- hash['minzoom'] = v.is_a?(Range) ? v.first : v
307
- hash['maxzoom'] = v.is_a?(Range) ? v.last : v
308
- hash.delete('zoom')
309
- end
310
-
311
- # See if we can reuse an earlier layer's properties
312
- mk = ref_key(hash)
313
- if stylesheet.refs[mk]
314
- REF_PROPERTIES.each { |k| hash.delete(k) }
315
- hash['ref'] = stylesheet.refs[mk]
316
- else
317
- stylesheet.refs[mk] = hash['id']
318
- end
319
-
320
- hash[:layout].empty? && hash[:paint].empty? ? nil : hash
321
- end
322
-
323
- # Key to identify matching layer properties (slow but...)
324
- def ref_key(hash)
325
- (REF_PROPERTIES.collect { |k| hash[k] } ).to_json
326
- end
327
- end
4
+ module Glug
328
5
  end
6
+
7
+ require_relative 'glug/condition'
8
+ require_relative 'glug/stylesheet'
9
+ require_relative 'glug/layer'
10
+ require_relative 'glug/extensions'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glug
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Fairhurst
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-23 00:00:00.000000000 Z
11
+ date: 2022-07-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: neatjson
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: chroma
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description: Text-based markup for Mapbox GL styles
28
42
  email: richard@systemeD.net
29
43
  executables:
@@ -33,6 +47,10 @@ extra_rdoc_files: []
33
47
  files:
34
48
  - bin/glug
35
49
  - lib/glug.rb
50
+ - lib/glug/condition.rb
51
+ - lib/glug/extensions.rb
52
+ - lib/glug/layer.rb
53
+ - lib/glug/stylesheet.rb
36
54
  homepage: http://github.com/systemed/glug
37
55
  licenses:
38
56
  - FTWPL
@@ -52,8 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
70
  - !ruby/object:Gem::Version
53
71
  version: '0'
54
72
  requirements: []
55
- rubyforge_project:
56
- rubygems_version: 2.6.13
73
+ rubygems_version: 3.1.2
57
74
  signing_key:
58
75
  specification_version: 4
59
76
  summary: Glug