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) {
|
2011-01-06 01:46:31 +08:00
|
|
|
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-06 01:46:31 +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();
|
2011-01-07 03:51:36 +08:00
|
|
|
m.Layer.forEach(function(l) {
|
2011-01-05 04:15:06 +08:00
|
|
|
if (l.Datasource.file) {
|
|
|
|
that.grab(l.Datasource.file, group());
|
|
|
|
}
|
|
|
|
});
|
2011-01-14 04:15:25 +08:00
|
|
|
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);
|
2011-01-14 07:11:31 +08:00
|
|
|
m.Layer = _.map(_.filter(m.Layer,
|
2011-01-18 03:46:22 +08:00
|
|
|
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-14 07:11:31 +08:00
|
|
|
);
|
2011-01-05 04:15:06 +08:00
|
|
|
callback(err, m);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
2011-01-06 01:46:31 +08:00
|
|
|
|
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();
|
2011-01-07 03:51:36 +08:00
|
|
|
m.Stylesheet.forEach(function(s) {
|
2011-01-06 01:46:31 +08:00
|
|
|
if (!s.id) {
|
|
|
|
that.grab(s, group());
|
|
|
|
}
|
2011-01-05 04:15:06 +08:00
|
|
|
});
|
2011-01-06 01:46:31 +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);
|
2011-01-07 03:51:36 +08:00
|
|
|
for (s in m.Stylesheet) {
|
|
|
|
if (!m.Stylesheet[s].id) {
|
|
|
|
m.Stylesheet[s] = result_map[m.Stylesheet[s]];
|
2011-01-06 01:46:31 +08:00
|
|
|
}
|
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();
|
2011-01-07 03:51:36 +08:00
|
|
|
m.Stylesheet.forEach(function(s) {
|
2011-01-06 01:46:31 +08:00
|
|
|
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-19 03:26:57 +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);
|
2011-01-08 04:15:18 +08:00
|
|
|
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],
|
2011-01-08 04:15:18 +08:00
|
|
|
tree.toCSS({ compress: false }),
|
2011-01-05 07:30:23 +08:00
|
|
|
tree]);
|
2011-01-08 05:14:37 +08:00
|
|
|
return;
|
2011-01-05 04:15:06 +08:00
|
|
|
} catch (e) {
|
2011-01-08 04:15:18 +08:00
|
|
|
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-08 04:15:18 +08:00
|
|
|
|
2011-01-05 06:44:09 +08:00
|
|
|
var result_map = to(results);
|
2011-01-07 03:51:36 +08:00
|
|
|
for (s in m.Stylesheet) {
|
|
|
|
if (!m.Stylesheet[s].id) {
|
|
|
|
m.Stylesheet[s] = result_map[m.Stylesheet[s]];
|
2011-01-06 01:46:31 +08:00
|
|
|
} else {
|
2011-01-07 03:51:36 +08:00
|
|
|
m.Stylesheet[s] = result_map[m.Stylesheet[s].id];
|
2011-01-06 01:46:31 +08:00
|
|
|
}
|
2011-01-05 04:15:06 +08:00
|
|
|
}
|
2011-01-05 07:30:23 +08:00
|
|
|
callback(err, [m, results]);
|
2011-01-05 04:15:06 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
2011-01-06 01:46:31 +08:00
|
|
|
|
2011-01-21 03:20:33 +08:00
|
|
|
split_symbolizers: function(definitions) {
|
|
|
|
var by_symbolizer = {};
|
|
|
|
for (var i = 0; i < definitions.length; i++) {
|
|
|
|
definitions[i].symbolizers().forEach(function(sym) {
|
|
|
|
if(!by_symbolizer[sym]) {
|
|
|
|
by_symbolizer[sym] = [];
|
|
|
|
}
|
|
|
|
by_symbolizer[sym].push(
|
|
|
|
definitions[i].filter_symbolizer(sym));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return by_symbolizer;
|
|
|
|
},
|
|
|
|
|
2011-01-21 04:06:23 +08:00
|
|
|
/**
|
|
|
|
* Pick the 'winners' - all elements that select
|
|
|
|
* properly
|
|
|
|
*/
|
|
|
|
|
2011-01-21 03:47:34 +08:00
|
|
|
process_chain: function(definitions) {
|
|
|
|
// definitions are ordered in specificity,
|
|
|
|
// high to low
|
|
|
|
//
|
|
|
|
// basically if 'this level' has
|
|
|
|
// a filter, then keep going, otherwise
|
|
|
|
// this is the final selector.
|
|
|
|
var filter_negations = [];
|
2011-01-21 04:06:23 +08:00
|
|
|
var winners = [];
|
|
|
|
var ancestors = [];
|
|
|
|
var below_threshold = false;
|
|
|
|
|
|
|
|
while (def = definitions.shift()) {
|
|
|
|
if (below_threshold) {
|
|
|
|
ancestors.push(def)
|
|
|
|
} else if (def.selector.specificity()[2] > 0) {
|
|
|
|
winners.push(def);
|
|
|
|
} else {
|
|
|
|
winners.push(def);
|
|
|
|
// nothing below this level will win
|
|
|
|
below_threshold = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// inherit properties from lower elements in
|
|
|
|
// order of specificity low to high.
|
|
|
|
ancestors.reverse();
|
|
|
|
|
|
|
|
for (var i = 0; i < ancestors.length; i++) {
|
|
|
|
for (var j = 0; j < winners.length; j++) {
|
|
|
|
winners[j].inherit_from(ancestors[i]);
|
|
|
|
}
|
2011-01-21 03:47:34 +08:00
|
|
|
}
|
2011-01-21 04:06:23 +08:00
|
|
|
|
|
|
|
return winners;
|
2011-01-21 03:47:34 +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
|
|
|
|
*/
|
2011-01-08 04:15:18 +08:00
|
|
|
template: function(err, res, callback) {
|
2011-01-08 04:19:23 +08:00
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2011-01-18 03:46:22 +08:00
|
|
|
|
2011-01-21 03:20:33 +08:00
|
|
|
var that = this,
|
|
|
|
m = res[0],
|
2011-01-05 07:30:23 +08:00
|
|
|
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'),
|
2011-01-05 07:30:23 +08:00
|
|
|
|
|
|
|
styles = _.template('<StyleName><%= name %></StyleName>'),
|
2011-01-06 02:01:10 +08:00
|
|
|
|
2011-01-16 00:28:43 +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' +
|
2011-01-16 00:28:43 +08:00
|
|
|
'<% } %> </Datasource>\n' +
|
2011-01-10 23:55:34 +08:00
|
|
|
' </Layer>\n'),
|
2011-01-06 02:01:10 +08:00
|
|
|
|
2011-01-06 01:46:31 +08:00
|
|
|
stylesheet_tmpl = _.template(' <% for (i in Stylesheet) { %>' +
|
|
|
|
'<%= Stylesheet[i] %>\n' +
|
|
|
|
'<% } %>\n'),
|
2011-01-05 07:30:23 +08:00
|
|
|
|
2011-01-06 01:46:31 +08:00
|
|
|
// 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-05 07:30:23 +08:00
|
|
|
|
2011-01-06 02:01:10 +08:00
|
|
|
// for the Map[] doctype: XML entities to
|
2011-01-06 01:46:31 +08:00
|
|
|
// organize code
|
|
|
|
entity_list = { externals: [], symbols: [] };
|
2011-01-05 07:30:23 +08:00
|
|
|
|
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
|
|
|
*/
|
2011-01-18 03:46:22 +08:00
|
|
|
|
2011-01-20 22:59:28 +08:00
|
|
|
// 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:30:23 +08:00
|
|
|
|
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
|
|
|
*/
|
2011-01-20 22:59:28 +08:00
|
|
|
|
2011-01-20 23:35:01 +08:00
|
|
|
var rulesets = _.flatten(stylesheets.map(function(rulesets) {
|
2011-01-20 22:59:28 +08:00
|
|
|
return rulesets[2].toMSS();
|
|
|
|
}));
|
2011-01-05 07:30:23 +08:00
|
|
|
|
2011-01-06 01:46:31 +08:00
|
|
|
var output = [entities(entity_list)];
|
2011-01-05 07:37:40 +08:00
|
|
|
// TODO: must change when background colors are available
|
2011-01-20 22:59:28 +08:00
|
|
|
output.push('<Map background-color="'
|
|
|
|
// findBackground(stylesheets)
|
2011-01-11 07:49:58 +08:00
|
|
|
+ '" 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 '
|
2011-01-11 07:49:58 +08:00
|
|
|
+ '+nadgrids=@null +no_defs">\n');
|
2011-01-20 22:59:28 +08:00
|
|
|
|
2011-01-20 23:35:01 +08:00
|
|
|
m.Layer.forEach(function(l) {
|
2011-01-20 22:59:28 +08:00
|
|
|
var classes = (l['class'] || '').split(/\s+/g);
|
|
|
|
|
2011-01-21 00:12:51 +08:00
|
|
|
var matching = rulesets.filter(function(ruleset) {
|
|
|
|
return ruleset.selector.matches(l.id, classes);
|
2011-01-20 22:59:28 +08:00
|
|
|
});
|
2011-01-21 03:20:33 +08:00
|
|
|
|
|
|
|
// matching is an array of matching selectors,
|
|
|
|
// in order from high specificity to low.
|
2011-01-21 03:47:34 +08:00
|
|
|
var by_symbolizer = that.split_symbolizers(matching);
|
|
|
|
|
|
|
|
var complete_chains = {};
|
|
|
|
for (sym in by_symbolizer) {
|
|
|
|
complete_chains[sym] = that.process_chain(by_symbolizer[sym]);
|
|
|
|
}
|
2011-01-20 23:35:01 +08:00
|
|
|
|
2011-01-20 23:51:19 +08:00
|
|
|
console.log('Layer ' + l.id);
|
2011-01-20 23:57:10 +08:00
|
|
|
console.log(sys.inspect(matching, false, null));
|
2011-01-21 00:57:28 +08:00
|
|
|
console.log('______');
|
2011-01-05 04:15:06 +08:00
|
|
|
});
|
2011-01-20 22:59:28 +08:00
|
|
|
|
|
|
|
// output.push(references(entity_list));
|
|
|
|
// output.push(stylesheet_tmpl(m));
|
2011-01-05 06:35:21 +08:00
|
|
|
output.push('</Map>');
|
2011-01-20 22:59:28 +08:00
|
|
|
|
2011-01-05 04:15:06 +08:00
|
|
|
callback(null, output.join(''));
|
|
|
|
},
|
2011-01-06 01:46:31 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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-06 01:46:31 +08:00
|
|
|
*/
|
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) {
|
2011-01-08 04:15:18 +08:00
|
|
|
that.template(err, res, function(err, res) {
|
2011-01-06 01:46:31 +08:00
|
|
|
callback(err, res);
|
2011-01-05 04:15:06 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = mess;
|