diff --git a/lib/glug/stylesheet.rb b/lib/glug/stylesheet.rb index 0b5a334..73dad4a 100644 --- a/lib/glug/stylesheet.rb +++ b/lib/glug/stylesheet.rb @@ -9,6 +9,7 @@ def initialize(base_dir: nil, params: nil, &block) @sources = {} @kv = {} @layers = [] + @layer_order = nil @base_dir = base_dir || '' @params = params || {} @dsl = StylesheetDSL.new(self) @@ -25,6 +26,10 @@ def source(source_name, opts = {}) @sources[source_name] = opts end + def layer_order(order) + @layer_order = order + end + # Add a layer # creates a new Layer object using the block supplied def layer(id, opts = {}, &block) @@ -41,7 +46,7 @@ def to_hash v.delete(:default) out['sources'][k] = v end - out['layers'] = @layers.select(&:write?).collect(&:to_hash).compact + out['layers'] = ordered_layers.collect(&:to_hash).compact out end @@ -58,5 +63,44 @@ def _add_layer(layer) def include_file(filename) @dsl.instance_eval(File.read(File.join(@base_dir, filename))) end + + private + + def ordered_layers + writable = @layers.select(&:write?) + return writable unless @layer_order + + order_strings = @layer_order.map(&:to_s) + + # Include sublayers automatically if parent layer is included + groups = Hash.new { |h, k| h[k] = [] } + writable.each do |layer| + lid = layer.kv[:id].to_s + group = find_order_group(lid, order_strings) + groups[group] << layer if group + end + + order_strings.flat_map { |entry| groups[entry] || [] } + end + + def find_order_group(layer_id, order_strings) + return layer_id if order_strings.include?(layer_id) + + # Find the longest order entry that is a prefix of this layer's ID + best = nil + order_strings.each do |entry| + best = entry if layer_id.start_with?("#{entry}__") && (best.nil? || entry.length > best.length) + end + + # If a parent matched, only auto-include when no sibling sublayer + # is explicitly in the order (explicit reference opts into manual control) + if best + order_strings.each do |entry| + return nil if entry.start_with?("#{best}__") + end + end + + best + end end end diff --git a/lib/glug/stylesheet_dsl.rb b/lib/glug/stylesheet_dsl.rb index 9590090..5ee1218 100644 --- a/lib/glug/stylesheet_dsl.rb +++ b/lib/glug/stylesheet_dsl.rb @@ -19,6 +19,10 @@ def include_file(filename) @__impl.include_file(filename) end + def layer_order(order) + @__impl.layer_order(order) + end + # Arbitrary properties can be defined, e.g. "foo :bar" results in a top-level "foo":"bar" in the style def respond_to_missing?(*) true diff --git a/spec/fixtures/layer_ordering.glug b/spec/fixtures/layer_ordering.glug new file mode 100644 index 0000000..bc148e5 --- /dev/null +++ b/spec/fixtures/layer_ordering.glug @@ -0,0 +1,30 @@ +version 8 +name 'My first stylesheet' +source :shortbread, type: 'vector', url: 'https://vector.openstreetmap.org/shortbread_v1/tilejson.json' + +layer_order([ + :first, + :second, + "third", + :nonexistant +]) + +# layer with symbol name +layer(:first, source: :shortbread) do + line_color 0x998877 +end + +# layer with symbol name, but string in ordering +layer(:third, source: :shortbread) do + line_color 0x998877 +end + +# layer with string name, but symbol in ordering +layer("second", source: :shortbread) do + line_color 0x998877 +end + +# layer that doesn't appear in ordering, and so not in output +layer(:unused, source: :shortbread) do + line_color 0x998877 +end diff --git a/spec/fixtures/layer_ordering.json b/spec/fixtures/layer_ordering.json new file mode 100644 index 0000000..e9feb6e --- /dev/null +++ b/spec/fixtures/layer_ordering.json @@ -0,0 +1,33 @@ +{ + "version":8, + "name":"My first stylesheet", + "sources":{ + "shortbread":{ + "type":"vector", + "url":"https://vector.openstreetmap.org/shortbread_v1/tilejson.json" + } + }, + "layers":[ + { + "paint":{"line-color":"#998877"}, + "source":"shortbread", + "id":"first", + "source-layer":"first", + "type":"line" + }, + { + "paint":{"line-color":"#998877"}, + "source":"shortbread", + "id":"second", + "source-layer":"second", + "type":"line" + }, + { + "paint":{"line-color":"#998877"}, + "source":"shortbread", + "id":"third", + "source-layer":"third", + "type":"line" + } + ] +} diff --git a/spec/fixtures/layer_ordering_sublayers.glug b/spec/fixtures/layer_ordering_sublayers.glug new file mode 100644 index 0000000..2d70d3e --- /dev/null +++ b/spec/fixtures/layer_ordering_sublayers.glug @@ -0,0 +1,16 @@ +version 8 +source :shortbread, type: 'vector', url: 'https://example.com/tiles.json' + +layer_order([:roads]) + +# If the sublayers aren't explicitly referred to in the layer_order +# then they are included automatically +layer(:roads, source: :shortbread) do + line_color 0x998877 + on(5..10) do + line_width 2 + end + on(10..15) do + line_width 4 + end +end diff --git a/spec/fixtures/layer_ordering_sublayers.json b/spec/fixtures/layer_ordering_sublayers.json new file mode 100644 index 0000000..02c7f7c --- /dev/null +++ b/spec/fixtures/layer_ordering_sublayers.json @@ -0,0 +1,31 @@ +{ + "version":8, + "sources":{"shortbread":{"type":"vector","url":"https://example.com/tiles.json"}}, + "layers":[ + { + "paint":{"line-color":"#998877"}, + "source":"shortbread", + "id":"roads", + "source-layer":"roads", + "type":"line" + }, + { + "paint":{"line-color":"#998877","line-width":2}, + "source":"shortbread", + "id":"roads__1", + "source-layer":"roads", + "type":"line", + "minzoom":5, + "maxzoom":10 + }, + { + "paint":{"line-color":"#998877","line-width":4}, + "source":"shortbread", + "id":"roads__2", + "source-layer":"roads", + "type":"line", + "minzoom":10, + "maxzoom":15 + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/layer_ordering_sublayers_explicit.glug b/spec/fixtures/layer_ordering_sublayers_explicit.glug new file mode 100644 index 0000000..3c6507f --- /dev/null +++ b/spec/fixtures/layer_ordering_sublayers_explicit.glug @@ -0,0 +1,16 @@ +version 8 +source :shortbread, type: 'vector', url: 'https://example.com/tiles.json' + +layer_order([:roads__2]) + +# If you refer to a sublayer explicitly in the layer_order +# then unreferenced sublayers are ignored +layer(:roads, source: :shortbread) do + line_color 0x998877 + on(5..10) do + line_width 2 + end + on(10..15) do + line_width 4 + end +end diff --git a/spec/fixtures/layer_ordering_sublayers_explicit.json b/spec/fixtures/layer_ordering_sublayers_explicit.json new file mode 100644 index 0000000..7d56a48 --- /dev/null +++ b/spec/fixtures/layer_ordering_sublayers_explicit.json @@ -0,0 +1,15 @@ +{ + "version":8, + "sources":{"shortbread":{"type":"vector","url":"https://example.com/tiles.json"}}, + "layers":[ + { + "paint":{"line-color":"#998877","line-width":4}, + "source":"shortbread", + "id":"roads__2", + "source-layer":"roads", + "type":"line", + "minzoom":10, + "maxzoom":15 + } + ] +} diff --git a/spec/fixtures/layer_ordering_sublayers_with_parent.glug b/spec/fixtures/layer_ordering_sublayers_with_parent.glug new file mode 100644 index 0000000..20caca9 --- /dev/null +++ b/spec/fixtures/layer_ordering_sublayers_with_parent.glug @@ -0,0 +1,16 @@ +version 8 +source :shortbread, type: 'vector', url: 'https://example.com/tiles.json' + +layer_order([:roads, :roads__2]) + +# If you refer to a sublayer explicitly in the layer_order +# then unreferenced sublayers are ignored +layer(:roads, source: :shortbread) do + line_color 0x998877 + on(5..10) do + line_width 2 + end + on(10..15) do + line_width 4 + end +end diff --git a/spec/fixtures/layer_ordering_sublayers_with_parent.json b/spec/fixtures/layer_ordering_sublayers_with_parent.json new file mode 100644 index 0000000..f8f7719 --- /dev/null +++ b/spec/fixtures/layer_ordering_sublayers_with_parent.json @@ -0,0 +1,22 @@ +{ + "version":8, + "sources":{"shortbread":{"type":"vector","url":"https://example.com/tiles.json"}}, + "layers":[ + { + "paint":{"line-color":"#998877"}, + "source":"shortbread", + "id":"roads", + "source-layer":"roads", + "type":"line" + }, + { + "paint":{"line-color":"#998877","line-width":4}, + "source":"shortbread", + "id":"roads__2", + "source-layer":"roads", + "type":"line", + "minzoom":10, + "maxzoom":15 + } + ] +} diff --git a/spec/lib/glug/stylesheet_spec.rb b/spec/lib/glug/stylesheet_spec.rb index f25811e..137ac7b 100644 --- a/spec/lib/glug/stylesheet_spec.rb +++ b/spec/lib/glug/stylesheet_spec.rb @@ -113,5 +113,41 @@ output = File.read(File.join(fixture_dir, 'expression_ivars.json')) expect(stylesheet.to_json).to eql(output.strip) end + + it 'handles explicit layer ordering' do + glug = File.read(File.join(fixture_dir, 'layer_ordering.glug')) + stylesheet = described_class.new(base_dir: fixture_dir) do + instance_eval(glug) + end + output = File.read(File.join(fixture_dir, 'layer_ordering.json')) + expect(stylesheet.to_json).to eql(output.strip) + end + + it 'includes sublayers automatically when parent is in layer_order' do + glug = File.read(File.join(fixture_dir, 'layer_ordering_sublayers.glug')) + stylesheet = described_class.new(base_dir: fixture_dir) do + instance_eval(glug) + end + output = File.read(File.join(fixture_dir, 'layer_ordering_sublayers.json')) + expect(stylesheet.to_json).to eql(output.strip) + end + + it 'excludes other sublayers when one is explicitly in layer_order' do + glug = File.read(File.join(fixture_dir, 'layer_ordering_sublayers_explicit.glug')) + stylesheet = described_class.new(base_dir: fixture_dir) do + instance_eval(glug) + end + output = File.read(File.join(fixture_dir, 'layer_ordering_sublayers_explicit.json')) + expect(stylesheet.to_json).to eql(output.strip) + end + + it 'excludes other sublayers when one other is mentioned and parent included' do + glug = File.read(File.join(fixture_dir, 'layer_ordering_sublayers_with_parent.glug')) + stylesheet = described_class.new(base_dir: fixture_dir) do + instance_eval(glug) + end + output = File.read(File.join(fixture_dir, 'layer_ordering_sublayers_with_parent.json')) + expect(stylesheet.to_json).to eql(output.strip) + end end end