Compare commits

...

1 Commits

@ -370,7 +370,7 @@ carto.Parser = function Parser(env) {
// These can start with either a letter or a dash (-), // These can start with either a letter or a dash (-),
// and then contain numbers, underscores, and letters. // and then contain numbers, underscores, and letters.
keyword: function() { 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); } if (k) { return new tree.Keyword(k); }
}, },

@ -1,14 +1,12 @@
(function(carto) { (function(carto) {
var tree = require('./tree'); var tree = require('./tree');
var _ = global._ || require('underscore'); var _ = global._ || require('underscore');
var async = require('async');
function CartoCSS(style, options) { function CartoCSS(options) {
this.options = options || {}; this.options = options || {};
this.imageURLs = []; this.imageURLs = [];
if(style) {
this.setStyle(style);
}
} }
CartoCSS.Layer = function(shader, options) { 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 = { CartoCSS.prototype = {
setStyle: function(style) { /*setStyle: function(style) {
var layers = this.parse(style); var layers = this.parse(style);
if(!layers) { if(!layers) {
throw new Error(this.parse_env.errors); throw new Error(this.parse_env.errors);
@ -124,7 +138,7 @@ CartoCSS.prototype = {
this.layers = layers.map(function(shader) { this.layers = layers.map(function(shader) {
return new CartoCSS.Layer(shader); return new CartoCSS.Layer(shader);
}); });
}, },*/
getLayers: function() { getLayers: function() {
return this.layers; return this.layers;
@ -169,7 +183,41 @@ CartoCSS.prototype = {
return this.imageURLs; 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 = { var parse_env = {
frames: [], frames: [],
errors: [], errors: [],
@ -185,85 +233,92 @@ CartoCSS.prototype = {
} catch(e) { } catch(e) {
// add the style.mss string to match the response from the server // add the style.mss string to match the response from the server
parse_env.errors.push(e.message); parse_env.errors.push(e.message);
callback(parse_env);
return; return;
} }
if(ruleset) { if (ruleset) {
function defKey(def) { function defKey(def) {
return def.elements[0] + "::" + def.attachment; return def.elements[0] + "::" + def.attachment;
} }
var defs = ruleset.toList(parse_env); var defs = ruleset.toList(parse_env);
defs.reverse(); defs.reverse();
// group by elements[0].value::attachment // group by elements[0].value::attachment
var layers = {}; async.each(defs, this.preprocess.bind(this), function(err) {
for(var i = 0; i < defs.length; ++i) { var layers = {};
var def = defs[i]; for(var i = 0; i < defs.length; ++i) {
var key = defKey(def); var def = defs[i];
var layer = layers[key] = (layers[key] || { var key = defKey(def);
symbolizers: [] var layer = layers[key] = (layers[key] || {
}); symbolizers: []
for(var u = 0; u<def.rules.length; u++){ });
if(def.rules[u].name === "marker-file" || def.rules[u].name === "point-file"){ for(var u = 0; u<def.rules.length; u++){
var value = def.rules[u].value.value[0].value[0].value.value; if(def.rules[u].name === "marker-file" || def.rules[u].name === "point-file"){
this.imageURLs.push(value); var value = def.rules[u].value.value[0].value[0].value.value;
} this.imageURLs.push(value);
} }
layer.frames = []; }
layer.zoom = tree.Zoom.all; layer.frames = [];
var props = def.toJS(parse_env); layer.zoom = tree.Zoom.all;
if (this.options.debug) console.log("props", props); var props = def.toJS(parse_env);
for(var v in props) { if (self.options.debug) console.log("props", props);
var lyr = layer[v] = layer[v] || { for(var v in props) {
constant: false, var lyr = layer[v] = layer[v] || {
symbolizer: null, constant: false,
js: [], symbolizer: null,
index: 0 js: [],
}; index: 0
// build javascript statements };
lyr.js.push(props[v].map(function(a) { return a.js; }).join('\n')); // build javascript statements
// get symbolizer for prop lyr.js.push(props[v].map(function(a) { return a.js; }).join('\n'));
lyr.symbolizer = _.first(props[v].map(function(a) { return a.symbolizer; })); // get symbolizer for prop
// serach the max index to know rendering order lyr.symbolizer = _.first(props[v].map(function(a) { return a.symbolizer; }));
lyr.index = _.max(props[v].map(function(a) { return a.index; }).concat(lyr.index)); // serach the max index to know rendering order
lyr.constant = !_.any(props[v].map(function(a) { return !a.constant; })); lyr.index = _.max(props[v].map(function(a) { return a.index; }).concat(lyr.index));
lyr.constant = !_.any(props[v].map(function(a) { return !a.constant; }));
}
} }
} var ordered_layers = [];
if (self.options.debug) console.log(layers);
var ordered_layers = [];
if (this.options.debug) console.log(layers); var done = {};
for(var i = 0; i < defs.length; ++i) {
var done = {}; var def = defs[i];
for(var i = 0; i < defs.length; ++i) { var k = defKey(def);
var def = defs[i]; var layer = layers[k];
var k = defKey(def); if(!done[k]) {
var layer = layers[k]; if(self.options.debug) console.log("**", k);
if(!done[k]) { for(var prop in layer) {
if(this.options.debug) console.log("**", k); if (prop !== 'zoom' && prop !== 'frames' && prop !== 'symbolizers') {
for(var prop in layer) { if(self.options.debug) console.log("*", prop);
if (prop !== 'zoom' && prop !== 'frames' && prop !== 'symbolizers') { layer[prop].style = self._createFn(layer[prop].js);
if(this.options.debug) console.log("*", prop); layer.symbolizers.push(layer[prop].symbolizer);
layer[prop].style = this._createFn(layer[prop].js); layer.symbolizers = _.uniq(layer.symbolizers);
layer.symbolizers.push(layer[prop].symbolizer); }
layer.symbolizers = _.uniq(layer.symbolizers);
} }
layer.attachment = k;
ordered_layers.push(layer);
done[k] = true;
} }
layer.attachment = k; layer.zoom |= def.zoom;
ordered_layers.push(layer); layer.frames.push(def.frame_offset);
done[k] = true;
} }
layer.zoom |= def.zoom;
layer.frames.push(def.frame_offset);
}
// uniq the frames // uniq the frames
for(i = 0; i < ordered_layers.length; ++i) { for(i = 0; i < ordered_layers.length; ++i) {
ordered_layers[i].frames = _.uniq(ordered_layers[i].frames); ordered_layers[i].frames = _.uniq(ordered_layers[i].frames);
} }
return ordered_layers; self.layers = ordered_layers.map(function(shader) {
return new CartoCSS.Layer(shader);
});
callback(null, self.layers);
return;
});
} else {
callback(new Error("not rules found"));
} }
return null;
} }
}; };
@ -271,13 +326,19 @@ CartoCSS.prototype = {
carto.RendererJS = function (options) { carto.RendererJS = function (options) {
this.options = options || {}; this.options = options || {};
this.options.mapnik_version = this.options.mapnik_version || 'latest'; this.options.mapnik_version = this.options.mapnik_version || 'latest';
this._functions = {}
}; };
// Prepare a javascript object which contains the layers // Prepare a javascript object which contains the layers
carto.RendererJS.prototype.render = function render(cartocss, callback) { carto.RendererJS.prototype.render = function render(cartocss, callback) {
var reference = require('./torque-reference'); var reference = require('./torque-reference');
tree.Reference.setData(reference.version.latest); tree.Reference.setData(reference.version.latest);
return new CartoCSS(cartocss, this.options); if (cartocss) {
CartoCSS.parse(cartocss, _.extend({}, this.options, { functions: this._functions }), callback);
}
}
carto.RendererJS.prototype.addFunction = function(name, process) {
this._functions[name] = process;
} }
if(typeof(module) !== 'undefined') { if(typeof(module) !== 'undefined') {

@ -148,6 +148,12 @@ var _mapnik_reference_latest = {
}, },
"symbolizers" : { "symbolizers" : {
"*": { "*": {
"transition-time": {
"css": "transition-time",
"default-value": 0,
"default-meaning": "transition time",
"doc": ""
},
"image-filters": { "image-filters": {
"css": "image-filters", "css": "image-filters",
"default-value": "none", "default-value": "none",

@ -92,7 +92,14 @@ tree.Filterset.prototype.toJS = function(env) {
val = filter._val.toString(true); val = filter._val.toString(true);
} }
var attrs = "data"; var attrs = "data";
return attrs + "." + filter.key.value + " " + op + " " + (val.is === 'string' ? "'"+ val +"'" : val); var value = val;
if (val.is === 'string') {
value = "'" + val + "'";
} else if (val.is === 'keyword') {
// comparasions like [var == other_var]
value = attrs + "." + val.value;
}
return attrs + "." + filter.key.value + " " + op + " " + value;
}).join(' && '); }).join(' && ');
}; };

@ -15,11 +15,13 @@ tree.Value.prototype = {
})); }));
} }
}, },
toString: function(env, selector, sep, format) { toString: function(env, selector, sep, format) {
return this.value.map(function(e) { return this.value.map(function(e) {
return e.toString(env, format); return e.toString(env, format);
}).join(sep || ', '); }).join(sep || ', ');
}, },
clone: function() { clone: function() {
var obj = Object.create(tree.Value.prototype); var obj = Object.create(tree.Value.prototype);
if (Array.isArray(obj)) obj.value = this.value.slice(); if (Array.isArray(obj)) obj.value = this.value.slice();
@ -37,7 +39,9 @@ tree.Value.prototype = {
} else if (val.is === 'field') { } else if (val.is === 'field') {
// replace [variable] by ctx['variable'] // replace [variable] by ctx['variable']
v = v.replace(/\[(.*)\]/g, "data['$1']"); v = v.replace(/\[(.*)\]/g, "data['$1']");
}else if (val.is === 'call') { } else if (val.is === 'custom') {
v = val.toString();
} else if (val.is === 'call') {
v = JSON.stringify({ v = JSON.stringify({
name: val.name, name: val.name,
args: val.args args: val.args

@ -39,15 +39,16 @@
"dependencies": { "dependencies": {
"underscore": "~1.6.0", "underscore": "~1.6.0",
"mapnik-reference": "~6.0.2", "mapnik-reference": "~6.0.2",
"optimist": "~0.6.0" "optimist": "~0.6.0",
"async": "^1.4.2"
}, },
"devDependencies": { "devDependencies": {
"mocha": "1.12.x", "browserify": "~7.0.0",
"coveralls": "~2.10.1",
"istanbul": "~0.2.14",
"jshint": "0.2.x", "jshint": "0.2.x",
"mocha": "1.12.x",
"sax": "0.1.x", "sax": "0.1.x",
"istanbul": "~0.2.14",
"coveralls": "~2.10.1",
"browserify": "~7.0.0",
"uglify-js": "1.3.3" "uglify-js": "1.3.3"
}, },
"scripts": { "scripts": {

@ -1,6 +1,9 @@
var assert = require('assert'); var assert = require('assert');
var carto = require('../lib/carto'); var carto = require('../lib/carto');
var tree = require('../lib/carto/tree');
var _ = require('underscore');
describe('RenderingJS', function() { describe('RenderingJS', function() {
var shader; var shader;
var style = [ var style = [
@ -20,21 +23,24 @@ describe('RenderingJS', function() {
'}' '}'
].join('\n'); ].join('\n');
beforeEach(function() { beforeEach(function(done) {
shader = (new carto.RendererJS({ debug: true })).render(style); (new carto.RendererJS({ debug: true })).render(style, function (err, s) {
shader = s;
done();
});
}); });
it ("shold render layers", function() { it("shold render layers", function() {
assert(shader.getLayers().length === 2); assert(shader.getLayers().length === 2);
}); });
it ("shold report frames used in the layer", function() { it("shold report frames used in the layer", function() {
var layer = shader.getLayers()[0]; var layer = shader.getLayers()[0];
assert( layer.frames()[0] === 0); assert(layer.frames()[0] === 0);
assert( layer.frames()[1] === 1); assert(layer.frames()[1] === 1);
layer = shader.getLayers()[1]; layer = shader.getLayers()[1];
assert( layer.frames()[0] === 10); assert(layer.frames()[0] === 10);
}); });
it ("shold render with frames var", function() { it ("shold render with frames var", function() {
@ -43,49 +49,59 @@ describe('RenderingJS', function() {
assert( props['line-width'] === 4); assert( props['line-width'] === 4);
}); });
it ("shold render variables", function() { it("shold render variables", function(done) {
var style = '#test { marker-width: [testing]; }'; var style = '#test { marker-width: [testing]; }';
shader = (new carto.RendererJS({ debug: true })).render(style); (new carto.RendererJS({ debug: true })).render(style, function(err, s) {
var layer = shader.getLayers()[0]; console.log("#### --> ", s);
var props = layer.getStyle({testing: 2}, { 'zoom': 0, 'frame-offset': 10 }); debugger
assert( props['marker-width'] === 2); 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; } }'; var style = '#test { marker-width: 10; [zoom = 1] { marker-width: 1; } }';
shader = (new carto.RendererJS({ debug: true })).render(style); (new carto.RendererJS({ debug: true })).render(style, function(err, shader) {
var layer = shader.getLayers()[0]; var layer = shader.getLayers()[0];
var props = layer.getStyle({}, { 'zoom': 0, 'frame-offset': 10 }); var props = layer.getStyle({}, { 'zoom': 0, 'frame-offset': 10 });
assert( props['marker-width'] === 10); assert( props['marker-width'] === 10);
props = layer.getStyle({}, { 'zoom': 1, 'frame-offset': 10 }); props = layer.getStyle({}, { 'zoom': 1, 'frame-offset': 10 });
assert( props['marker-width'] === 1); 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; }'; var style = '#test { polygon-fill: red; line-color: red; }';
style += '#test2 { line-color: red;polygon-fill: red; line-witdh: 10; }'; style += '#test2 { line-color: red;polygon-fill: red; line-witdh: 10; }';
var shader = (new carto.RendererJS({ debug: true })).render(style); (new carto.RendererJS({ debug: true })).render(style, function(err, shader) {
var layer0 = shader.getLayers()[0]; var layer0 = shader.getLayers()[0];
assert(layer0.getSymbolizers()[0] === 'polygon'); assert(layer0.getSymbolizers()[0] === 'polygon');
assert(layer0.getSymbolizers()[1] === 'line'); assert(layer0.getSymbolizers()[1] === 'line');
var layer1 = shader.getLayers()[1]; var layer1 = shader.getLayers()[1];
assert(layer0.getSymbolizers()[0] === 'polygon'); assert(layer0.getSymbolizers()[0] === 'polygon');
assert(layer0.getSymbolizers()[1] === 'line'); 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 style = '#test { image-filters: colorize-alpha(blue, cyan, green, yellow, orange, red); }';
var shader = (new carto.RendererJS({ debug: true })).render(style); (new carto.RendererJS({ debug: true })).render(style, function(err, shader) {
var layer0 = shader.getLayers()[0]; var layer0 = shader.getLayers()[0];
var st = layer0.getStyle({ value: 1 }, {"frame-offset": 0, "zoom": 3}); 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]]; 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++){ for (var i = 0; i < st["image-filters"].args; i++){
assert (st["image-filters"].args[i].rgb === expectedColours[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 = [ var css = [
'Map {', 'Map {',
'-torque-time-attribute: "date";', '-torque-time-attribute: "date";',
@ -106,30 +122,79 @@ describe('RenderingJS', function() {
' [frame-offset = 2] { marker-width: 15; marker-fill-opacity: 0.02;}', ' [frame-offset = 2] { marker-width: 15; marker-fill-opacity: 0.02;}',
'}' '}'
].join('\n'); ].join('\n');
var shader = (new carto.RendererJS({ debug: true })).render(css); (new carto.RendererJS({ debug: true })).render(style, function(err, shader) {
var markerURLs = shader.getImageURLs(); 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"]; 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<against.length; i++){ for(var i = 0; i< against.length; i++){
assert(against[i] == markerURLs[i]) assert(against[i] == markerURLs[i])
} }
done();
});
}) })
it ("should return variable for styles that change", function() { describe("isVariable", function() {
var style = '#test { marker-width: [prop]; }';
var shader = (new carto.RendererJS({ debug: true })).render(style); it("prop", function(done) {
var layer0 = shader.getLayers()[0]; var style = '#test { marker-width: [prop]; }';
assert(layer0.isVariable()); (new carto.RendererJS({ debug: true })).render(style, function(err, shader) {
var layer0 = shader.getLayers()[0];
style = '#test { marker-width: 1; }'; assert(layer0.isVariable());
shader = (new carto.RendererJS({ debug: true })).render(style); done();
layer0 = shader.getLayers()[0]; });
assert(!layer0.isVariable()); });
it ("constant", function(done) {
style = '#test { marker-width: 1; }';
(new carto.RendererJS({ debug: true })).render(style, function(err, shader) {
layer0 = shader.getLayers()[0];
assert(!layer0.isVariable());
done();
});
});
it ("both", function(done) {
style = '#test { marker-width: [prop]; marker-fill: red; }';
(new carto.RendererJS({ debug: true })).render(style, function(err, shader) {
layer0 = shader.getLayers()[0];
assert(layer0.isVariable());
done();
});
});
});
style = '#test { marker-width: [prop]; marker-fill: red; }'; it ("should be able to provide external functions", function(done) {
shader = (new carto.RendererJS({ debug: true })).render(style); var style = '#test { marker-width: testing([prop]); }';
layer0 = shader.getLayers()[0]; var renderer = new carto.RendererJS({ debug: true })
assert(layer0.isVariable()); renderer.addFunction('testing', function(args, callback) {
_.defer(function() {
callback(function(a) {
return {
is: 'custom',
toString: function() {
return "(function() { return data['" + a.value + "']})();";
}
}
})
});
})
renderer.render(style, function(err, shader) {
var layer = shader.getLayers()[0];
var props = layer.getStyle({prop: 1}, { 'zoom': 0, 'frame-offset': 10 });
assert(props['marker-width'] === 1);
done();
});
});
it("should be able to provide do var to var comparasions", function(done) {
var style = '#test { marker-width: 1; [prop = test.a] {marker-width: 10 }}';
(new carto.RendererJS({ debug: true })).render(style, function(err, shader) {
var layer = shader.getLayers()[0];
var props = layer.getStyle({ prop: 1, test: {a: 1} }, { 'zoom': 0, 'frame-offset': 10 });
assert(props['marker-width'] === 10);
var props = layer.getStyle({prop: 1, test: {a: 0}}, { 'zoom': 0, 'frame-offset': 10 });
assert(props['marker-width'] === 1);
done();
});
}); });
}); });

Loading…
Cancel
Save