Files
iD/js/id/connection.js
Tom MacWright 4bef54e0f5 Microoptimize
2012-12-11 17:40:33 -05:00

236 lines
7.2 KiB
JavaScript

iD.Connection = function() {
var event = d3.dispatch('auth', 'load'),
apiURL = 'http://www.openstreetmap.org',
connection = {},
refNodes = {},
user = {},
inflight = [],
loadedTiles = {},
oauth = iD.OAuth().api(apiURL);
function bboxUrl(b) {
return apiURL + '/api/0.6/map?bbox=' + [b[0][0],b[1][1],b[1][0],b[0][1]];
}
function bboxFromAPI(box, callback) {
loadFromURL(bboxUrl(box), function(err, parsed) {
loadedTiles[box] = true;
callback(err, parsed);
});
}
function loadFromURL(url, callback) {
inflight.push(d3.xml(url).get()
.on('load', function(dom) {
return callback(null, parse(dom));
}));
}
function getNodes(obj) {
var nelems = obj.getElementsByTagName('nd'), nodes = new Array(nelems.length);
for (var i = 0, l = nelems.length; i < l; i++) {
nodes[i] = 'n' + nelems[i].attributes.ref.nodeValue;
refNodes['n' + nelems[i].attributes.ref.nodeValue] = true;
}
return nodes;
}
function getTags(obj) {
var tags = {}, tagelems = obj.getElementsByTagName('tag');
for (var i = 0, l = tagelems.length; i < l; i++) {
var item = tagelems[i];
tags[item.attributes.k.nodeValue] = item.attributes.v.nodeValue;
}
return tags;
}
function getMembers(obj) {
var elems = obj.getElementsByTagName('member'),
members = new Array(elems.length);
for (var i = 0, l = elems.length; i < l; i++) {
members[i] = {
id: elems[i].attributes.type.nodeValue[0] + elems[i].attributes.ref.nodeValue,
type: elems[i].attributes.type.nodeValue,
role: elems[i].attributes.role.nodeValue
};
}
return members;
}
function objectData(obj) {
var o = {
type: obj.nodeName,
members: getMembers(obj),
nodes: getNodes(obj),
tags: getTags(obj)
};
for (var i = 0, l = obj.attributes.length; i < l; i++) {
var n = obj.attributes[i].nodeName;
var v = obj.attributes[i].nodeValue;
o[n] = v;
}
if (o.lon && o.lat) {
o.loc = [parseFloat(o.lon), parseFloat(o.lat)];
delete o.lon;
delete o.lat;
}
o.id = iD.Entity.id.fromOSM(o.type, o.id);
if (o.type === 'node') o._poi = !refNodes[o.id];
return iD.Entity(o);
}
function parse(dom) {
if (!dom || !dom.childNodes) return new Error('Bad request');
var root = dom.childNodes[0];
var entities = {};
refNodes = {};
function addEntity(obj) {
var o = objectData(obj);
entities[o.id] = o;
}
_.forEach(root.getElementsByTagName('way'), addEntity);
_.forEach(root.getElementsByTagName('node'), addEntity);
_.forEach(root.getElementsByTagName('relation'), addEntity);
return iD.Graph(entities);
}
function authenticated() {
return oauth.authenticated();
}
connection.putChangeset = function(changes, comment, callback) {
oauth.xhr({
method: 'PUT',
path: '/api/0.6/changeset/create',
options: { header: { 'Content-Type': 'text/xml' } },
content: iD.format.XML.changeset(comment)
}, 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: iD.format.XML.osmChange(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);
});
});
});
};
function userDetails(callback) {
oauth.xhr({ method: 'GET', path: '/api/0.6/user/details' }, function(err, user_details) {
var u = user_details.getElementsByTagName('user')[0];
callback(connection.user({
display_name: u.attributes.display_name.nodeValue,
id: u.attributes.id.nodeValue
}).user());
});
}
function tileAtZoom(t, distance) {
var power = Math.pow(2, distance);
return [
Math.floor(t[0] * power),
Math.floor(t[1] * power),
t[2] + distance];
}
function tileAlreadyLoaded(c) {
if (loadedTiles[c]) return false;
for (var i = 0; i < 4; i++) {
if (loadedTiles[tileAtZoom(c, -i)]) return false;
}
return true;
}
function loadTiles(projection) {
var scaleExtent = [16, 16],
s = projection.scale(),
tiles = d3.geo.tile()
.scaleExtent(scaleExtent)
.scale(s)
.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 apiExtentBox(c) {
var x = (c[0] * ts) - tile_origin[0];
var y = (c[1] * ts) - tile_origin[1];
return [
projection.invert([x, y]),
projection.invert([x + ts, y + ts])];
}
inflight.map(function(i) {
i.abort();
});
inflight = [];
tiles
.map(apiExtentBox)
.filter(tileAlreadyLoaded)
.forEach(function(e) {
bboxFromAPI(e, function(err, g) {
event.load(err, g);
});
});
}
connection.url = function(_) {
if (!arguments.length) return apiURL;
apiURL = _;
oauth.api(_);
return connection;
};
connection.user = function(_) {
if (!arguments.length) return user;
user = _;
return connection;
};
connection.flush = function() {
loadedTiles = {};
return connection;
};
connection.logout = function() {
oauth.logout();
event.auth();
return connection;
};
connection.authenticate = function(callback) {
return oauth.authenticate(function(err, res) {
event.auth();
if (callback) callback(err, res);
});
};
connection.bboxFromAPI = bboxFromAPI;
connection.loadFromURL = loadFromURL;
connection.loadTiles = _.debounce(loadTiles, 100);
connection.userDetails = userDetails;
connection.authenticated = authenticated;
connection.objectData = objectData;
connection.apiURL = apiURL;
return d3.rebind(connection, event, 'on');
};