summaryrefslogtreecommitdiffstats
path: root/src/Styler.coffee
blob: f3f90152a4a3a20e56bd3b5ac904bd7c7ea583d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
###
  termap - Terminal Map Viewer
  by Michael Strassburger <codepoet@cpan.org>

  Minimalistic parser and compiler for Mapbox (Studio) Map Style files
  See: https://www.mapbox.com/mapbox-gl-style-spec/

  Compiles layer filter instructions into a chain of true/false returning
  anonymous functions to improve rendering speed compared to realtime parsing.
###

fs = require 'fs'

module.exports = class Styler
  styleById: {}
  styleByLayer: {}

  constructor: (file) ->
    json = JSON.parse fs.readFileSync(file).toString()
    @styleName = json.name

    @_replaceConstants json.constants, json.layers if json.constants

    for style in json.layers
      if style.ref and @styleById[style.ref]
        for ref in ['type', 'source-layer', 'minzoom', 'maxzoom', 'filter']
          if @styleById[style.ref][ref] and not style[ref]
            style[ref] = @styleById[style.ref][ref]

      style.appliesTo = @_compileFilter style.filter

      @styleByLayer[style['source-layer']] ?= []
      @styleByLayer[style['source-layer']].push style
      @styleById[style.id] = style

  getStyleFor: (layer, feature, zoom) ->
    return false unless @styleByLayer[layer]

    for style in @styleByLayer[layer]
      if style.appliesTo feature
          return style

    return false

  _replaceConstants: (constants, tree, level=0) ->
    for id, node of tree
      switch typeof node
        when 'object'
          continue if level>1
          @_replaceConstants constants, node, level+1

        when 'string'
          if node.charAt(0) is '@'
            tree[id] = constants[node]
    null

  _compileFilter: (filter) ->
    switch filter?[0]
      when "all"
        filters = (@_compileFilter subFilter for subFilter in filter[1..])
        (feature) ->
          return false for appliesTo in filters when not appliesTo feature
          true

      when "any"
        filters = (@_compileFilter subFilter for subFilter in filter[1..])
        (feature) ->
          return true for appliesTo in filters when appliesTo feature
          false

      when "none"
        filters = (@_compileFilter subFilter for subFilter in filter[1..])
        (feature) ->
          return false for appliesTo in filters when appliesTo feature
          true

      when "=="
        (feature) -> feature.properties[filter[1]] is filter[2]

      when "!="
        (feature) -> feature.properties[filter[1]] isnt filter[2]

      when "in"
        (feature) ->
          return true for value in filter[2..] when feature.properties[filter[1]] is value
          false

      when "!in"
        (feature) ->
          return false for value in filter[2..] when feature.properties[filter[1]] is value
          true

      when "has"
        (feature) -> !!feature.properties[filter[1]]

      when "!has"
        (feature) -> !feature.properties[filter[1]]

      when ">"
        (feature) -> feature.properties[filter[1]] > filter[2]

      when ">="
        (feature) -> feature.properties[filter[1]] >= filter[2]

      when "<"
        (feature) -> feature.properties[filter[1]] < filter[2]

      when "<="
        (feature) -> feature.properties[filter[1]] <= filter[2]

      else
        -> true