modules/presets/preset.js (255 lines of code) (raw):
import { t } from '../util/locale';
import { osmAreaKeys } from '../osm/tags';
import { groupManager } from '../entities/group_manager';
import { utilArrayUniq, utilObjectOmit } from '../util';
export function presetPreset(id, preset, fields, addable, rawPresets) {
preset = Object.assign({}, preset); // shallow copy
preset.id = id;
preset.parentPresetID = function() {
var endIndex = preset.id.lastIndexOf('/');
if (endIndex < 0) return null;
return preset.id.substring(0, endIndex);
};
// For a preset without fields, use the fields of the parent preset.
// Replace {preset} placeholders with the fields of the specified presets.
function resolveFieldInheritance() {
// Skip `fields` for the keys which define the preset.
// These are usually `typeCombo` fields like `shop=*`
function shouldInheritFieldWithID(fieldID) {
var f = fields[fieldID];
if (f.key) {
if (preset.tags[f.key] !== undefined &&
// inherit anyway if multiple values are allowed or just a checkbox
f.type !== 'multiCombo' && f.type !== 'semiCombo' && f.type !== 'check') {
return false;
}
}
return true;
}
// returns an array of field IDs to inherit from the given presetID, if found
function inheritedFieldIDs(presetID, prop) {
if (!presetID) return null;
var inheritPreset = rawPresets[presetID];
if (!inheritPreset) return null;
var inheritFieldIDs = inheritPreset[prop] || [];
if (prop === 'fields') {
inheritFieldIDs = inheritFieldIDs.filter(shouldInheritFieldWithID);
}
return inheritFieldIDs;
}
['fields', 'moreFields'].forEach(function(prop) {
var fieldIDs = [];
if (preset[prop] && preset[prop].length) { // fields were defined
preset[prop].forEach(function(fieldID) {
var match = fieldID.match(/\{(.*)\}/);
if (match !== null) { // presetID wrapped in braces {}
var inheritIDs = inheritedFieldIDs(match[1], prop);
if (inheritIDs !== null) {
fieldIDs = fieldIDs.concat(inheritIDs);
} else {
/* eslint-disable no-console */
console.log('Cannot resolve presetID ' + match[0] +
' found in ' + preset.id + ' ' + prop);
/* eslint-enable no-console */
}
} else {
fieldIDs.push(fieldID); // no braces - just a normal field
}
});
} else { // no fields defined, so use the parent's if possible
fieldIDs = inheritedFieldIDs(preset.parentPresetID(), prop);
}
// resolve duplicate fields
fieldIDs = utilArrayUniq(fieldIDs);
// update this preset with the results
preset[prop] = fieldIDs;
// update the raw object to allow for multiple levels of inheritance
rawPresets[preset.id][prop] = fieldIDs;
});
}
if (rawPresets) {
resolveFieldInheritance();
}
preset.fields = (preset.fields || []).map(getFields);
preset.moreFields = (preset.moreFields || []).map(getFields);
function getFields(f) {
return fields[f];
}
preset.geometry = (preset.geometry || []);
addable = addable || false;
preset.matchGeometry = function(geometry) {
return preset.geometry.indexOf(geometry) >= 0;
};
preset.originalScore = preset.matchScore || 1;
preset.matchScore = function(entityTags) {
var tags = preset.tags;
var seen = {};
var score = 0;
var k;
// match on tags
for (k in tags) {
seen[k] = true;
if (entityTags[k] === tags[k]) {
score += preset.originalScore;
} else if (tags[k] === '*' && k in entityTags) {
score += preset.originalScore / 2;
} else {
return -1;
}
}
// boost score for additional matches in addTags - #6802
var addTags = preset.addTags;
for (k in addTags) {
if (!seen[k] && entityTags[k] === addTags[k]) {
score += preset.originalScore;
}
}
return score;
};
var _textCache = {};
preset.t = function(scope, options) {
var textID = 'presets.presets.' + id + '.' + scope;
if (_textCache[textID]) return _textCache[textID];
var text = t(textID, options);
_textCache[textID] = text;
return text;
};
preset.originalName = preset.name || '';
preset.name = function() {
if (preset.suggestion) {
var path = id.split('/');
path.pop(); // remove brand name
// NOTE: insert an en-dash, not a hypen (to avoid conflict with fr - nl names in Brussels etc)
return preset.originalName + ' – ' + t('presets.presets.' + path.join('/') + '.name');
}
return preset.t('name', { 'default': preset.originalName });
};
preset.originalTerms = (preset.terms || []).join();
preset.terms = function() {
return preset.t('terms', { 'default': preset.originalTerms }).toLowerCase().trim().split(/\s*,+\s*/);
};
preset.isFallback = function() {
var tagCount = Object.keys(preset.tags).length;
return tagCount === 0 || (tagCount === 1 && preset.tags.hasOwnProperty('area'));
};
preset.addable = function(val) {
if (!arguments.length) return addable;
addable = val;
return addable;
};
var reference = preset.reference || {};
preset.reference = function(geometry) {
// Lookup documentation on Wikidata...
var qid = preset.tags.wikidata || preset.tags['brand:wikidata'] || preset.tags['operator:wikidata'];
if (qid) {
return { qid: qid };
}
// Lookup documentation on OSM Wikibase...
var key = reference.key || Object.keys(utilObjectOmit(preset.tags, 'name'))[0];
var value = reference.value || preset.tags[key];
if (geometry === 'relation' && key === 'type') {
if (value in preset.tags) {
key = value;
value = preset.tags[key];
} else {
return { rtype: value };
}
}
if (value === '*') {
return { key: key };
} else {
return { key: key, value: value };
}
};
preset.removeTags = preset.removeTags || preset.addTags || preset.tags || {};
preset.unsetTags = function(tags, geometry) {
tags = utilObjectOmit(tags, Object.keys(preset.removeTags));
for (var f in preset.fields) {
var field = preset.fields[f];
if (field.matchGeometry(geometry) && field.default === tags[field.key]) {
delete tags[field.key];
}
}
delete tags.area;
return tags;
};
preset.addTags = preset.addTags || preset.tags || {};
preset.setTags = function(tags, geometry, skipFieldDefaults) {
var addTags = preset.addTags;
var k;
tags = Object.assign({}, tags); // shallow copy
for (k in addTags) {
if (addTags[k] === '*') {
tags[k] = 'yes';
} else {
tags[k] = addTags[k];
}
}
// Add area=yes if necessary.
// This is necessary if the geometry is already an area (e.g. user drew an area) AND any of:
// 1. chosen preset could be either an area or a line (`barrier=city_wall`)
// 2. chosen preset doesn't have a key in osmAreaKeys (`railway=station`)
if (!addTags.hasOwnProperty('area')) {
delete tags.area;
if (geometry === 'area') {
var needsAreaTag = true;
if (preset.geometry.indexOf('line') === -1) {
for (k in addTags) {
if (k in osmAreaKeys) {
needsAreaTag = false;
break;
}
}
}
if (needsAreaTag) {
tags.area = 'yes';
}
}
}
if (geometry && !skipFieldDefaults) {
for (var f in preset.fields) {
var field = preset.fields[f];
if (field.matchGeometry(geometry) && field.key && !tags[field.key] && field.default) {
tags[field.key] = field.default;
}
}
}
return tags;
};
function loadGroups() {
if (preset.suggestion) return {};
var groupsByGeometry = {};
var tags = preset.tags;
var allGroups = groupManager.groupsArray();
preset.geometry.forEach(function(geom) {
allGroups.forEach(function(group) {
if (!group.matchesTags(tags, geom)) return;
var score = 1;
/*
for (var key in tags) {
var subtags = {};
subtags[key] = tags[key];
if (!group.matchesTags(subtags, geom)) return;
score += 0.15;
}
*/
if (!groupsByGeometry[geom]) groupsByGeometry[geom] = [];
groupsByGeometry[geom].push({
group: group,
score: score
});
if (!group.scoredPresetsByGeometry[geom]) group.scoredPresetsByGeometry[geom] = [];
group.scoredPresetsByGeometry[geom].push({
preset: preset,
score: score
});
});
});
return groupsByGeometry;
}
if (!window.mocha) {
preset.groupsByGeometry = loadGroups();
}
// The geometry type to use when adding a new feature of this preset
preset.defaultAddGeometry = function(context, allowedGeometries) {
var geometry = preset.geometry.slice().filter(function(geom) {
if (allowedGeometries && allowedGeometries.indexOf(geom) === -1) return false;
if (context.features().isHiddenPreset(preset, geom)) return false;
return true;
});
var mostRecentAddGeom = context.storage('preset.' + preset.id + '.addGeom');
if (mostRecentAddGeom === 'vertex') mostRecentAddGeom = 'point';
if (mostRecentAddGeom && geometry.indexOf(mostRecentAddGeom) !== -1) {
return mostRecentAddGeom;
}
var vertexIndex = geometry.indexOf('vertex');
if (vertexIndex !== -1 && geometry.indexOf('point') !== -1) {
// both point and vertex allowed, just use point
geometry.splice(vertexIndex, 1);
}
if (geometry.length) {
return geometry[0];
}
return null;
};
preset.setMostRecentAddGeometry = function(context, geometry) {
if (preset.geometry.length > 1 &&
preset.geometry.indexOf(geometry) !== -1) {
context.storage('preset.' + preset.id + '.addGeom', geometry);
}
};
return preset;
}