Files
iD/modules/services/taginfo.js
Bryan Housel 053074d076 Export live binding for services in iD.services, fix init/reset
(closes #3324)

Previously we allowed devs to swap out services that they didn't need.
This became difficult now that ES6 exports are immutable bindings.
But we can wrap the immutable bindings themselves in a live object,
to get back the flexibility that we used to have.

This change also drops the `taginfo` accessor on Context, since devs who want
to swap out taginfo service with something else can now do so through the live
binding.  `iD.services.taginfo = myTaginfo()`
2016-10-14 10:38:09 -04:00

272 lines
7.2 KiB
JavaScript

import * as d3 from 'd3';
import _ from 'lodash';
import { utilQsString } from '../util/index';
var endpoint = 'https://taginfo.openstreetmap.org/api/4/',
taginfoCache = {},
tag_sorts = {
point: 'count_nodes',
vertex: 'count_nodes',
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) {
if (parameters.geometry && o[parameters.geometry]) {
parameters[n] = o[parameters.geometry];
}
return parameters;
}
function setFilter(parameters) {
return sets(parameters, 'filter', tag_filters);
}
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');
}
function filterKeys(type) {
var count_type = type ? 'count_' + type : 'count_all';
return function(d) {
return parseFloat(d[count_type]) > 2500 || d.in_wiki;
};
}
function filterMultikeys() {
return function(d) {
return (d.key.match(/:/g) || []).length === 1; // exactly one ':'
};
}
function filterValues(allowUpperCase) {
return function(d) {
if (d.value.match(/[;,]/) !== null) return false; // exclude some punctuation
if (!allowUpperCase && d.value.match(/[A-Z*]/) !== null) return false; // exclude uppercase letters
return parseFloat(d.fraction) > 0.0 || d.in_wiki;
};
}
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,
title: d.key
};
}
function valKeyDescription(d) {
return {
value: d.value,
title: d.description || d.value
};
}
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
: (a.key.indexOf(':') !== -1 && b.key.indexOf(':') === -1) ? 1
: 0;
}
var debounced = _.debounce(d3.json, 100, true);
function request(url, debounce, callback) {
if (taginfoCache[url]) {
callback(null, taginfoCache[url]);
} else if (debounce) {
debounced(url, done);
} else {
d3.json(url, done);
}
function done(err, data) {
if (!err) {
taginfoCache[url] = data;
}
callback(err, data);
}
}
export default {
init: function() { taginfoCache = {}; },
reset: function() { taginfoCache = {}; },
keys: function(parameters, callback) {
var debounce = parameters.debounce;
parameters = clean(setSort(parameters));
request(endpoint + 'keys/all?' +
utilQsString(_.extend({
rp: 10,
sortname: 'count_all',
sortorder: 'desc',
page: 1
}, parameters)), debounce, function(err, d) {
if (err) {
callback(err);
} else {
var f = filterKeys(parameters.filter);
callback(null, d.data.filter(f).sort(sortKeys).map(valKey));
}
}
);
},
multikeys: function(parameters, callback) {
var debounce = parameters.debounce;
parameters = clean(setSort(parameters));
request(endpoint + 'keys/all?' +
utilQsString(_.extend({
rp: 25,
sortname: 'count_all',
sortorder: 'desc',
page: 1
}, parameters)), debounce, function(err, d) {
if (err) {
callback(err);
} else {
var f = filterMultikeys();
callback(null, d.data.filter(f).map(valKey));
}
}
);
},
values: function(parameters, callback) {
var debounce = parameters.debounce;
parameters = clean(setSort(setFilter(parameters)));
request(endpoint + 'key/values?' +
utilQsString(_.extend({
rp: 25,
sortname: 'count_all',
sortorder: 'desc',
page: 1
}, parameters)), debounce, function(err, d) {
if (err) {
callback(err);
} else {
// In most cases we prefer taginfo value results with lowercase letters.
// A few OSM keys expect values to contain uppercase values (see #3377).
// This is not an exhaustive list (e.g. `name` also has uppercase values)
// but these are the fields where taginfo value lookup is most useful.
var re = /network|taxon|genus|species/;
var allowUpperCase = (parameters.key.match(re) !== null);
var f = filterValues(allowUpperCase);
callback(null, d.data.filter(f).map(valKeyDescription));
}
}
);
},
roles: function(parameters, callback) {
var debounce = parameters.debounce;
var geometry = parameters.geometry;
parameters = clean(setSortMembers(parameters));
request(endpoint + 'relation/roles?' +
utilQsString(_.extend({
rp: 25,
sortname: 'count_all_members',
sortorder: 'desc',
page: 1
}, parameters)), debounce, function(err, d) {
if (err) {
callback(err);
} else {
var f = filterRoles(geometry);
callback(null, d.data.filter(f).map(roleKey));
}
}
);
},
docs: function(parameters, callback) {
var debounce = parameters.debounce;
parameters = clean(setSort(parameters));
var path = 'key/wiki_pages?';
if (parameters.value) path = 'tag/wiki_pages?';
else if (parameters.rtype) path = 'relation/wiki_pages?';
request(endpoint + path + utilQsString(parameters), debounce, function(err, d) {
if (err) {
callback(err);
} else {
callback(null, d.data);
}
});
},
endpoint: function(_) {
if (!arguments.length) return endpoint;
endpoint = _;
return this;
}
};