diff --git a/src/Styler.coffee b/src/Styler.coffee deleted file mode 100644 index b0f6711..0000000 --- a/src/Styler.coffee +++ /dev/null @@ -1,112 +0,0 @@ -### - termap - Terminal Map Viewer - by Michael Strassburger - - 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) -> - for id, node of tree - switch typeof node - when 'object' - continue if node.constructor.name.match /Stream/ - @_replaceConstants constants, node - - 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 diff --git a/src/Styler.js b/src/Styler.js new file mode 100644 index 0000000..c54e0ef --- /dev/null +++ b/src/Styler.js @@ -0,0 +1,163 @@ +/* + termap - Terminal Map Viewer + by Michael Strassburger + + 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. +*/ +var Styler; + +const fs = require('fs'); + +module.exports = class Styler { + constructor(file) { + this.styleById = {}; + this.styleByLayer = {}; + var base, name; + const json = JSON.parse(fs.readFileSync(file).toString()); + this.styleName = json.name; + if (json.constants) { + this._replaceConstants(json.constants, json.layers); + } + + for (const style of json.layers) { + if (style.ref && this.styleById[style.ref]) { + for (const ref of ['type', 'source-layer', 'minzoom', 'maxzoom', 'filter']) { + if (this.styleById[style.ref][ref] && !style[ref]) { + style[ref] = this.styleById[style.ref][ref]; + } + } + } + + style.appliesTo = this._compileFilter(style.filter); + + //TODO Better translation of: @styleByLayer[style['source-layer']] ?= [] + if ((base = this.styleByLayer)[name = style['source-layer']] == null) { + base[name] = []; + } + this.styleByLayer[style['source-layer']].push(style); + this.styleById[style.id] = style; + } + } + + getStyleFor(layer, feature, zoom) { + if (!this.styleByLayer[layer]) { + return false; + } + + for (const style of this.styleByLayer[layer]) { + if (style.appliesTo(feature)) { + return style; + } + } + + return false; + } + + _replaceConstants(constants, tree) { + for (const id in tree) { + const node = tree[id]; + switch (typeof node) { + case 'object': + if (node.constructor.name.match(/Stream/)) { + continue; + } + this._replaceConstants(constants, node); + break; + case 'string': + if (node.charAt(0) === '@') { + tree[id] = constants[node]; + } + } + } + } + + //TODO Better translation of the long cases. + _compileFilter(filter) { + var filters; + switch (filter != null ? filter[0] : void 0) { + case 'all': + filter = filter.slice(1); + filters = (() => { + return filter.map((sub) => this._compileFilter(sub)); + }).call(this); + //TODO Use Array.prototype.find + return (feature) => { + for (const appliesTo of filters) { + if (!appliesTo(feature)) { + return false; + } + } + return true; + }; + case 'any': + filter = filter.slice(1); + filters = (() => { + return filter.map((sub) => this._compileFilter(sub)); + }).call(this); + //TODO Use Array.prototype.find + return (feature) => { + for (const appliesTo of filters) { + if (appliesTo(feature)) { + return true; + } + } + return false; + }; + case 'none': + filter = filter.slice(1); + filters = (() => { + return filter.map((sub) => this._compileFilter(sub)); + }).call(this); + return (feature) => { + for (const appliesTo of filters) { + if (appliesTo(feature)) { + return false; + } + } + return true; + }; + case '==': + return (feature) => feature.properties[filter[1]] === filter[2]; + case "!=": + return (feature) => feature.properties[filter[1]] !== filter[2]; + case "in": + return (feature) => { + filter = filter.slice(2); + for (const value of filter) { + if (feature.properties[filter[1]] === value) { + return true; + } + } + return false; + }; + case '!in': + return (feature) => { + filter = filter.slice(2); + for (const value of filter) { + if (feature.properties[filter[1]] === value) { + return false; + } + } + return true; + }; + case "has": + return (feature) => !!feature.properties[filter[1]]; + case "!has": + return (feature) => !feature.properties[filter[1]]; + case ">": + return (feature) => feature.properties[filter[1]] > filter[2]; + case ">=": + return (feature) => feature.properties[filter[1]] >= filter[2]; + case "<": + return (feature) => feature.properties[filter[1]] < filter[2]; + case "<=": + return (feature) => feature.properties[filter[1]] <= filter[2]; + default: + return () => true; + } + } +}