Compare commits

..

1 Commits

Author SHA1 Message Date
javi
873f22c0d9 added the possibility of using custom defined functions from cartocss 2015-09-21 11:05:07 +02:00
23 changed files with 1153 additions and 4021 deletions

1
.gitignore vendored
View File

@ -3,4 +3,3 @@
test/rendering/layers/
test/rendering/cache/
test/rendering-mss/npm-debug.log
.idea/

View File

@ -1,9 +1,7 @@
language: node_js
node_js:
- '6'
- '8'
- '10'
- "0.11"
- "0.10"
script:
- npm test

View File

@ -1,9 +0,0 @@
## CARTO's Changelog
## 0.15.1-cdb5
2018-11-20
* Support Node.js 6, 8 and, 10
* Drop support for Node.js 0.10 and 0.11
* Add package-lock.json
* Add CHANGELOG.carto.md

View File

@ -1,6 +1,6 @@
# CartoCSS
[![Build Status](https://travis-ci.org/CartoDB/carto.png?branch=master)](https://travis-ci.org/CartoDB/carto)
[![Build Status](https://secure.travis-ci.org/mapbox/carto.png)](http://travis-ci.org/mapbox/carto)
Is as stylesheet renderer for javascript, It's an evolution of the Mapnik renderer from Mapbox.
Please, see original [Mapbox repo](http://github.com/mapbox/carto) for more information and credits

69
bin/mml2json.js Executable file
View File

@ -0,0 +1,69 @@
#!/usr/bin/env node
var xml2js = require('xml2js'),
fs = require('fs');
if (!process.argv[2]) {
console.log('Please specify a XML file.');
process.exit(1);
}
fs.readFile(process.argv[2], 'utf-8', function(err, data) {
if (err) throw err;
// Replace entities.
var entities = {};
var match = data.match(/<!ENTITY([^>]|"([^"]|\\")*")+>/g)
if (match != null) {
match.forEach(function(entity) {
var parts = entity.match(/^<!ENTITY\s+(\w+)\s+"(.+)">$/);
entities['&' + parts[1] + ';'] = parts[2];
});
}
data = data.replace(/&\w+;/g, function(entity) {
return entities[entity];
});
function addAttributes(obj) {
if (obj['$']) for (var key in obj['$']) obj[key] = obj['$'][key];
delete obj['$'];
return obj;
}
function simplifyExternal(obj) {
if (obj.src) return obj.src;
else return obj;
}
var parser = new xml2js.Parser({
explicitRoot: false,
explicitArray: false
});
parser.addListener('end', function(json) {
console.log(JSON.stringify(json, function(key, value) {
if (!key) {
return addAttributes(value);
}
else if (key === 'Stylesheet') {
if (Array.isArray(value)) return value.map(addAttributes).map(simplifyExternal);
else return [ simplifyExternal(addAttributes(value)) ];
}
else if (key === 'Layer' || key === 'Stylesheet') {
if (Array.isArray(value)) return value.map(addAttributes);
else return [ addAttributes(value) ];
}
else if (key === 'Datasource') {
value = addAttributes(value);
value.Parameter.forEach(function(parameter) {
value[parameter['$'].name] = parameter['_'];
});
delete value.Parameter;
return value;
}
else {
return value;
}
}, 4));
});
parser.parseString(data);
});

8
dist/carto.js vendored

File diff suppressed because one or more lines are too long

View File

@ -268,7 +268,7 @@ var carto = {
if (typeof(extract[2]) === 'string') {
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
}
error = options.indent + error.join('\n' + options.indent) + '\x1B[0m\n';
error = options.indent + error.join('\n' + options.indent) + '\033[0m\n';
message = options.indent + message + stylize(ctx.message, 'red');
if (ctx.filename) (message += stylize(' in ', 'red') + ctx.filename);
@ -326,12 +326,12 @@ function stylize(str, style) {
'red' : [31, 39],
'grey' : [90, 39]
};
return '\x1B[' + styles[style][0] + 'm' + str +
'\x1B[' + styles[style][1] + 'm';
return '\033[' + styles[style][0] + 'm' + str +
'\033[' + styles[style][1] + 'm';
}
}).call(this,require('_process'),"/lib/carto")
},{"../../package.json":44,"./functions":1,"./parser":3,"./renderer":4,"./renderer_js":5,"./torque-reference":6,"./tree":7,"./tree/call":8,"./tree/color":9,"./tree/comment":10,"./tree/definition":11,"./tree/dimension":12,"./tree/element":13,"./tree/expression":14,"./tree/field":15,"./tree/filter":16,"./tree/filterset":17,"./tree/fontset":18,"./tree/frame_offset":19,"./tree/imagefilter":20,"./tree/invalid":21,"./tree/keyword":22,"./tree/layer":23,"./tree/literal":24,"./tree/operation":25,"./tree/quoted":26,"./tree/reference":27,"./tree/rule":28,"./tree/ruleset":29,"./tree/selector":30,"./tree/style":31,"./tree/url":32,"./tree/value":33,"./tree/variable":34,"./tree/zoom":35,"_process":40,"fs":37,"path":39,"util":43}],3:[function(require,module,exports){
},{"../../package.json":44,"./functions":1,"./parser":3,"./renderer":4,"./renderer_js":5,"./torque-reference":6,"./tree":7,"./tree/call":8,"./tree/color":9,"./tree/comment":10,"./tree/definition":11,"./tree/dimension":12,"./tree/element":13,"./tree/expression":14,"./tree/field":15,"./tree/filter":16,"./tree/filterset":17,"./tree/fontset":18,"./tree/frame_offset":19,"./tree/imagefilter":20,"./tree/invalid":21,"./tree/keyword":22,"./tree/layer":23,"./tree/literal":24,"./tree/operation":25,"./tree/quoted":26,"./tree/reference":27,"./tree/rule":28,"./tree/ruleset":29,"./tree/selector":30,"./tree/style":31,"./tree/url":32,"./tree/value":33,"./tree/variable":34,"./tree/zoom":35,"_process":40,"fs":36,"path":39,"util":42}],3:[function(require,module,exports){
(function (global){
var carto = exports,
tree = require('./tree'),
@ -449,9 +449,8 @@ carto.Parser = function Parser(env) {
// - `index`: Char. index where the error occurred.
function makeError(err) {
var einput;
var errorTemplate;
_.defaults(err, {
_(err).defaults({
index: furthest,
filename: env.filename,
message: 'Parse error.',
@ -469,8 +468,8 @@ carto.Parser = function Parser(env) {
for (var n = err.index; n >= 0 && einput.charAt(n) !== '\n'; n--) {
err.column++;
}
errorTemplate = _.template('<%=filename%>:<%=line%>:<%=column%> <%=message%>');
return new Error(errorTemplate(err));
return new Error(_('<%=filename%>:<%=line%>:<%=column%> <%=message%>').template(err));
}
this.env = env = env || {};
@ -812,7 +811,6 @@ carto.Parser = function Parser(env) {
return new tree.Dimension(value[1], value[2], memo);
}
}
},
// The variable part of a variable definition.
@ -1061,22 +1059,10 @@ carto.Parser = function Parser(env) {
},
// A sub-expression, contained by parenthensis
sub: function() {
var e, expressions = [];
var e;
if ($('(')) {
while (e = $(this.expression)) {
expressions.push(e);
if (! $(',')) { break; }
}
$(')');
}
if (expressions.length > 1) {
return new tree.Value(expressions.map(function(e) {
return e.value[0];
}));
} else if (expressions.length === 1) {
return new tree.Value(expressions);
if ($('(') && (e = $(this.expression)) && $(')')) {
return e;
}
},
// This is a misnomer because it actually handles multiplication
@ -1150,7 +1136,7 @@ carto.Renderer = function Renderer(env, options) {
carto.Renderer.prototype.renderMSS = function render(data) {
// effects is a container for side-effects, which currently
// are limited to FontSets.
var env = _.defaults(this.env, {
var env = _(this.env).defaults({
benchmark: false,
validation_data: false,
effects: []
@ -1204,7 +1190,7 @@ carto.Renderer.prototype.renderMSS = function render(data) {
carto.Renderer.prototype.render = function render(m) {
// effects is a container for side-effects, which currently
// are limited to FontSets.
var env = _.defaults(this.env, {
var env = _(this.env).defaults({
benchmark: false,
validation_data: false,
effects: [],
@ -1218,14 +1204,14 @@ carto.Renderer.prototype.render = function render(m) {
var output = [];
// Transform stylesheets into definitions.
var definitions = _.chain(m.Stylesheet)
var definitions = _(m.Stylesheet).chain()
.map(function(s) {
if (typeof s == 'string') {
throw new Error("Stylesheet object is expected not a string: '" + s + "'");
}
// Passing the environment from stylesheet to stylesheet,
// allows frames and effects to be maintained.
env = _.extend(env, {filename:s.id});
env = _(env).extend({filename:s.id});
var time = +new Date(),
root = (carto.Parser(env)).parse(s.data);
@ -1286,7 +1272,7 @@ carto.Renderer.prototype.render = function render(m) {
if (env.errors) throw env.errors;
// Pass TileJSON and other custom parameters through to Mapnik XML.
var parameters = _.reduce(m, function(memo, v, k) {
var parameters = _(m).reduce(function(memo, v, k) {
if (!v && v !== 0) return memo;
switch (k) {
@ -1339,7 +1325,7 @@ carto.Renderer.prototype.render = function render(m) {
'\n</Parameters>\n'
);
var properties = _.map(map_properties, function(v) { return ' ' + v; }).join('');
var properties = _(map_properties).map(function(v) { return ' ' + v; }).join('');
output.unshift(
'<?xml version="1.0" ' +
@ -1597,7 +1583,7 @@ CartoCSS.Layer.prototype = {
},
/**
* return the symbolizers that need to be rendered with
* return the symbolizers that need to be rendered with
* this style. The order is the rendering order.
* @returns a list with 3 possible values 'line', 'marker', 'polygon'
*/
@ -1639,7 +1625,7 @@ CartoCSS.Layer.prototype = {
//
// given a geoemtry type returns the transformed one acording the CartoCSS
// For points there are two kind of types: point and sprite, the first one
// For points there are two kind of types: point and sprite, the first one
// is a circle, second one is an image sprite
//
// the other geometry types are the same than geojson (polygon, linestring...)
@ -1742,15 +1728,12 @@ CartoCSS.prototype = {
var layer = layers[key] = (layers[key] || {
symbolizers: []
});
for(var u = 0; u<def.rules.length; u++){
var rule = def.rules[u];
if(rule.name === "marker-file" || rule.name === "point-file"){
var value = rule.value.value[0].value[0].value.value;
this.imageURLs.push(value);
if(def.rules[u].name === "marker-file" || def.rules[u].name === "point-file"){
var value = def.rules[u].value.value[0].value[0].value.value;
this.imageURLs.push(value);
}
}
}
layer.frames = [];
layer.zoom = tree.Zoom.all;
var props = def.toJS(parse_env);
@ -1769,8 +1752,6 @@ CartoCSS.prototype = {
// serach the max index to know rendering order
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; }));
// True when the property is filtered.
lyr.filtered = props[v][0].filtered;
}
}
@ -1780,14 +1761,6 @@ CartoCSS.prototype = {
var done = {};
for(var i = 0; i < defs.length; ++i) {
var def = defs[i];
if (this.options.strict) {
def.toXML(parse_env, {});
if (parse_env.errors.message) {
throw new Error(parse_env.errors.message);
}
}
var k = defKey(def);
var layer = layers[k];
if(!done[k]) {
@ -1824,13 +1797,12 @@ CartoCSS.prototype = {
carto.RendererJS = function (options) {
this.options = options || {};
this.options.mapnik_version = this.options.mapnik_version || 'latest';
this.reference = this.options.reference || require('./torque-reference').version.latest;
this.options.strict = this.options.hasOwnProperty('strict') ? this.options.strict : false;
};
// Prepare a javascript object which contains the layers
carto.RendererJS.prototype.render = function render(cartocss, callback) {
tree.Reference.setData(this.reference);
var reference = require('./torque-reference');
tree.Reference.setData(reference.version.latest);
return new CartoCSS(cartocss, this.options);
}
@ -1870,10 +1842,7 @@ var _mapnik_reference_latest = {
["x-gradient", 0],
["y-gradient", 0],
["invert", 0],
["sharpen", 0],
["colorize-alpha", -1],
["color-to-alpha", 1],
["scale-hsla", 8]
["sharpen", 0]
],
"doc": "A list of image filters."
},
@ -2008,20 +1977,7 @@ var _mapnik_reference_latest = {
["x-gradient", 0],
["y-gradient", 0],
["invert", 0],
["sharpen", 0],
["colorize-alpha", -1],
["color-to-alpha", 1],
["scale-hsla", 8],
["buckets", -1],
["category", -1],
["equal", -1],
["headtails", -1],
["jenks", -1],
["quantiles", -1],
["cartocolor", -1],
["colorbrewer", -1],
["range", -1],
["ramp", -1]
["sharpen", 0]
],
"doc": "A list of image filters."
},
@ -2148,16 +2104,14 @@ var _mapnik_reference_latest = {
"type": "color",
"default-value": "rgba(128,128,128,1)",
"default-meaning": "gray and fully opaque (alpha = 1), same as rgb(128,128,128)",
"doc": "Fill color to assign to a polygon",
"expression": true
"doc": "Fill color to assign to a polygon"
},
"fill-opacity": {
"css": "polygon-opacity",
"type": "float",
"doc": "The opacity of the polygon",
"default-value": 1,
"default-meaning": "opaque",
"expression": true
"default-meaning": "opaque"
},
"gamma": {
"css": "polygon-gamma",
@ -2258,15 +2212,13 @@ var _mapnik_reference_latest = {
"default-value": "rgba(0,0,0,1)",
"type": "color",
"default-meaning": "black and fully opaque (alpha = 1), same as rgb(0,0,0)",
"doc": "The color of a drawn line",
"expression": true
"doc": "The color of a drawn line"
},
"stroke-width": {
"css": "line-width",
"default-value": 1,
"type": "float",
"doc": "The width of a line in pixels",
"expression": true
"doc": "The width of a line in pixels"
},
"stroke-opacity": {
"css": "line-opacity",
@ -2431,8 +2383,7 @@ var _mapnik_reference_latest = {
"doc": "An SVG file that this marker shows at each placement. If no file is given, the marker will show an ellipse.",
"default-value": "",
"default-meaning": "An ellipse or circle, if width equals height",
"type": "uri",
"expression": true
"type": "uri"
},
"opacity": {
"css": "marker-opacity",
@ -2516,8 +2467,7 @@ var _mapnik_reference_latest = {
"css": "marker-fill",
"default-value": "blue",
"doc": "The color of the area of the marker.",
"type": "color",
"expression": true
"type": "color"
},
"allow-overlap": {
"css": "marker-allow-overlap",
@ -2892,8 +2842,7 @@ var _mapnik_reference_latest = {
"type": "uri",
"default-value": "none",
"required": true,
"doc": "An image file to be repeated and warped along a line",
"expression": true
"doc": "An image file to be repeated and warped along a line"
},
"clip": {
"css": "line-pattern-clip",
@ -2973,8 +2922,7 @@ var _mapnik_reference_latest = {
"type": "uri",
"default-value": "none",
"required": true,
"doc": "Image to use as a repeated pattern fill within a polygon",
"expression": true
"doc": "Image to use as a repeated pattern fill within a polygon"
},
"alignment": {
"css": "polygon-pattern-alignment",
@ -4231,37 +4179,48 @@ tree.Definition.prototype.toXML = function(env, existing) {
tree.Definition.prototype.toJS = function(env) {
var shaderAttrs = {};
// merge conditions from filters with zoom condition of the
// definition
var zoom = "(" + this.zoom + " & (1 << ctx.zoom))";
var frame_offset = this.frame_offset;
var zoomFilter = "(" + this.zoom + " & (1 << ctx.zoom))";
var filters = [zoomFilter];
var originalFilters = this.filters.toJS(env);
// Ignore default zoom for filtering (https://github.com/CartoDB/carto/issues/40)
var zoomFiltered = this.zoom !== tree.Zoom.all;
if (originalFilters) {
filters.push(originalFilters);
}
var _if = this.filters.toJS(env);
var filters = [zoom];
if(_if) filters.push(_if);
if(frame_offset) filters.push('ctx["frame-offset"] === ' + frame_offset);
_if = filters.join(" && ");
_.each(this.rules, function(rule) {
if(rule instanceof tree.Rule) {
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
if (frame_offset) {
filters.push('ctx["frame-offset"] === ' + frame_offset);
}
var r = {
index: rule.index,
symbolizer: rule.symbolizer
};
_.each(this.rules, function (rule) {
var exportedRule = {};
if (_if) {
r.js = "if(" + _if + "){" + rule.value.toJS(env) + "}"
} else {
r.js = rule.value.toJS(env);
}
if (!rule instanceof tree.Rule) {
throw new Error("Ruleset not supported");
r.constant = rule.value.ev(env).is !== 'field';
r.filtered = !!_if;
shaderAttrs[rule.name].push(r);
} else {
throw new Error("Ruleset not supported");
//if (rule instanceof tree.Ruleset) {
//var sh = rule.toJS(env);
//for(var v in sh) {
//shaderAttrs[v] = shaderAttrs[v] || [];
//for(var attr in sh[v]) {
//shaderAttrs[v].push(sh[v][attr]);
//}
//}
//}
}
exportedRule.index = rule.index;
exportedRule.symbolizer = rule.symbolizer;
exportedRule.js = "if(" + filters.join(" && ") + "){" + rule.value.toJS(env) + "}";
exportedRule.constant = rule.value.ev(env).is !== 'field';
exportedRule.filtered = zoomFiltered || (originalFilters !== '');
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
shaderAttrs[rule.name].push(exportedRule);
});
return shaderAttrs;
};
@ -4269,7 +4228,7 @@ tree.Definition.prototype.toJS = function(env) {
})(require('../tree'));
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../tree":7,"assert":36,"underscore":undefined}],12:[function(require,module,exports){
},{"../tree":7,"assert":37,"underscore":undefined}],12:[function(require,module,exports){
(function (global){
(function(tree) {
var _ = global._ || require('underscore');
@ -4617,10 +4576,7 @@ tree.Filterset.prototype.toJS = function(env) {
val = filter._val.toString(true);
}
var attrs = "data";
if (op === '=~') {
return "(" + attrs + "['" + filter.key.value + "'] + '').match(" + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'") + "'" : val) + ")";
}
return attrs + "['" + filter.key.value + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'") + "'" : val);
return attrs + "." + filter.key.value + " " + op + " " + (val.is === 'string' ? "'"+ val +"'" : val);
}).join(' && ');
};
@ -5339,7 +5295,7 @@ tree.Reference = ref;
})(require('../tree'));
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../tree":7,"mapnik-reference":38,"underscore":undefined}],28:[function(require,module,exports){
},{"../tree":7,"mapnik-reference":43,"underscore":undefined}],28:[function(require,module,exports){
(function(tree) {
// a rule is a single property and value combination, or variable
// name and value combination, like
@ -5800,14 +5756,7 @@ tree.Value.prototype = {
v = "'" + v + "'";
} else if (val.is === 'field') {
// replace [variable] by ctx['variable']
v = v.replace(/\[([^\]]*)\]/g, function(matched) {
return matched.replace(/\[(.*)\]/g, "data['$1']");
});
}else if (val.is === 'call') {
v = JSON.stringify({
name: val.name,
args: val.args
})
v = v.replace(/\[(.*)\]/g, "data['$1']");
}
return "_value = " + v + ";";
}
@ -5980,6 +5929,8 @@ tree.Zoom.prototype.toString = function() {
};
},{"../tree":7}],36:[function(require,module,exports){
},{}],37:[function(require,module,exports){
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
//
// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
@ -6341,38 +6292,32 @@ var objectKeys = Object.keys || function (obj) {
return keys;
};
},{"util/":43}],37:[function(require,module,exports){
},{"util/":42}],38:[function(require,module,exports){
if (typeof Object.create === 'function') {
// implementation from standard node.js 'util' module
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
} else {
// old school shim for old browsers
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
var TempCtor = function () {}
TempCtor.prototype = superCtor.prototype
ctor.prototype = new TempCtor()
ctor.prototype.constructor = ctor
}
}
},{}],38:[function(require,module,exports){
(function (__dirname){
var fs = require('fs'),
path = require('path'),
existsSync = require('fs').existsSync || require('path').existsSync;
// Load all stated versions into the module exports
module.exports.version = {};
var refs = [
'2.0.0',
'2.0.1',
'2.0.2',
'2.1.0',
'2.1.1',
'2.2.0',
'2.3.0',
'3.0.0'
];
refs.map(function(version) {
module.exports.version[version] = require(path.join(__dirname, version, 'reference.json'));
var ds_path = path.join(__dirname, version, 'datasources.json');
if (existsSync(ds_path)) {
module.exports.version[version].datasources = require(ds_path).datasources;
}
});
}).call(this,"/node_modules/mapnik-reference")
},{"fs":37,"path":39}],39:[function(require,module,exports){
},{}],39:[function(require,module,exports){
(function (process){
// Copyright Joyent, Inc. and other Node contributors.
//
@ -6689,38 +6634,13 @@ process.chdir = function (dir) {
};
},{}],41:[function(require,module,exports){
if (typeof Object.create === 'function') {
// implementation from standard node.js 'util' module
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
} else {
// old school shim for old browsers
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
var TempCtor = function () {}
TempCtor.prototype = superCtor.prototype
ctor.prototype = new TempCtor()
ctor.prototype.constructor = ctor
}
}
},{}],42:[function(require,module,exports){
module.exports = function isBuffer(arg) {
return arg && typeof arg === 'object'
&& typeof arg.copy === 'function'
&& typeof arg.fill === 'function'
&& typeof arg.readUInt8 === 'function';
}
},{}],43:[function(require,module,exports){
},{}],42:[function(require,module,exports){
(function (process,global){
// Copyright Joyent, Inc. and other Node contributors.
//
@ -7310,10 +7230,39 @@ function hasOwnProperty(obj, prop) {
}
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./support/isBuffer":42,"_process":40,"inherits":41}],44:[function(require,module,exports){
},{"./support/isBuffer":41,"_process":40,"inherits":38}],43:[function(require,module,exports){
(function (__dirname){
var fs = require('fs'),
path = require('path'),
existsSync = require('fs').existsSync || require('path').existsSync;
// Load all stated versions into the module exports
module.exports.version = {};
var refs = [
'2.0.0',
'2.0.1',
'2.0.2',
'2.1.0',
'2.1.1',
'2.2.0',
'2.3.0',
'3.0.0'
];
refs.map(function(version) {
module.exports.version[version] = require(path.join(__dirname, version, 'reference.json'));
var ds_path = path.join(__dirname, version, 'datasources.json');
if (existsSync(ds_path)) {
module.exports.version[version].datasources = require(ds_path).datasources;
}
});
}).call(this,"/node_modules/mapnik-reference")
},{"fs":36,"path":39}],44:[function(require,module,exports){
module.exports={
"name": "carto",
"version": "0.15.1-cdb4",
"version": "0.15.1",
"description": "CartoCSS Stylesheet Compiler",
"url": "https://github.com/cartodb/carto",
"repository": {
@ -7350,7 +7299,7 @@ module.exports={
"node": ">=0.4.x"
},
"dependencies": {
"underscore": "1.8.3",
"underscore": "~1.6.0",
"mapnik-reference": "~6.0.2",
"optimist": "~0.6.0"
},
@ -7366,7 +7315,6 @@ module.exports={
"scripts": {
"pretest": "npm install",
"test": "mocha -R spec",
"tdd" : "env HIDE_LOGS=true mocha -w -R spec",
"coverage": "istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info"
}
}

View File

@ -23,7 +23,7 @@ line-color: yellow;
}
```
Especially of note is the support for hsl, which can be [easier to reason about than rgb()](http://mothereffinghsl.com/). Carto also includes several color operation functions [borrowed from less](http://lesscss.org/functions/#color-operations):
Especially of note is the support for hsl, which can be [easier to reason about than rgb()](http://mothereffinghsl.com/). Carto also includes several color functions [borrowed from less](http://lesscss.org/#-color-functions):
``` css
// lighten and darken colors

View File

@ -53,7 +53,7 @@ var carto = {
if (typeof(extract[2]) === 'string') {
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
}
error = options.indent + error.join('\n' + options.indent) + '\x1B[0m\n';
error = options.indent + error.join('\n' + options.indent) + '\033[0m\n';
message = options.indent + message + stylize(ctx.message, 'red');
if (ctx.filename) (message += stylize(' in ', 'red') + ctx.filename);
@ -111,6 +111,6 @@ function stylize(str, style) {
'red' : [31, 39],
'grey' : [90, 39]
};
return '\x1B[' + styles[style][0] + 'm' + str +
'\x1B[' + styles[style][1] + 'm';
return '\033[' + styles[style][0] + 'm' + str +
'\033[' + styles[style][1] + 'm';
}

View File

@ -114,9 +114,8 @@ carto.Parser = function Parser(env) {
// - `index`: Char. index where the error occurred.
function makeError(err) {
var einput;
var errorTemplate;
_.defaults(err, {
_(err).defaults({
index: furthest,
filename: env.filename,
message: 'Parse error.',
@ -134,8 +133,8 @@ carto.Parser = function Parser(env) {
for (var n = err.index; n >= 0 && einput.charAt(n) !== '\n'; n--) {
err.column++;
}
errorTemplate = _.template('<%=filename%>:<%=line%>:<%=column%> <%=message%>');
return new Error(errorTemplate(err));
return new Error(_('<%=filename%>:<%=line%>:<%=column%> <%=message%>').template(err));
}
this.env = env = env || {};
@ -371,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); }
},
@ -477,7 +476,6 @@ carto.Parser = function Parser(env) {
return new tree.Dimension(value[1], value[2], memo);
}
}
},
// The variable part of a variable definition.
@ -726,22 +724,10 @@ carto.Parser = function Parser(env) {
},
// A sub-expression, contained by parenthensis
sub: function() {
var e, expressions = [];
var e;
if ($('(')) {
while (e = $(this.expression)) {
expressions.push(e);
if (! $(',')) { break; }
}
$(')');
}
if (expressions.length > 1) {
return new tree.Value(expressions.map(function(e) {
return e.value[0];
}));
} else if (expressions.length === 1) {
return new tree.Value(expressions);
if ($('(') && (e = $(this.expression)) && $(')')) {
return e;
}
},
// This is a misnomer because it actually handles multiplication

View File

@ -16,7 +16,7 @@ carto.Renderer = function Renderer(env, options) {
carto.Renderer.prototype.renderMSS = function render(data) {
// effects is a container for side-effects, which currently
// are limited to FontSets.
var env = _.defaults(this.env, {
var env = _(this.env).defaults({
benchmark: false,
validation_data: false,
effects: []
@ -70,7 +70,7 @@ carto.Renderer.prototype.renderMSS = function render(data) {
carto.Renderer.prototype.render = function render(m) {
// effects is a container for side-effects, which currently
// are limited to FontSets.
var env = _.defaults(this.env, {
var env = _(this.env).defaults({
benchmark: false,
validation_data: false,
effects: [],
@ -84,14 +84,14 @@ carto.Renderer.prototype.render = function render(m) {
var output = [];
// Transform stylesheets into definitions.
var definitions = _.chain(m.Stylesheet)
var definitions = _(m.Stylesheet).chain()
.map(function(s) {
if (typeof s == 'string') {
throw new Error("Stylesheet object is expected not a string: '" + s + "'");
}
// Passing the environment from stylesheet to stylesheet,
// allows frames and effects to be maintained.
env = _.extend(env, {filename:s.id});
env = _(env).extend({filename:s.id});
var time = +new Date(),
root = (carto.Parser(env)).parse(s.data);
@ -152,7 +152,7 @@ carto.Renderer.prototype.render = function render(m) {
if (env.errors) throw env.errors;
// Pass TileJSON and other custom parameters through to Mapnik XML.
var parameters = _.reduce(m, function(memo, v, k) {
var parameters = _(m).reduce(function(memo, v, k) {
if (!v && v !== 0) return memo;
switch (k) {
@ -205,7 +205,7 @@ carto.Renderer.prototype.render = function render(m) {
'\n</Parameters>\n'
);
var properties = _.map(map_properties, function(v) { return ' ' + v; }).join('');
var properties = _(map_properties).map(function(v) { return ' ' + v; }).join('');
output.unshift(
'<?xml version="1.0" ' +

View File

@ -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) {
@ -57,7 +55,7 @@ CartoCSS.Layer.prototype = {
},
/**
* return the symbolizers that need to be rendered with
* return the symbolizers that need to be rendered with
* this style. The order is the rendering order.
* @returns a list with 3 possible values 'line', 'marker', 'polygon'
*/
@ -99,7 +97,7 @@ CartoCSS.Layer.prototype = {
//
// given a geoemtry type returns the transformed one acording the CartoCSS
// For points there are two kind of types: point and sprite, the first one
// For points there are two kind of types: point and sprite, the first one
// is a circle, second one is an image sprite
//
// the other geometry types are the same than geojson (polygon, linestring...)
@ -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,98 +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: []
async.each(defs, this.preprocess.bind(this), function(err) {
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<def.rules.length; u++){
if(def.rules[u].name === "marker-file" || def.rules[u].name === "point-file"){
var value = def.rules[u].value.value[0].value[0].value.value;
this.imageURLs.push(value);
}
}
layer.frames = [];
layer.zoom = tree.Zoom.all;
var props = def.toJS(parse_env);
if (self.options.debug) console.log("props", props);
for(var v in props) {
var lyr = layer[v] = layer[v] || {
constant: false,
symbolizer: null,
js: [],
index: 0
};
// build javascript statements
lyr.js.push(props[v].map(function(a) { return a.js; }).join('\n'));
// get symbolizer for prop
lyr.symbolizer = _.first(props[v].map(function(a) { return a.symbolizer; }));
// serach the max index to know rendering order
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 done = {};
for(var i = 0; i < defs.length; ++i) {
var def = defs[i];
var k = defKey(def);
var layer = layers[k];
if(!done[k]) {
if(self.options.debug) console.log("**", k);
for(var prop in layer) {
if (prop !== 'zoom' && prop !== 'frames' && prop !== 'symbolizers') {
if(self.options.debug) console.log("*", prop);
layer[prop].style = self._createFn(layer[prop].js);
layer.symbolizers.push(layer[prop].symbolizer);
layer.symbolizers = _.uniq(layer.symbolizers);
}
}
layer.attachment = k;
ordered_layers.push(layer);
done[k] = true;
}
layer.zoom |= def.zoom;
layer.frames.push(def.frame_offset);
}
// uniq the frames
for(i = 0; i < ordered_layers.length; ++i) {
ordered_layers[i].frames = _.uniq(ordered_layers[i].frames);
}
self.layers = ordered_layers.map(function(shader) {
return new CartoCSS.Layer(shader);
});
for(var u = 0; u<def.rules.length; u++){
var rule = def.rules[u];
if(rule.name === "marker-file" || rule.name === "point-file"){
var value = rule.value.value[0].value[0].value.value;
this.imageURLs.push(value);
}
}
layer.frames = [];
layer.zoom = tree.Zoom.all;
var props = def.toJS(parse_env);
if (this.options.debug) console.log("props", props);
for(var v in props) {
var lyr = layer[v] = layer[v] || {
constant: false,
symbolizer: null,
js: [],
index: 0
};
// build javascript statements
lyr.js.push(props[v].map(function(a) { return a.js; }).join('\n'));
// get symbolizer for prop
lyr.symbolizer = _.first(props[v].map(function(a) { return a.symbolizer; }));
// serach the max index to know rendering order
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; }));
// True when the property is filtered.
lyr.filtered = props[v][0].filtered;
}
}
var ordered_layers = [];
if (this.options.debug) console.log(layers);
var done = {};
for(var i = 0; i < defs.length; ++i) {
var def = defs[i];
if (this.options.strict) {
def.toXML(parse_env, {});
if (parse_env.errors.message) {
throw new Error(parse_env.errors.message);
}
}
var k = defKey(def);
var layer = layers[k];
if(!done[k]) {
if(this.options.debug) console.log("**", k);
for(var prop in layer) {
if (prop !== 'zoom' && prop !== 'frames' && prop !== 'symbolizers') {
if(this.options.debug) console.log("*", prop);
layer[prop].style = this._createFn(layer[prop].js);
layer.symbolizers.push(layer[prop].symbolizer);
layer.symbolizers = _.uniq(layer.symbolizers);
}
}
layer.attachment = k;
ordered_layers.push(layer);
done[k] = true;
}
layer.zoom |= def.zoom;
layer.frames.push(def.frame_offset);
}
// uniq the frames
for(i = 0; i < ordered_layers.length; ++i) {
ordered_layers[i].frames = _.uniq(ordered_layers[i].frames);
}
return ordered_layers;
callback(null, self.layers);
return;
});
} else {
callback(new Error("not rules found"));
}
return null;
}
};
@ -284,14 +326,19 @@ CartoCSS.prototype = {
carto.RendererJS = function (options) {
this.options = options || {};
this.options.mapnik_version = this.options.mapnik_version || 'latest';
this.reference = this.options.reference || require('./torque-reference').version.latest;
this.options.strict = this.options.hasOwnProperty('strict') ? this.options.strict : false;
this._functions = {}
};
// Prepare a javascript object which contains the layers
carto.RendererJS.prototype.render = function render(cartocss, callback) {
tree.Reference.setData(this.reference);
return new CartoCSS(cartocss, this.options);
var reference = require('./torque-reference');
tree.Reference.setData(reference.version.latest);
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') {

View File

@ -148,6 +148,12 @@ var _mapnik_reference_latest = {
},
"symbolizers" : {
"*": {
"transition-time": {
"css": "transition-time",
"default-value": 0,
"default-meaning": "transition time",
"doc": ""
},
"image-filters": {
"css": "image-filters",
"default-value": "none",
@ -166,17 +172,7 @@ var _mapnik_reference_latest = {
["sharpen", 0],
["colorize-alpha", -1],
["color-to-alpha", 1],
["scale-hsla", 8],
["buckets", -1],
["category", -1],
["equal", -1],
["headtails", -1],
["jenks", -1],
["quantiles", -1],
["cartocolor", -1],
["colorbrewer", -1],
["range", -1],
["ramp", -1]
["scale-hsla", 8]
],
"doc": "A list of image filters."
},
@ -303,16 +299,14 @@ var _mapnik_reference_latest = {
"type": "color",
"default-value": "rgba(128,128,128,1)",
"default-meaning": "gray and fully opaque (alpha = 1), same as rgb(128,128,128)",
"doc": "Fill color to assign to a polygon",
"expression": true
"doc": "Fill color to assign to a polygon"
},
"fill-opacity": {
"css": "polygon-opacity",
"type": "float",
"doc": "The opacity of the polygon",
"default-value": 1,
"default-meaning": "opaque",
"expression": true
"default-meaning": "opaque"
},
"gamma": {
"css": "polygon-gamma",
@ -413,15 +407,13 @@ var _mapnik_reference_latest = {
"default-value": "rgba(0,0,0,1)",
"type": "color",
"default-meaning": "black and fully opaque (alpha = 1), same as rgb(0,0,0)",
"doc": "The color of a drawn line",
"expression": true
"doc": "The color of a drawn line"
},
"stroke-width": {
"css": "line-width",
"default-value": 1,
"type": "float",
"doc": "The width of a line in pixels",
"expression": true
"doc": "The width of a line in pixels"
},
"stroke-opacity": {
"css": "line-opacity",
@ -586,8 +578,7 @@ var _mapnik_reference_latest = {
"doc": "An SVG file that this marker shows at each placement. If no file is given, the marker will show an ellipse.",
"default-value": "",
"default-meaning": "An ellipse or circle, if width equals height",
"type": "uri",
"expression": true
"type": "uri"
},
"opacity": {
"css": "marker-opacity",
@ -671,8 +662,7 @@ var _mapnik_reference_latest = {
"css": "marker-fill",
"default-value": "blue",
"doc": "The color of the area of the marker.",
"type": "color",
"expression": true
"type": "color"
},
"allow-overlap": {
"css": "marker-allow-overlap",
@ -838,18 +828,6 @@ var _mapnik_reference_latest = {
"default-value": "point",
"doc": "How this shield should be placed. Point placement attempts to place it on top of points, line places along lines multiple times per feature, vertex places on the vertexes of polygons, and interior attempts to place inside of polygons."
},
"placement-type": {
"css": "shield-placement-type",
"doc": "Re-position and/or re-size shield to avoid overlaps. \"simple\" for basic algorithm (using shield-placements string,) \"dummy\" to turn this feature off.",
"type": [
"dummy",
"simple",
"list"
],
"expression":true,
"default-meaning": "Alternative placements will not be enabled.",
"default-value": "dummy"
},
"avoid-edges": {
"css": "shield-avoid-edges",
"doc": "Tell positioning algorithm to avoid labeling near intersection edges.",
@ -1059,8 +1037,7 @@ var _mapnik_reference_latest = {
"type": "uri",
"default-value": "none",
"required": true,
"doc": "An image file to be repeated and warped along a line",
"expression": true
"doc": "An image file to be repeated and warped along a line"
},
"clip": {
"css": "line-pattern-clip",
@ -1140,8 +1117,7 @@ var _mapnik_reference_latest = {
"type": "uri",
"default-value": "none",
"required": true,
"doc": "Image to use as a repeated pattern fill within a polygon",
"expression": true
"doc": "Image to use as a repeated pattern fill within a polygon"
},
"alignment": {
"css": "polygon-pattern-alignment",

View File

@ -210,37 +210,48 @@ tree.Definition.prototype.toXML = function(env, existing) {
tree.Definition.prototype.toJS = function(env) {
var shaderAttrs = {};
// merge conditions from filters with zoom condition of the
// definition
var zoom = "(" + this.zoom + " & (1 << ctx.zoom))";
var frame_offset = this.frame_offset;
var zoomFilter = "(" + this.zoom + " & (1 << ctx.zoom))";
var filters = [zoomFilter];
var originalFilters = this.filters.toJS(env);
// Ignore default zoom for filtering (https://github.com/CartoDB/carto/issues/40)
var zoomFiltered = this.zoom !== tree.Zoom.all;
if (originalFilters) {
filters.push(originalFilters);
}
var _if = this.filters.toJS(env);
var filters = [zoom];
if(_if) filters.push(_if);
if(frame_offset) filters.push('ctx["frame-offset"] === ' + frame_offset);
_if = filters.join(" && ");
_.each(this.rules, function(rule) {
if(rule instanceof tree.Rule) {
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
if (frame_offset) {
filters.push('ctx["frame-offset"] === ' + frame_offset);
}
var r = {
index: rule.index,
symbolizer: rule.symbolizer
};
_.each(this.rules, function (rule) {
var exportedRule = {};
if (_if) {
r.js = "if(" + _if + "){" + rule.value.toJS(env) + "}"
} else {
r.js = rule.value.toJS(env);
}
if (!rule instanceof tree.Rule) {
throw new Error("Ruleset not supported");
r.constant = rule.value.ev(env).is !== 'field';
r.filtered = !!_if;
shaderAttrs[rule.name].push(r);
} else {
throw new Error("Ruleset not supported");
//if (rule instanceof tree.Ruleset) {
//var sh = rule.toJS(env);
//for(var v in sh) {
//shaderAttrs[v] = shaderAttrs[v] || [];
//for(var attr in sh[v]) {
//shaderAttrs[v].push(sh[v][attr]);
//}
//}
//}
}
exportedRule.index = rule.index;
exportedRule.symbolizer = rule.symbolizer;
exportedRule.js = "if(" + filters.join(" && ") + "){" + rule.value.toJS(env) + "}";
exportedRule.constant = rule.value.ev(env).is !== 'field';
exportedRule.filtered = zoomFiltered || (originalFilters !== '');
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
shaderAttrs[rule.name].push(exportedRule);
});
return shaderAttrs;
};

View File

@ -92,10 +92,14 @@ tree.Filterset.prototype.toJS = function(env) {
val = filter._val.toString(true);
}
var attrs = "data";
if (op === '=~') {
return "(" + attrs + "['" + filter.key.value + "'] + '').match(" + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&amp;/g, '&') + "'" : 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 + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&amp;/g, '&') + "'" : val);
return attrs + "." + filter.key.value + " " + op + " " + value;
}).join(' && ');
};

View File

@ -15,11 +15,13 @@ tree.Value.prototype = {
}));
}
},
toString: function(env, selector, sep, format) {
return this.value.map(function(e) {
return e.toString(env, format);
}).join(sep || ', ');
},
clone: function() {
var obj = Object.create(tree.Value.prototype);
if (Array.isArray(obj)) obj.value = this.value.slice();
@ -33,19 +35,13 @@ tree.Value.prototype = {
var val = this.ev(env);
var v = val.toString();
if(val.is === "color" || val.is === 'uri' || val.is === 'string' || val.is === 'keyword') {
v = "'" + v.replace(/&amp;/g, '&') + "'";
} else if (Array.isArray(this.value) && this.value.length > 1) {
// This covers something like `line-dasharray: 5, 10;`
// where the return _value has more than one element.
// Without this the generated code will look like:
// _value = 5, 10; which will ignore the 10.
v = '[' + this.value.join(',') + ']';
v = "'" + v + "'";
} else if (val.is === 'field') {
// replace [variable] by ctx['variable']
v = v.replace(/\[([^\]]*)\]/g, function(matched) {
return matched.replace(/\[(.*)\]/g, "data['$1']");
});
}else if (val.is === 'call') {
v = v.replace(/\[(.*)\]/g, "data['$1']");
} else if (val.is === 'custom') {
v = val.toString();
} else if (val.is === 'call') {
v = JSON.stringify({
name: val.name,
args: val.args

2110
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "carto",
"version": "0.15.1-cdb5",
"version": "0.15.1-cdb1",
"description": "CartoCSS Stylesheet Compiler",
"url": "https://github.com/cartodb/carto",
"repository": {
@ -37,27 +37,23 @@
"node": ">=0.4.x"
},
"dependencies": {
"underscore": "1.8.3",
"underscore": "~1.6.0",
"mapnik-reference": "~6.0.2",
"optimist": "~0.6.0"
"optimist": "~0.6.0",
"async": "^1.4.2"
},
"devDependencies": {
"mocha": "1.12.x",
"jshint": "0.2.x",
"sax": "0.1.x",
"istanbul": "~0.2.14",
"coveralls": "~2.10.1",
"browserify": "~7.0.0",
"coveralls": "~2.10.1",
"istanbul": "~0.2.14",
"jshint": "0.2.x",
"mocha": "1.12.x",
"sax": "0.1.x",
"uglify-js": "1.3.3"
},
"scripts": {
"pretest": "npm install",
"test": "mocha -R spec",
"tdd": "env HIDE_LOGS=true mocha -w -R spec",
"coverage": "istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info",
"bump": "npm version patch",
"bump:major": "npm version major",
"bump:minor": "npm version minor",
"postversion": "git push origin master --follow-tags"
"coverage": "istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info"
}
}

View File

@ -1,102 +0,0 @@
/**
* Test the filtered field.
*
* When compiled, a rule provides metainformation fields like index, constant...etc
* one of this fields is the "filtered field".
*
* This field gives information about whether a property is filtered or not.
*
* A property is filtered if it was activated inside a filter. In the following cartocss
* code marker-color.filtered will be true because it's inside a population filter.
*
* #layer {
* maker-width: 20;
* [population > 100] {
* marker-color: red; // this property is filtered
* }
* }
*
* "zoom" is a special case, and it only should be considered when its value is not the default.
*/
var assert = require('assert');
var Carto = require('../lib/carto/index.js');
var renderer = new Carto.RendererJS({ strict: true });
describe('property.filtered', function () {
it('should be false when the property is not filtered', function () {
var style = [
'#layer {',
' marker-fill: red;',
'}'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(!layers['marker-fill'].filtered);
});
it('should be true when the property is filtered', function () {
var style = [
'#layer {',
' [foo > 30] {',
' marker-fill: red;',
' }',
'}'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(layers['marker-fill'].filtered);
});
it('should be true when the property is filtered at first level', function () {
var style = [
'#layer [foo > 30] {',
' marker-fill: red;',
'}`'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(layers['marker-fill'].filtered);
});
it('should be false when the property is not filterd but there is another filtered properties', function () {
var style = [
'#layer {',
' marker-fill: red;',
' [bar < 200]{',
' marker-allow-overlap: false;',
' }',
'}`'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(!layers['marker-fill'].filtered);
assert(layers['marker-allow-overlap'].filtered);
});
it('should be true when the property is filtered and have a default value', function () {
var style = [
'#layer {',
' marker-fill: red;',
' [bar < 200]{',
' marker-fill: blue;',
' }',
'}`'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(layers['marker-fill'].filtered);
});
it('should be true when filtering by zoom', function () {
var style = [
'#layer {',
' [zoom < 5]{',
' marker-fill: blue;',
' }',
'}`'
].join('\n');
var layers = renderer.render(style).layers[0].shader;
assert(layers['marker-fill'].filtered);
});
});

View File

@ -54,8 +54,7 @@ var _mapnik_reference_latest = {
["x-gradient", 0],
["y-gradient", 0],
["invert", 0],
["sharpen", 0],
["ramp", 0]
["sharpen", 0]
],
"doc": "A list of image filters."
},

View File

@ -1,148 +0,0 @@
var assert = require('assert');
var carto = require('../lib/carto');
var tree = require('../lib/carto/tree');
describe('RendererJS Strict Mode', function() {
var style = [
'#world {',
'polygon-fill: red;',
'line-width: 2;',
'line-color: #f00;',
'[frame-offset = 1] {',
'line-width: 3;',
'}',
'[frame-offset = 2] {',
'line-width: 3;',
'}',
'}',
'',
'#worls[frame-offset = 10] {',
'line-width: 4;',
'}'
].join('\n');
var reference = {
version: '1.0.0',
style: {},
layer: {},
colors: {},
filter: {},
symbolizers: {
line: {
"stroke": {
"css": "line-color",
"default-value": "rgba(0,0,0,1)",
"type": "color",
"default-meaning": "black and fully opaque (alpha = 1), same as rgb(0,0,0)",
"doc": "The color of a drawn line"
},
"stroke-width": {
"css": "line-width",
"default-value": 1,
"type": "float",
"doc": "The width of a line in pixels"
},
"stroke-opacity": {
"css": "line-opacity",
"default-value": 1,
"type": "float",
"default-meaning": "opaque",
"doc": "The opacity of a line"
},
"stroke-linejoin": {
"css": "line-join",
"default-value": "miter",
"type": [
"miter",
"miter-revert",
"round",
"bevel"
],
"expression": true,
"doc": "The behavior of lines when joining.",
"default-meaning": "The line joins will be rendered using a miter look."
},
"stroke-linecap": {
"css": "line-cap",
"default-value": "butt",
"type": [
"butt",
"round",
"square"
],
"expression": true,
"doc": "The display of line endings.",
"default-meaning": "The line endings will be rendered using a butt look."
},
"comp-op": {
"css": "line-comp-op",
"default-value": "overlay",
"default-meaning": "Add the current symbolizer on top of other symbolizer.",
"doc": "Composite operation. This defines how this symbolizer should behave relative to symbolizers atop or below it.",
"type": [
"multiply",
"add",
"overlay"
],
"expression": true
},
"stroke-dasharray": {
"css": "line-dasharray",
"type": "numbers",
"expression": true,
"doc": "A pair of length values [a,b], where (a) is the dash length and (b) is the gap length respectively. More than two values are supported for more complex patterns.",
"default-value": "none",
"default-meaning": "The line will be drawn without dashes."
}
}
}
};
var expectedErrorMessageRegex = /Unrecognized rule: polygon-fill/;
before(function() {
this.referenceData = tree.Reference.data;
});
after(function() {
if (this.referenceData) {
tree.Reference.setData(this.referenceData);
}
});
it('should fail if a feature is not supported and strict is turned on', function () {
assert.throws(
function () {
var RendererJS = new carto.RendererJS({reference: reference, mapnik_version: '1.0.0', strict: true });
var shader = RendererJS.render(style);
},
expectedErrorMessageRegex
);
});
function rendererStrictModeOffTest(RendererJS) {
return function () {
var shader = RendererJS.render(style);
assert.ok(shader.layers);
assert.equal(shader.layers.length, 2);
};
}
it('should pass if a feature is not supported but strict mode is not specified', rendererStrictModeOffTest(
new carto.RendererJS({reference: reference, mapnik_version: '1.0.0' })
));
it('should pass if a feature is not supported but strict is turned off', function () {
new carto.RendererJS({reference: reference, mapnik_version: '1.0.0', strict: false })
});
it('should pass if a feature is supported and strict is turned on', function () {
var RendererJS = new carto.RendererJS({reference: reference, mapnik_version: '1.0.0', strict: true });
var cartocss = '#layer { line-width: 10 }';
var shader = RendererJS.render(cartocss);
assert.ok(shader);
});
});

View File

@ -1,40 +1,46 @@
var SHOW_LOGS = (process.env.HIDE_LOGS !== 'true');
var assert = require('assert');
var carto = require('../lib/carto');
var tree = require('../lib/carto/tree');
var _ = require('underscore');
describe('RenderingJS', function() {
var shader;
var style = [
'#world {',
'line-width: 2;',
'line-color: #f00;',
'[frame-offset = 1] {',
'line-width: 3;',
'}',
'[frame-offset = 2] {',
'line-width: 3;',
'}',
'}',
'',
'#worls[frame-offset = 10] {',
'line-width: 4;',
'#world {',
'line-width: 2;',
'line-color: #f00;',
'[frame-offset = 1] {',
'line-width: 3;',
'}',
'[frame-offset = 2] {',
'line-width: 3;',
'}',
'}',
'',
'#worls[frame-offset = 10] {',
'line-width: 4;',
'}'
].join('\n');
beforeEach(function() {
shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
beforeEach(function(done) {
(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);
});
it ("shold report frames used in the layer", function() {
it("shold report frames used in the layer", function() {
var layer = shader.getLayers()[0];
assert( layer.frames()[0] === 0);
assert( layer.frames()[1] === 1);
assert(layer.frames()[0] === 0);
assert(layer.frames()[1] === 1);
layer = shader.getLayers()[1];
assert( layer.frames()[0] === 10);
assert(layer.frames()[0] === 10);
});
it ("shold render with frames var", function() {
@ -43,49 +49,59 @@ describe('RenderingJS', function() {
assert( props['line-width'] === 4);
});
it ("shold render variables", function() {
it("shold render variables", function(done) {
var style = '#test { marker-width: [testing]; }';
shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
var layer = shader.getLayers()[0];
var props = layer.getStyle({testing: 2}, { 'zoom': 0, 'frame-offset': 10 });
assert( props['marker-width'] === 2);
(new carto.RendererJS({ debug: true })).render(style, function(err, s) {
console.log("#### --> ", 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: SHOW_LOGS })).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-width: 10; }';
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
var layer0 = shader.getLayers()[0];
assert(layer0.getSymbolizers()[0] === 'polygon');
assert(layer0.getSymbolizers()[1] === 'line');
style += '#test2 { line-color: red;polygon-fill: red; line-witdh: 10; }';
(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');
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: SHOW_LOGS })).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,134 +122,79 @@ describe('RenderingJS', function() {
' [frame-offset = 2] { marker-width: 15; marker-fill-opacity: 0.02;}',
'}'
].join('\n');
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).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<against.length; i++){
assert(against[i] == markerURLs[i])
}
(new carto.RendererJS({ debug: true })).render(style, function(err, shader) {
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< against.length; i++){
assert(against[i] == markerURLs[i])
}
done();
});
})
it ("should return variable for styles that change", function() {
var style = '#test { marker-width: [prop]; }';
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
var layer0 = shader.getLayers()[0];
assert(layer0.isVariable());
describe("isVariable", function() {
style = '#test { marker-width: 1; }';
shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
layer0 = shader.getLayers()[0];
assert(!layer0.isVariable());
it("prop", function(done) {
var style = '#test { marker-width: [prop]; }';
(new carto.RendererJS({ debug: true })).render(style, function(err, shader) {
var layer0 = shader.getLayers()[0];
assert(layer0.isVariable());
done();
});
});
style = '#test { marker-width: [prop]; marker-fill: red; }';
shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
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();
});
});
});
it("should parse styles with string", function() {
var style = '#test { [column = "test\'ing"] { marker-width: 10; } }';
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
var layer = shader.getLayers()[0];
var props = layer.getStyle({column: 'test\'ing'}, { 'zoom': 0, 'frame-offset': 10 });
assert(props['marker-width'] === 10);
});
it("should parse styles with filters not supported by dot notation", function() {
var style = '#test["mapnik::geometry_type"=1] { marker-width: 10; }';
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(style);
var layer = shader.getLayers()[0];
var props = layer.getStyle({"mapnik::geometry_type": 1}, { 'zoom': 0 });
assert.equal(props['marker-width'], 10);
var emptyFilterProps = layer.getStyle({"mapnik::geometry_type": 2}, { 'zoom': 0 });
assert.equal(emptyFilterProps['marker-width'], null);
});
it ("should parse turbocarto", function(){
var css = [
'#layer {',
' marker-width: ramp([cartodb_id], (#fff, #bbb), jenks);',
'}'
].join('\n');
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(css);
it ("should be able to provide external functions", function(done) {
var style = '#test { marker-width: testing([prop]); }';
var renderer = new carto.RendererJS({ debug: true })
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 st = layer.shader['marker-width'].style({}, {zoom: 1})
assert.equal(st.name, "ramp")
assert.equal(st.args.length, 3);
assert.equal(st.args[1].value[0].rgb[0], 255);
assert.equal(st.args[1].value[0].rgb[1], 255);
assert.equal(st.args[1].value[0].rgb[2], 255);
assert.equal(st.args[2].value, 'jenks');
})
it("should parse turbocarto with inner functions", function(){
var css = [
'#layer {',
' marker-width: ramp([cartodb_id], cartocolor(Bold), category(10));',
'}'
].join('\n');
var shader = (new carto.RendererJS({ debug: SHOW_LOGS })).render(css);
var layer = shader.getLayers()[0];
var st = layer.shader['marker-width'].style({}, {zoom: 1});
assert.equal(st.name, "ramp");
assert.equal(st.args.length, 3);
assert.equal(st.args[1].name, 'cartocolor');
assert.equal(st.args[1].args[0].value, 'Bold');
assert.equal(st.args[2].name, 'category');
assert.equal(st.args[2].args[0].value, 10);
var props = layer.getStyle({prop: 1}, { 'zoom': 0, 'frame-offset': 10 });
assert(props['marker-width'] === 1);
done();
});
});
it("should work with multiple operands", function(){
var css = [
'#layer {',
' marker-width: [value] * [value] * 0.5;',
'}'
].join('\n');
var shader = (new carto.RendererJS({ debug: false })).render(css);
var layer = shader.getLayers()[0];
var width = layer.shader['marker-width'].style({value: 4}, {zoom: 1});
assert.equal(width, 8);
});
it("should work with numbers", function(){
var css = [
'#layer {',
' line-dasharray: 5, 10;',
'}'
].join('\n');
var shader = (new carto.RendererJS({ debug: false })).render(css);
var layer = shader.getLayers()[0];
var dasharray = layer.shader['line-dasharray'].style({value: 4}, {zoom: 1});
assert.deepEqual(dasharray, [5, 10]);
});
it("should not throw `ReferenceError` with `=~` operator", function(){
var css = [
'#layer[name=~".*wadus*"] {',
' marker-width: 14;',
'}'
].join('\n');
assert.doesNotThrow(function () {
var shader = (new carto.RendererJS({})).render(css);
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 value = layer.shader['marker-width'].style({ name: 'wadus' }, { zoom: 1 });
assert.equal(value, 14);
}, ReferenceError);
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();
});
});
it("`=~` operator should support numbers", function(){
var css = [
'#layer[value=~"^10"] {',
' marker-width: 14;',
'}'
].join('\n');
assert.doesNotThrow(function () {
var shader = (new carto.RendererJS({})).render(css);
var layer = shader.getLayers()[0];
var value = layer.shader['marker-width'].style({ value: 10 }, { zoom: 1 });
assert.equal(value, 14);
}, Error);
});
});

File diff suppressed because it is too large Load Diff