diff --git a/js/id/core/connection.js b/js/id/core/connection.js index f3860ce61..71dc876d6 100644 --- a/js/id/core/connection.js +++ b/js/id/core/connection.js @@ -275,40 +275,44 @@ iD.Connection = function() { 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())(), + var s = projection.scale() * 2 * Math.PI, 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 = [ + ts = 256 * Math.pow(2, z - 16), + 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])]; + var tiles = d3.geo.tile() + .scaleExtent([16, 16]) + .scale(s) + .size(dimensions) + .translate(projection.translate())() + .map(function(tile) { + var x = tile[0] * ts - origin[0], + y = tile[1] * ts - origin[1]; - return url + '/api/0.6/map?bbox=' + [b[0][0], b[1][1], b[1][0], b[0][1]]; + return { + id: tile.toString(), + extent: iD.geo.Extent( + projection.invert([x, y + ts]), + projection.invert([x + ts, y])) + } + }); + + function bboxUrl(tile) { + return url + '/api/0.6/map?bbox=' + tile.extent.toParam(); } _.filter(inflight, function(v, i) { var wanted = _.find(tiles, function(tile) { - return i === tile.toString(); + return i === tile.id; }); if (!wanted) delete inflight[i]; return !wanted; }).map(abortRequest); tiles.forEach(function(tile) { - var id = tile.toString(); + var id = tile.id; if (loadedTiles[id] || inflight[id]) return; @@ -320,7 +324,7 @@ iD.Connection = function() { loadedTiles[id] = true; delete inflight[id]; - event.load(err, parsed); + event.load(err, _.extend({data: parsed}, tile)); if (_.isEmpty(inflight)) { event.loaded(); diff --git a/js/id/core/history.js b/js/id/core/history.js index 7a315a965..badcc57c2 100644 --- a/js/id/core/history.js +++ b/js/id/core/history.js @@ -41,7 +41,7 @@ iD.History = function(context) { return stack[index].graph; }, - merge: function(entities) { + merge: function(entities, extent) { var base = stack[0].graph.base(), newentities = Object.keys(entities).filter(function(i) { @@ -54,7 +54,7 @@ iD.History = function(context) { tree.rebase(newentities); - dispatch.change(); + dispatch.change(undefined, extent); }, perform: function() { diff --git a/js/id/geo/extent.js b/js/id/geo/extent.js index 9d1cfe627..be7cd83bb 100644 --- a/js/id/geo/extent.js +++ b/js/id/geo/extent.js @@ -35,6 +35,14 @@ _.extend(iD.geo.Extent.prototype, { obj[1][1] >= this[0][1]; }, + intersection: function(obj) { + if (!this.intersects(obj)) return new iD.geo.Extent(); + return new iD.geo.Extent([Math.max(obj[0][0], this[0][0]), + Math.max(obj[0][1], this[0][1])], + [Math.min(obj[1][0], this[1][0]), + Math.min(obj[1][1], this[1][1])]); + }, + padByMeters: function(meters) { var dLat = meters / 111200, dLon = meters / 111200 / Math.abs(Math.cos(this.center()[1])); diff --git a/js/id/id.js b/js/id/id.js index 319452da0..199fab231 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -31,7 +31,7 @@ window.iD = function () { } connection.on('load.context', function loadContext(err, result) { - history.merge(result); + history.merge(result.data, result.extent); }); context.preauth = function(options) { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index f29e5a8c7..08c1ad4f8 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -96,16 +96,12 @@ iD.Map = function(context) { function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; } - function drawVector(difference) { + function drawVector(difference, extent) { var filter, all, - extent = map.extent(), graph = context.graph(); - if (!difference) { - all = context.intersects(extent); - filter = d3.functor(true); - } else { - var complete = difference.complete(extent); + if (difference) { + var complete = difference.complete(map.extent()); all = _.compact(_.values(complete)); filter = function(d) { if (d.type === 'midpoint') { @@ -131,15 +127,24 @@ iD.Map = function(context) { return d.id in complete; } }; + + } else if (extent) { + all = context.intersects(map.extent().intersection(extent)); + var set = d3.set(_.pluck(all, 'id')); + filter = function(d) { return set.has(d.id); }; + + } else { + all = context.intersects(map.extent()); + filter = d3.functor(true); } surface .call(points, graph, all, filter) - .call(vertices, graph, all, filter, extent, map.zoom()) + .call(vertices, graph, all, filter, map.extent(), map.zoom()) .call(lines, graph, all, filter) .call(areas, graph, all, filter) - .call(midpoints, graph, all, filter, extent) - .call(labels, graph, all, filter, dimensions, !difference); + .call(midpoints, graph, all, filter, map.extent()) + .call(labels, graph, all, filter, dimensions, !difference && !extent); dispatch.drawn(map); } @@ -192,7 +197,7 @@ iD.Map = function(context) { return true; } - function redraw(difference) { + function redraw(difference, extent) { if (!surface) return; @@ -202,7 +207,7 @@ iD.Map = function(context) { // It would result in artifacts where differenced entities are redrawn with // one transform and unchanged entities with another. if (resetTransform()) { - difference = undefined; + difference = extent = undefined; } var zoom = String(~~map.zoom()); @@ -218,7 +223,7 @@ iD.Map = function(context) { if (map.editable()) { context.connection().loadTiles(projection, dimensions); - drawVector(difference); + drawVector(difference, extent); } else { editOff(); } diff --git a/test/spec/core/history.js b/test/spec/core/history.js index c1f571b42..a19d919b2 100644 --- a/test/spec/core/history.js +++ b/test/spec/core/history.js @@ -23,10 +23,11 @@ describe("iD.History", function () { expect(history.graph().entity('n')).to.equal(n); }); - it("emits a change event", function () { + it("emits a change event with the specified extent", function () { + var extent = {}; history.on('change', spy); - history.merge({}); - expect(spy).to.have.been.called; + history.merge({}, extent); + expect(spy).to.have.been.calledWith(undefined, extent); }); }); diff --git a/test/spec/geo/extent.js b/test/spec/geo/extent.js index 71a42be82..66526c1b6 100644 --- a/test/spec/geo/extent.js +++ b/test/spec/geo/extent.js @@ -109,4 +109,47 @@ describe("iD.geo.Extent", function () { expect(iD.geo.Extent([[6, 6], [7, 7]]).intersects([[0, 0], [5, 5]])).to.be.false; }); }); + + describe("#intersection", function () { + it("returns an empty extent if self does not intersect with other", function () { + var a = iD.geo.Extent([0, 0], [5, 5]), + b = iD.geo.Extent([6, 6], [7, 7]); + expect(a.intersection(b)).to.eql(iD.geo.Extent()); + }); + + it("returns the intersection of self with other (1)", function () { + var a = iD.geo.Extent([0, 0], [5, 5]), + b = iD.geo.Extent([3, 4], [7, 7]); + expect(a.intersection(b)).to.eql(iD.geo.Extent([3, 4], [5, 5])); + expect(b.intersection(a)).to.eql(iD.geo.Extent([3, 4], [5, 5])); + }); + + it("returns the intersection of self with other (2)", function () { + var a = iD.geo.Extent([0, 0], [5, 5]), + b = iD.geo.Extent([3, -4], [7, 2]); + expect(a.intersection(b)).to.eql(iD.geo.Extent([3, 0], [5, 2])); + expect(b.intersection(a)).to.eql(iD.geo.Extent([3, 0], [5, 2])); + }); + + it("returns the intersection of self with other (3)", function () { + var a = iD.geo.Extent([0, 0], [5, 5]), + b = iD.geo.Extent([3, 3], [4, 7]); + expect(a.intersection(b)).to.eql(iD.geo.Extent([3, 3], [4, 5])); + expect(b.intersection(a)).to.eql(iD.geo.Extent([3, 3], [4, 5])); + }); + + it("returns the intersection of self with other (4)", function () { + var a = iD.geo.Extent([0, 0], [5, 5]), + b = iD.geo.Extent([3, -2], [4, 2]); + expect(a.intersection(b)).to.eql(iD.geo.Extent([3, 0], [4, 2])); + expect(b.intersection(a)).to.eql(iD.geo.Extent([3, 0], [4, 2])); + }); + + it("returns the intersection of self with other (5)", function () { + var a = iD.geo.Extent([0, 0], [5, 5]), + b = iD.geo.Extent([1, 1], [2, 2]); + expect(a.intersection(b)).to.eql(iD.geo.Extent([1, 1], [2, 2])); + expect(b.intersection(a)).to.eql(iD.geo.Extent([1, 1], [2, 2])); + }); + }); });