From cb0e8ab66c83511285a787a96701c4d207fd707b Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 3 Mar 2015 20:54:09 -0500 Subject: [PATCH] Initial support for Multi Fetch GET It will also be much faster to fetch the remote entities in batches rather than one at a time through LoadEntity. One bonus/hazard with Multi Fetch GET is that it will get deleted entities with `visible=false`, rather than returning a HTTP Status Code 410 (Gone). This will be the only way that we can really do proper undeletion (Incrementing the current version by 1 is not guaranteed to work. And if a way is moved, fetching way/full will tell us whether the childnodes are part of the way, but not necessarily whether they exist or not.) We must be careful never to merge deleted entities into the real graph. e.g, a deleted node will not have a 'loc' attribute, so code that assumes every node must have a `loc` will be broken. So because deleted entities are very special, the output from `loadMultiple` should only be used for conflict resolution for now. --- js/id/core/connection.js | 45 ++++++++++++++++++++++++++++++++---- js/id/core/entity.js | 3 +++ js/id/core/graph.js | 2 +- js/id/core/tree.js | 2 +- test/spec/core/connection.js | 15 ++++++++++++ test/spec/core/graph.js | 9 ++++++++ 6 files changed, 70 insertions(+), 6 deletions(-) diff --git a/js/id/core/connection.js b/js/id/core/connection.js index e98627e55..65cb5fbf7 100644 --- a/js/id/core/connection.js +++ b/js/id/core/connection.js @@ -58,6 +58,30 @@ iD.Connection = function() { }); }; + connection.loadMultiple = function(ids, callback) { + // TODO: upgrade lodash and just use _.chunk + function chunk(arr, chunkSize) { + var result = []; + for (var i = 0; i < arr.length; i += chunkSize) { + result.push(arr.slice(i, i + chunkSize)); + } + return result; + } + + _.each(_.groupBy(ids, iD.Entity.id.type), function(v, k) { + var type = k + 's', + osmIDs = _.map(v, iD.Entity.id.toOSM); + + _.each(chunk(osmIDs, 150), function(arr) { + connection.loadFromURL( + url + '/api/0.6/' + type + '?' + type + '=' + arr.join(), + function(err, entities) { + if (callback) callback(err, {data: entities}); + }); + }); + }); + }; + function authenticating() { event.authenticating(); } @@ -66,6 +90,12 @@ iD.Connection = function() { event.authenticated(); } + function getLoc(attrs) { + var lon = attrs.lon && attrs.lon.value, + lat = attrs.lat && attrs.lat.value; + return [parseFloat(lon), parseFloat(lat)]; + } + function getNodes(obj) { var elems = obj.getElementsByTagName(ndStr), nodes = new Array(elems.length); @@ -99,15 +129,20 @@ iD.Connection = function() { return members; } + function getVisible(attrs) { + return (!attrs.visible || attrs.visible.value !== 'false'); + } + var parsers = { node: function nodeData(obj) { var attrs = obj.attributes; return new iD.Node({ id: iD.Entity.id.fromOSM(nodeStr, attrs.id.value), - loc: [parseFloat(attrs.lon.value), parseFloat(attrs.lat.value)], + loc: getLoc(attrs), version: attrs.version.value, user: attrs.user && attrs.user.value, - tags: getTags(obj) + tags: getTags(obj), + visible: getVisible(attrs) }); }, @@ -118,7 +153,8 @@ iD.Connection = function() { version: attrs.version.value, user: attrs.user && attrs.user.value, tags: getTags(obj), - nodes: getNodes(obj) + nodes: getNodes(obj), + visible: getVisible(attrs) }); }, @@ -129,7 +165,8 @@ iD.Connection = function() { version: attrs.version.value, user: attrs.user && attrs.user.value, tags: getTags(obj), - members: getMembers(obj) + members: getMembers(obj), + visible: getVisible(attrs) }); } }; diff --git a/js/id/core/entity.js b/js/id/core/entity.js index f4f52274c..dcc825fef 100644 --- a/js/id/core/entity.js +++ b/js/id/core/entity.js @@ -56,6 +56,9 @@ iD.Entity.prototype = { if (!this.id && this.type) { this.id = iD.Entity.id(this.type); } + if (!this.hasOwnProperty('visible')) { + this.visible = true; + } if (iD.debug) { Object.freeze(this); diff --git a/js/id/core/graph.js b/js/id/core/graph.js index d641b064c..2fa09a6c7 100644 --- a/js/id/core/graph.js +++ b/js/id/core/graph.js @@ -116,7 +116,7 @@ iD.Graph.prototype = { for (i = 0; i < entities.length; i++) { var entity = entities[i]; - if (!force && base.entities[entity.id]) + if (!entity.visible || (!force && base.entities[entity.id])) continue; // Merging data into the base graph diff --git a/js/id/core/tree.js b/js/id/core/tree.js index 17d5419f0..538e9894e 100644 --- a/js/id/core/tree.js +++ b/js/id/core/tree.js @@ -45,7 +45,7 @@ iD.Tree = function(head) { for (var i = 0; i < entities.length; i++) { var entity = entities[i]; - if (!force && (head.entities.hasOwnProperty(entity.id) || rectangles[entity.id])) + if (!entity.visible || (!force && (head.entities.hasOwnProperty(entity.id) || rectangles[entity.id]))) continue; insertions[entity.id] = entity; diff --git a/test/spec/core/connection.js b/test/spec/core/connection.js index fe267ee4b..770b7d981 100644 --- a/test/spec/core/connection.js +++ b/test/spec/core/connection.js @@ -116,6 +116,21 @@ describe('iD.Connection', function () { }); }); + describe('#loadMultiple', function () { + beforeEach(function() { + server = sinon.fakeServer.create(); + }); + + afterEach(function() { + server.restore(); + }); + + it('loads nodes'); + it('loads ways'); + + }); + + describe('#osmChangeJXON', function() { it('converts change data to JXON', function() { var jxon = c.osmChangeJXON('1234', {created: [], modified: [], deleted: []}); diff --git a/test/spec/core/graph.js b/test/spec/core/graph.js index 4bd7aeb7d..d5238270d 100644 --- a/test/spec/core/graph.js +++ b/test/spec/core/graph.js @@ -77,6 +77,15 @@ describe('iD.Graph', function() { expect(graph.entity('n')).to.equal(node); }); + it("doesn't rebase deleted entities", function () { + var node = iD.Node({id: 'n', visible: false}), + graph = iD.Graph(); + + graph.rebase([node], [graph]); + + expect(graph.hasEntity('n')).to.be.not.ok; + }); + it("gives precedence to existing entities", function () { var a = iD.Node({id: 'n'}), b = iD.Node({id: 'n'}),