diff --git a/Makefile b/Makefile
index 70f5d558e..66df62a45 100644
--- a/Makefile
+++ b/Makefile
@@ -44,30 +44,43 @@ $(BUILDJS_TARGETS): $(BUILDJS_SOURCES) build.js
MODULE_TARGETS = \
js/lib/id/actions.js \
+ js/lib/id/geo.js \
js/lib/id/modes.js \
js/lib/id/presets.js \
+ js/lib/id/services.js \
js/lib/id/util.js \
- js/lib/id/validations.js \
js/lib/id/geo.js \
- js/lib/id/operations.js
+ js/lib/id/operations.js \
+ js/lib/id/validations.js
-js/lib/id/actions.js: modules/
- node_modules/.bin/rollup -f umd -n iD.actions modules/actions/index.js --no-strict > $@
+js/lib/id/actions.js: $(shell find modules/actions -type f)
+ @rm -f $@
+ node_modules/.bin/rollup -f umd -n iD.actions modules/actions/index.js --no-strict -o $@
-js/lib/id/modes.js: modules/
- node_modules/.bin/rollup -f umd -n iD.modes modules/modes/index.js --no-strict > $@
+js/lib/id/geo.js: $(shell find modules/geo -type f)
+ @rm -f $@
+ node_modules/.bin/rollup -f umd -n iD.geo modules/geo/index.js --no-strict -o $@
-js/lib/id/presets.js: modules/
- node_modules/.bin/rollup -f umd -n iD.presets modules/presets/index.js --no-strict > $@
+js/lib/id/modes.js: $(shell find modules/modes -type f)
+ @rm -f $@
+ node_modules/.bin/rollup -f umd -n iD.modes modules/modes/index.js --no-strict -o $@
-js/lib/id/util.js: modules/
- node_modules/.bin/rollup -f umd -n iD.util modules/util/index.js --no-strict > $@
+js/lib/id/presets.js: $(shell find modules/presets -type f)
+ @rm -f $@
+ node_modules/.bin/rollup -f umd -n iD.presets modules/presets/index.js --no-strict -o $@
-js/lib/id/validations.js: modules/
- node_modules/.bin/rollup -f umd -n iD.validations modules/validations/index.js --no-strict > $@
+js/lib/id/services.js: $(shell find modules/services -type f)
+ @rm -f $@
+ node_modules/.bin/rollup -f umd -n iD.services modules/services/index.js --no-strict -o $@
+
+js/lib/id/util.js: $(shell find modules/util -type f)
+ @rm -f $@
+ node_modules/.bin/rollup -f umd -n iD.util modules/util/index.js --no-strict -o $@
+
+js/lib/id/validations.js: $(shell find modules/validations -type f)
+ @rm -f $@
+ node_modules/.bin/rollup -f umd -n iD.validations modules/validations/index.js --no-strict -o $@
-js/lib/id/geo.js: modules/
- node_modules/.bin/rollup -f umd -n iD.geo modules/geo/index.js --no-strict > $@
js/lib/id/operations.js: modules/
node_modules/.bin/rollup -f umd -n iD.operations modules/operations/index.js --no-strict > $@
@@ -95,12 +108,6 @@ dist/iD.js: \
js/id/start.js \
js/id/id.js \
$(MODULE_TARGETS) \
- js/id/services.js \
- js/id/services/mapillary.js \
- js/id/services/nominatim.js \
- js/id/services/taginfo.js \
- js/id/services/wikidata.js \
- js/id/services/wikipedia.js \
js/id/behavior.js \
js/id/behavior/add_way.js \
js/id/behavior/breathe.js \
diff --git a/index.html b/index.html
index 16256a922..2e83560f9 100644
--- a/index.html
+++ b/index.html
@@ -37,17 +37,12 @@
+
-
-
-
-
-
-
-
+
diff --git a/js/id/modes.js b/js/id/modes.js
deleted file mode 100644
index bae5c1837..000000000
--- a/js/id/modes.js
+++ /dev/null
@@ -1 +0,0 @@
-iD.modes = {};
diff --git a/js/id/services.js b/js/id/services.js
deleted file mode 100644
index c11dc788f..000000000
--- a/js/id/services.js
+++ /dev/null
@@ -1 +0,0 @@
-iD.services = {};
diff --git a/js/lib/id/services.js b/js/lib/id/services.js
new file mode 100644
index 000000000..58810eb77
--- /dev/null
+++ b/js/lib/id/services.js
@@ -0,0 +1,720 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (factory((global.iD = global.iD || {}, global.iD.services = global.iD.services || {})));
+}(this, function (exports) { 'use strict';
+
+ function mapillary() {
+ var mapillary = {},
+ apibase = 'https://a.mapillary.com/v2/',
+ viewercss = 'https://npmcdn.com/mapillary-js@1.3.0/dist/mapillary-js.min.css',
+ viewerjs = 'https://npmcdn.com/mapillary-js@1.3.0/dist/mapillary-js.min.js',
+ clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi',
+ maxResults = 1000,
+ maxPages = 10,
+ tileZoom = 14,
+ dispatch = d3.dispatch('loadedImages', 'loadedSigns');
+
+
+ function loadSignStyles(context) {
+ d3.select('head').selectAll('#traffico')
+ .data([0])
+ .enter()
+ .append('link')
+ .attr('id', 'traffico')
+ .attr('rel', 'stylesheet')
+ .attr('href', context.asset('traffico/stylesheets/traffico.css'));
+ }
+
+ function loadSignDefs(context) {
+ if (iD.services.mapillary.sign_defs) return;
+ iD.services.mapillary.sign_defs = {};
+
+ _.each(['au', 'br', 'ca', 'de', 'us'], function(region) {
+ d3.json(context.asset('traffico/string-maps/' + region + '-map.json'), function(err, data) {
+ if (err) return;
+ if (region === 'de') region = 'eu';
+ iD.services.mapillary.sign_defs[region] = data;
+ });
+ });
+ }
+
+ function loadViewer() {
+ // mapillary-wrap
+ var wrap = d3.select('#content').selectAll('.mapillary-wrap')
+ .data([0]);
+
+ var enter = wrap.enter().append('div')
+ .attr('class', 'mapillary-wrap')
+ .classed('al', true) // 'al'=left, 'ar'=right
+ .classed('hidden', true);
+
+ enter.append('button')
+ .attr('class', 'thumb-hide')
+ .on('click', function () { mapillary.hideViewer(); })
+ .append('div')
+ .call(iD.svg.Icon('#icon-close'));
+
+ enter.append('div')
+ .attr('id', 'mly')
+ .attr('class', 'mly-wrapper')
+ .classed('active', false);
+
+ // mapillary-viewercss
+ d3.select('head').selectAll('#mapillary-viewercss')
+ .data([0])
+ .enter()
+ .append('link')
+ .attr('id', 'mapillary-viewercss')
+ .attr('rel', 'stylesheet')
+ .attr('href', viewercss);
+
+ // mapillary-viewerjs
+ d3.select('head').selectAll('#mapillary-viewerjs')
+ .data([0])
+ .enter()
+ .append('script')
+ .attr('id', 'mapillary-viewerjs')
+ .attr('src', viewerjs);
+ }
+
+ function initViewer(imageKey, context) {
+
+ function nodeChanged(d) {
+ var clicks = iD.services.mapillary.clicks;
+ var index = clicks.indexOf(d.key);
+ if (index > -1) { // nodechange initiated from clicking on a marker..
+ clicks.splice(index, 1);
+ } else { // nodechange initiated from the Mapillary viewer controls..
+ var loc = d.apiNavImIm ? [d.apiNavImIm.lon, d.apiNavImIm.lat] : [d.latLon.lon, d.latLon.lat];
+ context.map().centerEase(loc);
+ mapillary.setSelectedImage(d.key, false);
+ }
+ }
+
+ if (Mapillary && imageKey) {
+ var opts = {
+ baseImageSize: 320,
+ cover: false,
+ cache: true,
+ debug: false,
+ imagePlane: true,
+ loading: true,
+ sequence: true
+ };
+
+ var viewer = new Mapillary.Viewer('mly', clientId, imageKey, opts);
+ viewer.on('nodechanged', nodeChanged);
+ viewer.on('loadingchanged', mapillary.setViewerLoading);
+ iD.services.mapillary.viewer = viewer;
+ }
+ }
+
+ function abortRequest(i) {
+ i.abort();
+ }
+
+ function nearNullIsland(x, y, z) {
+ if (z >= 7) {
+ var center = Math.pow(2, z - 1),
+ width = Math.pow(2, z - 6),
+ min = center - (width / 2),
+ max = center + (width / 2) - 1;
+ return x >= min && x <= max && y >= min && y <= max;
+ }
+ return false;
+ }
+
+ function getTiles(projection, dimensions) {
+ var s = projection.scale() * 2 * Math.PI,
+ z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
+ ts = 256 * Math.pow(2, z - tileZoom),
+ origin = [
+ s / 2 - projection.translate()[0],
+ s / 2 - projection.translate()[1]];
+
+ return d3.geo.tile()
+ .scaleExtent([tileZoom, tileZoom])
+ .scale(s)
+ .size(dimensions)
+ .translate(projection.translate())()
+ .map(function(tile) {
+ var x = tile[0] * ts - origin[0],
+ y = tile[1] * ts - origin[1];
+
+ return {
+ id: tile.toString(),
+ extent: iD.geo.Extent(
+ projection.invert([x, y + ts]),
+ projection.invert([x + ts, y]))
+ };
+ });
+ }
+
+
+ function loadTiles(which, url, projection, dimensions) {
+ var tiles = getTiles(projection, dimensions).filter(function(t) {
+ var xyz = t.id.split(',');
+ return !nearNullIsland(xyz[0], xyz[1], xyz[2]);
+ });
+
+ _.filter(which.inflight, function(v, k) {
+ var wanted = _.find(tiles, function(tile) { return k === (tile.id + ',0'); });
+ if (!wanted) delete which.inflight[k];
+ return !wanted;
+ }).map(abortRequest);
+
+ tiles.forEach(function(tile) {
+ loadTilePage(which, url, tile, 0);
+ });
+ }
+
+ function loadTilePage(which, url, tile, page) {
+ var cache = iD.services.mapillary.cache[which],
+ id = tile.id + ',' + String(page),
+ rect = tile.extent.rectangle();
+
+ if (cache.loaded[id] || cache.inflight[id]) return;
+
+ cache.inflight[id] = d3.json(url +
+ iD.util.qsString({
+ geojson: 'true',
+ limit: maxResults,
+ page: page,
+ client_id: clientId,
+ min_lon: rect[0],
+ min_lat: rect[1],
+ max_lon: rect[2],
+ max_lat: rect[3]
+ }), function(err, data) {
+ cache.loaded[id] = true;
+ delete cache.inflight[id];
+ if (err || !data.features || !data.features.length) return;
+
+ var features = [],
+ nextPage = page + 1,
+ feature, loc, d;
+
+ for (var i = 0; i < data.features.length; i++) {
+ feature = data.features[i];
+ loc = feature.geometry.coordinates;
+ d = { key: feature.properties.key, loc: loc };
+ if (which === 'images') d.ca = feature.properties.ca;
+ if (which === 'signs') d.signs = feature.properties.rects;
+
+ features.push([loc[0], loc[1], loc[0], loc[1], d]);
+ }
+
+ cache.rtree.load(features);
+
+ if (which === 'images') dispatch.loadedImages();
+ if (which === 'signs') dispatch.loadedSigns();
+
+ if (data.features.length === maxResults && nextPage < maxPages) {
+ loadTilePage(which, url, tile, nextPage);
+ }
+ }
+ );
+ }
+
+ mapillary.loadImages = function(projection, dimensions) {
+ var url = apibase + 'search/im/geojson?';
+ loadTiles('images', url, projection, dimensions);
+ };
+
+ mapillary.loadSigns = function(context, projection, dimensions) {
+ var url = apibase + 'search/im/geojson/or?';
+ loadSignStyles(context);
+ loadSignDefs(context);
+ loadTiles('signs', url, projection, dimensions);
+ };
+
+ mapillary.loadViewer = function() {
+ loadViewer();
+ };
+
+
+ // partition viewport into `psize` x `psize` regions
+ function partitionViewport(psize, projection, dimensions) {
+ psize = psize || 16;
+ var cols = d3.range(0, dimensions[0], psize),
+ rows = d3.range(0, dimensions[1], psize),
+ partitions = [];
+
+ rows.forEach(function(y) {
+ cols.forEach(function(x) {
+ var min = [x, y + psize],
+ max = [x + psize, y];
+ partitions.push(
+ iD.geo.Extent(projection.invert(min), projection.invert(max)));
+ });
+ });
+
+ return partitions;
+ }
+
+ // no more than `limit` results per partition.
+ function searchLimited(psize, limit, projection, dimensions, rtree) {
+ limit = limit || 3;
+
+ var partitions = partitionViewport(psize, projection, dimensions);
+ return _.flatten(_.compact(_.map(partitions, function(extent) {
+ return rtree.search(extent.rectangle())
+ .slice(0, limit)
+ .map(function(d) { return d[4]; });
+ })));
+ }
+
+ mapillary.images = function(projection, dimensions) {
+ var psize = 16, limit = 3;
+ return searchLimited(psize, limit, projection, dimensions, iD.services.mapillary.cache.images.rtree);
+ };
+
+ mapillary.signs = function(projection, dimensions) {
+ var psize = 32, limit = 3;
+ return searchLimited(psize, limit, projection, dimensions, iD.services.mapillary.cache.signs.rtree);
+ };
+
+ mapillary.signsSupported = function() {
+ var detected = iD.detect();
+ return (!(detected.ie || detected.browser.toLowerCase() === 'safari'));
+ };
+
+ mapillary.signHTML = function(d) {
+ if (!iD.services.mapillary.sign_defs) return;
+
+ var detectionPackage = d.signs[0].package,
+ type = d.signs[0].type,
+ country = detectionPackage.split('_')[1];
+
+ return iD.services.mapillary.sign_defs[country][type];
+ };
+
+ mapillary.showViewer = function() {
+ d3.select('#content')
+ .selectAll('.mapillary-wrap')
+ .classed('hidden', false)
+ .selectAll('.mly-wrapper')
+ .classed('active', true);
+
+ return mapillary;
+ };
+
+ mapillary.hideViewer = function() {
+ d3.select('#content')
+ .selectAll('.mapillary-wrap')
+ .classed('hidden', true)
+ .selectAll('.mly-wrapper')
+ .classed('active', false);
+
+ d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
+ .classed('selected', false);
+
+ iD.services.mapillary.image = null;
+
+ return mapillary;
+ };
+
+ mapillary.setViewerLoading = function(loading) {
+ var canvas = d3.select('#content')
+ .selectAll('.mly-wrapper canvas');
+
+ if (canvas.empty()) return; // viewer not loaded yet
+
+ var cover = d3.select('#content')
+ .selectAll('.mly-wrapper .Cover');
+
+ cover.classed('CoverDone', !loading);
+
+ var button = cover.selectAll('.CoverButton')
+ .data(loading ? [0] : []);
+
+ button.enter()
+ .append('div')
+ .attr('class', 'CoverButton')
+ .append('div')
+ .attr('class', 'uil-ripple-css')
+ .append('div');
+
+ button.exit()
+ .remove();
+
+ return mapillary;
+ };
+
+ mapillary.updateViewer = function(imageKey, context) {
+ if (!iD.services.mapillary) return;
+ if (!imageKey) return;
+
+ if (!iD.services.mapillary.viewer) {
+ initViewer(imageKey, context);
+ } else {
+ iD.services.mapillary.viewer.moveToKey(imageKey);
+ }
+
+ return mapillary;
+ };
+
+ mapillary.getSelectedImage = function() {
+ if (!iD.services.mapillary) return null;
+ return iD.services.mapillary.image;
+ };
+
+ mapillary.setSelectedImage = function(imageKey, fromClick) {
+ if (!iD.services.mapillary) return null;
+
+ iD.services.mapillary.image = imageKey;
+ if (fromClick) {
+ iD.services.mapillary.clicks.push(imageKey);
+ }
+
+ d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
+ .classed('selected', function(d) { return d.key === imageKey; });
+
+ return mapillary;
+ };
+
+ mapillary.reset = function() {
+ var cache = iD.services.mapillary.cache;
+
+ if (cache) {
+ _.forEach(cache.images.inflight, abortRequest);
+ _.forEach(cache.signs.inflight, abortRequest);
+ }
+
+ iD.services.mapillary.cache = {
+ images: { inflight: {}, loaded: {}, rtree: rbush() },
+ signs: { inflight: {}, loaded: {}, rtree: rbush() }
+ };
+
+ iD.services.mapillary.image = null;
+ iD.services.mapillary.clicks = [];
+
+ return mapillary;
+ };
+
+
+ if (!iD.services.mapillary.cache) {
+ mapillary.reset();
+ }
+
+ return d3.rebind(mapillary, dispatch, 'on');
+ }
+
+ function nominatim() {
+ var nominatim = {},
+ endpoint = 'https://nominatim.openstreetmap.org/reverse?';
+
+
+ nominatim.countryCode = function(location, callback) {
+ var cache = iD.services.nominatim.cache,
+ countryCodes = cache.search([location[0], location[1], location[0], location[1]]);
+
+ if (countryCodes.length > 0)
+ return callback(null, countryCodes[0][4]);
+
+ d3.json(endpoint +
+ iD.util.qsString({
+ format: 'json',
+ addressdetails: 1,
+ lat: location[1],
+ lon: location[0]
+ }), function(err, result) {
+ if (err)
+ return callback(err);
+ else if (result && result.error)
+ return callback(result.error);
+
+ var extent = iD.geo.Extent(location).padByMeters(1000);
+
+ cache.insert(extent.rectangle().concat(result.address.country_code));
+
+ callback(null, result.address.country_code);
+ });
+ };
+
+ nominatim.reset = function() {
+ iD.services.nominatim.cache = rbush();
+ return this;
+ };
+
+ if (!iD.services.nominatim.cache) {
+ nominatim.reset();
+ }
+
+ return nominatim;
+ }
+
+ function taginfo() {
+ var taginfo = {},
+ endpoint = 'https://taginfo.openstreetmap.org/api/4/',
+ tag_sorts = {
+ point: 'count_nodes',
+ vertex: 'count_nodes',
+ area: 'count_ways',
+ line: 'count_ways'
+ },
+ tag_filters = {
+ point: 'nodes',
+ vertex: 'nodes',
+ area: 'ways',
+ line: 'ways'
+ };
+
+
+ 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 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() {
+ return function(d) {
+ if (d.value.match(/[A-Z*;,]/) !== null) return false; // exclude some punctuation, uppercase letters
+ return parseFloat(d.fraction) > 0.0 || d.in_wiki;
+ };
+ }
+
+ function valKey(d) {
+ return {
+ value: d.key,
+ title: d.key
+ };
+ }
+
+ function valKeyDescription(d) {
+ return {
+ value: d.value,
+ title: d.description || d.value
+ };
+ }
+
+ // 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) {
+ var cache = iD.services.taginfo.cache;
+
+ if (cache[url]) {
+ callback(null, cache[url]);
+ } else if (debounce) {
+ debounced(url, done);
+ } else {
+ d3.json(url, done);
+ }
+
+ function done(err, data) {
+ if (!err) cache[url] = data;
+ callback(err, data);
+ }
+ }
+
+ taginfo.keys = function(parameters, callback) {
+ var debounce = parameters.debounce;
+ parameters = clean(setSort(parameters));
+ request(endpoint + 'keys/all?' +
+ iD.util.qsString(_.extend({
+ rp: 10,
+ sortname: 'count_all',
+ sortorder: 'desc',
+ page: 1
+ }, parameters)), debounce, function(err, d) {
+ if (err) return callback(err);
+ var f = filterKeys(parameters.filter);
+ callback(null, d.data.filter(f).sort(sortKeys).map(valKey));
+ });
+ };
+
+ taginfo.multikeys = function(parameters, callback) {
+ var debounce = parameters.debounce;
+ parameters = clean(setSort(parameters));
+ request(endpoint + 'keys/all?' +
+ iD.util.qsString(_.extend({
+ rp: 25,
+ sortname: 'count_all',
+ sortorder: 'desc',
+ page: 1
+ }, parameters)), debounce, function(err, d) {
+ if (err) return callback(err);
+ var f = filterMultikeys();
+ callback(null, d.data.filter(f).map(valKey));
+ });
+ };
+
+ taginfo.values = function(parameters, callback) {
+ var debounce = parameters.debounce;
+ parameters = clean(setSort(setFilter(parameters)));
+ request(endpoint + 'key/values?' +
+ iD.util.qsString(_.extend({
+ rp: 25,
+ sortname: 'count_all',
+ sortorder: 'desc',
+ page: 1
+ }, parameters)), debounce, function(err, d) {
+ if (err) return callback(err);
+ var f = filterValues();
+ callback(null, d.data.filter(f).map(valKeyDescription));
+ });
+ };
+
+ taginfo.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 + iD.util.qsString(parameters), debounce, function(err, d) {
+ if (err) return callback(err);
+ callback(null, d.data);
+ });
+ };
+
+ taginfo.endpoint = function(_) {
+ if (!arguments.length) return endpoint;
+ endpoint = _;
+ return taginfo;
+ };
+
+ taginfo.reset = function() {
+ iD.services.taginfo.cache = {};
+ return taginfo;
+ };
+
+
+ if (!iD.services.taginfo.cache) {
+ taginfo.reset();
+ }
+
+ return taginfo;
+ }
+
+ function wikidata() {
+ var wikidata = {},
+ endpoint = 'https://www.wikidata.org/w/api.php?';
+
+
+ // Given a Wikipedia language and article title, return an array of
+ // corresponding Wikidata entities.
+ wikidata.itemsByTitle = function(lang, title, callback) {
+ lang = lang || 'en';
+ d3.jsonp(endpoint + iD.util.qsString({
+ action: 'wbgetentities',
+ format: 'json',
+ sites: lang.replace(/-/g, '_') + 'wiki',
+ titles: title,
+ languages: 'en', // shrink response by filtering to one language
+ callback: '{callback}'
+ }), function(data) {
+ callback(title, data.entities || {});
+ });
+ };
+
+ return wikidata;
+ }
+
+ function wikipedia() {
+ var wikipedia = {},
+ endpoint = 'https://en.wikipedia.org/w/api.php?';
+
+
+ wikipedia.search = function(lang, query, callback) {
+ lang = lang || 'en';
+ d3.jsonp(endpoint.replace('en', lang) +
+ iD.util.qsString({
+ action: 'query',
+ list: 'search',
+ srlimit: '10',
+ srinfo: 'suggestion',
+ format: 'json',
+ callback: '{callback}',
+ srsearch: query
+ }), function(data) {
+ if (!data.query) return;
+ callback(query, data.query.search.map(function(d) {
+ return d.title;
+ }));
+ });
+ };
+
+ wikipedia.suggestions = function(lang, query, callback) {
+ lang = lang || 'en';
+ d3.jsonp(endpoint.replace('en', lang) +
+ iD.util.qsString({
+ action: 'opensearch',
+ namespace: 0,
+ suggest: '',
+ format: 'json',
+ callback: '{callback}',
+ search: query
+ }), function(d) {
+ callback(d[0], d[1]);
+ });
+ };
+
+ wikipedia.translations = function(lang, title, callback) {
+ d3.jsonp(endpoint.replace('en', lang) +
+ iD.util.qsString({
+ action: 'query',
+ prop: 'langlinks',
+ format: 'json',
+ callback: '{callback}',
+ lllimit: 500,
+ titles: title
+ }), function(d) {
+ var list = d.query.pages[Object.keys(d.query.pages)[0]],
+ translations = {};
+ if (list && list.langlinks) {
+ list.langlinks.forEach(function(d) {
+ translations[d.lang] = d['*'];
+ });
+ callback(translations);
+ }
+ });
+ };
+
+ return wikipedia;
+ }
+
+ exports.mapillary = mapillary;
+ exports.nominatim = nominatim;
+ exports.taginfo = taginfo;
+ exports.wikidata = wikidata;
+ exports.wikipedia = wikipedia;
+
+ Object.defineProperty(exports, '__esModule', { value: true });
+
+}));
\ No newline at end of file
diff --git a/modules/services/index.js b/modules/services/index.js
new file mode 100644
index 000000000..44f5a6f08
--- /dev/null
+++ b/modules/services/index.js
@@ -0,0 +1,5 @@
+export { mapillary } from './mapillary';
+export { nominatim } from './nominatim';
+export { taginfo } from './taginfo';
+export { wikidata } from './wikidata';
+export { wikipedia } from './wikipedia';
diff --git a/js/id/services/mapillary.js b/modules/services/mapillary.js
similarity index 99%
rename from js/id/services/mapillary.js
rename to modules/services/mapillary.js
index 960714b59..ed313746d 100644
--- a/js/id/services/mapillary.js
+++ b/modules/services/mapillary.js
@@ -1,13 +1,13 @@
-iD.services.mapillary = function() {
+export function mapillary() {
var mapillary = {},
- dispatch = d3.dispatch('loadedImages', 'loadedSigns'),
apibase = 'https://a.mapillary.com/v2/',
viewercss = 'https://npmcdn.com/mapillary-js@1.3.0/dist/mapillary-js.min.css',
viewerjs = 'https://npmcdn.com/mapillary-js@1.3.0/dist/mapillary-js.min.js',
clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi',
maxResults = 1000,
maxPages = 10,
- tileZoom = 14;
+ tileZoom = 14,
+ dispatch = d3.dispatch('loadedImages', 'loadedSigns');
function loadSignStyles(context) {
@@ -393,4 +393,4 @@ iD.services.mapillary = function() {
}
return d3.rebind(mapillary, dispatch, 'on');
-};
+}
diff --git a/js/id/services/nominatim.js b/modules/services/nominatim.js
similarity index 94%
rename from js/id/services/nominatim.js
rename to modules/services/nominatim.js
index e57921331..987ceea8c 100644
--- a/js/id/services/nominatim.js
+++ b/modules/services/nominatim.js
@@ -1,4 +1,4 @@
-iD.services.nominatim = function() {
+export function nominatim() {
var nominatim = {},
endpoint = 'https://nominatim.openstreetmap.org/reverse?';
@@ -32,13 +32,13 @@ iD.services.nominatim = function() {
nominatim.reset = function() {
iD.services.nominatim.cache = rbush();
- return nominatim;
+ return this;
};
-
if (!iD.services.nominatim.cache) {
nominatim.reset();
}
return nominatim;
-};
+}
+
diff --git a/js/id/services/taginfo.js b/modules/services/taginfo.js
similarity index 99%
rename from js/id/services/taginfo.js
rename to modules/services/taginfo.js
index 76148c014..2e423ba52 100644
--- a/js/id/services/taginfo.js
+++ b/modules/services/taginfo.js
@@ -1,4 +1,4 @@
-iD.services.taginfo = function() {
+export function taginfo() {
var taginfo = {},
endpoint = 'https://taginfo.openstreetmap.org/api/4/',
tag_sorts = {
@@ -173,4 +173,4 @@ iD.services.taginfo = function() {
}
return taginfo;
-};
+}
diff --git a/js/id/services/wikidata.js b/modules/services/wikidata.js
similarity index 81%
rename from js/id/services/wikidata.js
rename to modules/services/wikidata.js
index 0718e2fdd..e8e682523 100644
--- a/js/id/services/wikidata.js
+++ b/modules/services/wikidata.js
@@ -1,10 +1,11 @@
-iD.services.wikidata = function() {
- var wiki = {},
+export function wikidata() {
+ var wikidata = {},
endpoint = 'https://www.wikidata.org/w/api.php?';
+
// Given a Wikipedia language and article title, return an array of
// corresponding Wikidata entities.
- wiki.itemsByTitle = function(lang, title, callback) {
+ wikidata.itemsByTitle = function(lang, title, callback) {
lang = lang || 'en';
d3.jsonp(endpoint + iD.util.qsString({
action: 'wbgetentities',
@@ -18,5 +19,5 @@ iD.services.wikidata = function() {
});
};
- return wiki;
-};
+ return wikidata;
+}
diff --git a/js/id/services/wikipedia.js b/modules/services/wikipedia.js
similarity index 86%
rename from js/id/services/wikipedia.js
rename to modules/services/wikipedia.js
index a33143a07..35334e6c8 100644
--- a/js/id/services/wikipedia.js
+++ b/modules/services/wikipedia.js
@@ -1,8 +1,9 @@
-iD.services.wikipedia = function() {
- var wiki = {},
+export function wikipedia() {
+ var wikipedia = {},
endpoint = 'https://en.wikipedia.org/w/api.php?';
- wiki.search = function(lang, query, callback) {
+
+ wikipedia.search = function(lang, query, callback) {
lang = lang || 'en';
d3.jsonp(endpoint.replace('en', lang) +
iD.util.qsString({
@@ -21,7 +22,7 @@ iD.services.wikipedia = function() {
});
};
- wiki.suggestions = function(lang, query, callback) {
+ wikipedia.suggestions = function(lang, query, callback) {
lang = lang || 'en';
d3.jsonp(endpoint.replace('en', lang) +
iD.util.qsString({
@@ -36,7 +37,7 @@ iD.services.wikipedia = function() {
});
};
- wiki.translations = function(lang, title, callback) {
+ wikipedia.translations = function(lang, title, callback) {
d3.jsonp(endpoint.replace('en', lang) +
iD.util.qsString({
action: 'query',
@@ -57,5 +58,5 @@ iD.services.wikipedia = function() {
});
};
- return wiki;
-};
+ return wikipedia;
+}
diff --git a/test/index.html b/test/index.html
index 01c41b4f3..4d0a833ad 100644
--- a/test/index.html
+++ b/test/index.html
@@ -42,20 +42,15 @@
+
+
-
-
-
-
-
-
-
diff --git a/test/rendering.html b/test/rendering.html
index f24097f7c..798427917 100644
--- a/test/rendering.html
+++ b/test/rendering.html
@@ -11,12 +11,10 @@
+
-
-
-