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()`
This commit is contained in:
Bryan Housel
2016-10-14 10:38:09 -04:00
parent b07bf8eaa2
commit 053074d076
24 changed files with 513 additions and 507 deletions
-15
View File
@@ -150,7 +150,6 @@ iD can use external presets exclusively or along with the default OpenStreetMap
var id = iD.Context(window)
.presets(customPresets)
.taginfo(iD.serviceTaginfo())
.imagery(iD.dataImagery);
```
@@ -165,7 +164,6 @@ Just like Presets, Imagery can be configured using the `context.imagery` accesso
var id = iD.Context(window)
.presets(customPresets)
.taginfo(iD.serviceTaginfo())
.imagery(customImagery);
```
@@ -173,19 +171,6 @@ var id = iD.Context(window)
The Imagery object should follow the structure defined by [editor-layer-index](https://github.com/osmlab/editor-layer-index/blob/gh-pages/schema.json)
### Taginfo
[Taginfo](http://taginfo.openstreetmap.org/) is a service that provides comprehensive documentation about the tags used in OpenStreetMap. iD uses Taginfo to display description and also autocomplete keys and values. This can be completely disabled by removing the `context.taginfo` accessor. To point iD to a different instance of Taginfo other than the default OpenStreetMap instance:
```js
var id = iD.Context(window)
.presets(customPresets)
.taginfo(iD.serviceTaginfo().endpoint('url'))
.imagery(customImagery);
```
### Minimum Editable Zoom
The minimum zoom at which iD enters the edit mode is configured using the `context.minEditableZoom()` accessor. The default value is 16. To change this initialise the iD context as:
+1 -2
View File
@@ -39,8 +39,7 @@
} else {
var id = iD.Context(window)
.presets(iD.dataPresets)
.imagery(iD.dataImagery)
.taginfo(iD.serviceTaginfo.init());
.imagery(iD.dataImagery);
id.ui()(document.getElementById('id-container'));
}
-1
View File
@@ -20,7 +20,6 @@
id = iD.Context(window)
.presets(iD.dataPresets)
.imagery(iD.dataImagery)
.taginfo(iD.serviceTaginfo.init())
.assetPath('dist/');
id.ui()(document.getElementById('id-container'));
+23 -23
View File
@@ -10,7 +10,7 @@ import { presetInit } from '../presets/init';
import { rendererBackground } from '../renderer/background';
import { rendererFeatures } from '../renderer/features';
import { rendererMap } from '../renderer/map';
import * as services from '../services/index';
import { services } from '../services/index';
import { uiInit } from '../ui/init';
import { utilDetect } from '../util/detect';
import { utilRebind } from '../util/rebind';
@@ -142,19 +142,6 @@ export function coreContext(root) {
if (history.hasChanges()) return t('save.unsaved_changes');
};
context.flush = function() {
context.debouncedSave.cancel();
connection.flush();
features.reset();
history.reset();
_.each(services, function(service) {
// TODO: fix access to services #3324
// var reset = service().reset;
// if (reset) reset(context);
});
return context;
};
/* Graph */
context.hasEntity = function(id) {
@@ -300,15 +287,6 @@ export function coreContext(root) {
};
/* Taginfo */
var taginfo;
context.taginfo = function(_) {
if (!arguments.length) return taginfo;
taginfo = _;
return context;
};
/* Assets */
var assetPath = '';
context.assetPath = function(_) {
@@ -355,6 +333,21 @@ export function coreContext(root) {
};
/* reset (aka flush) */
context.reset = context.flush = function() {
context.debouncedSave.cancel();
connection.flush();
features.reset();
history.reset();
_.each(services, function(service) {
if (typeof service.reset === 'function') {
service.reset(context);
}
});
return context;
};
/* Init */
context.version = '2.0.0-alpha.2';
@@ -407,5 +400,12 @@ export function coreContext(root) {
presets = presetInit();
_.each(services, function(service) {
if (typeof service.init === 'function') {
service.init(context);
}
});
return utilRebind(context, dispatch, 'on');
}
+11 -11
View File
@@ -1,13 +1,13 @@
import * as serviceMapillary from './mapillary';
import * as serviceNominatim from './nominatim';
import * as serviceTaginfo from './taginfo';
import * as serviceWikidata from './wikidata';
import * as serviceWikipedia from './wikipedia';
import serviceMapillary from './mapillary';
import serviceNominatim from './nominatim';
import serviceTaginfo from './taginfo';
import serviceWikidata from './wikidata';
import serviceWikipedia from './wikipedia';
export {
serviceMapillary,
serviceTaginfo,
serviceNominatim,
serviceWikidata,
serviceWikipedia
export var services = {
mapillary: serviceMapillary,
nominatim: serviceNominatim,
taginfo: serviceTaginfo,
wikidata: serviceWikidata,
wikipedia: serviceWikipedia
};
+183 -185
View File
@@ -10,15 +10,19 @@ import { svgIcon } from '../svg/index';
import { utilQsString } from '../util/index';
var mapillary = {},
apibase = 'https://a.mapillary.com/v2/',
var apibase = 'https://a.mapillary.com/v2/',
viewercss = 'https://unpkg.com/mapillary-js@1.3.0/dist/mapillary-js.min.css',
viewerjs = 'https://unpkg.com/mapillary-js@1.3.0/dist/mapillary-js.min.js',
clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi',
maxResults = 1000,
maxPages = 10,
tileZoom = 14,
dispatch = d3.dispatch('loadedImages', 'loadedSigns');
dispatch = d3.dispatch('loadedImages', 'loadedSigns'),
mapillaryCache,
mapillaryClicks,
mapillaryImage,
mapillarySignDefs,
mapillaryViewer;
function loadSignStyles(context) {
@@ -33,95 +37,19 @@ function loadSignStyles(context) {
function loadSignDefs(context) {
if (mapillary.sign_defs) return;
mapillary.sign_defs = {};
if (mapillarySignDefs) return;
mapillarySignDefs = {};
_.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';
mapillary.sign_defs[region] = data;
mapillarySignDefs[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(svgIcon('#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 = 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);
mapillary.viewer = viewer;
}
}
function abortRequest(i) {
i.abort();
}
@@ -186,7 +114,7 @@ function loadTiles(which, url, projection, dimensions) {
function loadTilePage(which, url, tile, page) {
var cache = mapillary.cache[which],
var cache = mapillaryCache[which],
id = tile.id + ',' + String(page),
rect = tile.extent.rectangle();
@@ -267,74 +195,137 @@ function searchLimited(psize, limit, projection, dimensions, rtree) {
}
// this function is only used by test cases
export function getMapillary() {
return mapillary;
}
export default {
init: function() {
if (!mapillaryCache) {
this.reset();
}
this.event = utilRebind(this, dispatch, 'on');
},
reset: function() {
var cache = mapillaryCache;
if (cache) {
if (cache.images && cache.images.inflight) {
_.forEach(cache.images.inflight, abortRequest);
}
if (cache.signs && cache.signs.inflight) {
_.forEach(cache.signs.inflight, abortRequest);
}
}
mapillaryCache = {
images: { inflight: {}, loaded: {}, rtree: rbush() },
signs: { inflight: {}, loaded: {}, rtree: rbush() }
};
mapillaryImage = null;
mapillaryClicks = [];
},
export function init() {
mapillary.loadImages = function(projection, dimensions) {
loadImages: function(projection, dimensions) {
var url = apibase + 'search/im/geojson?';
loadTiles('images', url, projection, dimensions);
};
},
mapillary.loadSigns = function(context, projection, dimensions) {
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();
};
loadViewer: function() {
var that = this;
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 () { that.hideViewer(); })
.append('div')
.call(svgIcon('#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);
},
mapillary.images = function(projection, dimensions) {
images: function(projection, dimensions) {
var psize = 16, limit = 3;
return searchLimited(psize, limit, projection, dimensions, mapillary.cache.images.rtree);
};
return searchLimited(psize, limit, projection, dimensions, mapillaryCache.images.rtree);
},
mapillary.signs = function(projection, dimensions) {
signs: function(projection, dimensions) {
var psize = 32, limit = 3;
return searchLimited(psize, limit, projection, dimensions, mapillary.cache.signs.rtree);
};
return searchLimited(psize, limit, projection, dimensions, mapillaryCache.signs.rtree);
},
mapillary.signsSupported = function() {
signsSupported: function() {
var detected = utilDetect();
return (!(detected.ie || detected.browser.toLowerCase() === 'safari'));
};
},
mapillary.signHTML = function(d) {
if (!mapillary.sign_defs) return;
signHTML: function(d) {
if (!mapillarySignDefs) return;
var detectionPackage = d.signs[0].package,
type = d.signs[0].type,
country = detectionPackage.split('_')[1];
return mapillary.sign_defs[country][type];
};
return mapillarySignDefs[country][type];
},
mapillary.showViewer = function() {
showViewer: function() {
d3.select('#content')
.selectAll('.mapillary-wrap')
.classed('hidden', false)
.selectAll('.mly-wrapper')
.classed('active', true);
return mapillary;
};
return this;
},
mapillary.hideViewer = function() {
hideViewer: function() {
d3.select('#content')
.selectAll('.mapillary-wrap')
.classed('hidden', true)
@@ -344,100 +335,107 @@ export function init() {
d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
.classed('selected', false);
mapillary.image = null;
return mapillary;
};
mapillaryImage = null;
return this;
},
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.exit()
.remove();
button.enter()
.append('div')
.attr('class', 'CoverButton')
.append('div')
.attr('class', 'uil-ripple-css')
.append('div');
return mapillary;
};
mapillary.updateViewer = function(imageKey, context) {
if (!mapillary) return;
updateViewer: function(imageKey, context) {
if (!imageKey) return;
if (!mapillary.viewer) {
initViewer(imageKey, context);
if (!mapillaryViewer) {
this.initViewer(imageKey, context);
} else {
mapillary.viewer.moveToKey(imageKey);
mapillaryViewer.moveToKey(imageKey);
}
return mapillary;
};
return this;
},
mapillary.getSelectedImage = function() {
if (!mapillary) return null;
return mapillary.image;
};
initViewer: function(imageKey, context) {
var that = this;
if (Mapillary && imageKey) {
var opts = {
baseImageSize: 320,
cover: false,
cache: true,
debug: false,
imagePlane: true,
loading: true,
sequence: true
};
mapillaryViewer = new Mapillary.Viewer('mly', clientId, imageKey, opts);
mapillaryViewer.on('nodechanged', nodeChanged);
mapillaryViewer.on('loadingchanged', loadingChanged);
}
function nodeChanged(node) {
var clicks = mapillaryClicks;
var index = clicks.indexOf(node.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 = node.apiNavImIm ? [node.apiNavImIm.lon, node.apiNavImIm.lat] : [node.latLon.lon, node.latLon.lat];
context.map().centerEase(loc);
that.selectedImage(node.key, false);
}
}
function loadingChanged(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.exit()
.remove();
button.enter()
.append('div')
.attr('class', 'CoverButton')
.append('div')
.attr('class', 'uil-ripple-css')
.append('div');
}
},
mapillary.setSelectedImage = function(imageKey, fromClick) {
if (!mapillary) return null;
selectedImage: function(imageKey, fromClick) {
if (!arguments.length) return mapillaryImage;
mapillary.image = imageKey;
mapillaryImage = imageKey;
if (fromClick) {
mapillary.clicks.push(imageKey);
mapillaryClicks.push(imageKey);
}
d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
.classed('selected', function(d) { return d.key === imageKey; });
return mapillary;
};
return this;
},
mapillary.reset = function() {
var cache = mapillary.cache;
if (cache) {
_.forEach(cache.images.inflight, abortRequest);
_.forEach(cache.signs.inflight, abortRequest);
}
mapillary.cache = {
images: { inflight: {}, loaded: {}, rtree: rbush() },
signs: { inflight: {}, loaded: {}, rtree: rbush() }
};
mapillary.image = null;
mapillary.clicks = [];
return mapillary;
};
cache: function(_) {
if (!arguments.length) return mapillaryCache;
mapillaryCache = _;
return this;
},
if (!mapillary.cache) {
mapillary.reset();
signDefs: function(_) {
if (!arguments.length) return mapillarySignDefs;
mapillarySignDefs = _;
return this;
}
mapillary.event = utilRebind(mapillary, dispatch, 'on');
return mapillary;
}
};
+39 -39
View File
@@ -4,45 +4,45 @@ import { geoExtent } from '../geo/index';
import { utilQsString } from '../util/index';
var endpoint, cache;
var endpoint = 'https://nominatim.openstreetmap.org/reverse?',
nominatimCache;
export function init() {
endpoint = 'https://nominatim.openstreetmap.org/reverse?';
if (!cache) {
reset();
export default {
init: function() { nominatimCache = rbush(); },
reset: function() { nominatimCache = rbush(); },
countryCode: function (location, callback) {
var countryCodes = nominatimCache.search(
{ minX: location[0], minY: location[1], maxX: location[0], maxY: location[1] }
);
if (countryCodes.length > 0) {
return callback(null, countryCodes[0].data);
}
d3.json(endpoint +
utilQsString({
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 = geoExtent(location).padByMeters(1000);
nominatimCache.insert(Object.assign(extent.bbox(),
{ data: result.address.country_code }
));
callback(null, result.address.country_code);
}
);
}
}
export function reset() {
cache = rbush();
}
export function countryCode(location, callback) {
var countryCodes = cache.search({
minX: location[0], minY: location[1], maxX: location[0], maxY: location[1]
});
if (countryCodes.length > 0)
return callback(null, countryCodes[0].data);
d3.json(endpoint +
utilQsString({
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 = geoExtent(location).padByMeters(1000);
cache.insert(Object.assign(extent.bbox(), { data: result.address.country_code }));
callback(null, result.address.country_code);
});
}
};
+74 -56
View File
@@ -3,8 +3,8 @@ import _ from 'lodash';
import { utilQsString } from '../util/index';
var taginfo = {},
endpoint = 'https://taginfo.openstreetmap.org/api/4/',
var endpoint = 'https://taginfo.openstreetmap.org/api/4/',
taginfoCache = {},
tag_sorts = {
point: 'count_nodes',
vertex: 'count_nodes',
@@ -130,10 +130,8 @@ var debounced = _.debounce(d3.json, 100, true);
function request(url, debounce, callback) {
var cache = taginfo.cache;
if (cache[url]) {
callback(null, cache[url]);
if (taginfoCache[url]) {
callback(null, taginfoCache[url]);
} else if (debounce) {
debounced(url, done);
} else {
@@ -141,14 +139,21 @@ function request(url, debounce, callback) {
}
function done(err, data) {
if (!err) cache[url] = data;
if (!err) {
taginfoCache[url] = data;
}
callback(err, data);
}
}
export function init() {
taginfo.keys = function(parameters, callback) {
export default {
init: function() { taginfoCache = {}; },
reset: function() { taginfoCache = {}; },
keys: function(parameters, callback) {
var debounce = parameters.debounce;
parameters = clean(setSort(parameters));
request(endpoint + 'keys/all?' +
@@ -158,13 +163,18 @@ export function init() {
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));
});
};
if (err) {
callback(err);
} else {
var f = filterKeys(parameters.filter);
callback(null, d.data.filter(f).sort(sortKeys).map(valKey));
}
}
);
},
taginfo.multikeys = function(parameters, callback) {
multikeys: function(parameters, callback) {
var debounce = parameters.debounce;
parameters = clean(setSort(parameters));
request(endpoint + 'keys/all?' +
@@ -174,13 +184,18 @@ export function init() {
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));
});
};
if (err) {
callback(err);
} else {
var f = filterMultikeys();
callback(null, d.data.filter(f).map(valKey));
}
}
);
},
taginfo.values = function(parameters, callback) {
values: function(parameters, callback) {
var debounce = parameters.debounce;
parameters = clean(setSort(setFilter(parameters)));
request(endpoint + 'key/values?' +
@@ -190,19 +205,24 @@ export function init() {
sortorder: 'desc',
page: 1
}, parameters)), debounce, function(err, d) {
if (err) return callback(err);
// 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));
});
};
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));
}
}
);
},
taginfo.roles = function(parameters, callback) {
roles: function(parameters, callback) {
var debounce = parameters.debounce;
var geometry = parameters.geometry;
parameters = clean(setSortMembers(parameters));
@@ -213,13 +233,18 @@ export function init() {
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));
});
};
if (err) {
callback(err);
} else {
var f = filterRoles(geometry);
callback(null, d.data.filter(f).map(roleKey));
}
}
);
},
taginfo.docs = function(parameters, callback) {
docs: function(parameters, callback) {
var debounce = parameters.debounce;
parameters = clean(setSort(parameters));
@@ -228,26 +253,19 @@ export function init() {
else if (parameters.rtype) path = 'relation/wiki_pages?';
request(endpoint + path + utilQsString(parameters), debounce, function(err, d) {
if (err) return callback(err);
callback(null, d.data);
if (err) {
callback(err);
} else {
callback(null, d.data);
}
});
};
},
taginfo.endpoint = function(_) {
endpoint: function(_) {
if (!arguments.length) return endpoint;
endpoint = _;
return taginfo;
};
taginfo.reset = function() {
taginfo.cache = {};
return taginfo;
};
if (!taginfo.cache) {
taginfo.reset();
return this;
}
return taginfo;
}
};
+8 -8
View File
@@ -2,15 +2,17 @@ import { jsonpRequest } from '../util/jsonp_request';
import { utilQsString } from '../util/index';
var wikidata = {},
endpoint = 'https://www.wikidata.org/w/api.php?';
var endpoint = 'https://www.wikidata.org/w/api.php?';
export default {
init: function() {},
reset: function() {},
export function init() {
// Given a Wikipedia language and article title, return an array of
// corresponding Wikidata entities.
wikidata.itemsByTitle = function(lang, title, callback) {
itemsByTitle: function(lang, title, callback) {
if (!title) {
callback('', {});
return;
@@ -31,8 +33,6 @@ export function init() {
callback(title, data.entities || {});
}
});
};
}
return wikidata;
}
};
+13 -12
View File
@@ -2,12 +2,15 @@ import { jsonpRequest } from '../util/jsonp_request';
import { utilQsString } from '../util/index';
var wikipedia = {},
endpoint = 'https://en.wikipedia.org/w/api.php?';
var endpoint = 'https://en.wikipedia.org/w/api.php?';
export default {
init: function() {},
reset: function() {},
export function init() {
wikipedia.search = function(lang, query, callback) {
search: function(lang, query, callback) {
if (!query) {
callback('', []);
return;
@@ -32,10 +35,10 @@ export function init() {
}
}
);
};
},
wikipedia.suggestions = function(lang, query, callback) {
suggestions: function(lang, query, callback) {
if (!query) {
callback('', []);
return;
@@ -58,10 +61,10 @@ export function init() {
}
}
);
};
},
wikipedia.translations = function(lang, title, callback) {
translations: function(lang, title, callback) {
if (!title) {
callback({});
return;
@@ -90,8 +93,6 @@ export function init() {
}
}
);
};
}
return wikipedia;
}
};
+6 -6
View File
@@ -2,7 +2,7 @@ import * as d3 from 'd3';
import _ from 'lodash';
import { svgPointTransform } from './point_transform';
import { utilGetDimensions, utilSetDimensions } from '../util/dimensions';
import { serviceMapillary } from '../services/index';
import { services } from '../services/index';
export function svgMapillaryImages(projection, context, dispatch) {
@@ -20,10 +20,10 @@ export function svgMapillaryImages(projection, context, dispatch) {
function getMapillary() {
if (serviceMapillary && !_mapillary) {
_mapillary = serviceMapillary.init();
if (services.mapillary && !_mapillary) {
_mapillary = services.mapillary;
_mapillary.event.on('loadedImages', debouncedRedraw);
} else if (!serviceMapillary && _mapillary) {
} else if (!services.mapillary && _mapillary) {
_mapillary = null;
}
@@ -81,7 +81,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
context.map().centerEase(d.loc);
mapillary
.setSelectedImage(d.key, true)
.selectedImage(d.key, true)
.updateViewer(d.key, context)
.showViewer();
}
@@ -97,7 +97,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
function update() {
var mapillary = getMapillary(),
data = (mapillary ? mapillary.images(projection, utilGetDimensions(layer)) : []),
imageKey = mapillary ? mapillary.getSelectedImage() : null;
imageKey = mapillary ? mapillary.selectedImage() : null;
var markers = layer.selectAll('.viewfield-group')
.data(data, function(d) { return d.key; });
+6 -6
View File
@@ -2,7 +2,7 @@ import * as d3 from 'd3';
import _ from 'lodash';
import { utilGetDimensions, utilSetDimensions } from '../util/dimensions';
import { svgPointTransform } from './point_transform';
import { serviceMapillary } from '../services/index';
import { services } from '../services/index';
export function svgMapillarySigns(projection, context, dispatch) {
@@ -20,10 +20,10 @@ export function svgMapillarySigns(projection, context, dispatch) {
function getMapillary() {
if (serviceMapillary && !_mapillary) {
_mapillary = serviceMapillary.init();
if (services.mapillary && !_mapillary) {
_mapillary = services.mapillary;
_mapillary.event.on('loadedSigns', debouncedRedraw);
} else if (!serviceMapillary && _mapillary) {
} else if (!services.mapillary && _mapillary) {
_mapillary = null;
}
return _mapillary;
@@ -60,7 +60,7 @@ export function svgMapillarySigns(projection, context, dispatch) {
context.map().centerEase(d.loc);
mapillary
.setSelectedImage(d.key, true)
.selectedImage(d.key, true)
.updateViewer(d.key, context)
.showViewer();
}
@@ -69,7 +69,7 @@ export function svgMapillarySigns(projection, context, dispatch) {
function update() {
var mapillary = getMapillary(),
data = (mapillary ? mapillary.signs(projection, utilGetDimensions(layer)) : []),
imageKey = mapillary ? mapillary.getSelectedImage() : null;
imageKey = mapillary ? mapillary.selectedImage() : null;
var signs = layer.selectAll('.icon-sign')
.data(data, function(d) { return d.key; });
+75 -70
View File
@@ -9,16 +9,17 @@ import {
geoSphericalDistance
} from '../../geo/index';
import { serviceNominatim } from '../../services/index';
import { services } from '../../services/index';
import { utilRebind } from '../../util/rebind';
import { utilGetSetValue } from '../../util/get_set_value';
export function uiFieldAddress(field, context) {
var dispatch = d3.dispatch('init', 'change'),
wrap,
entity,
isInitialized;
nominatim = services.nominatim,
wrap = d3.select(null),
isInitialized = false,
entity;
var widths = {
housenumber: 1/3,
@@ -114,6 +115,72 @@ export function uiFieldAddress(field, context) {
}
function initCallback(err, countryCode) {
if (err) return;
var addressFormat = _.find(dataAddressFormats, function (a) {
return a && a.countryCodes && _.includes(a.countryCodes, countryCode);
}) || _.first(dataAddressFormats);
function row(r) {
// Normalize widths.
var total = _.reduce(r, function(sum, field) {
return sum + (widths[field] || 0.5);
}, 0);
return r.map(function (field) {
return {
id: field,
width: (widths[field] || 0.5) / total
};
});
}
wrap.selectAll('div')
.data(addressFormat.format)
.enter()
.append('div')
.attr('class', 'addr-row')
.selectAll('input')
.data(row)
.enter()
.append('input')
.property('type', 'text')
.attr('placeholder', function (d) { return field.t('placeholders.' + d.id); })
.attr('class', function (d) { return 'addr-' + d.id; })
.style('width', function (d) { return d.width * 100 + '%'; });
// Update
wrap.selectAll('.addr-street')
.call(d3combobox()
.fetcher(function(value, callback) {
callback(getStreets());
}));
wrap.selectAll('.addr-city')
.call(d3combobox()
.fetcher(function(value, callback) {
callback(getCities());
}));
wrap.selectAll('.addr-postcode')
.call(d3combobox()
.fetcher(function(value, callback) {
callback(getPostCodes());
}));
wrap.selectAll('input')
.on('blur', change())
.on('change', change());
wrap.selectAll('input:not(.combobox-input)')
.on('input', change(true));
dispatch.call('init');
isInitialized = true;
}
function address(selection) {
isInitialized = false;
@@ -126,72 +193,10 @@ export function uiFieldAddress(field, context) {
.merge(wrap);
var center = entity.extent(context.graph()).center(),
addressFormat;
serviceNominatim.init();
serviceNominatim.countryCode(center, function (err, countryCode) {
addressFormat = _.find(dataAddressFormats, function (a) {
return a && a.countryCodes && _.includes(a.countryCodes, countryCode);
}) || _.first(dataAddressFormats);
function row(r) {
// Normalize widths.
var total = _.reduce(r, function(sum, field) {
return sum + (widths[field] || 0.5);
}, 0);
return r.map(function (field) {
return {
id: field,
width: (widths[field] || 0.5) / total
};
});
}
wrap.selectAll('div')
.data(addressFormat.format)
.enter()
.append('div')
.attr('class', 'addr-row')
.selectAll('input')
.data(row)
.enter()
.append('input')
.property('type', 'text')
.attr('placeholder', function (d) { return field.t('placeholders.' + d.id); })
.attr('class', function (d) { return 'addr-' + d.id; })
.style('width', function (d) { return d.width * 100 + '%'; });
// Update
wrap.selectAll('.addr-street')
.call(d3combobox()
.fetcher(function(value, callback) {
callback(getStreets());
}));
wrap.selectAll('.addr-city')
.call(d3combobox()
.fetcher(function(value, callback) {
callback(getCities());
}));
wrap.selectAll('.addr-postcode')
.call(d3combobox()
.fetcher(function(value, callback) {
callback(getPostCodes());
}));
wrap.selectAll('input')
.on('blur', change())
.on('change', change());
wrap.selectAll('input:not(.combobox-input)')
.on('input', change(true));
dispatch.call('init');
isInitialized = true;
});
if (nominatim && entity) {
var center = entity.extent(context.graph()).center();
nominatim.countryCode(center, initCallback);
}
}
+7 -6
View File
@@ -2,7 +2,7 @@ import * as d3 from 'd3';
import _ from 'lodash';
import { t } from '../../util/locale';
import { d3combobox } from '../../lib/d3.combobox.js';
import { serviceNominatim } from '../../services/index';
import { services } from '../../services/index';
import { utilRebind } from '../../util/rebind';
import { utilGetSetValue } from '../../util/get_set_value';
@@ -15,6 +15,8 @@ export {
export function uiFieldCombo(field, context) {
var dispatch = d3.dispatch('change'),
nominatim = services.nominatim,
taginfo = services.taginfo,
isMulti = (field.type === 'multiCombo'),
isNetwork = (field.type === 'networkCombo'),
optstrings = field.strings && field.strings.options,
@@ -108,7 +110,7 @@ export function uiFieldCombo(field, context) {
selection.call(combobox, attachTo);
setStaticValues(setPlaceholder);
} else if (context.taginfo()) {
} else if (taginfo) {
selection.call(combobox.fetcher(setTaginfoValues), attachTo);
setTaginfoValues('', setPlaceholder);
}
@@ -151,7 +153,7 @@ export function uiFieldCombo(field, context) {
if (hasCountryPrefix) {
query = country + ':';
}
context.taginfo()[fn]({
taginfo[fn]({
debounce: true,
key: field.key,
geometry: context.geometry(entity.id),
@@ -248,10 +250,9 @@ export function uiFieldCombo(field, context) {
.call(initCombo, selection)
.merge(input);
if (isNetwork) {
if (isNetwork && nominatim && entity) {
var center = entity.extent(context.graph()).center();
serviceNominatim.init();
serviceNominatim.countryCode(center, function (err, code) {
nominatim.countryCode(center, function (err, code) {
country = code;
});
}
+4 -4
View File
@@ -1,7 +1,7 @@
import * as d3 from 'd3';
import { t } from '../../util/locale';
import { serviceNominatim } from '../../services/index';
import { dataPhoneFormats } from '../../../data/index';
import { services } from '../../services/index';
import { utilRebind } from '../../util/rebind';
import { utilGetSetValue } from '../../util/get_set_value';
@@ -15,6 +15,7 @@ export {
export function uiFieldText(field, context) {
var dispatch = d3.dispatch('change'),
nominatim = services.nominatim,
input,
entity;
@@ -37,10 +38,9 @@ export function uiFieldText(field, context) {
.on('blur', change())
.on('change', change());
if (field.type === 'tel') {
if (field.type === 'tel' && nominatim && entity) {
var center = entity.extent(context.graph()).center();
serviceNominatim.init();
serviceNominatim.countryCode(center, function (err, countryCode) {
nominatim.countryCode(center, function (err, countryCode) {
if (err || !dataPhoneFormats[countryCode]) return;
selection.selectAll('#' + fieldId)
.attr('placeholder', dataPhoneFormats[countryCode]);
+2 -2
View File
@@ -3,7 +3,7 @@ import _ from 'lodash';
import { t } from '../../util/locale';
import { d3combobox } from '../../lib/d3.combobox.js';
import { dataSuggestions, dataWikipedia } from '../../../data/index';
import { serviceWikipedia } from '../../services/index';
import { services } from '../../services/index';
import { svgIcon } from '../../svg/index';
import { tooltip } from '../../util/tooltip';
import { utilDetect } from '../../util/detect';
@@ -14,7 +14,7 @@ import { utilSuggestNames } from '../../util/index';
export function uiFieldLocalized(field, context) {
var dispatch = d3.dispatch('change', 'input'),
wikipedia = serviceWikipedia.init(),
wikipedia = services.wikipedia,
input = d3.select(null),
localizedInputs = d3.select(null),
wikiTitles,
+3 -8
View File
@@ -4,12 +4,7 @@ import { t } from '../../util/locale';
import { actionChangeTags } from '../../actions/index';
import { d3combobox } from '../../lib/d3.combobox.js';
import { dataWikipedia } from '../../../data/index';
import {
serviceWikipedia,
serviceWikidata
} from '../../services/index';
import { services } from '../../services/index';
import { svgIcon } from '../../svg/index';
import { utilDetect } from '../../util/detect';
import { utilGetSetValue } from '../../util/get_set_value';
@@ -18,8 +13,8 @@ import { utilRebind } from '../../util/rebind';
export function uiFieldWikipedia(field, context) {
var dispatch = d3.dispatch('change'),
wikipedia = serviceWikipedia.init(),
wikidata = serviceWikidata.init(),
wikipedia = services.wikipedia,
wikidata = services.wikidata,
link = d3.select(null),
lang = d3.select(null),
title = d3.select(null),
+5 -3
View File
@@ -5,12 +5,14 @@ import { actionChangeMember, actionDeleteMember } from '../actions/index';
import { modeBrowse, modeSelect } from '../modes/index';
import { osmEntity } from '../osm/index';
import { svgIcon } from '../svg/index';
import { services } from '../services/index';
import { uiDisclosure } from './disclosure';
import { utilDisplayName } from '../util/index';
export function uiRawMemberEditor(context) {
var id;
var id,
taginfo = services.taginfo;
function selectMember(d) {
@@ -134,7 +136,7 @@ export function uiRawMemberEditor(context) {
.on('click', deleteMember)
.call(svgIcon('#operation-delete'));
if (context.taginfo()) {
if (taginfo) {
enter.each(bindTypeahead);
}
@@ -159,7 +161,7 @@ export function uiRawMemberEditor(context) {
role.call(d3combobox()
.fetcher(function(role, callback) {
var rtype = entity.tags.type;
context.taginfo().roles({
taginfo.roles({
debounce: true,
rtype: rtype || '',
geometry: context.geometry(d.member.id),
+6 -3
View File
@@ -12,13 +12,16 @@ import {
import { modeSelect } from '../modes/index';
import { osmEntity, osmRelation } from '../osm/index';
import { services } from '../services/index';
import { svgIcon } from '../svg/index';
import { uiDisclosure } from './disclosure';
import { utilDisplayName } from '../util/index';
export function uiRawMembershipEditor(context) {
var id, showBlank;
var taginfo = services.taginfo,
id, showBlank;
function selectRelation(d) {
@@ -197,7 +200,7 @@ export function uiRawMembershipEditor(context) {
.on('click', deleteMembership)
.call(svgIcon('#operation-delete'));
if (context.taginfo()) {
if (taginfo) {
enter.each(bindTypeahead);
}
@@ -286,7 +289,7 @@ export function uiRawMembershipEditor(context) {
role.call(d3combobox()
.fetcher(function(role, callback) {
var rtype = d.relation.tags.type;
context.taginfo().roles({
taginfo.roles({
debounce: true,
rtype: rtype || '',
geometry: context.geometry(id),
+6 -4
View File
@@ -1,6 +1,7 @@
import * as d3 from 'd3';
import { d3combobox } from '../lib/d3.combobox.js';
import { t } from '../util/locale';
import { services } from '../services/index';
import { svgIcon } from '../svg/index';
import { uiDisclosure } from './disclosure';
import { uiTagReference } from './tag_reference';
@@ -9,7 +10,8 @@ import { utilRebind } from '../util/rebind';
export function uiRawTagEditor(context) {
var dispatch = d3.dispatch('change'),
var taginfo = services.taginfo,
dispatch = d3.dispatch('change'),
showBlank = false,
state,
preset,
@@ -118,7 +120,7 @@ export function uiRawTagEditor(context) {
key = row.select('input.key'), // propagate bound data to child
value = row.select('input.value'); // propagate bound data to child
if (context.taginfo()) {
if (taginfo) {
bindTypeahead(key, value);
}
@@ -177,7 +179,7 @@ export function uiRawTagEditor(context) {
key.call(d3combobox()
.fetcher(function(value, callback) {
context.taginfo().keys({
taginfo.keys({
debounce: true,
geometry: context.geometry(id),
query: value
@@ -188,7 +190,7 @@ export function uiRawTagEditor(context) {
value.call(d3combobox()
.fetcher(function(value, callback) {
context.taginfo().values({
taginfo.values({
debounce: true,
key: utilGetSetValue(key),
geometry: context.geometry(id),
+7 -5
View File
@@ -2,11 +2,13 @@ import * as d3 from 'd3';
import _ from 'lodash';
import { t } from '../util/locale';
import { utilDetect } from '../util/detect';
import { services } from '../services/index';
import { svgIcon } from '../svg/index';
export function uiTagReference(tag, context) {
var tagReference = {},
var taginfo = services.taginfo,
tagReference = {},
button,
body,
loaded,
@@ -40,9 +42,11 @@ export function uiTagReference(tag, context) {
function load(param) {
if (!taginfo) return;
button.classed('tag-reference-loading', true);
context.taginfo().docs(param, function show(err, data) {
taginfo.docs(param, function show(err, data) {
var docs;
if (!err && data) {
docs = findLocal(data);
@@ -126,9 +130,7 @@ export function uiTagReference(tag, context) {
} else if (loaded) {
done();
} else {
if (context.taginfo()) {
load(tag);
}
load(tag);
}
})
.attr('class', 'tag-reference-button')
+27 -34
View File
@@ -12,7 +12,7 @@ describe('iD.serviceMapillary', function() {
context.projection.translate([-116508, 0]); // 10,0
server = sinon.fakeServer.create();
mapillary = iD.serviceMapillary.init();
mapillary = iD.services.mapillary;
mapillary.reset();
/* eslint-disable no-native-reassign */
@@ -39,18 +39,29 @@ describe('iD.serviceMapillary', function() {
});
describe('Mapillary service', function() {
describe('#init', function() {
it('Initializes cache one time', function() {
var cache = iD.serviceMapillary.getMapillary().cache;
var cache = mapillary.cache();
expect(cache).to.have.property('images');
expect(cache).to.have.property('signs');
iD.serviceMapillary.init();
var cache2 = iD.serviceMapillary.getMapillary().cache;
mapillary.init();
var cache2 = mapillary.cache();
expect(cache).to.equal(cache2);
});
});
describe('#reset', function() {
it('resets cache and image', function() {
mapillary.cache({foo: 'bar'});
mapillary.selectedImage('baz');
mapillary.reset();
expect(mapillary.cache()).to.not.have.property('foo');
expect(mapillary.selectedImage()).to.be.null;
});
});
describe('#loadImages', function() {
it('fires loadedImages when images are loaded', function() {
var spy = sinon.spy();
@@ -145,7 +156,7 @@ describe('iD.serviceMapillary', function() {
});
server.respond();
var sign_defs = iD.serviceMapillary.getMapillary().sign_defs;
var sign_defs = mapillary.signDefs();
expect(sign_defs).to.have.property('au')
.that.is.an('object')
@@ -274,7 +285,7 @@ describe('iD.serviceMapillary', function() {
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90 } }
];
iD.serviceMapillary.getMapillary().cache.images.rtree.load(features);
mapillary.cache().images.rtree.load(features);
var res = mapillary.images(context.projection, dimensions);
expect(res).to.deep.eql([
@@ -292,7 +303,7 @@ describe('iD.serviceMapillary', function() {
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], ca: 90 } }
];
iD.serviceMapillary.getMapillary().cache.images.rtree.load(features);
mapillary.cache().images.rtree.load(features);
var res = mapillary.images(context.projection, dimensions);
expect(res).to.have.length.of.at.most(3);
});
@@ -313,7 +324,7 @@ describe('iD.serviceMapillary', function() {
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], signs: signs } }
];
iD.serviceMapillary.getMapillary().cache.signs.rtree.load(features);
mapillary.cache().signs.rtree.load(features);
var res = mapillary.signs(context.projection, dimensions);
expect(res).to.deep.eql([
@@ -338,7 +349,7 @@ describe('iD.serviceMapillary', function() {
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], signs: signs } }
];
iD.serviceMapillary.getMapillary().cache.signs.rtree.load(features);
mapillary.cache().signs.rtree.load(features);
var res = mapillary.signs(context.projection, dimensions);
expect(res).to.have.length.of.at.most(3);
});
@@ -358,9 +369,9 @@ describe('iD.serviceMapillary', function() {
describe('#signHTML', function() {
it('returns sign HTML', function() {
iD.serviceMapillary.getMapillary().sign_defs = {
mapillary.signDefs({
us: {'regulatory--maximum-speed-limit-65--us': '<span class="t">65</span>'}
};
});
var signdata = {
key: '0',
@@ -378,28 +389,10 @@ describe('iD.serviceMapillary', function() {
});
});
describe('#setSelectedImage', function() {
it('sets selected image', function() {
mapillary.setSelectedImage('foo');
expect(iD.serviceMapillary.getMapillary().image).to.eql('foo');
});
});
describe('#getSelectedImage', function() {
it('gets selected image', function() {
iD.serviceMapillary.getMapillary().image = 'bar';
expect(mapillary.getSelectedImage()).to.eql('bar');
});
});
describe('#reset', function() {
it('resets cache and image', function() {
iD.serviceMapillary.getMapillary().cache.foo = 'bar';
iD.serviceMapillary.getMapillary().image = 'bar';
mapillary.reset();
expect(iD.serviceMapillary.getMapillary().cache).to.not.have.property('foo');
expect(iD.serviceMapillary.getMapillary().image).to.be.null;
describe('#selectedImage', function() {
it('sets and gets selected image', function() {
mapillary.selectedImage('foo');
expect(mapillary.selectedImage()).to.eql('foo');
});
});
+6 -3
View File
@@ -3,9 +3,8 @@ describe('iD.serviceNominatim', function() {
beforeEach(function() {
server = sinon.fakeServer.create();
iD.serviceNominatim.init();
nominatim = iD.serviceNominatim;
iD.serviceNominatim.reset();
nominatim = iD.services.nominatim;
nominatim.reset();
});
afterEach(function() {
@@ -17,6 +16,7 @@ describe('iD.serviceNominatim', function() {
}
describe.skip('#countryCode', function() {
it('calls the given callback with the results of the country code query', function() {
var callback = sinon.spy();
nominatim.countryCode([16, 48], callback);
@@ -30,6 +30,7 @@ describe('iD.serviceNominatim', function() {
{format: 'json', addressdetails: '1', lat: '48', lon: '16'});
expect(callback).to.have.been.calledWith(null, 'at');
});
it('should not cache the first country code result', function() {
var callback = sinon.spy();
nominatim.countryCode([16, 48], callback);
@@ -57,6 +58,7 @@ describe('iD.serviceNominatim', function() {
{format: 'json', addressdetails: '1', lat: '49', lon: '17'});
expect(callback).to.have.been.calledWith(null, 'cz');
});
it('should cache the first country code result', function() {
var callback = sinon.spy();
nominatim.countryCode([16, 48], callback);
@@ -82,6 +84,7 @@ describe('iD.serviceNominatim', function() {
expect(callback).to.have.been.calledWith(null, 'at');
});
it('calls the given callback with an error', function() {
var callback = sinon.spy();
nominatim.countryCode([1000, 1000], callback);
+1 -1
View File
@@ -3,7 +3,7 @@ describe('iD.serviceTaginfo', function() {
beforeEach(function() {
server = sinon.fakeServer.create();
taginfo = iD.serviceTaginfo.init();
taginfo = iD.services.taginfo;
taginfo.reset();
});