Unpolished translation of Styler.coffee

This commit is contained in:
Christian Paul 2017-11-23 23:43:18 -08:00
parent 2d22e33ff1
commit 554ae593be
2 changed files with 163 additions and 112 deletions

View File

@ -1,112 +0,0 @@
###
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) ->
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

163
src/Styler.js Normal file
View File

@ -0,0 +1,163 @@
/*
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.
*/
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;
}
}
}