glug 0.0.2
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/bin/glug +5 -0
- data/lib/glug.rb +317 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 58209224a893c7908aec8ff0b9f9c85211353842
|
4
|
+
data.tar.gz: 5b75fb0f41c1276914b0bddd27569be07c352002
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f3368acdea157ec8e53f69f9361d185d44dc35f97f3bf8ecd1e0edad04ddf16fb491429341cb4045beaaaacfa3a2547334a04704a6c0729608fd2c2746d13a7b
|
7
|
+
data.tar.gz: 1fbf8f7471e5a029400b8bc0239ccec3d2599867f8844203ea0d85b502299a929dc97ed8758e3e4382c96d9ca066b483f8bf230528837bc6bbae3911ff8d7db9
|
data/bin/glug
ADDED
data/lib/glug.rb
ADDED
@@ -0,0 +1,317 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'neatjson'
|
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 }
|
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(:not_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 = sub_cond & @condition
|
199
|
+
end
|
200
|
+
r._set_filter(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(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
|
+
# Add a cascading condition
|
217
|
+
def cascade(*args, &block)
|
218
|
+
cond = (args.length==1) ? args[0] : Condition.new.from_list(:any,args)
|
219
|
+
@cascade_cond = cond
|
220
|
+
self.instance_eval(&block)
|
221
|
+
@cascade_cond = nil
|
222
|
+
end
|
223
|
+
def _add_cascade_condition(k, v)
|
224
|
+
if @cascades.length>0 && @cascades[-1][0].to_s==@cascade_cond.to_s
|
225
|
+
@cascades[-1][1][k]=v
|
226
|
+
else
|
227
|
+
@cascades << [@cascade_cond, { k=>v }]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
def uncascaded(*args)
|
231
|
+
cond = case args.length
|
232
|
+
when 0; nil
|
233
|
+
when 1; args[0]
|
234
|
+
else; Condition.new.from_list(:any,args)
|
235
|
+
end
|
236
|
+
@uncascaded = cond
|
237
|
+
end
|
238
|
+
|
239
|
+
# Setters for @condition (making sure we copy when inheriting)
|
240
|
+
def filter(*args)
|
241
|
+
_set_filter(args.length==1 ? args[0] : Condition.new.from_list(:any,args))
|
242
|
+
end
|
243
|
+
def _set_filter(condition)
|
244
|
+
@condition = condition.dup
|
245
|
+
end
|
246
|
+
|
247
|
+
# Set layer name
|
248
|
+
def id(name)
|
249
|
+
@kv[:id] = name
|
250
|
+
end
|
251
|
+
|
252
|
+
# Suppress output of this layer
|
253
|
+
def suppress; @write = false end
|
254
|
+
def write?; @write end
|
255
|
+
|
256
|
+
# Square-bracket filters (any[...], all[...], none[...])
|
257
|
+
def any ; return Subscriptable.new(:any ) end
|
258
|
+
def all ; return Subscriptable.new(:all ) end
|
259
|
+
def none; return Subscriptable.new(:none) end
|
260
|
+
|
261
|
+
# Deduce 'type' attribute from style attributes
|
262
|
+
def set_type_from(s)
|
263
|
+
return unless s.include?('-')
|
264
|
+
t = s.split('-')[0].to_sym
|
265
|
+
if t==:icon || t==:text then t=:symbol end
|
266
|
+
if @type && @type!=t then raise "Attribute #{s} conflicts with deduced type #{@type} in layer #{@kv[:id]}" end
|
267
|
+
@type=t
|
268
|
+
end
|
269
|
+
|
270
|
+
# Create a Mapbox GL-format hash from a layer definition
|
271
|
+
def to_hash
|
272
|
+
hash = { :layout=> {}, :paint => {} }
|
273
|
+
|
274
|
+
# Assign key/values to correct place
|
275
|
+
@kv.each do |k,v|
|
276
|
+
s = k.to_s.gsub('_','-')
|
277
|
+
if s.include?('-color') && v.is_a?(Fixnum) then v = "#%06x" % v end
|
278
|
+
|
279
|
+
if LAYOUT.include?(k)
|
280
|
+
hash[:layout][s]=v
|
281
|
+
set_type_from s
|
282
|
+
elsif PAINT.include?(k)
|
283
|
+
hash[:paint][s]=v
|
284
|
+
set_type_from s
|
285
|
+
elsif TOP_LEVEL.include?(k) || HIDDEN.include?(k)
|
286
|
+
hash[s]=v
|
287
|
+
else raise "#{s} isn't a recognised layer attribute"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
hash['type'] = @type
|
292
|
+
if @condition then hash['filter'] = @condition.encode end
|
293
|
+
|
294
|
+
# Convert zoom level
|
295
|
+
if (v=hash['zoom'])
|
296
|
+
hash['minzoom'] = v.is_a?(Range) ? v.first : v
|
297
|
+
hash['maxzoom'] = v.is_a?(Range) ? v.last : v
|
298
|
+
hash.delete('zoom')
|
299
|
+
end
|
300
|
+
|
301
|
+
# See if we can reuse an earlier layer's properties
|
302
|
+
mk = ref_key(hash)
|
303
|
+
if stylesheet.refs[mk]
|
304
|
+
REF_PROPERTIES.each { |k| hash.delete(k) }
|
305
|
+
hash['ref'] = stylesheet.refs[mk]
|
306
|
+
else
|
307
|
+
stylesheet.refs[mk] = hash['id']
|
308
|
+
end
|
309
|
+
hash
|
310
|
+
end
|
311
|
+
|
312
|
+
# Key to identify matching layer properties (slow but...)
|
313
|
+
def ref_key(hash)
|
314
|
+
(REF_PROPERTIES.collect { |k| hash[k] } ).to_json
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: glug
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Fairhurst
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: neatjson
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Text-based markup for Mapbox GL styles
|
28
|
+
email: richard@systemeD.net
|
29
|
+
executables:
|
30
|
+
- glug
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/glug.rb
|
35
|
+
- bin/glug
|
36
|
+
homepage: http://github.com/systemed/glug
|
37
|
+
licenses:
|
38
|
+
- FTWPL
|
39
|
+
metadata: {}
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 2.0.14
|
57
|
+
signing_key:
|
58
|
+
specification_version: 4
|
59
|
+
summary: Glug
|
60
|
+
test_files: []
|