carto/lib/mess/renderer.js

349 lines
12 KiB
JavaScript
Raw Normal View History

2011-01-05 04:15:06 +08:00
var path = require('path'),
fs = require('fs'),
External = require('./external'),
2011-01-06 03:23:28 +08:00
Step = require('step'),
2011-01-05 04:15:06 +08:00
_ = require('underscore')._,
2011-01-18 02:19:14 +08:00
sys = require('sys'),
mess = require('mess');
2011-01-05 04:15:06 +08:00
require.paths.unshift(path.join(__dirname, '..', 'lib'));
2011-01-18 02:19:14 +08:00
/**
* Rendering circuitry for JSON map manifests.
*
* This is node-only for the time being.
*/
2011-01-05 04:15:06 +08:00
2011-01-05 07:29:00 +08:00
/**
* Convert a two-element-per-item Array
* into an object, for the purpose of checking membership
* and replacing stuff.
* @param {Array} list a list.
*/
2011-01-05 06:44:09 +08:00
var to = function(list) {
return list && list[0] && _.reduce(list, function(m, r) {
if (r && r.length > 1) {
m[r[0]] = r[1];
}
2011-01-05 06:44:09 +08:00
return m;
}, {});
};
2011-01-05 04:15:06 +08:00
mess.Renderer = function Renderer(env) {
2011-01-07 05:33:49 +08:00
var env = {
2011-01-15 07:51:36 +08:00
data_dir: env.data_dir || '/tmp/',
local_data_dir: env.local_data_dir || '',
validation_data: env.validation_data || false
2011-01-07 05:33:49 +08:00
};
2011-01-05 04:15:06 +08:00
return {
2011-01-18 02:19:14 +08:00
/**
* Keep a copy of passed-in environment variables
*/
2011-01-07 05:33:49 +08:00
env: env,
2011-01-05 07:37:40 +08:00
/**
* Wrapper for downloading externals: likely removable
*
2011-01-06 02:01:10 +08:00
* @param {String} uri the URI of the resource.
2011-01-05 07:37:40 +08:00
* @param {Function} callback
*/
2011-01-05 04:15:06 +08:00
grab: function(uri, callback) {
2011-01-07 05:33:49 +08:00
new(External)(this.env).process(uri, callback);
2011-01-05 04:15:06 +08:00
},
2011-01-05 04:15:06 +08:00
/**
2011-01-05 07:37:40 +08:00
* Download any file-based remote datsources.
*
* Usable as an entry point: does not expect any modification to
* the map object beyond JSON parsing.
*
2011-01-06 02:01:10 +08:00
* @param {Object} m map object.
2011-01-05 07:37:40 +08:00
* @param {Function} callback
2011-01-05 04:15:06 +08:00
*/
localizeExternals: function(m, callback) {
var that = this;
Step(
function() {
var group = this.group();
m.Layer.forEach(function(l) {
2011-01-05 04:15:06 +08:00
if (l.Datasource.file) {
that.grab(l.Datasource.file, group());
}
});
if (m.Layer.length == 0) group()();
2011-01-05 04:15:06 +08:00
},
function(err, results) {
2011-01-05 06:44:09 +08:00
var result_map = to(results);
m.Layer = _.map(_.filter(m.Layer,
function(l) {
return l.Datasource.file &&
result_map[l.Datasource.file];
}),
function(l) {
l.Datasource.file = result_map[l.Datasource.file];
return l;
}
);
2011-01-05 04:15:06 +08:00
callback(err, m);
}
);
},
2011-01-05 04:15:06 +08:00
/**
* Download any remote stylesheets
2011-01-05 07:37:40 +08:00
*
2011-01-06 02:01:10 +08:00
* @param {Object} m map object.
2011-01-05 07:37:40 +08:00
* @param {Function} callback
2011-01-05 04:15:06 +08:00
*/
localizeStyle: function(m, callback) {
var that = this;
Step(
function() {
var group = this.group();
m.Stylesheet.forEach(function(s) {
if (!s.id) {
that.grab(s, group());
}
2011-01-05 04:15:06 +08:00
});
group()();
2011-01-05 04:15:06 +08:00
},
function(err, results) {
2011-01-05 06:44:09 +08:00
var result_map = to(results);
for (s in m.Stylesheet) {
if (!m.Stylesheet[s].id) {
m.Stylesheet[s] = result_map[m.Stylesheet[s]];
}
2011-01-05 04:15:06 +08:00
}
callback(err, m);
}
);
},
2011-01-05 07:37:40 +08:00
/**
* Compile (already downloaded) styles with mess.js,
* calling callback with an array of [map object, [stylesheet objects]]
*
2011-01-06 02:01:10 +08:00
* Called with the results of localizeStyle or localizeExternals:
2011-01-05 07:37:40 +08:00
* expects not to handle downloading.
*
2011-01-06 02:01:10 +08:00
* @param {Object} m map object.
2011-01-05 07:37:40 +08:00
* @param {Function} callback
*/
2011-01-05 04:15:06 +08:00
style: function(m, callback) {
2011-01-15 07:51:36 +08:00
var that = this;
2011-01-05 04:15:06 +08:00
Step(
function() {
var group = this.group();
m.Stylesheet.forEach(function(s) {
if (s.id) {
group()(null, [s.id, s.data]);
} else {
fs.readFile(s, 'utf-8', function(err, data) {
group()(err, [s, data]);
});
}
2011-01-05 04:15:06 +08:00
});
},
function(e, results) {
var options = {},
group = this.group();
2011-01-05 06:44:09 +08:00
for (var i = 0, l = results.length; i < l; i++) {
2011-01-15 07:51:36 +08:00
new(mess.Parser)(_.extend(_.extend({
2011-01-05 04:15:06 +08:00
filename: s
2011-01-15 07:51:36 +08:00
}, that.env), this.env)).parse(results[i][1], function(err, tree) {
2011-01-05 04:15:06 +08:00
if (err) {
2011-01-06 03:23:28 +08:00
mess.writeError(err, options);
throw err;
2011-01-05 04:15:06 +08:00
} else {
try {
2011-01-05 06:44:09 +08:00
group()(err, [
results[i][0],
tree.toCSS({ compress: false }),
tree]);
2011-01-08 05:14:37 +08:00
return;
2011-01-05 04:15:06 +08:00
} catch (e) {
throw e;
return;
2011-01-05 04:15:06 +08:00
}
}
});
}
},
function(err, results) {
2011-01-08 05:14:37 +08:00
if (err) {
callback(err);
return;
}
2011-01-05 06:44:09 +08:00
var result_map = to(results);
for (s in m.Stylesheet) {
if (!m.Stylesheet[s].id) {
m.Stylesheet[s] = result_map[m.Stylesheet[s]];
} else {
m.Stylesheet[s] = result_map[m.Stylesheet[s].id];
}
2011-01-05 04:15:06 +08:00
}
callback(err, [m, results]);
2011-01-05 04:15:06 +08:00
}
);
},
2011-01-05 07:37:40 +08:00
/**
* Prepare full XML map output. Called with the results
* of this.style
*
2011-01-06 02:01:10 +08:00
* @param {Array} res array of [map object, stylesheets].
2011-01-05 07:37:40 +08:00
* @param {Function} callback
*/
template: function(err, res, callback) {
2011-01-08 04:19:23 +08:00
if (err) {
callback(err);
return;
}
var m = res[0],
stylesheets = res[1],
entities = _.template('<?xml version="1.0" ' +
2011-01-10 23:55:34 +08:00
'encoding="utf-8"?>\n' +
'<!DOCTYPE Map[\n' +
'<% for (ref in externals) { %>' +
' <!ENTITY <%= ref %> SYSTEM "<%= externals[ref] %>">\n' +
'<% } %>]>\n'),
styles = _.template('<StyleName><%= name %></StyleName>'),
2011-01-06 02:01:10 +08:00
layer = _.template('\n <Layer\n id="<%= id %>"\n' +
2011-01-10 23:55:34 +08:00
' name="<%= name %>"\n' +
' srs="<%= srs %>">\n' +
'<% for (s in styles) { %>' +
' <StyleName><%= styles[s] %></StyleName>\n' +
'<% } %>' +
' <Datasource>\n<% for (n in Datasource) { %>' +
' <Parameter name="<%= n %>"><%= Datasource[n] %>' +
'</Parameter>\n' +
'<% } %> </Datasource>\n' +
2011-01-10 23:55:34 +08:00
' </Layer>\n'),
2011-01-06 02:01:10 +08:00
stylesheet_tmpl = _.template(' <% for (i in Stylesheet) { %>' +
'<%= Stylesheet[i] %>\n' +
'<% } %>\n'),
// referencing externals in the body of the map
references = _.template('<% for (ref in externals) { %>' +
2011-01-10 23:55:34 +08:00
'&<%= ref %>;\n' +
'<% } %>'),
2011-01-06 02:01:10 +08:00
// for the Map[] doctype: XML entities to
// organize code
entity_list = { externals: [], symbols: [] };
2011-01-05 07:37:40 +08:00
/**
* Index a hash of the CSS rules in all stylesheets
* This is performance-critical: necessary to test
* at some point
*
* @param {Array} stylesheets
2011-01-06 02:01:10 +08:00
* @return {Array} list of selectors.
2011-01-05 07:37:40 +08:00
*/
var ruleHash = function(stylesheets) {
return _.flatten(_.map(stylesheets, function(s) {
return _.map(s[2].rules, function(r) {
2011-01-10 05:07:52 +08:00
if (r && r.selectors && r.selectors[0]._css) {
return r.selectors[0]._css;
}
});
}));
};
var findBackground = function(stylesheets) {
var b = '#FFFFFF';
_.each(stylesheets, function(s) {
_.each(s[2].rules, function(r) {
if (r && r.selectors && r.selectors[0]._css &&
r.selectors[0]._css === 'Map') {
var bg = _.detect(r.rules, function(rule) {
return rule.name == 'background-color';
});
if (bg) {
b = bg.value.toCSS({ compress: false });
}
}
});
});
return b;
};
2011-01-05 07:37:40 +08:00
/**
* Add styles to a layer object
*
2011-01-06 02:01:10 +08:00
* @param {Object} layer the layer object.
* @param {Array} stylesheets the results of ruleHash().
* @return Formalized layer definition with .styles = [].
2011-01-05 07:37:40 +08:00
*/
var styleIntersect = function(layer, stylesheets) {
2011-01-10 23:55:34 +08:00
var hash = ruleHash(stylesheets),
classes = _.map((layer['class'] || '').split(' '),
2011-01-10 23:55:34 +08:00
function(c) {
return 'class-' + c.trim();
}),
layer_id = 'id-' + layer.id;
layer.styles = [];
2011-01-15 07:51:36 +08:00
_.each(hash, function(h) {
if (h && (h.search(layer_id) != -1)) {
layer.styles.push(h);
2011-01-10 23:55:34 +08:00
}
});
2011-01-15 07:51:36 +08:00
_.each(hash, function(h) {
_.each(classes, function(c) {
if (h && (h.search(c) != -1)) {
layer.styles.push(c);
}
});
});
return layer;
};
var output = [entities(entity_list)];
2011-01-05 07:37:40 +08:00
// TODO: must change when background colors are available
output.push('<Map background-color="' +
findBackground(stylesheets)
+ '" srs="+proj=merc +a=6378137 +b=6378137 '
2011-01-10 23:55:34 +08:00
+ '+lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m '
+ '+nadgrids=@null +no_defs">\n');
2011-01-05 04:15:06 +08:00
// using array joins here: faster in IE7, unclear in general.
m.Layer.forEach(function(l) {
l = styleIntersect(l, stylesheets);
2011-01-05 04:15:06 +08:00
output.push(layer(l));
});
output.push(references(entity_list));
output.push(stylesheet_tmpl(m));
2011-01-05 06:35:21 +08:00
output.push('</Map>');
2011-01-05 04:15:06 +08:00
callback(null, output.join(''));
},
/**
* Prepare a JML document (given as a string) into a
* fully-localized XML file ready for Mapnik2 consumption
*
2011-01-06 02:01:10 +08:00
* @param {String} str the JSON file as a string.
* @param {Function} callback to be called with err, XML representation.
*/
2011-01-05 04:15:06 +08:00
render: function(str, callback) {
var m = JSON.parse(str),
that = this;
this.localizeExternals(m, function(err, res) {
that.localizeStyle(res, function(err, res) {
that.style(res, function(err, res) {
that.template(err, res, function(err, res) {
callback(err, res);
2011-01-05 04:15:06 +08:00
});
});
});
});
}
};
};
module.exports = mess;