Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

Commit

Permalink
enable carto to output a JSON variant of Mapnik XML, internally the J…
Browse files Browse the repository at this point in the history
…SON variant is used and then either output directly or afterwards converted to XML, ref #413
  • Loading branch information
nebulon42 committed Jun 4, 2017
1 parent 5e3a9f8 commit f37bd50
Show file tree
Hide file tree
Showing 68 changed files with 502 additions and 293 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ There is a new command line / API switch (`-q / --quiet` / `quiet`) to suppress
* Warnings are emitted if a layer has no associated styles or styles do not match a corresponding layer selector. ([#29](https://github.com/mapbox/carto/issues/29))
* New command line switch `-f / --file` to specify a file for output instead of `stdout`.
* carto now honors variable redefinition and uses the last defined value instead of the first one ([#338](https://github.com/mapbox/carto/issues/338)).
* Parameters of symbolizers are now ouput in alphabetical order according to the CartoCSS name of that parameter. This makes output more predictable.
* All parameters of XML tags are now ouput in alphabetical order. This makes output more predictable.
* carto is now able to output a JSON variant of Mapnik XML. There is a new command line / API switch
(`-o / --output` / `outputFormat`) to choose the output format. Possible values are `mapnik` (default)
for Mapnik XML and `json` for the JSON variant (part of [#413](https://github.com/mapbox/carto/issues/413)).
For Mapnik XML all character data as tag content is now prefixed with CDATA.

### Breaking changes

Expand All @@ -17,7 +21,7 @@ There is a new command line / API switch (`-q / --quiet` / `quiet`) to suppress
* The deprecated `name` attribute for layers is no longer supported. Use `id` instead.
* The deprecated color functions `husl` and `husla` are no longer supported. Use `hsluv` and `hsluva` instead.
* If you define a variable with the same name twice the latest defined value is now used in contrast to the first defined one as before.
* Parameters of symbolizers are now ouput in alphabetical order according to the CartoCSS name of that parameter. If you somehow depend on the order
* All parameters of XML tags are now ouput in alphabetical order. If you somehow depend on the order
of parameters (e.g. for tests) expect them to change.

#### API
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
[![Build Status](https://secure.travis-ci.org/mapbox/carto.svg)](http://travis-ci.org/mapbox/carto) [![Build status](https://ci.appveyor.com/api/projects/status/github/mapbox/carto?svg=true)](https://ci.appveyor.com/project/Mapbox/carto) [![Coverage Status](https://coveralls.io/repos/github/mapbox/carto/badge.svg?branch=master)](https://coveralls.io/github/mapbox/carto?branch=master) [![Package Version](https://img.shields.io/npm/v/carto.svg)](https://www.npmjs.com/package/carto) [![Dependencies](https://david-dm.org/mapbox/carto.svg)](https://david-dm.org/mapbox/carto) [![Documentation Status](https://readthedocs.org/projects/cartocss/badge/?version=latest)](http://cartocss.readthedocs.io/en/latest/?badge=latest)

CartoCSS (short: Carto) is a language for map design. It is similar in syntax to CSS, but builds upon it with specific abilities to filter map data and by providing things like variables.
It targets the [Mapnik renderer](http://mapnik.org) and is able to generate Mapnik XML. It can run from the command line or in the browser.
It targets the [Mapnik renderer](http://mapnik.org) and is able to generate Mapnik XML and a JSON variant of Mapnik XML.
It can run from the command line or in the browser.

Carto is an evolution of the [Cascadenik](https://github.com/mapnik/Cascadenik) idea and language, with an emphasis on speed and flexibility.

Expand Down Expand Up @@ -52,6 +53,7 @@ Available parameters:
* -h / --help - Display help message
* -l / --localize - Use millstone to localize resources when loading an MML (default: off)
* -n / --nosymlink - Use absolute paths instead of symlinking files
* -o / --output - Specify output format, possible values are `mapnik` and `json` (default: `mapnik`)
* -ppi RESOLUTION - Pixels per inch used to convert m, mm, cm, in, pt, pc to pixels (default: 90.714)
* -q / --quiet - Do not output any warnings (default: off)
* -v / --version - Display version information
Expand Down
4 changes: 3 additions & 1 deletion bin/carto
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var yargs = require('yargs')
.options('n', {alias:'nosymlink', boolean:true, describe:'Use absolute paths instead of symlinking files'})
.options('a', {alias:'api', describe:'Specify Mapnik API version', default:carto.tree.Reference.getLatest()})
.options('f', {alias:'file', describe:'Outputs to the given file instead of stdout.'})
.options('o', {alias:'output', describe:'Specify output format (mapnik, json)', default:'mapnik'})
.options('q', {alias:'quiet', boolean:true, default:false, describe:'Do not output any warnings'})
.options('ppi', {describe:'Pixels per inch used to convert m, mm, cm, in, pt, pc to pixels', default:90.714});

Expand Down Expand Up @@ -94,7 +95,8 @@ function compile(err, data) {
filename: input,
benchmark: options.benchmark,
ppi: options.ppi,
quiet: options.quiet
quiet: options.quiet,
outputFormat: options.output
},
{
mapnik_version: options.api
Expand Down
4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ CartoCSS
==================================

CartoCSS (short: Carto) is a language for map design. It is similar in syntax to CSS, but builds upon it with specific abilities to
filter map data and by providing things like variables. It targets the `Mapnik renderer <http://mapnik.org>`_ and is able to generate Mapnik XML.
It can run from the command line or in the browser by using a bundler like Browserify.
filter map data and by providing things like variables. It targets the `Mapnik renderer <http://mapnik.org>`_ and is able to generate Mapnik XML or
a JSON variant of Mapnik XML. It can run from the command line or in the browser by using a bundler like Browserify.

Carto is an evolution of the `Cascadenik <https://github.com/mapnik/Cascadenik>`_ idea and language, with an emphasis on speed and flexibility.

Expand Down
3 changes: 3 additions & 0 deletions docs/installation_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ The following command line options are available:
-n / --nosymlink
Use absolute paths instead of symlinking files

-o / --output
Specify output format, possible values are ``mapnik`` and ``json`` (default: ``mapnik``)

-ppi RESOLUTION
Pixels per inch used to convert m, mm, cm, in, pt, pc to pixels (default: 90.714)

Expand Down
17 changes: 13 additions & 4 deletions lib/carto/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,19 @@ tree.functions = {
color: color,
mode: mode,
toString: function(env) {
return '\n\t<stop value="' + val.ev(env) + '"' +
(color ? ' color="' + color.ev(env) + '" ' : '') +
(mode ? ' mode="' + mode.ev(env) + '" ' : '') +
'/>';
var attr = {};

_.set(attr, 'value', val.ev(env).toString());
if (color) {
_.set(attr, 'color', color.ev(env).toString());
}
if (mode) {
_.set(attr, 'mode', mode.ev(env).toString());
}
return {
'_name': 'stop',
'_attributes': attr
};
}
};
},
Expand Down
120 changes: 76 additions & 44 deletions lib/carto/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ carto.Renderer.prototype.renderMSS = function render(data) {
benchmark: false,
validation_data: false,
effects: [],
quiet: false
quiet: false,
outputFormat: 'mapnik'
}).value();

try {
Expand Down Expand Up @@ -66,13 +67,21 @@ carto.Renderer.prototype.renderMSS = function render(data) {
var bench_name = '\tStyle "'+style_name+'" (#'+k+') toXML';
if (env.benchmark) console.time(bench_name);
// env.effects can be modified by this call
output.push(carto.tree.StyleXML(style_name, rule.attachment, rule, env));
output.push(carto.tree.StyleObject(style_name, rule.attachment, rule, env));
if (env.benchmark) console.timeEnd(bench_name);
}
if (env.benchmark) console.timeEnd('Total Style generation');

if (!util.hasErrors(env.msg)) {
output = output.join('\n');
switch (env.outputFormat) {
case 'json':
output = JSON.stringify(output, null, 2);
break;
case 'mapnik':
default:
output = util.jsonToXML(output);
break;
}
}
else {
output = null;
Expand Down Expand Up @@ -110,7 +119,8 @@ carto.Renderer.prototype.render = function render(m) {
validation_data: false,
effects: [],
ppi: 90.714,
quiet: false
quiet: false,
outputFormat: 'mapnik'
}).value();

try {
Expand Down Expand Up @@ -236,10 +246,10 @@ carto.Renderer.prototype.render = function render(m) {
style_name = l.layerId + (rule.attachment !== '__default__' ? '-' + rule.attachment : '');

// env.effects can be modified by this call
var styleXML = carto.tree.StyleXML(style_name, rule.attachment, rule, env);
var styleObj = carto.tree.StyleObject(style_name, rule.attachment, rule, env);

if (styleXML) {
output.push(styleXML);
if (Object.keys(styleObj).length) {
output.push(styleObj);
styles.push(style_name);
}
}
Expand All @@ -255,12 +265,14 @@ carto.Renderer.prototype.render = function render(m) {
l.properties = props;
}

output.push(carto.tree.LayerXML(l, styles));
output.push(carto.tree.LayerObject(l, styles));
}

output.unshift(env.effects.map(function(e) {
return e.toXML(env);
}).join('\n'));
if (env.effects.length) {
output = _.concat(env.effects.map(function(e) {
return e.toObject(env);
}), output);
}

var map_properties = getMapProperties(m, definitions, env);

Expand All @@ -282,52 +294,57 @@ carto.Renderer.prototype.render = function render(m) {
case 'minzoom':
case 'maxzoom':
case 'version':
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
break;
// Properties that require CDATA.
case 'name':
case 'description':
case 'legend':
case 'attribution':
case 'template':
memo.push(' <Parameter name="' + k + '"><![CDATA[' + v + ']]></Parameter>');
break;
// Mapnik image format.
case 'format':
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
memo.push({
'_name': 'Parameter',
'_attributes': {
'name': k
},
'_content': v
});
break;
// Mapnik interactivity settings.
case 'interactivity':
memo.push(' <Parameter name="interactivity_layer">' + v.layer + '</Parameter>');
memo.push(' <Parameter name="interactivity_fields">' + v.fields + '</Parameter>');
memo.push({
'_name': 'Parameter',
'_attributes': {
'name': 'interactivity_layer'
},
'_content': v.layer
});
memo.push({
'_name': 'Parameter',
'_attributes': {
'name': 'interactivity_fields'
},
'_content': v.fields
});
break;
// Support any additional scalar properties.
default:
if ('string' === typeof v) {
memo.push(' <Parameter name="' + k + '"><![CDATA[' + v + ']]></Parameter>');
} else if ('number' === typeof v) {
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
} else if ('boolean' === typeof v) {
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
if ('string' === typeof v || 'number' === typeof v || 'boolean' === typeof v) {
memo.push({
'_name': 'Parameter',
'_attributes': {
'name': k
},
'_content': v
});
}
break;
}
return memo;
}, []);
if (parameters.length) output.unshift(
'<Parameters>\n' +
parameters.join('\n') +
'\n</Parameters>\n'
);

var properties = _(map_properties).map(function(v) { return ' ' + v; }).join('');

output.unshift(
'<?xml version="1.0" ' +
'encoding="utf-8"?>\n' +
'<!DOCTYPE Map[]>\n' +
'<Map' + properties +'>\n');
output.push('</Map>');
if (parameters.length) {
output.unshift({
'_name': 'Parameters',
'_content': parameters
});
}

// issue warnings for definitions that do not match layers
_.forEach(definitions, function (v) {
Expand All @@ -351,7 +368,22 @@ carto.Renderer.prototype.render = function render(m) {
}
});

output = output.join('\n');
output = {
'_name': 'Map',
'_attributes': map_properties,
'_content': output
};

switch (env.outputFormat) {
case 'json':
output = JSON.stringify(output, null, 2);
break;
case 'mapnik':
default:
output = '<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE Map[]>\n' + util.jsonToXML(output);
break;
}

}
else {
output = null;
Expand Down Expand Up @@ -548,7 +580,7 @@ function getMapProperties(m, definitions, env) {
var symbolizers = carto.tree.Reference.data.symbolizers.map;

_(m).each(function(value, key) {
if (key in symbolizers) rules[key] = key + '="' + value + '"';
if (key in symbolizers) rules[key] = value;
});

definitions.filter(function(r) {
Expand All @@ -562,7 +594,7 @@ function getMapProperties(m, definitions, env) {
index: r.rules[i].index
});
}
rules[key] = r.rules[i].ev(env).toXML(env);
rules[key] = r.rules[i].ev(env).toObject(env)[key];
}
});
return rules;
Expand Down
Loading

0 comments on commit f37bd50

Please sign in to comment.