mirror of
https://github.com/FoggedLens/iD.git
synced 2026-03-19 17:43:39 +00:00
Connection defaults to osm.org URL and keys for the simple case. Customization is done via connection.switch(). Externalize SourceSwitch's use of iD.data.keys.
370 lines
12 KiB
JavaScript
370 lines
12 KiB
JavaScript
iD.Connection = function() {
|
|
|
|
var event = d3.dispatch('authenticating', 'authenticated', 'auth', 'loading', 'load', 'loaded'),
|
|
url = 'http://www.openstreetmap.org',
|
|
connection = {},
|
|
user = {},
|
|
inflight = {},
|
|
loadedTiles = {},
|
|
oauth = osmAuth({
|
|
url: 'http://www.openstreetmap.org',
|
|
oauth_consumer_key: '5A043yRSEugj4DJ5TljuapfnrflWDte8jTOcWLlT',
|
|
oauth_secret: 'aB3jKq1TRsCOUrfOIZ6oQMEDmv2ptV76PA54NGLL',
|
|
loading: authenticating,
|
|
done: authenticated
|
|
}),
|
|
ndStr = 'nd',
|
|
tagStr = 'tag',
|
|
memberStr = 'member',
|
|
nodeStr = 'node',
|
|
wayStr = 'way',
|
|
relationStr = 'relation',
|
|
off;
|
|
|
|
connection.changesetURL = function(changesetId) {
|
|
return url + '/browse/changeset/' + changesetId;
|
|
};
|
|
|
|
connection.entityURL = function(entity) {
|
|
return url + '/browse/' + entity.type + '/' + entity.osmId();
|
|
};
|
|
|
|
connection.userURL = function(username) {
|
|
return url + "/user/" + username;
|
|
};
|
|
|
|
connection.loadFromURL = function(url, callback) {
|
|
function done(dom) {
|
|
return callback(null, parse(dom));
|
|
}
|
|
return d3.xml(url).get().on('load', done);
|
|
};
|
|
|
|
function authenticating() {
|
|
event.authenticating();
|
|
}
|
|
|
|
function authenticated() {
|
|
event.authenticated();
|
|
}
|
|
|
|
function getNodes(obj) {
|
|
var elems = obj.getElementsByTagName(ndStr),
|
|
nodes = new Array(elems.length);
|
|
for (var i = 0, l = elems.length; i < l; i++) {
|
|
nodes[i] = 'n' + elems[i].attributes.ref.nodeValue;
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
function getTags(obj) {
|
|
var elems = obj.getElementsByTagName(tagStr),
|
|
tags = {};
|
|
for (var i = 0, l = elems.length; i < l; i++) {
|
|
var attrs = elems[i].attributes;
|
|
tags[attrs.k.nodeValue] = attrs.v.nodeValue;
|
|
}
|
|
return tags;
|
|
}
|
|
|
|
function getMembers(obj) {
|
|
var elems = obj.getElementsByTagName(memberStr),
|
|
members = new Array(elems.length);
|
|
for (var i = 0, l = elems.length; i < l; i++) {
|
|
var attrs = elems[i].attributes;
|
|
members[i] = {
|
|
id: attrs.type.nodeValue[0] + attrs.ref.nodeValue,
|
|
type: attrs.type.nodeValue,
|
|
role: attrs.role.nodeValue
|
|
};
|
|
}
|
|
return members;
|
|
}
|
|
|
|
var parsers = {
|
|
node: function nodeData(obj) {
|
|
var attrs = obj.attributes;
|
|
return new iD.Node({
|
|
id: iD.Entity.id.fromOSM(nodeStr, attrs.id.nodeValue),
|
|
loc: [parseFloat(attrs.lon.nodeValue), parseFloat(attrs.lat.nodeValue)],
|
|
version: attrs.version.nodeValue,
|
|
changeset: attrs.changeset.nodeValue,
|
|
user: attrs.user && attrs.user.nodeValue,
|
|
uid: attrs.uid && attrs.uid.nodeValue,
|
|
visible: attrs.visible.nodeValue,
|
|
timestamp: attrs.timestamp.nodeValue,
|
|
tags: getTags(obj)
|
|
});
|
|
},
|
|
|
|
way: function wayData(obj) {
|
|
var attrs = obj.attributes;
|
|
return new iD.Way({
|
|
id: iD.Entity.id.fromOSM(wayStr, attrs.id.nodeValue),
|
|
version: attrs.version.nodeValue,
|
|
changeset: attrs.changeset.nodeValue,
|
|
user: attrs.user && attrs.user.nodeValue,
|
|
uid: attrs.uid && attrs.uid.nodeValue,
|
|
visible: attrs.visible.nodeValue,
|
|
timestamp: attrs.timestamp.nodeValue,
|
|
tags: getTags(obj),
|
|
nodes: getNodes(obj)
|
|
});
|
|
},
|
|
|
|
relation: function relationData(obj) {
|
|
var attrs = obj.attributes;
|
|
return new iD.Relation({
|
|
id: iD.Entity.id.fromOSM(relationStr, attrs.id.nodeValue),
|
|
version: attrs.version.nodeValue,
|
|
changeset: attrs.changeset.nodeValue,
|
|
user: attrs.user && attrs.user.nodeValue,
|
|
uid: attrs.uid && attrs.uid.nodeValue,
|
|
visible: attrs.visible.nodeValue,
|
|
timestamp: attrs.timestamp.nodeValue,
|
|
tags: getTags(obj),
|
|
members: getMembers(obj)
|
|
});
|
|
}
|
|
};
|
|
|
|
function parse(dom) {
|
|
if (!dom || !dom.childNodes) return new Error('Bad request');
|
|
|
|
var root = dom.childNodes[0],
|
|
children = root.childNodes,
|
|
entities = {};
|
|
|
|
var i, o, l;
|
|
for (i = 0, l = children.length; i < l; i++) {
|
|
var child = children[i],
|
|
parser = parsers[child.nodeName];
|
|
if (parser) {
|
|
o = parser(child);
|
|
entities[o.id] = o;
|
|
}
|
|
}
|
|
|
|
return entities;
|
|
}
|
|
|
|
connection.authenticated = function() {
|
|
return oauth.authenticated();
|
|
};
|
|
|
|
// Generate Changeset XML. Returns a string.
|
|
connection.changesetJXON = function(tags) {
|
|
return {
|
|
osm: {
|
|
changeset: {
|
|
tag: _.map(tags, function(value, key) {
|
|
return { '@k': key, '@v': value };
|
|
}),
|
|
'@version': 0.3,
|
|
'@generator': 'iD'
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
// Generate [osmChange](http://wiki.openstreetmap.org/wiki/OsmChange)
|
|
// XML. Returns a string.
|
|
connection.osmChangeJXON = function(userid, changeset_id, changes) {
|
|
function nest(x, order) {
|
|
var groups = {};
|
|
for (var i = 0; i < x.length; i++) {
|
|
var tagName = Object.keys(x[i])[0];
|
|
if (!groups[tagName]) groups[tagName] = [];
|
|
groups[tagName].push(x[i][tagName]);
|
|
}
|
|
var ordered = {};
|
|
order.forEach(function(o) {
|
|
if (groups[o]) ordered[o] = groups[o];
|
|
});
|
|
return ordered;
|
|
}
|
|
|
|
function rep(entity) {
|
|
return entity.asJXON(changeset_id);
|
|
}
|
|
|
|
return {
|
|
osmChange: {
|
|
'@version': 0.3,
|
|
'@generator': 'iD',
|
|
'create': nest(changes.created.map(rep), ['node', 'way', 'relation']),
|
|
'modify': nest(changes.modified.map(rep), ['node', 'way', 'relation']),
|
|
'delete': _.extend(nest(changes.deleted.map(rep), ['relation', 'way', 'node']), {'@if-unused': true})
|
|
}
|
|
};
|
|
};
|
|
|
|
connection.putChangeset = function(changes, comment, imagery_used, callback) {
|
|
oauth.xhr({
|
|
method: 'PUT',
|
|
path: '/api/0.6/changeset/create',
|
|
options: { header: { 'Content-Type': 'text/xml' } },
|
|
content: JXON.stringify(connection.changesetJXON({
|
|
imagery_used: imagery_used.join(';'),
|
|
comment: comment,
|
|
created_by: 'iD ' + iD.version
|
|
}))
|
|
}, function(err, changeset_id) {
|
|
if (err) return callback(err);
|
|
oauth.xhr({
|
|
method: 'POST',
|
|
path: '/api/0.6/changeset/' + changeset_id + '/upload',
|
|
options: { header: { 'Content-Type': 'text/xml' } },
|
|
content: JXON.stringify(connection.osmChangeJXON(user.id, changeset_id, changes))
|
|
}, function(err) {
|
|
if (err) return callback(err);
|
|
oauth.xhr({
|
|
method: 'PUT',
|
|
path: '/api/0.6/changeset/' + changeset_id + '/close'
|
|
}, function(err) {
|
|
callback(err, changeset_id);
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
connection.userDetails = function(callback) {
|
|
function done(err, user_details) {
|
|
if (err) return callback(err);
|
|
var u = user_details.getElementsByTagName('user')[0],
|
|
img = u.getElementsByTagName('img'),
|
|
image_url = '';
|
|
if (img && img[0].getAttribute('href')) {
|
|
image_url = img[0].getAttribute('href');
|
|
}
|
|
callback(undefined, connection.user({
|
|
display_name: u.attributes.display_name.nodeValue,
|
|
image_url: image_url,
|
|
id: u.attributes.id.nodeValue
|
|
}).user());
|
|
}
|
|
oauth.xhr({ method: 'GET', path: '/api/0.6/user/details' }, done);
|
|
};
|
|
|
|
connection.status = function(callback) {
|
|
function done(capabilities) {
|
|
var apiStatus = capabilities.getElementsByTagName('status');
|
|
callback(undefined, apiStatus[0].getAttribute('api'));
|
|
}
|
|
d3.xml(url + '/api/capabilities').get()
|
|
.on('load', done)
|
|
.on('error', callback);
|
|
};
|
|
|
|
function abortRequest(i) { i.abort(); }
|
|
|
|
connection.loadTiles = function(projection, dimensions) {
|
|
|
|
if (off) return;
|
|
|
|
var scaleExtent = [16, 16],
|
|
s = projection.scale() * 2 * Math.PI,
|
|
tiles = d3.geo.tile()
|
|
.scaleExtent(scaleExtent)
|
|
.scale(s)
|
|
.size(dimensions)
|
|
.translate(projection.translate())(),
|
|
z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
|
|
rz = Math.max(scaleExtent[0], Math.min(scaleExtent[1], Math.floor(z))),
|
|
ts = 256 * Math.pow(2, z - rz),
|
|
tile_origin = [
|
|
s / 2 - projection.translate()[0],
|
|
s / 2 - projection.translate()[1]];
|
|
|
|
function bboxUrl(tile) {
|
|
var x = (tile[0] * ts) - tile_origin[0];
|
|
var y = (tile[1] * ts) - tile_origin[1];
|
|
var b = [
|
|
projection.invert([x, y]),
|
|
projection.invert([x + ts, y + ts])];
|
|
|
|
return url + '/api/0.6/map?bbox=' + [b[0][0], b[1][1], b[1][0], b[0][1]];
|
|
}
|
|
|
|
_.filter(inflight, function(v, i) {
|
|
var wanted = _.find(tiles, function(tile) {
|
|
return i === tile.toString();
|
|
});
|
|
if (!wanted) delete inflight[i];
|
|
return !wanted;
|
|
}).map(abortRequest);
|
|
|
|
tiles.forEach(function(tile) {
|
|
var id = tile.toString();
|
|
|
|
if (loadedTiles[id] || inflight[id]) return;
|
|
|
|
if (_.isEmpty(inflight)) {
|
|
event.loading();
|
|
}
|
|
|
|
inflight[id] = connection.loadFromURL(bboxUrl(tile), function(err, parsed) {
|
|
loadedTiles[id] = true;
|
|
delete inflight[id];
|
|
|
|
event.load(err, parsed);
|
|
|
|
if (_.isEmpty(inflight)) {
|
|
event.loaded();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
connection.switch = function(options) {
|
|
url = options.url;
|
|
oauth.options(_.extend({
|
|
loading: authenticating,
|
|
done: authenticated
|
|
}, options));
|
|
event.auth();
|
|
connection.flush();
|
|
return connection;
|
|
};
|
|
|
|
connection.toggle = function(_) {
|
|
off = !_;
|
|
return connection;
|
|
};
|
|
|
|
connection.user = function(_) {
|
|
if (!arguments.length) return user;
|
|
user = _;
|
|
return connection;
|
|
};
|
|
|
|
connection.flush = function() {
|
|
_.forEach(inflight, abortRequest);
|
|
loadedTiles = {};
|
|
inflight = {};
|
|
return connection;
|
|
};
|
|
|
|
connection.loadedTiles = function(_) {
|
|
if (!arguments.length) return loadedTiles;
|
|
loadedTiles = _;
|
|
return connection;
|
|
};
|
|
|
|
connection.logout = function() {
|
|
oauth.logout();
|
|
event.auth();
|
|
return connection;
|
|
};
|
|
|
|
connection.authenticate = function(callback) {
|
|
function done(err, res) {
|
|
event.auth();
|
|
if (callback) callback(err, res);
|
|
}
|
|
return oauth.authenticate(done);
|
|
};
|
|
|
|
return d3.rebind(connection, event, 'on');
|
|
};
|