filters are now indexed by hash as opposed to throwing them in an array
This commit is contained in:
parent
d6f3f6b2f8
commit
d06ea5e4fc
@ -769,7 +769,7 @@ mess.Parser = function Parser(env) {
|
||||
selector: function() {
|
||||
var a, attachment;
|
||||
var e, elements = [];
|
||||
var f, filters = [];
|
||||
var f, filters = {};
|
||||
var z, zoom = tree.Zoom.all;
|
||||
var segments = 0, conditions = 0;
|
||||
|
||||
@ -786,7 +786,7 @@ mess.Parser = function Parser(env) {
|
||||
zoom &= z;
|
||||
conditions++;
|
||||
} else if (f) {
|
||||
filters.push(f);
|
||||
filters[f.id] = f;
|
||||
conditions++;
|
||||
} else if (attachment) {
|
||||
throw errorMessage('Encountered second attachment name', i - 1);
|
||||
|
@ -285,19 +285,19 @@ mess.Renderer = function Renderer(env) {
|
||||
var result = [];
|
||||
outer: for (var i = 0; i < selectors.length; i++) {
|
||||
for (var j = result.length - 1; j >= 0; j--) {
|
||||
if (result[j].includesFiltersOf(selectors[i])) {
|
||||
if (result[j].isIncludedIn(selectors[i])) {
|
||||
if (result[j].includesZoomOf(selectors[i])) {
|
||||
// The existing selector is already more generic than
|
||||
// this one.
|
||||
continue outer;
|
||||
}
|
||||
if (selectors[i].includesFiltersOf(result[j])) {
|
||||
if (selectors[i].isIncludedIn(result[j])) {
|
||||
// Merge selectors with the same filters but different zooms.
|
||||
result[j].zoom |= selectors[i].zoom;
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
if (selectors[i].includesFiltersOf(result[j]) &&
|
||||
if (selectors[i].isIncludedIn(result[j]) &&
|
||||
selectors[i].includesZoomOf(result[j])) {
|
||||
// This selector is more generic; remove the old one.
|
||||
result.splice(j, 1);
|
||||
@ -335,7 +335,6 @@ mess.Renderer = function Renderer(env) {
|
||||
//
|
||||
// if (shortcut) return definitions;
|
||||
|
||||
|
||||
for (var i = 0; i < definitions.length; i++) {
|
||||
if (!definitions[i].selector.sound()) {
|
||||
continue;
|
||||
@ -345,7 +344,6 @@ mess.Renderer = function Renderer(env) {
|
||||
var selectors = [ definitions[i].selector.clone() ];
|
||||
|
||||
// Add the negated previous selectors.
|
||||
|
||||
var mergeTime = +new Date;
|
||||
for (var j = 0; j < previousSelectors.length; j++) {
|
||||
for (var k = selectors.length - 1; k >= 0; k--) {
|
||||
@ -372,6 +370,7 @@ mess.Renderer = function Renderer(env) {
|
||||
for (var k = 0; k < selectors.length; k++) {
|
||||
var rule = definitions[i].clone();
|
||||
rule.selector = selectors[k];
|
||||
tree.Filter.simplify(rule.selector.filters);
|
||||
rules.push(rule);
|
||||
}
|
||||
}
|
||||
@ -429,7 +428,7 @@ mess.Renderer = function Renderer(env) {
|
||||
output = [];
|
||||
|
||||
if (err) {
|
||||
callback(err)
|
||||
callback(err)
|
||||
return;
|
||||
}
|
||||
|
||||
@ -500,7 +499,7 @@ mess.Renderer = function Renderer(env) {
|
||||
+ '+nadgrids=@null +no_defs">\n');
|
||||
|
||||
output.push('</Map>');
|
||||
|
||||
|
||||
env.errors.map(function(e) {
|
||||
if (!e.line && e.index && e.filename) {
|
||||
var matches = stylesheets.filter(function(s) {
|
||||
|
@ -5,6 +5,7 @@ tree.Filter = function Filter(key, op, val, index) {
|
||||
this.op = op;
|
||||
this.val = val;
|
||||
this.index = index;
|
||||
this.id = this.key + this.op + this.val;
|
||||
};
|
||||
|
||||
tree.Filter.prototype.toXML = function(env) {
|
||||
@ -21,7 +22,7 @@ tree.Filter.prototype.toXML = function(env) {
|
||||
};
|
||||
|
||||
tree.Filter.prototype.toString = function() {
|
||||
return '[' + this.key + ' ' + this.op.value + ' ' + this.val + ']';
|
||||
return '[' + this.id + ']';
|
||||
};
|
||||
|
||||
/**
|
||||
@ -60,16 +61,107 @@ tree.Filter.prototype.clone = function() {
|
||||
};
|
||||
|
||||
tree.Filter.sound = function(filters) {
|
||||
// shortcut for single-filter filters
|
||||
if (filters.length == 1) return true;
|
||||
for (var i = 0; i < filters.length; i++) {
|
||||
for (var j = i + 1; j < filters.length; j++) {
|
||||
if (filters[i].conflictsWith(filters[j])) {
|
||||
return false;
|
||||
}
|
||||
// Shortcut for single-filter filters.
|
||||
if (Object.keys(filters).length == 1) return true;
|
||||
|
||||
var byKey = {};
|
||||
var filter, key, value;
|
||||
|
||||
for (var id in filters) {
|
||||
filter = filters[id];
|
||||
key = filters[id].key;
|
||||
value = filters[id].val.toString();
|
||||
|
||||
if (!(key in byKey)) {
|
||||
byKey[key] = {};
|
||||
}
|
||||
|
||||
switch (filter.op.value) {
|
||||
case '=':
|
||||
if ('=' in byKey[key] && byKey[key]['='] != value) return false;
|
||||
if ('!=' in byKey[key] && byKey[key]['!='].indexOf(value) >= 0) return false;
|
||||
if ('>' in byKey[key] && byKey[key]['>'] <= value) return false;
|
||||
if ('<' in byKey[key] && byKey[key]['<'] >= value) return false;
|
||||
if ('>=' in byKey[key] && byKey[key]['>='] < value) return false;
|
||||
if ('<=' in byKey[key] && byKey[key]['<='] > value) return false;
|
||||
byKey[key]['='] = value;
|
||||
break;
|
||||
|
||||
case '!=':
|
||||
if ('=' in byKey[key] && byKey[key]['='] == value) return false;
|
||||
if (!('!=' in byKey[key])) byKey[key]['!='] = [];
|
||||
byKey[key]['!='].push(value);
|
||||
break;
|
||||
|
||||
case '>':
|
||||
if ('=' in byKey[key] && byKey[key]['='] <= value) return false;
|
||||
if ('<' in byKey[key] && byKey[key]['<'] <= value) return false;
|
||||
if ('<=' in byKey[key] && byKey[key]['<='] <= value) return false;
|
||||
byKey[key]['>'] = value;
|
||||
break;
|
||||
|
||||
case '<':
|
||||
if ('=' in byKey[key] && byKey[key]['='] >= value) return false;
|
||||
if ('>' in byKey[key] && byKey[key]['<'] >= value) return false;
|
||||
if ('>=' in byKey[key] && byKey[key]['<='] >= value) return false;
|
||||
byKey[key]['<'] = value;
|
||||
break;
|
||||
|
||||
case '>=':
|
||||
if ('=' in byKey[key] && byKey[key]['='] < value) return false;
|
||||
if ('<' in byKey[key] && byKey[key]['<'] < value) return false;
|
||||
if ('<=' in byKey[key] && byKey[key]['<='] < value) return false;
|
||||
byKey[key]['>='] = value;
|
||||
break;
|
||||
|
||||
case '<=':
|
||||
if ('=' in byKey[key] && byKey[key]['='] > value) return false;
|
||||
if ('<' in byKey[key] && byKey[key]['<'] > value) return false;
|
||||
if ('<=' in byKey[key] && byKey[key]['<='] > value) return false;
|
||||
byKey[key]['<='] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete byKey;
|
||||
return true;
|
||||
};
|
||||
|
||||
tree.Filter.simplify = function(filters) {
|
||||
// Shortcut for single-filter filters.
|
||||
if (Object.keys(filters).length == 1) return true;
|
||||
|
||||
var byKey = {};
|
||||
var filter, key, value;
|
||||
|
||||
for (var id in filters) {
|
||||
filter = filters[id];
|
||||
key = filters[id].key;
|
||||
value = filters[id].val.toString();
|
||||
|
||||
switch (filter.op.value) {
|
||||
case '=':
|
||||
if (byKey[key]) {
|
||||
for (var i in byKey[key]) {
|
||||
delete filters[i];
|
||||
}
|
||||
}
|
||||
byKey[key] = '=';
|
||||
break;
|
||||
|
||||
|
||||
case '!=':
|
||||
if (byKey[key] == '=') {
|
||||
delete filters[id];
|
||||
} else {
|
||||
if (!byKey[key]) byKey[key] = {};
|
||||
byKey[key][id] = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// TODO: >, <, >=, <=
|
||||
}
|
||||
}
|
||||
delete byKey;
|
||||
};
|
||||
|
||||
})(require('mess/tree'));
|
||||
|
@ -5,7 +5,7 @@ var assert = require('assert');
|
||||
tree.Selector = function Selector(elements, filters, zoom, attachment, conditions, index) {
|
||||
this.elements = elements || [];
|
||||
this.attachment = attachment || '__default__';
|
||||
this.filters = filters || [];
|
||||
this.filters = filters;
|
||||
this.zoom = zoom;
|
||||
this.conditions = conditions;
|
||||
this.index = index;
|
||||
@ -19,23 +19,32 @@ tree.Selector.prototype.debug = function() {
|
||||
|
||||
var str = "[" + num + "] " + this.elements.join('');
|
||||
if (this.attachment !== '__default__') str += '::' + this.attachment;
|
||||
str += ': ';
|
||||
str += tree.Zoom.toString(this.zoom);
|
||||
str += ' ' + this.filters.join(' ');
|
||||
str += ': Zoom[' + tree.Zoom.toString(this.zoom) + '] ';
|
||||
for (var id in this.filters) {
|
||||
str += this.filters[id] + ' ';
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
tree.Selector.prototype.hash = function() {
|
||||
var filters = Object.keys(this.filters);
|
||||
filters.sort();
|
||||
return (this.zoom & tree.Zoom.all) + '[' + filters.join('][') + ']';
|
||||
};
|
||||
|
||||
tree.Selector.prototype.sound = function() {
|
||||
if (!this.zoom) return false;
|
||||
if (this.filters.length && !tree.Filter.sound(this.filters)) return false;
|
||||
return true;
|
||||
return tree.Filter.sound(this.filters);
|
||||
};
|
||||
|
||||
tree.Selector.prototype.clone = function() {
|
||||
var obj = Object.create(Object.getPrototypeOf(this));
|
||||
obj.elements = this.elements.slice();
|
||||
obj.attachment = this.attachment;
|
||||
obj.filters = this.filters.slice();
|
||||
obj.filters = {};
|
||||
for (var id in this.filters) {
|
||||
obj.filters[id] = this.filters[id];
|
||||
}
|
||||
obj.zoom = this.zoom;
|
||||
obj.conditions = this.conditions;
|
||||
obj.index = this.index;
|
||||
@ -44,7 +53,9 @@ tree.Selector.prototype.clone = function() {
|
||||
|
||||
tree.Selector.prototype.merge = function(obj) {
|
||||
Array.prototype.push.apply(this.elements, obj.elements);
|
||||
Array.prototype.push.apply(this.filters, obj.filters);
|
||||
for (var id in obj.filters) {
|
||||
this.filters[id] = obj.filters[id];
|
||||
}
|
||||
if (obj.attachment) this.attachment = obj.attachment;
|
||||
this.index = obj.index;
|
||||
this.zoom &= obj.zoom;
|
||||
@ -58,100 +69,67 @@ tree.Selector.prototype.merge = function(obj) {
|
||||
* it splits up the selector and creates one for each condition.
|
||||
*/
|
||||
tree.Selector.prototype.mergeOrConditions = function(obj) {
|
||||
var result = [ this ];
|
||||
var result = {};
|
||||
|
||||
if (obj.filters.length) {
|
||||
if (Object.keys(obj.filters).length) {
|
||||
if (obj.zoom != tree.Zoom.all) {
|
||||
var clone = this.clone();
|
||||
clone.zoom &= obj.zoom;
|
||||
if (clone.sound()) result[clone.hash()] = clone;
|
||||
|
||||
var negatedZoom = ~obj.zoom;
|
||||
for (var i = 0; i < obj.filters.length; i++) {
|
||||
for (var id in obj.filters) {
|
||||
var selector = this.clone();
|
||||
selector.filters.push(obj.filters[i]);
|
||||
selector.filters[id] = obj.filters[id];
|
||||
selector.zoom &= negatedZoom;
|
||||
result.push(selector);
|
||||
if (selector.sound()) {
|
||||
result[selector.hash()] = selector;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No zoom conditions, just split the selectors for each filter in obj.
|
||||
for (var i = 1; i < obj.filters.length; i++) {
|
||||
for (var id in obj.filters) {
|
||||
var selector = this.clone();
|
||||
selector.filters.push(obj.filters[i]);
|
||||
result.push(selector);
|
||||
selector.filters[id] = obj.filters[id];
|
||||
if (selector.sound()) {
|
||||
result[selector.hash()] = selector;
|
||||
}
|
||||
}
|
||||
this.filters.push(obj.filters[0]);
|
||||
}
|
||||
} else {
|
||||
var clone = this.clone();
|
||||
clone.zoom &= obj.zoom;
|
||||
if (clone.sound()) result[clone.hash()] = clone;
|
||||
}
|
||||
this.zoom &= obj.zoom;
|
||||
|
||||
// Simplify the resulting sound selectors.
|
||||
result = result.filter(function(selector) {
|
||||
if (selector.sound()) {
|
||||
selector.simplifyFilters();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Check selectors for soundness before we return them.
|
||||
return result;
|
||||
var arr = [];
|
||||
for (var id in result) arr.push(result[id]);
|
||||
return arr;
|
||||
};
|
||||
|
||||
tree.Selector.prototype.simplifyFilters = function() {
|
||||
var simplified = [];
|
||||
var a, b;
|
||||
filters: for (var i = 0; i < this.filters.length; i++) {
|
||||
a = this.filters[i];
|
||||
// Operate from the back so that we don't run into renumbering problems
|
||||
// when deleting items from the array.
|
||||
for (var j = simplified.length - 1; j >= 0; j--) {
|
||||
b = simplified[j];
|
||||
if (b.key === a.key &&
|
||||
(b.op.value === '=' ||
|
||||
(b.op.value == a.op.value && b.val.value == a.val.value)
|
||||
)) {
|
||||
continue filters;
|
||||
}
|
||||
else if (a.op.value === '=' &&
|
||||
a.key === b.key) {
|
||||
simplified.splice(j, 1);
|
||||
}
|
||||
}
|
||||
simplified.push(a);
|
||||
}
|
||||
this.filters = simplified;
|
||||
};
|
||||
|
||||
tree.Selector.prototype.includesFiltersOf = function(other) {
|
||||
// Check that this is a strict subset of other.
|
||||
var a, b;
|
||||
outer: for (var i = 0; i < this.filters.length; i++) {
|
||||
a = this.filters[i];
|
||||
for (var j = 0; j < other.filters.length; j++) {
|
||||
b = other.filters[j];
|
||||
if (a.key === b.key &&
|
||||
a.op.value == b.op.value &&
|
||||
a.val.value == b.val.value) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
// Couldn't find the filter in other.
|
||||
return false;
|
||||
tree.Selector.prototype.isIncludedIn = function(other) {
|
||||
for (var id in this.filters) {
|
||||
if (!(id in other.filters)) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
tree.Selector.prototype.includesZoomOf = function(other) {
|
||||
return (this.zoom | other.zoom) == this.zoom;
|
||||
return (this.zoom | other.zoom) == other.zoom;
|
||||
};
|
||||
|
||||
tree.Selector.prototype.negate = function() {
|
||||
var obj = Object.create(Object.getPrototypeOf(this));
|
||||
obj.elements = this.elements.slice();
|
||||
obj.attachment = this.attachment;
|
||||
obj.index = this.index;
|
||||
|
||||
obj.filters = this.filters.map(function(filter) {
|
||||
return filter.negate();
|
||||
});
|
||||
obj.filters = {};
|
||||
for (var id in this.filters) {
|
||||
var negated = this.filters[id].negate();
|
||||
obj.filters[negated.id] = negated;
|
||||
}
|
||||
obj.zoom = ~this.zoom;
|
||||
obj.conditions = this.conditions
|
||||
obj.index = this.index;
|
||||
return obj;
|
||||
};
|
||||
|
||||
@ -194,9 +172,11 @@ tree.Selector.prototype.layers = function(env) {
|
||||
tree.Selector.prototype.combinedFilter = function(env) {
|
||||
var conditions = tree.Zoom.toXML(this.zoom);
|
||||
|
||||
var filters = this.filters.map(function(filter) {
|
||||
return '(' + filter.toXML(env).trim() + ')';
|
||||
});
|
||||
var filters = [];
|
||||
for (var id in this.filters) {
|
||||
filters.push('(' + this.filters[id].toXML(env).trim() + ')');
|
||||
}
|
||||
|
||||
if (filters.length) {
|
||||
conditions.push('<Filter>' + filters.join(' and ') + '</Filter>');
|
||||
}
|
||||
|
@ -44,8 +44,8 @@ helper.files('rendering', 'mml', function(file) {
|
||||
|
||||
beforeExit(function() {
|
||||
if (!completed && renderResult) {
|
||||
// console.warn(helper.stylize('renderer produced:', 'bold'));
|
||||
// console.warn(renderResult);
|
||||
console.warn(helper.stylize('renderer produced:', 'bold'));
|
||||
console.warn(renderResult);
|
||||
}
|
||||
assert.ok(completed, 'Rendering finished.');
|
||||
});
|
||||
|
@ -10,7 +10,12 @@
|
||||
<PolygonSymbolizer fill="#666666"/>
|
||||
</Rule>
|
||||
<Rule>
|
||||
|
||||
<MinScaleDenominator>12500000</MinScaleDenominator>
|
||||
<Filter>([NAME] = 'Canada')</Filter>
|
||||
<PolygonSymbolizer fill="#eeeeee"/>
|
||||
</Rule>
|
||||
<Rule>
|
||||
<MaxScaleDenominator>12500000</MaxScaleDenominator>
|
||||
<Filter>([NAME] = 'Canada')</Filter>
|
||||
<PolygonSymbolizer fill="#eeeeee"/>
|
||||
</Rule>
|
||||
|
@ -10,11 +10,13 @@ var helper = require('./support/helper');
|
||||
function cleanupItem(key, value) {
|
||||
if (!key) return value.map(function(item) { return item.selector });
|
||||
else if (key === 'elements') return value.map(function(item) { return item.value; });
|
||||
else if (key === 'filters' && !value.length) return undefined;
|
||||
else if (key === 'filters') {
|
||||
if (Object.keys(value).length) return Object.keys(value);
|
||||
}
|
||||
else if (key === 'attachment' && value === '__default__') return undefined;
|
||||
else if (key === 'index') return undefined;
|
||||
else if (key === 'zoom') {
|
||||
if (value == tree.Zoom.all) return void null;
|
||||
if (value == tree.Zoom.all) return undefined;
|
||||
else return tree.Zoom.toString(value);
|
||||
}
|
||||
else if (key === 'op') return value.value;
|
||||
|
@ -1,8 +1,8 @@
|
||||
[
|
||||
{"elements":["#countries",".foo",".bar",".baz"]},
|
||||
{"elements":["#countries",".countries",".two"]},
|
||||
{"elements":["#world"],"filters":[{"key":"NAME","op":"=","val":"United States"},{"key":"BLUE","op":"=","val":"red"}],"conditions":2},
|
||||
{"elements":["#world"],"filters":[{"key":"NAME","op":"=","val":"United States"}],"conditions":1},
|
||||
{"elements":["#world"],"filters":["NAME=United States","BLUE=red"],"conditions":2},
|
||||
{"elements":["#world"],"filters":["NAME=United States"],"conditions":1},
|
||||
{"elements":["#countries"]},
|
||||
{"elements":["#countries"]},
|
||||
{"elements":["#world"]},
|
||||
|
@ -1,8 +1,8 @@
|
||||
[
|
||||
{"elements":["#world","#countries"],"filters":[{"key":"NAME","op":"=","val":"United States"}],"conditions":1},
|
||||
{"elements":["#world"],"filters":[{"key":"NAME","op":"=","val":"United States"}],"zoom":"......XXXXXXXXXXXXXXXXX","conditions":2},
|
||||
{"elements":["#world"],"filters":[{"key":"NAME","op":"=","val":"United States"}],"conditions":1},
|
||||
{"elements":["#world"],"filters":[{"key":"NAME","op":"=","val":"Canada"}],"conditions":1},
|
||||
{"elements":["#world","#countries"],"filters":["NAME=United States"],"conditions":1},
|
||||
{"elements":["#world"],"filters":["NAME=United States"],"zoom":"......XXXXXXXXXXXXXXXXX","conditions":2},
|
||||
{"elements":["#world"],"filters":["NAME=United States"],"conditions":1},
|
||||
{"elements":["#world"],"filters":["NAME=Canada"],"conditions":1},
|
||||
{"elements":[],"zoom":"......XXXXXXXXXXXXXXXXX","conditions":1},
|
||||
{"elements":[],"filters":[{"key":"NAME","op":"=","val":"United States"}],"conditions":1}
|
||||
{"elements":[],"filters":["NAME=United States"],"conditions":1}
|
||||
]
|
Loading…
Reference in New Issue
Block a user