From 873f22c0d98088a972aff88522e16d8c5c10e533 Mon Sep 17 00:00:00 2001 From: javi Date: Mon, 21 Sep 2015 11:05:07 +0200 Subject: [PATCH] added the possibility of using custom defined functions from cartocss --- lib/carto/parser.js | 2 +- lib/carto/renderer_js.js | 203 ++++++++++++++++++++++------------ lib/carto/torque-reference.js | 6 + lib/carto/tree/filterset.js | 9 +- lib/carto/tree/value.js | 6 +- package.json | 11 +- test/rendering_js.test.js | 179 ++++++++++++++++++++---------- 7 files changed, 280 insertions(+), 136 deletions(-) diff --git a/lib/carto/parser.js b/lib/carto/parser.js index 158e04a..42f3390 100644 --- a/lib/carto/parser.js +++ b/lib/carto/parser.js @@ -370,7 +370,7 @@ carto.Parser = function Parser(env) { // These can start with either a letter or a dash (-), // and then contain numbers, underscores, and letters. keyword: function() { - var k = $(/^[A-Za-z-]+[A-Za-z-0-9_]*/); + var k = $(/^[A-Za-z-]+[A-Za-z-0-9_\.]*/); if (k) { return new tree.Keyword(k); } }, diff --git a/lib/carto/renderer_js.js b/lib/carto/renderer_js.js index dd88cee..c5cdb9a 100644 --- a/lib/carto/renderer_js.js +++ b/lib/carto/renderer_js.js @@ -1,14 +1,12 @@ (function(carto) { var tree = require('./tree'); var _ = global._ || require('underscore'); +var async = require('async'); -function CartoCSS(style, options) { +function CartoCSS(options) { this.options = options || {}; this.imageURLs = []; - if(style) { - this.setStyle(style); - } } CartoCSS.Layer = function(shader, options) { @@ -114,9 +112,25 @@ CartoCSS.Layer.prototype = { }; +CartoCSS.parse = function(style, options) { + var args = Array.prototype.slice.call(arguments); + var callback = args[args.length - 1]; + // TODO: raise an error if not a function + var cartocss = new CartoCSS(options); + cartocss._functions = options.functions; + cartocss._parse(style, function(err, layers) { + if (err) { + callback(err); + return; + } + callback(null, cartocss); + }); + +}; + CartoCSS.prototype = { - setStyle: function(style) { + /*setStyle: function(style) { var layers = this.parse(style); if(!layers) { throw new Error(this.parse_env.errors); @@ -124,7 +138,7 @@ CartoCSS.prototype = { this.layers = layers.map(function(shader) { return new CartoCSS.Layer(shader); }); - }, + },*/ getLayers: function() { return this.layers; @@ -169,7 +183,41 @@ CartoCSS.prototype = { return this.imageURLs; }, - parse: function(cartocss) { + preprocess: function(def, done) { + var self = this; + var functions = [] + // look for functions that need preprocess + _.each(def.rules, function(rule) { + if (rule.value.value[0] instanceof tree.Expression && + rule.value.value[0].value[0] instanceof tree.Call) { + var call = rule.value.value[0].value[0]; + var fn = self._functions[call.name]; + if (fn) { + functions.push({ + fn: fn, + callNode: call + }); + } + } + }) + if (functions.length === 0) { + done(null, this); + return; + } + // call all of them + // TODO: we should check for uniqueness to avoid extra calls + var finished = _.after(functions.length, done.bind(this)) + _.each(functions, function(f) { + f.fn(f.callNode.args, function(finalFunction) { + tree.functions[f.callNode.name] = finalFunction; + finished(null); + }); + }); + }, + + + _parse: function(cartocss, callback) { + var self = this; var parse_env = { frames: [], errors: [], @@ -185,85 +233,92 @@ CartoCSS.prototype = { } catch(e) { // add the style.mss string to match the response from the server parse_env.errors.push(e.message); + callback(parse_env); return; } - if(ruleset) { - + if (ruleset) { function defKey(def) { return def.elements[0] + "::" + def.attachment; } var defs = ruleset.toList(parse_env); defs.reverse(); // group by elements[0].value::attachment - var layers = {}; - for(var i = 0; i < defs.length; ++i) { - var def = defs[i]; - var key = defKey(def); - var layer = layers[key] = (layers[key] || { - symbolizers: [] - }); - for(var u = 0; u ", s); + debugger + var layer = s.getLayers()[0]; + var props = layer.getStyle({testing: 2}, { 'zoom': 0, 'frame-offset': 10 }); + assert( props['marker-width'] === 2); + done(); + }); }); - it ("should allow filter based rendering", function() { + it ("should allow filter based rendering", function(done) { var style = '#test { marker-width: 10; [zoom = 1] { marker-width: 1; } }'; - shader = (new carto.RendererJS({ debug: true })).render(style); - var layer = shader.getLayers()[0]; - var props = layer.getStyle({}, { 'zoom': 0, 'frame-offset': 10 }); - assert( props['marker-width'] === 10); - props = layer.getStyle({}, { 'zoom': 1, 'frame-offset': 10 }); - assert( props['marker-width'] === 1); + (new carto.RendererJS({ debug: true })).render(style, function(err, shader) { + var layer = shader.getLayers()[0]; + var props = layer.getStyle({}, { 'zoom': 0, 'frame-offset': 10 }); + assert( props['marker-width'] === 10); + props = layer.getStyle({}, { 'zoom': 1, 'frame-offset': 10 }); + assert( props['marker-width'] === 1); + done(); + }); }); - it ("symbolizers should be in rendering order", function() { + it ("symbolizers should be in rendering order", function(done) { var style = '#test { polygon-fill: red; line-color: red; }'; style += '#test2 { line-color: red;polygon-fill: red; line-witdh: 10; }'; - var shader = (new carto.RendererJS({ debug: true })).render(style); - var layer0 = shader.getLayers()[0]; - assert(layer0.getSymbolizers()[0] === 'polygon'); - assert(layer0.getSymbolizers()[1] === 'line'); - - var layer1 = shader.getLayers()[1]; - assert(layer0.getSymbolizers()[0] === 'polygon'); - assert(layer0.getSymbolizers()[1] === 'line'); + (new carto.RendererJS({ debug: true })).render(style, function(err, shader) { + var layer0 = shader.getLayers()[0]; + assert(layer0.getSymbolizers()[0] === 'polygon'); + assert(layer0.getSymbolizers()[1] === 'line'); + + var layer1 = shader.getLayers()[1]; + assert(layer0.getSymbolizers()[0] === 'polygon'); + assert(layer0.getSymbolizers()[1] === 'line'); + done(); + }); }); - it ("colorize should return a list of colours in same order", function() { + it ("colorize should return a list of colours in same order", function(done) { var style = '#test { image-filters: colorize-alpha(blue, cyan, green, yellow, orange, red); }'; - var shader = (new carto.RendererJS({ debug: true })).render(style); - var layer0 = shader.getLayers()[0]; - var st = layer0.getStyle({ value: 1 }, {"frame-offset": 0, "zoom": 3}); - var expectedColours = [[0, 0, 255], [0, 255, 255], [0, 128, 0], [255, 255, 0], [255, 165, 0], [255, 0, 0]]; - for (var i = 0; i < st["image-filters"].args; i++){ - assert (st["image-filters"].args[i].rgb === expectedColours[i]); - } + (new carto.RendererJS({ debug: true })).render(style, function(err, shader) { + var layer0 = shader.getLayers()[0]; + var st = layer0.getStyle({ value: 1 }, {"frame-offset": 0, "zoom": 3}); + var expectedColours = [[0, 0, 255], [0, 255, 255], [0, 128, 0], [255, 255, 0], [255, 165, 0], [255, 0, 0]]; + for (var i = 0; i < st["image-filters"].args; i++){ + assert (st["image-filters"].args[i].rgb === expectedColours[i]); + } + done(); + }); }); - it ("should return list of marker-files", function(){ + it ("should return list of marker-files", function(done) { var css = [ 'Map {', '-torque-time-attribute: "date";', @@ -106,30 +122,79 @@ describe('RenderingJS', function() { ' [frame-offset = 2] { marker-width: 15; marker-fill-opacity: 0.02;}', '}' ].join('\n'); - var shader = (new carto.RendererJS({ debug: true })).render(css); - var markerURLs = shader.getImageURLs(); - var against = ["http://localhost:8081/gal.svg", "http://upload.wikimedia.org/wikipedia/commons/4/43/Flag_of_the_Galactic_Empire.svg", "http://upload.wikimedia.org/wikipedia/commons/c/c9/Flag_of_Syldavia.svg"]; - for(var i = 0; i