modules/entities/group_manager.js (219 lines of code) (raw):
import { data } from '../../data/index';
import { t } from '../util/locale';
function entityGroup(id, group) {
group = Object.assign({}, group); // shallow copy
group.id = id;
group.scoredPresetsByGeometry = {};
group.scoredPresets = function() {
var allScoredPresets = [];
function addScoredPreset(scoredPresetForGeom) {
var existingScoredPresetIndex = allScoredPresets.findIndex(function(item) {
return item.preset === scoredPresetForGeom.preset;
});
if (existingScoredPresetIndex === -1) {
allScoredPresets.push(scoredPresetForGeom);
}
}
for (var geom in group.scoredPresetsByGeometry) {
group.scoredPresetsByGeometry[geom].forEach(addScoredPreset);
}
return allScoredPresets;
};
// returns the part of the `id` after the last slash
group.basicID = function() {
var index = group.id.lastIndexOf('/');
return index === -1 ? group.id : group.id.substring(index + 1);
};
group.localizedName = function() {
return group.name ? t('presets.groups.' + id + '.name') : null;
};
group.localizedDescription = function() {
return group.description ? t('presets.groups.' + id + '.description') : null;
};
group.toggleableMax = function() {
if (group.toggleable && typeof group.toggleable === 'object') return group.toggleable.maxShown;
return null;
};
// returns all tags specified by the given rule, regardless of positive or negative matching
function ruleTagsFor(rule) {
var _ruleTags = {};
function addTagsForRule(rule) {
for (var rulesKey in {any: true, all: true, none: true, notAll: true}) {
if (rule[rulesKey]) {
rule[rulesKey].forEach(addTagsForRule);
}
}
for (var tagsKey in {anyTags: true, allTags: true, notAnyTags: true}) {
if (rule[tagsKey]) {
var tagsObj = rule[tagsKey];
for (var key in tagsObj) {
var val = tagsObj[key];
if (typeof val === 'boolean') {
_ruleTags[key] = true;
} else if (typeof val === 'string') {
if (val === '*') _ruleTags[key] = true;
if (_ruleTags[key] === undefined) _ruleTags[key] = {};
if (typeof _ruleTags[key] === 'object') _ruleTags[key][val] = true;
} else {
for (var value in val) {
if (value === '*') _ruleTags[key] = true;
if (_ruleTags[key] === undefined) _ruleTags[key] = {};
if (typeof _ruleTags[key] === 'object') _ruleTags[key][value] = true;
}
}
}
}
}
}
addTagsForRule(rule);
return _ruleTags;
}
group.matchesTags = function(tags, geometry) {
var allGroups = groupManager.groups();
return matchesRule(group.matches);
function matchesTagComponent(ruleKey, tagComponent) {
var keysToCheck = [ruleKey];
if (ruleKey === '*') {
// check if any key has one of the tag values
keysToCheck = Object.keys(tags);
if (keysToCheck.length === 0) return false;
}
var val = tagComponent[ruleKey];
for (var i in keysToCheck) {
var key = keysToCheck[i];
var entityValue = tags[key];
if (typeof val === 'boolean') {
if (val && !entityValue) continue;
if (!val && entityValue) continue;
} else if (typeof val === 'string') {
if (!entityValue || (val !== entityValue && val !== '*')) continue;
} else {
// object like { "value1": boolean }
if (!entityValue || (!val['*'] && !val[entityValue])) continue;
if (val[entityValue] === false) continue;
}
return true;
}
return false;
}
function matchesRule(rule) {
if (rule.any) {
return rule.any.some(matchesRule);
} else if (rule.all) {
return rule.all.every(matchesRule);
} else if (rule.none) {
return !rule.none.some(matchesRule);
} else if (rule.notAll) {
return !rule.notAll.every(matchesRule);
}
if (rule.geometry) {
if (Array.isArray(rule.geometry)) {
if (rule.geometry.indexOf(geometry) === -1) return false;
} else {
if (rule.geometry !== geometry) return false;
}
}
var ruleKey;
if (rule.allTags) {
for (ruleKey in rule.allTags) {
if (!matchesTagComponent(ruleKey, rule.allTags)) return false;
}
}
if (rule.anyTags) {
var didMatch = false;
for (ruleKey in rule.anyTags) {
if (matchesTagComponent(ruleKey, rule.anyTags)) {
didMatch = true;
break;
}
}
if (!didMatch) return false;
}
if (rule.notAnyTags) {
for (ruleKey in rule.notAnyTags) {
if (matchesTagComponent(ruleKey, rule.notAnyTags)) return false;
}
}
if (rule.allowOtherTags === false) {
var ruleTags = ruleTagsFor(rule);
for (var key in tags) {
if (!ruleTags[key]) return false;
if (typeof ruleTags[key] === 'object') {
if (!ruleTags[key][tags[key]]) return false;
}
}
}
var otherGroupID, matchesOther;
if (rule.allGroups) {
for (otherGroupID in rule.allGroups) {
// avoid simple infinte recursion
if (otherGroupID === group.id) continue;
// skip erroneous group IDs
if (!allGroups[otherGroupID]) continue;
matchesOther = allGroups[otherGroupID].matchesTags(tags, geometry);
if ((rule.allGroups[otherGroupID] && !matchesOther) ||
(!rule.allGroups[otherGroupID] && matchesOther)) return false;
}
}
if (rule.anyGroups) {
var didMatchGroup = false;
for (otherGroupID in rule.anyGroups) {
// avoid simple infinte recursion
if (otherGroupID === group.id) continue;
// skip erroneous group IDs
if (!allGroups[otherGroupID]) continue;
matchesOther = allGroups[otherGroupID].matchesTags(tags, geometry);
if ((rule.anyGroups[otherGroupID] && matchesOther) ||
(!rule.anyGroups[otherGroupID] && !matchesOther)) {
didMatchGroup = true;
break;
}
}
if (!didMatchGroup) return false;
}
return true;
}
};
return group;
}
function entityGroupManager() {
var manager = {};
var _groups = {};
var _groupsArray = [];
var nestedRuleKeys = ['nearby', 'vertexOf'];
for (var id in data.groups) {
var group = entityGroup(id, data.groups[id]);
_groups[id] = group;
_groupsArray.push(group);
for (var i in nestedRuleKeys) {
var nestedRuleKey = nestedRuleKeys[i];
var nestedRule = group[nestedRuleKey];
if (!nestedRule || (typeof nestedRule) !== 'object') continue;
var nestedGroupID = id + '#' + nestedRuleKey;
var nestedGroup = entityGroup(nestedGroupID, {
matches: nestedRule
});
_groups[nestedGroupID] = nestedGroup;
_groupsArray.push(nestedGroup);
group[nestedRuleKey] = nestedGroupID;
}
}
manager.group = function(id) {
return _groups[id];
};
manager.groups = function() {
return _groups;
};
manager.groupsArray = function() {
return _groupsArray;
};
manager.toggleableGroups = _groupsArray.filter(function(group) {
return group.toggleable;
});
manager.groupsWithNearby = _groupsArray.filter(function(group) {
return group.nearby;
});
manager.groupsWithVertexOf = _groupsArray.filter(function(group) {
return group.vertexOf;
});
manager.clearCachedPresets = function() {
_groupsArray.forEach(function(group) {
group.scoredPresetsByGeometry = {};
});
};
return manager;
}
var groupManager = entityGroupManager();
// use a singleton
export { groupManager };