filters are now indexed by hash as opposed to throwing them in an array

This commit is contained in:
Konstantin Käfer 2011-01-27 20:50:53 -05:00
parent d6f3f6b2f8
commit d06ea5e4fc
9 changed files with 185 additions and 107 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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'));

View File

@ -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>');
}

View File

@ -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.');
});

View File

@ -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>

View File

@ -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;

View File

@ -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"]},

View File

@ -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}
]