diff --git a/modules/services/taginfo.js b/modules/services/taginfo.js index 0df8cd83c..2cb12fbfc 100644 --- a/modules/services/taginfo.js +++ b/modules/services/taginfo.js @@ -9,12 +9,26 @@ var taginfo = {}, area: 'count_ways', line: 'count_ways' }, + tag_sort_members = { + point: 'count_node_members', + vertex: 'count_node_members', + area: 'count_way_members', + line: 'count_way_members', + relation: 'count_relation_members' + }, tag_filters = { point: 'nodes', vertex: 'nodes', area: 'ways', line: 'ways' }; + tag_members_fractions = { + point: 'count_node_members_fraction', + vertex: 'count_node_members_fraction', + area: 'count_way_members_fraction', + line: 'count_way_members_fraction', + relation: 'count_relation_members_fraction' + }; function sets(parameters, n, o) { @@ -32,6 +46,10 @@ function setSort(parameters) { return sets(parameters, 'sortname', tag_sorts); } +function setSortMembers(parameters) { + return sets(parameters, 'sortname', tag_sort_members); +} + function clean(parameters) { return _.omit(parameters, 'geometry', 'debounce'); } @@ -57,6 +75,14 @@ function filterValues(allowUpperCase) { }; } +function filterRoles(geometry) { + return function(d) { + if (d.role === '') return false; // exclude empty role + if (d.role.match(/[A-Z*;,]/) !== null) return false; // exclude uppercase letters and some punctuation + return parseFloat(d[tag_members_fractions[geometry]]) > 0.0; + }; +} + function valKey(d) { return { value: d.key, @@ -71,6 +97,13 @@ function valKeyDescription(d) { }; } +function roleKey(d) { + return { + value: d.role, + title: d.role + }; +} + // sort keys with ':' lower than keys without ':' function sortKeys(a, b) { return (a.key.indexOf(':') === -1 && b.key.indexOf(':') !== -1) ? -1 @@ -146,6 +179,23 @@ export function init() { }); }; + taginfo.roles = function(parameters, callback) { + var debounce = parameters.debounce; + var geometry = parameters.geometry; + parameters = clean(setSortMembers(parameters)); + request(endpoint + 'relation/roles?' + + qsString(_.extend({ + rp: 25, + sortname: 'count_all_members', + sortorder: 'desc', + page: 1 + }, parameters)), debounce, function(err, d) { + if (err) return callback(err); + var f = filterRoles(geometry); + callback(null, d.data.filter(f).map(roleKey)); + }); + }; + taginfo.docs = function(parameters, callback) { var debounce = parameters.debounce; parameters = clean(setSort(parameters)); diff --git a/modules/ui/raw_member_editor.js b/modules/ui/raw_member_editor.js index 6e8b4c053..5e5e45830 100644 --- a/modules/ui/raw_member_editor.js +++ b/modules/ui/raw_member_editor.js @@ -113,8 +113,51 @@ export function RawMemberEditor(context) { .on('click', deleteMember) .call(Icon('#operation-delete')); + if (context.taginfo()) { + $enter.each(bindTypeahead); + } + $items.exit() + .each(unbind) .remove(); + + function bindTypeahead(d) { + var row = d3.select(this), + role = row.selectAll('input.member-role'); + + function sort(value, data) { + var sameletter = [], + other = []; + for (var i = 0; i < data.length; i++) { + if (data[i].value.substring(0, value.length) === value) { + sameletter.push(data[i]); + } else { + other.push(data[i]); + } + } + return sameletter.concat(other); + } + + role.call(d3.combobox() + .fetcher(function(role, callback) { + var rtype = entity.tags.type; + context.taginfo().roles({ + debounce: true, + rtype: rtype || '', + geometry: context.geometry(d.member.id), + query: role + }, function(err, data) { + if (!err) callback(sort(role, data)); + }); + })); + } + + function unbind() { + var row = d3.select(this); + + row.selectAll('input.member-role') + .call(d3.combobox.off); + } } } diff --git a/modules/ui/raw_membership_editor.js b/modules/ui/raw_membership_editor.js index 0168e626c..d4cc1b25f 100644 --- a/modules/ui/raw_membership_editor.js +++ b/modules/ui/raw_membership_editor.js @@ -159,7 +159,12 @@ export function RawMembershipEditor(context) { .on('click', deleteMembership) .call(Icon('#operation-delete')); + if (context.taginfo()) { + $enter.each(bindTypeahead); + } + $items.exit() + .each(unbind) .remove(); if (showBlank) { @@ -213,6 +218,44 @@ export function RawMembershipEditor(context) { content($wrap); $list.selectAll('.member-entity-input').node().focus(); }); + + function bindTypeahead(d) { + var row = d3.select(this), + role = row.selectAll('input.member-role'); + + function sort(value, data) { + var sameletter = [], + other = []; + for (var i = 0; i < data.length; i++) { + if (data[i].value.substring(0, value.length) === value) { + sameletter.push(data[i]); + } else { + other.push(data[i]); + } + } + return sameletter.concat(other); + } + + role.call(d3.combobox() + .fetcher(function(role, callback) { + var rtype = d.relation.tags.type; + context.taginfo().roles({ + debounce: true, + rtype: rtype || '', + geometry: context.geometry(id), + query: role + }, function(err, data) { + if (!err) callback(sort(role, data)); + }); + })); + } + + function unbind() { + var row = d3.select(this); + + row.selectAll('input.member-role') + .call(d3.combobox.off); + } } }