From c5d691d79d723545852236f45c3738efb5dc58d9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 28 Jan 2013 13:35:47 -0500 Subject: [PATCH 01/28] Graph#rebase --- js/id/graph/graph.js | 35 ++++++----- js/id/graph/history.js | 8 ++- test/spec/graph/graph.js | 121 +++++++++++++++++++++++++++++++++------ 3 files changed, 131 insertions(+), 33 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 120fa2fe7..3eea6a59a 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -1,13 +1,15 @@ -iD.Graph = function(entities, mutable) { - if (!(this instanceof iD.Graph)) return new iD.Graph(entities, mutable); +iD.Graph = function(other, mutable) { + if (!(this instanceof iD.Graph)) return new iD.Graph(other, mutable); - if (_.isArray(entities)) { - this.entities = {}; - for (var i = 0; i < entities.length; i++) { - this.entities[entities[i].id] = entities[i]; + if (_.isArray(other)) { + this.entities = Object.create({}); + for (var i = 0; i < other.length; i++) { + this.entities[other[i].id] = other[i]; } + } else if (other instanceof iD.Graph) { + this.rebase(other.base(), other.entities); } else { - this.entities = entities || {}; + this.entities = Object.create(other || {}); } this.transients = {}; @@ -97,10 +99,18 @@ iD.Graph.prototype = { return (this._childNodes[entity.id] = nodes); }, - merge: function(graph) { - return this.update(function () { - _.defaults(this.entities, graph.entities); - }); + base: function() { + return Object.getPrototypeOf ? + Object.getPrototypeOf(this.entities) : + this.entities.__proto__; + }, + + // Unlike other graph methods, rebase mutates in place. This is because it + // is used only during the history operation that merges newly downloaded + // data into each state. To external consumers, it should appear as if the + // graph always contained the newly downloaded data. + rebase: function(base, entities) { + this.entities = _.assign(Object.create(base), entities || this.entities); }, replace: function(entity) { @@ -120,7 +130,7 @@ iD.Graph.prototype = { }, update: function() { - var graph = this.frozen ? iD.Graph(_.clone(this.entities), true) : this; + var graph = this.frozen ? iD.Graph(this, true) : this; for (var i = 0; i < arguments.length; i++) { arguments[i].call(graph, graph); @@ -133,7 +143,6 @@ iD.Graph.prototype = { this.frozen = true; if (iD.debug) { - Object.freeze(this); Object.freeze(this.entities); } diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 29b652554..c9e3e19f5 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -30,8 +30,12 @@ iD.History = function() { }, merge: function (graph) { - for (var i = 0; i < stack.length; i++) { - stack[i].graph = stack[i].graph.merge(graph); + var base = stack[0].graph.base(); + + _.defaults(base, graph.entities); + + for (var i = 1; i < stack.length; i++) { + stack[i].graph.rebase(base); } }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 25382e7c9..fa83a3a03 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -1,25 +1,110 @@ describe('iD.Graph', function() { - it("can be constructed with an entities Object", function () { - var entity = iD.Entity(), - graph = iD.Graph({'n-1': entity}); - expect(graph.entity('n-1')).to.equal(entity); - }); - - it("can be constructed with an entities Array", function () { - var entity = iD.Entity(), - graph = iD.Graph([entity]); - expect(graph.entity(entity.id)).to.equal(entity); - }); - - if (iD.debug) { - it("is frozen", function () { - expect(Object.isFrozen(iD.Graph())).to.be.true; + describe("constructor", function () { + it("accepts an entities Object", function () { + var entity = iD.Entity(), + graph = iD.Graph({'n-1': entity}); + expect(graph.entity('n-1')).to.equal(entity); }); - it("freezes entities", function () { - expect(Object.isFrozen(iD.Graph().entities)).to.be.true; + it("accepts an entities Array", function () { + var entity = iD.Entity(), + graph = iD.Graph([entity]); + expect(graph.entity(entity.id)).to.equal(entity); }); - } + + it("accepts a Graph", function () { + var entity = iD.Entity(), + graph = iD.Graph(iD.Graph([entity])); + expect(graph.entity(entity.id)).to.equal(entity); + }); + + it("copies other's entities", function () { + var entity = iD.Entity(), + base = iD.Graph([entity]), + graph = iD.Graph(base); + expect(graph.entities).not.to.equal(base.entities); + }); + + it("rebases on other's base", function () { + var base = iD.Graph(), + graph = iD.Graph(base); + expect(graph.base()).to.equal(base.base()); + }); + + it("freezes by default", function () { + expect(iD.Graph().frozen).to.be.true; + }); + + it("remains mutable if passed true as second argument", function () { + expect(iD.Graph([], true).frozen).not.to.be.true; + }); + }); + + describe("#freeze", function () { + it("sets the frozen flag", function () { + expect(iD.Graph([], true).freeze().frozen).to.be.true; + }); + + if (iD.debug) { + it("freezes entities", function () { + expect(Object.isFrozen(iD.Graph().entities)).to.be.true; + }); + } + }); + + describe("#rebase", function () { + it("preserves existing entities", function () { + var node = iD.Node({id: 'n'}), + graph = iD.Graph([node]); + graph.rebase({}); + expect(graph.entity('n')).to.equal(node); + }); + + it("includes new entities", function () { + var node = iD.Node({id: 'n'}), + graph = iD.Graph(); + graph.rebase({'n': node}); + expect(graph.entity('n')).to.equal(node); + }); + + it("gives precedence to existing entities", function () { + var a = iD.Node({id: 'n'}), + b = iD.Node({id: 'n'}), + graph = iD.Graph([a]); + graph.rebase({'n': b}); + expect(graph.entity('n')).to.equal(a); + }); + + it("inherits entities from base prototypally", function () { + var graph = iD.Graph(); + graph.rebase({'n': iD.Node()}); + expect(graph.entities).not.to.have.ownProperty('n'); + }); + + xit("updates parentWays", function () { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + w2 = iD.Way({id: 'w2', nodes: ['n']}), + graph = iD.Graph([n, w1]); + + graph.parentWays(n); + graph.rebase({'w2': w2}); + + expect(graph.parentWays(n)).to.eql([w1, w2]); + }); + + xit("updates parentRelations", function () { + var n = iD.Node({id: 'n'}), + r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}), + graph = iD.Graph([n, r1]); + + graph.parentRelations(n); + graph.rebase({'r2': r2}); + + expect(graph.parentRelations(n)).to.eql([r1, r2]); + }); + }); describe("#remove", function () { it("returns a new graph", function () { From 7d32af52640fc8d5519849eb7c47a120e3ce0dae Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 29 Jan 2013 12:49:00 -0500 Subject: [PATCH 02/28] work towards using prototypes for entities --- js/id/graph/graph.js | 30 ++++++++++++++++++------------ js/id/graph/history.js | 9 ++++++--- js/id/renderer/map.js | 2 +- js/id/util.js | 2 ++ 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 3eea6a59a..d35f616a0 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -1,19 +1,25 @@ iD.Graph = function(other, mutable) { if (!(this instanceof iD.Graph)) return new iD.Graph(other, mutable); - if (_.isArray(other)) { - this.entities = Object.create({}); - for (var i = 0; i < other.length; i++) { - this.entities[other[i].id] = other[i]; - } - } else if (other instanceof iD.Graph) { + if (other instanceof iD.Graph) { + var base = other.base(); + this.entities = _.assign(Object.create(base.entities), other.entities); + this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays); this.rebase(other.base(), other.entities); + } else { - this.entities = Object.create(other || {}); + if (_.isArray(other)) { + this.entities = Object.create({}); + for (var i = 0; i < other.length; i++) { + this.entities[other[i].id] = other[i]; + } + } else { + this.entities = Object.create(other || {}); + } + this._parentWays = Object.create({}); } this.transients = {}; - this._parentWays = {}; this._parentRels = {}; this._childNodes = {}; @@ -100,9 +106,10 @@ iD.Graph.prototype = { }, base: function() { - return Object.getPrototypeOf ? - Object.getPrototypeOf(this.entities) : - this.entities.__proto__; + return { + 'entities': iD.util.getPrototypeOf(this.entities), + 'parentWays': iD.util.getPrototypeOf(this._parentWays) + }; }, // Unlike other graph methods, rebase mutates in place. This is because it @@ -110,7 +117,6 @@ iD.Graph.prototype = { // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. rebase: function(base, entities) { - this.entities = _.assign(Object.create(base), entities || this.entities); }, replace: function(entity) { diff --git a/js/id/graph/history.js b/js/id/graph/history.js index c9e3e19f5..e5bfd353f 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -30,11 +30,14 @@ iD.History = function() { }, merge: function (graph) { - var base = stack[0].graph.base(); + var base = stack[0].graph.base(), + mergee = graph.base(); - _.defaults(base, graph.entities); + for (var i in base) { + _.defaults(base[i], mergee[i]); + } - for (var i = 1; i < stack.length; i++) { + for (i = 1; i < stack.length; i++) { stack[i].graph.rebase(base); } }, diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index bc4ca8e03..e27fdc5b9 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -123,7 +123,7 @@ iD.Map = function() { function connectionLoad(err, result) { history.merge(result); - redraw(Object.keys(result.entities)); + redraw(Object.keys(iD.util.getPrototypeOf(result.entities))); } function zoomPan() { diff --git a/js/id/util.js b/js/id/util.js index da31a162b..645d15363 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -74,3 +74,5 @@ iD.util.getStyle = function(selector) { } } }; + +iD.util.getPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; }; From 6c1e4e5b4d4f019b2643f358a2152ab71f67d8fe Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 29 Jan 2013 13:32:39 -0500 Subject: [PATCH 03/28] Working more-efficient parentWays --- js/id/graph/graph.js | 57 ++++++++++++++++++++++++++++++++---------- js/id/graph/history.js | 9 ++++--- js/id/renderer/map.js | 2 +- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index d35f616a0..2705fd029 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -8,15 +8,12 @@ iD.Graph = function(other, mutable) { this.rebase(other.base(), other.entities); } else { - if (_.isArray(other)) { - this.entities = Object.create({}); - for (var i = 0; i < other.length; i++) { - this.entities[other[i].id] = other[i]; - } - } else { - this.entities = Object.create(other || {}); - } + this.entities = other || Object.create({}); this._parentWays = Object.create({}); + + for (var i in this.entities) { + this._updateCalculated(undefined, this.entities[i]); + } } this.transients = {}; @@ -119,19 +116,53 @@ iD.Graph.prototype = { rebase: function(base, entities) { }, + _updateCalculated: function(oldentity, entity) { + + var type = entity && entity.type || oldentity && oldentity.type, + removed, added, parentWays, i; + + + if (type === 'way') { + + // Update parentWays + if (oldentity && entity) { + removed = _.difference(oldentity.nodes, entity.nodes); + added = _.difference(entity.nodes, oldentity.nodes); + } else if (oldentity) { + removed = oldentity.nodes; + added = []; + } else if (entity) { + removed = []; + added = entity.nodes; + } + for (i = 0; i < removed.length; i++) { + this._parentWays[removed[i]] = _.without(this._parentWays[removed[i]], oldentity.id); + } + for (i = 0; i < added.length; i++) { + parentWays = _.without(this._parentWays[added[i]], entity.id); + parentWays.push(entity.id); + this._parentWays[added[i]] = parentWays; + } + } else if (type === 'node') { + + } else if (type === 'relation') { + + // TODO: iterate over members + + } + }, + replace: function(entity) { return this.update(function () { + this._updateCalculated(this.entities[entity.id], entity); this.entities[entity.id] = entity; }); }, remove: function(entity) { return this.update(function () { - if (entity.created()) { - delete this.entities[entity.id]; - } else { - this.entities[entity.id] = undefined; - } + this._updateCalculated(entity, undefined); + this.entities[entity.id] = undefined; }); }, diff --git a/js/id/graph/history.js b/js/id/graph/history.js index e5bfd353f..95e499ef2 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -30,12 +30,13 @@ iD.History = function() { }, merge: function (graph) { - var base = stack[0].graph.base(), - mergee = graph.base(); + var base = stack[0].graph.base(); - for (var i in base) { - _.defaults(base[i], mergee[i]); + _.defaults(base.entities, graph.entities); + for (var i in graph.entities) { + base.parentWays[i] = _.unique((base.parentWays[id] || []).concat(graph._parentWays[i] || [])); } + _.defaults(base.parentWays, graph._parentWays); for (i = 1; i < stack.length; i++) { stack[i].graph.rebase(base); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index e27fdc5b9..bc4ca8e03 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -123,7 +123,7 @@ iD.Map = function() { function connectionLoad(err, result) { history.merge(result); - redraw(Object.keys(iD.util.getPrototypeOf(result.entities))); + redraw(Object.keys(result.entities)); } function zoomPan() { From ba68a238fe2cd70e7c08382c9c08f6afca8a8542 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 12:26:40 -0500 Subject: [PATCH 04/28] Working rebase --- js/id/graph/graph.js | 90 ++++++++++++++++++++++------------------ js/id/graph/history.js | 12 +----- test/spec/graph/graph.js | 35 ++++++++++------ 3 files changed, 74 insertions(+), 63 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 2705fd029..76842cbf5 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -5,15 +5,19 @@ iD.Graph = function(other, mutable) { var base = other.base(); this.entities = _.assign(Object.create(base.entities), other.entities); this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays); - this.rebase(other.base(), other.entities); + this.inherited = true; } else { - this.entities = other || Object.create({}); - this._parentWays = Object.create({}); - - for (var i in this.entities) { - this._updateCalculated(undefined, this.entities[i]); + if (_.isArray(other)) { + var entities = {}; + for (var i = 0; i < other.length; i++) { + entities[other[i].id] = other[i]; + } + other = entities; } + this.entities = Object.create({}); + this._parentWays = Object.create({}); + this.rebase(other || {}); } this.transients = {}; @@ -43,25 +47,7 @@ iD.Graph.prototype = { }, parentWays: function(entity) { - var ent, id, parents; - - if (!this._parentWays.calculated) { - for (var i in this.entities) { - ent = this.entities[i]; - if (ent && ent.type === 'way') { - for (var j = 0; j < ent.nodes.length; j++) { - id = ent.nodes[j]; - parents = this._parentWays[id] = this._parentWays[id] || []; - if (parents.indexOf(ent) < 0) { - parents.push(ent); - } - } - } - } - this._parentWays.calculated = true; - } - - return this._parentWays[entity.id] || []; + return _.map(this._parentWays[entity.id], _.bind(this.entity, this)); }, isPoi: function(entity) { @@ -113,13 +99,31 @@ iD.Graph.prototype = { // is used only during the history operation that merges newly downloaded // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. - rebase: function(base, entities) { + rebase: function(entities) { + var i; + // Merging of data only needed if graph is the base graph + if (!this.inherited) { + _.defaults(this.base().entities, entities); + for (i in entities) { + this._updateCalculated(undefined, entities[i], this.base().parentWays); + } + } + + var keys = Object.keys(this._parentWays); + for (i = 0; i < keys.length; i++) { + this._parentWays[keys[i]] = _.unique((this._parentWays[keys[i]] || []) + .concat(this.base().parentWays[keys[i]] || [])); + } }, - _updateCalculated: function(oldentity, entity) { + // Updates calculated properties (parentWays, parentRels) for the specified change + _updateCalculated: function(oldentity, entity, parentWays, parentRels) { + + parentWays = parentWays || this._parentWays; + parentRels = parentRels || this._parentRels; var type = entity && entity.type || oldentity && oldentity.type, - removed, added, parentWays, i; + removed, added, ways, i; if (type === 'way') { @@ -136,12 +140,12 @@ iD.Graph.prototype = { added = entity.nodes; } for (i = 0; i < removed.length; i++) { - this._parentWays[removed[i]] = _.without(this._parentWays[removed[i]], oldentity.id); + parentWays[removed[i]] = _.without(parentWays[removed[i]], oldentity.id); } for (i = 0; i < added.length; i++) { - parentWays = _.without(this._parentWays[added[i]], entity.id); - parentWays.push(entity.id); - this._parentWays[added[i]] = parentWays; + ways = _.without(parentWays[added[i]], entity.id); + ways.push(entity.id); + parentWays[added[i]] = ways; } } else if (type === 'node') { @@ -199,9 +203,12 @@ iD.Graph.prototype = { }, difference: function (graph) { - var result = [], entity, oldentity, id; + var result = [], + keys = Object.keys(this.entities), + entity, oldentity, id, i; - for (id in this.entities) { + for (i = 0; i < keys.length; i++) { + id = keys[i]; entity = this.entities[id]; oldentity = graph.entities[id]; if (entity !== oldentity) { @@ -223,7 +230,8 @@ iD.Graph.prototype = { } } - for (id in graph.entities) { + keys = Object.keys(graph.entities); + for (i = 0; i < keys.length; i++) { entity = graph.entities[id]; if (entity && !this.entities.hasOwnProperty(id)) { result.push(id); @@ -235,25 +243,25 @@ iD.Graph.prototype = { }, modified: function() { - var result = []; + var result = [], base = this.base().entities; _.each(this.entities, function(entity, id) { - if (entity && entity.modified()) result.push(id); + if (entity && base[id]) result.push(id); }); return result; }, created: function() { - var result = []; + var result = [], base = this.base().entities; _.each(this.entities, function(entity, id) { - if (entity && entity.created()) result.push(id); + if (entity && !base[id]) result.push(id); }); return result; }, deleted: function() { - var result = []; + var result = [], base = this.base().entities; _.each(this.entities, function(entity, id) { - if (!entity) result.push(id); + if (!entity && base[id]) result.push(id); }); return result; } diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 95e499ef2..3630bb915 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -30,16 +30,8 @@ iD.History = function() { }, merge: function (graph) { - var base = stack[0].graph.base(); - - _.defaults(base.entities, graph.entities); - for (var i in graph.entities) { - base.parentWays[i] = _.unique((base.parentWays[id] || []).concat(graph._parentWays[i] || [])); - } - _.defaults(base.parentWays, graph._parentWays); - - for (i = 1; i < stack.length; i++) { - stack[i].graph.rebase(base); + for (var i = 0; i < stack.length; i++) { + stack[i].graph.rebase(graph.base().entities); } }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index fa83a3a03..61b620ef3 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -28,7 +28,7 @@ describe('iD.Graph', function() { it("rebases on other's base", function () { var base = iD.Graph(), graph = iD.Graph(base); - expect(graph.base()).to.equal(base.base()); + expect(graph.base().entities).to.equal(base.base().entities); }); it("freezes by default", function () { @@ -81,16 +81,27 @@ describe('iD.Graph', function() { expect(graph.entities).not.to.have.ownProperty('n'); }); - xit("updates parentWays", function () { + it("updates parentWays", function () { var n = iD.Node({id: 'n'}), w1 = iD.Way({id: 'w1', nodes: ['n']}), w2 = iD.Way({id: 'w2', nodes: ['n']}), + w3 = iD.Way({id: 'w3', nodes: ['n']}), graph = iD.Graph([n, w1]); - graph.parentWays(n); - graph.rebase({'w2': w2}); + var graph2 = graph.replace(w2); + graph.rebase({ 'w3': w3 }); + graph2.rebase({ 'w3': w3 }); - expect(graph.parentWays(n)).to.eql([w1, w2]); + expect(graph2.parentWays(n)).to.eql([w1, w2, w3]); + }); + + it("only sets non-base parentWays when necessary", function () { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + graph = iD.Graph([n, w1]); + graph.rebase({ 'w1': w1 }); + expect(graph._parentWays.hasOwnProperty('n')).to.be.false; + expect(graph.parentWays(n)).to.eql([w1]); }); xit("updates parentRelations", function () { @@ -244,18 +255,18 @@ describe('iD.Graph', function() { describe("#modified", function () { it("returns an Array of ids of modified entities", function () { - var node1 = iD.Node({id: 'n1', _updated: true}), - node2 = iD.Node({id: 'n2'}), - graph = iD.Graph([node1, node2]); - expect(graph.modified()).to.eql([node1.id]); + var node = iD.Node({id: 'n1'}), + node_ = iD.Node({id: 'n1'}), + graph = iD.Graph([node]).replace(node_); + expect(graph.modified()).to.eql([node.id]); }); }); describe("#created", function () { it("returns an Array of ids of created entities", function () { - var node1 = iD.Node({id: 'n-1', _updated: true}), + var node1 = iD.Node({id: 'n-1'}), node2 = iD.Node({id: 'n2'}), - graph = iD.Graph([node1, node2]); + graph = iD.Graph([node2]).replace(node1); expect(graph.created()).to.eql([node1.id]); }); }); @@ -270,7 +281,7 @@ describe('iD.Graph', function() { it("doesn't include created entities that were subsequently deleted", function () { var node = iD.Node(), - graph = iD.Graph([node]).remove(node); + graph = iD.Graph().replace(node).remove(node); expect(graph.deleted()).to.eql([]); }); }); From e08364d492685224ef3abeb5db60851be053bc91 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 13:18:44 -0500 Subject: [PATCH 05/28] working parentRels --- js/id/graph/graph.js | 57 ++++++++++++++++++++++++---------------- test/spec/graph/graph.js | 40 +++++++++++++++++++--------- 2 files changed, 61 insertions(+), 36 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 76842cbf5..055ab8df8 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -5,6 +5,7 @@ iD.Graph = function(other, mutable) { var base = other.base(); this.entities = _.assign(Object.create(base.entities), other.entities); this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays); + this._parentRels = _.assign(Object.create(base.parentRels), other._parentRels); this.inherited = true; } else { @@ -17,11 +18,11 @@ iD.Graph = function(other, mutable) { } this.entities = Object.create({}); this._parentWays = Object.create({}); + this._parentRels = Object.create({}); this.rebase(other || {}); } this.transients = {}; - this._parentRels = {}; this._childNodes = {}; if (!mutable) { @@ -55,23 +56,9 @@ iD.Graph.prototype = { }, parentRelations: function(entity) { + return _.map(this._parentRels[entity.id], _.bind(this.entity, this)); var ent, id, parents; - if (!this._parentRels.calculated) { - for (var i in this.entities) { - ent = this.entities[i]; - if (ent && ent.type === 'relation') { - for (var j = 0; j < ent.members.length; j++) { - id = ent.members[j].id; - parents = this._parentRels[id] = this._parentRels[id] || []; - if (parents.indexOf(ent) < 0) { - parents.push(ent); - } - } - } - } - this._parentRels.calculated = true; - } return this._parentRels[entity.id] || []; }, @@ -91,7 +78,8 @@ iD.Graph.prototype = { base: function() { return { 'entities': iD.util.getPrototypeOf(this.entities), - 'parentWays': iD.util.getPrototypeOf(this._parentWays) + 'parentWays': iD.util.getPrototypeOf(this._parentWays), + 'parentRels': iD.util.getPrototypeOf(this._parentRels) }; }, @@ -100,20 +88,26 @@ iD.Graph.prototype = { // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. rebase: function(entities) { - var i; + var i, keys; // Merging of data only needed if graph is the base graph if (!this.inherited) { _.defaults(this.base().entities, entities); for (i in entities) { - this._updateCalculated(undefined, entities[i], this.base().parentWays); + this._updateCalculated(undefined, entities[i], this.base().parentWays, this.base().parentRels); } } - var keys = Object.keys(this._parentWays); + keys = Object.keys(this._parentWays); for (i = 0; i < keys.length; i++) { this._parentWays[keys[i]] = _.unique((this._parentWays[keys[i]] || []) .concat(this.base().parentWays[keys[i]] || [])); } + + keys = Object.keys(this._parentRels); + for (i = 0; i < keys.length; i++) { + this._parentRels[keys[i]] = _.unique((this._parentRels[keys[i]] || []) + .concat(this.base().parentRels[keys[i]] || [])); + } }, // Updates calculated properties (parentWays, parentRels) for the specified change @@ -123,7 +117,7 @@ iD.Graph.prototype = { parentRels = parentRels || this._parentRels; var type = entity && entity.type || oldentity && oldentity.type, - removed, added, ways, i; + removed, added, ways, rels, i; if (type === 'way') { @@ -151,8 +145,25 @@ iD.Graph.prototype = { } else if (type === 'relation') { - // TODO: iterate over members - + // Update parentRels + if (oldentity && entity) { + removed = _.difference(oldentity.members, entity.members); + added = _.difference(entity.members, oldentity); + } else if (oldentity) { + removed = oldentity.members; + added = []; + } else if (entity) { + removed = []; + added = entity.members; + } + for (i = 0; i < removed.length; i++) { + parentRels[removed[i].id] = _.without(parentRels[removed[i].id], oldentity.id); + } + for (i = 0; i < added.length; i++) { + rels = _.without(parentRels[added[i].id], entity.id); + rels.push(entity.id); + parentRels[added[i].id] = rels; + } } }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 61b620ef3..9d918ae04 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -85,35 +85,49 @@ describe('iD.Graph', function() { var n = iD.Node({id: 'n'}), w1 = iD.Way({id: 'w1', nodes: ['n']}), w2 = iD.Way({id: 'w2', nodes: ['n']}), - w3 = iD.Way({id: 'w3', nodes: ['n']}), graph = iD.Graph([n, w1]); - var graph2 = graph.replace(w2); + graph.rebase({ 'w2': w2 }); + expect(graph.parentWays(n)).to.eql([w1, w2]); + expect(graph._parentWays.hasOwnProperty('n')).to.be.false; + }); + + it("updates parentWays for nodes with modified parentWays", function () { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + w2 = iD.Way({id: 'w2', nodes: ['n']}), + w3 = iD.Way({id: 'w3', nodes: ['n']}), + graph = iD.Graph([n, w1]), + graph2 = graph.replace(w2); graph.rebase({ 'w3': w3 }); graph2.rebase({ 'w3': w3 }); expect(graph2.parentWays(n)).to.eql([w1, w2, w3]); }); - it("only sets non-base parentWays when necessary", function () { - var n = iD.Node({id: 'n'}), - w1 = iD.Way({id: 'w1', nodes: ['n']}), - graph = iD.Graph([n, w1]); - graph.rebase({ 'w1': w1 }); - expect(graph._parentWays.hasOwnProperty('n')).to.be.false; - expect(graph.parentWays(n)).to.eql([w1]); - }); - - xit("updates parentRelations", function () { + it("updates parentRelations", function () { var n = iD.Node({id: 'n'}), r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}), graph = iD.Graph([n, r1]); - graph.parentRelations(n); graph.rebase({'r2': r2}); expect(graph.parentRelations(n)).to.eql([r1, r2]); + expect(graph._parentRels.hasOwnProperty('n')).to.be.false; + }); + + it("updates parentWays for nodes with modified parentWays", function () { + var n = iD.Node({id: 'n'}), + r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}), + r3 = iD.Relation({id: 'r3', members: [{id: 'n'}]}), + graph = iD.Graph([n, r1]), + graph2 = graph.replace(r2); + + graph.rebase({'r3': r3}); + graph2.rebase({'r3': r3}); + expect(graph2.parentRelations(n)).to.eql([r1, r2, r3]); }); }); From 5a35b6bfa4c84509ffa2d41ddb7a641c90fafe87 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 14:11:54 -0500 Subject: [PATCH 06/28] Fix difference --- js/id/graph/graph.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 055ab8df8..f2525daf5 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -243,6 +243,7 @@ iD.Graph.prototype = { keys = Object.keys(graph.entities); for (i = 0; i < keys.length; i++) { + id = keys[i]; entity = graph.entities[id]; if (entity && !this.entities.hasOwnProperty(id)) { result.push(id); From 1b5a1b8268675f53fce8b9138550a063e3d6174e Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 14:20:39 -0500 Subject: [PATCH 07/28] Merge takes an entities object, not graph --- js/id/connection.js | 2 +- js/id/graph/history.js | 4 ++-- js/id/renderer/map.js | 2 +- test/spec/connection.js | 8 ++++---- test/spec/graph/history.js | 12 +++++------- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index 53c96bd96..5eca647cd 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -120,7 +120,7 @@ iD.Connection = function() { } } - return iD.Graph(entities); + return entities; } function authenticated() { diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 3630bb915..42e1b1604 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -29,9 +29,9 @@ iD.History = function() { return stack[index].graph; }, - merge: function (graph) { + merge: function (entities) { for (var i = 0; i < stack.length; i++) { - stack[i].graph.rebase(graph.base().entities); + stack[i].graph.rebase(entities); } }, diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index bc4ca8e03..139abb4c7 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -123,7 +123,7 @@ iD.Map = function() { function connectionLoad(err, result) { history.merge(result); - redraw(Object.keys(result.entities)); + redraw(Object.keys(result)); } function zoomPan() { diff --git a/test/spec/connection.js b/test/spec/connection.js index 2f5d93808..652c1419b 100644 --- a/test/spec/connection.js +++ b/test/spec/connection.js @@ -26,24 +26,24 @@ describe('iD.Connection', function () { c.loadFromURL('data/node.xml', done); }); - it('returns a graph', function (done) { + it('returns an object', function (done) { c.loadFromURL('data/node.xml', function (err, graph) { expect(err).to.not.be.ok; - expect(graph).to.be.instanceOf(iD.Graph); + expect(typeof graph).to.eql('object'); done(); }); }); it('parses a node', function (done) { c.loadFromURL('data/node.xml', function (err, graph) { - expect(graph.entity('n356552551')).to.be.instanceOf(iD.Entity); + expect(graph.n356552551).to.be.instanceOf(iD.Entity); done(); }); }); it('parses a way', function (done) { c.loadFromURL('data/way.xml', function (err, graph) { - expect(graph.entity('w19698713')).to.be.instanceOf(iD.Entity); + expect(graph.w19698713).to.be.instanceOf(iD.Entity); done(); }); }); diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index b59190c0f..dc685b446 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -153,17 +153,15 @@ describe("iD.History", function () { it("includes modified entities", function () { var node1 = iD.Node({id: "n1"}), - node2 = node1.update({}), - graph = iD.Graph([node1]); - history.merge(graph); + node2 = node1.update({}); + history.merge({ n1: node1}); history.perform(function (graph) { return graph.replace(node2); }); expect(history.changes().modified).to.eql([node2]); }); it("includes deleted entities", function () { - var node = iD.Node({id: "n1"}), - graph = iD.Graph([node]); - history.merge(graph); + var node = iD.Node({id: "n1"}); + history.merge({ n1: node }); history.perform(function (graph) { return graph.remove(node); }); expect(history.changes().deleted).to.eql([node]); }); @@ -189,7 +187,7 @@ describe("iD.History", function () { it("is the sum of all types of changes", function() { var node1 = iD.Node({id: "n1"}), node2 = iD.Node(); - history.merge(iD.Graph([node1])); + history.merge({ n1: node1 }); history.perform(function (graph) { return graph.remove(node1); }); expect(history.numChanges()).to.eql(1); history.perform(function (graph) { return graph.replace(node2); }); From a317859b0f288eecfcfddc9b3f7fb014cd9e1ba3 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 15:44:03 -0500 Subject: [PATCH 08/28] Don't re-add removed parentways, don't duplicate --- js/id/graph/graph.js | 28 +++++++++++++++++++++------- test/spec/graph/graph.js | 23 ++++++++++++++++++++++- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index f2525daf5..5ef0f54b1 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -88,25 +88,39 @@ iD.Graph.prototype = { // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. rebase: function(entities) { - var i, keys; + var base = this.base(), + i, k, child, id, keys; // Merging of data only needed if graph is the base graph if (!this.inherited) { - _.defaults(this.base().entities, entities); for (i in entities) { - this._updateCalculated(undefined, entities[i], this.base().parentWays, this.base().parentRels); + if (!base.entities[i]) { + base.entities[i] = entities[i]; + this._updateCalculated(undefined, entities[i], + base.parentWays, base.parentRels); + } } } keys = Object.keys(this._parentWays); for (i = 0; i < keys.length; i++) { - this._parentWays[keys[i]] = _.unique((this._parentWays[keys[i]] || []) - .concat(this.base().parentWays[keys[i]] || [])); + child = keys[i]; + for (k = 0; k < base.parentWays[child].length; k++) { + id = base.parentWays[child][k]; + if (this.entity(id) && !_.contains(this._parentWays[child], id)) { + this._parentWays[child].push(id); + } + } } keys = Object.keys(this._parentRels); for (i = 0; i < keys.length; i++) { - this._parentRels[keys[i]] = _.unique((this._parentRels[keys[i]] || []) - .concat(this.base().parentRels[keys[i]] || [])); + child = keys[i]; + for (k = 0; k < base.parentRels[child].length; k++) { + id = base.parentRels[child][k]; + if (this.entity(id) && !_.contains(this._parentRels[child], id)) { + this._parentRels[child].push(id); + } + } } }, diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 9d918ae04..667cfe149 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -105,6 +105,16 @@ describe('iD.Graph', function() { expect(graph2.parentWays(n)).to.eql([w1, w2, w3]); }); + it("avoids re-adding removed parentWays", function() { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + graph = iD.Graph([n, w1]), + graph2 = graph.remove(w1); + graph.rebase({ 'w1': w1 }); + graph2.rebase({ 'w1': w1 }); + expect(graph2.parentWays(n)).to.eql([]); + }); + it("updates parentRelations", function () { var n = iD.Node({id: 'n'}), r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), @@ -117,7 +127,17 @@ describe('iD.Graph', function() { expect(graph._parentRels.hasOwnProperty('n')).to.be.false; }); - it("updates parentWays for nodes with modified parentWays", function () { + it("avoids re-adding removed parentRels", function() { + var n = iD.Node({id: 'n'}), + r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + graph = iD.Graph([n, r1]), + graph2 = graph.remove(r1); + graph.rebase({ 'w1': r1 }); + graph2.rebase({ 'w1': r1 }); + expect(graph2.parentWays(n)).to.eql([]); + }); + + it("updates parentRels for nodes with modified parentWays", function () { var n = iD.Node({id: 'n'}), r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), r2 = iD.Relation({id: 'r2', members: [{id: 'n'}]}), @@ -129,6 +149,7 @@ describe('iD.Graph', function() { graph2.rebase({'r3': r3}); expect(graph2.parentRelations(n)).to.eql([r1, r2, r3]); }); + }); describe("#remove", function () { From 459dc00ce51c8d8868f7f8e98d19b687dd9ab7e4 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 12:42:34 -0500 Subject: [PATCH 09/28] Change the midpoint data a bit Store the ways which share the segment and the index of the segment. This will be used in both DragWay and Draw behaviors. --- js/id/behavior/drag_midpoint.js | 34 ++++++++++----------- js/id/svg/midpoints.js | 20 ++++++------- test/index.html | 1 + test/index_packaged.html | 1 + test/spec/svg/midpoints.js | 52 +++++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 29 deletions(-) create mode 100644 test/spec/svg/midpoints.js diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index 9f73a7115..e0b0c7a1a 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -1,29 +1,26 @@ iD.behavior.DragMidpoint = function(mode) { var history = mode.history, - projection = mode.map.projection, - behavior = iD.behavior.drag() + projection = mode.map.projection; + + var behavior = iD.behavior.drag() .delegate(".midpoint") .origin(function(d) { return projection(d.loc); }) .on('start', function(d) { - var w, nds; - d.node = iD.Node({loc: d.loc}); - var args = [iD.actions.AddNode(d.node)]; - for (var i = 0; i < d.ways.length; i++) { - w = d.ways[i], nds = w.nodes; - for (var j = 0; j < nds.length; j++) { - if ((nds[j] === d.nodes[0] && nds[j + 1] === d.nodes[1]) || - (nds[j] === d.nodes[1] && nds[j + 1] === d.nodes[0])) { - args.push(iD.actions.AddWayNode(w.id, d.node.id, j + 1)); - } - } - } - history.perform.apply(history, args); - var node = d3.selectAll('.node.vertex') - .filter(function(data) { return data.id === d.node.id; }); - behavior.target(node.node(), node.datum()); + var node = iD.Node({loc: d.loc}); + var args = [iD.actions.AddNode(node)]; + for (var i = 0; i < d.ways.length; i++) { + args.push(iD.actions.AddWayNode(d.ways[i].id, node.id, d.ways[i].index)); + } + + history.perform.apply(history, args); + + var vertex = d3.selectAll('.vertex') + .filter(function(data) { return data.id === node.id; }); + + behavior.target(vertex.node(), vertex.datum()); }) .on('move', function(d) { d3.event.sourceEvent.stopPropagation(); @@ -35,5 +32,6 @@ iD.behavior.DragMidpoint = function(mode) { iD.actions.Noop(), 'added a node to a way'); }); + return behavior; }; diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 655e35013..da4af659c 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -15,19 +15,17 @@ iD.svg.Midpoints = function(projection) { b = nodes[j + 1], id = [a.id, b.id].sort().join('-'); - if (!midpoints[id] && - iD.geo.dist(projection(a.loc), projection(b.loc)) > 40) { - - var midpoint_loc = iD.geo.interp(a.loc, b.loc, 0.5), - parents = _.intersection(graph.parentWays(a), - graph.parentWays(b)); + if (midpoints[id]) { + midpoints[id].ways.push({id: entity.id, index: j + 1}); + } else if (iD.geo.dist(projection(a.loc), projection(b.loc)) > 40) { midpoints[id] = { - loc: midpoint_loc, - ways: parents, - nodes: [a.id, b.id], + midpoint: true, id: id, - midpoint: true + loc: iD.geo.interp(a.loc, b.loc, 0.5), + a: a.id, + b: b.id, + ways: [{id: entity.id, index: j + 1}] }; } } @@ -35,7 +33,7 @@ iD.svg.Midpoints = function(projection) { var groups = surface.select('.layer-hit').selectAll('g.midpoint') .filter(filter) - .data(_.values(midpoints), function (d) { return [d.parents, d.id].join(","); }); + .data(_.values(midpoints), function (d) { return d.id; }); var group = groups.enter() .insert('g', ':first-child') diff --git a/test/index.html b/test/index.html index 4bdd4b5ba..97cd63b9b 100644 --- a/test/index.html +++ b/test/index.html @@ -170,6 +170,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 7ae72a056..25093d5b3 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -65,6 +65,7 @@ + diff --git a/test/spec/svg/midpoints.js b/test/spec/svg/midpoints.js new file mode 100644 index 000000000..c34ac0350 --- /dev/null +++ b/test/spec/svg/midpoints.js @@ -0,0 +1,52 @@ +describe("iD.svg.Midpoints", function () { + var surface, + projection = Object, + filter = d3.functor(true); + + beforeEach(function () { + surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) + .call(iD.svg.Surface()); + }); + + it("finds the location of the midpoints", function () { + var a = iD.Node({loc: [0, 0]}), + b = iD.Node({loc: [50, 0]}), + line = iD.Way({nodes: [a.id, b.id]}), + graph = iD.Graph([a, b, line]); + + surface.call(iD.svg.Midpoints(projection), graph, [line], filter); + + expect(surface.select('.midpoint').datum().loc).to.eql([25, 0]); + }); + + it("doesn't create midpoints on segments with pixel length less than 40", function () { + var a = iD.Node({loc: [0, 0]}), + b = iD.Node({loc: [39, 0]}), + line = iD.Way({nodes: [a.id, b.id]}), + graph = iD.Graph([a, b, line]); + + surface.call(iD.svg.Midpoints(projection), graph, [line], filter); + + expect(surface.selectAll('.midpoint')[0]).to.have.length(0); + }); + + it("binds a datum whose 'ways' property lists ways which include the segement", function () { + var a = iD.Node({loc: [0, 0]}), + b = iD.Node({loc: [50, 0]}), + c = iD.Node({loc: [1, 1]}), + d = iD.Node({loc: [2, 2]}), + l1 = iD.Way({nodes: [a.id, b.id]}), + l2 = iD.Way({nodes: [b.id, a.id]}), + l3 = iD.Way({nodes: [c.id, a.id, b.id, d.id]}), + l4 = iD.Way({nodes: [a.id, d.id, b.id]}), + graph = iD.Graph([a, b, c, d, l1, l2, l3, l4]), + ab = function (d) { return d.id === [a.id, b.id].sort().join("-"); }; + + surface.call(iD.svg.Midpoints(projection), graph, [l1, l2, l3, l4], filter); + + expect(surface.selectAll('.midpoint').filter(ab).datum().ways).to.eql([ + {id: l1.id, index: 1}, + {id: l2.id, index: 1}, + {id: l3.id, index: 2}]); + }); +}); From e381b6ab261b9d83875c7c2ad6edf92bbaa5e860 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 15:44:21 -0500 Subject: [PATCH 10/28] Extract AddMidpoint action --- index.html | 1 + js/id/actions/add_midpoint.js | 11 +++++++++++ js/id/behavior/drag_midpoint.js | 9 ++------- test/index.html | 2 ++ test/index_packaged.html | 1 + test/spec/actions/add_midpoint.js | 22 ++++++++++++++++++++++ 6 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 js/id/actions/add_midpoint.js create mode 100644 test/spec/actions/add_midpoint.js diff --git a/index.html b/index.html index fae1fc940..5f62b0f8f 100644 --- a/index.html +++ b/index.html @@ -73,6 +73,7 @@ + diff --git a/js/id/actions/add_midpoint.js b/js/id/actions/add_midpoint.js new file mode 100644 index 000000000..c0cb97f59 --- /dev/null +++ b/js/id/actions/add_midpoint.js @@ -0,0 +1,11 @@ +iD.actions.AddMidpoint = function(midpoint, node) { + return function(graph) { + graph = graph.replace(node.move(midpoint.loc)); + + midpoint.ways.forEach(function(way) { + graph = graph.replace(graph.entity(way.id).addNode(node.id, way.index)); + }); + + return graph; + }; +}; diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index e0b0c7a1a..72691a512 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -8,14 +8,9 @@ iD.behavior.DragMidpoint = function(mode) { return projection(d.loc); }) .on('start', function(d) { - var node = iD.Node({loc: d.loc}); + var node = iD.Node(); - var args = [iD.actions.AddNode(node)]; - for (var i = 0; i < d.ways.length; i++) { - args.push(iD.actions.AddWayNode(d.ways[i].id, node.id, d.ways[i].index)); - } - - history.perform.apply(history, args); + history.perform(iD.actions.AddMidpoint(d, node)); var vertex = d3.selectAll('.vertex') .filter(function(data) { return data.id === node.id; }); diff --git a/test/index.html b/test/index.html index 97cd63b9b..731800f62 100644 --- a/test/index.html +++ b/test/index.html @@ -68,6 +68,7 @@ + @@ -137,6 +138,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 25093d5b3..aba99ba04 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -32,6 +32,7 @@ + diff --git a/test/spec/actions/add_midpoint.js b/test/spec/actions/add_midpoint.js new file mode 100644 index 000000000..1f749217e --- /dev/null +++ b/test/spec/actions/add_midpoint.js @@ -0,0 +1,22 @@ +describe("iD.actions.AddMidpoint", function () { + it("adds the node at the midpoint location", function () { + var node = iD.Node(), + midpoint = {loc: [1, 2], ways: []}, + graph = iD.actions.AddMidpoint(midpoint, node)(iD.Graph()); + + expect(graph.entity(node.id).loc).to.eql([1, 2]); + }); + + it("adds the node to all ways at the respective indexes", function () { + var node = iD.Node(), + a = iD.Node(), + b = iD.Node(), + w1 = iD.Way(), + w2 = iD.Way({nodes: [a.id, b.id]}), + midpoint = {loc: [1, 2], ways: [{id: w1.id, index: 0}, {id: w2.id, index: 1}]}, + graph = iD.actions.AddMidpoint(midpoint, node)(iD.Graph([a, b, w1, w2])); + + expect(graph.entity(w1.id).nodes).to.eql([node.id]); + expect(graph.entity(w2.id).nodes).to.eql([a.id, node.id, b.id]); + }); +}); From c1348e231e0c7bcd18f92689170ba390c5d53ded Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Wed, 30 Jan 2013 15:54:36 -0500 Subject: [PATCH 11/28] fix inspect icon on warnings. --- css/app.css | 98 +++++++++++++++++++++--------------------- img/source/sprite.svg | 6 +-- img/sprite.png | Bin 14025 -> 14014 bytes 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/css/app.css b/css/app.css index 0ba223a99..054863230 100644 --- a/css/app.css +++ b/css/app.css @@ -929,50 +929,6 @@ div.typeahead a:first-child { left:0px; right:0px; top:0px; bottom:0px; } -.commit-modal .user-info { - display: inline-block; -} - -.commit-modal .commit-info { - margin-top: 10px; -} - -.commit-modal .user-info img { - float: left; -} - -.commit-modal h3 small.count { - margin-right: 10px; - text-align: center; - float: left; - height: 12px; - min-width: 12px; - font-size:12px; - line-height: 12px; - border-radius:24px; - padding:5px; - background:#7092ff; - color:#fff; -} - -.commit-modal .changeset-list { - overflow: auto; - border:1px solid #ccc; - background:#fff; - max-height: 160px; -} - -.commit-modal .warning-section .changeset-list { - margin-right: 20px; - overflow-x: visible; -} - -.commit-section.modal-section { - padding-bottom: 0; -} - -.commit-section.modal-section:last-child { padding-bottom: 20px;} - .modal-section { padding: 20px; } @@ -1007,6 +963,56 @@ div.typeahead a:first-child { display:none; } +.loading-modal { + text-align: center; +} + +/* Commit Modal +------------------------------------------------------- */ + +.commit-modal .user-info { + display: inline-block; +} + +.commit-modal .commit-info { + margin-top: 10px; +} + +.commit-modal .user-info img { + float: left; +} + +.commit-modal h3 small.count { + margin-right: 10px; + text-align: center; + float: left; + height: 12px; + min-width: 12px; + font-size:12px; + line-height: 12px; + border-radius:24px; + padding:5px; + background:#7092ff; + color:#fff; +} + +.commit-modal .changeset-list { + overflow: auto; + border:1px solid #ccc; + background:#fff; + max-height: 160px; +} + +.commit-modal .warning-section .changeset-list button { + float: right; +} + +.commit-section.modal-section { + padding-bottom: 0; +} + +.commit-section.modal-section:last-child { padding-bottom: 20px;} + .changeset-list li { border-top:1px solid #ccc; padding:5px 10px; @@ -1029,10 +1035,6 @@ div.typeahead a:first-child { font:normal 12px/20px 'Helvetica Neue', Arial, sans-serif; } -.loading-modal { - text-align: center; -} - /* Success ------------------------------------------------------- */ a.success-action { diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 4cc5abb47..01a28e513 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -39,8 +39,8 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="4" - inkscape:cx="230.7911" - inkscape:cy="190.13176" + inkscape:cx="332.2911" + inkscape:cy="175.13176" inkscape:document-units="px" inkscape:current-layer="layer12" showgrid="false" @@ -733,7 +733,7 @@ inkscape:export-xdpi="90" inkscape:export-ydpi="90" /> diff --git a/img/sprite.png b/img/sprite.png index b0f45f2cd6b06d74235373880618d7a550ef047f..5922c06c17edfe79b8f96c5f48cc97092487fadf 100644 GIT binary patch delta 13286 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 `&vlbT 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 delta 13300 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 O^-xV4S*&6f`2PSXCA5VA From 2fa48df9a15c81198af4ef9a78f0b119c0285ff9 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 16:09:15 -0500 Subject: [PATCH 12/28] Small parentWays, parentRelations perf improvements --- js/id/graph/graph.js | 17 ++++++++++------- js/id/renderer/map.js | 2 +- js/id/svg/vertices.js | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 5ef0f54b1..bd5b87f2e 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -24,6 +24,7 @@ iD.Graph = function(other, mutable) { this.transients = {}; this._childNodes = {}; + this.getEntity = _.bind(this.entity, this); if (!mutable) { this.freeze(); @@ -48,19 +49,21 @@ iD.Graph.prototype = { }, parentWays: function(entity) { - return _.map(this._parentWays[entity.id], _.bind(this.entity, this)); + return _.map(this._parentWays[entity.id], this.getEntity); }, isPoi: function(entity) { - return this.parentWays(entity).length === 0; + var parentWays = this._parentWays[entity.id]; + return !parentWays || parentWays.length === 0; + }, + + isShared: function(entity) { + var parentWays = this._parentWays[entity.id]; + return parentWays && parentWays.length > 1; }, parentRelations: function(entity) { - return _.map(this._parentRels[entity.id], _.bind(this.entity, this)); - var ent, id, parents; - - - return this._parentRels[entity.id] || []; + return _.map(this._parentRels[entity.id], this.getEntity); }, childNodes: function(entity) { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 139abb4c7..0a22e1911 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -64,7 +64,7 @@ iD.Map = function() { for (var i = 0; i < parents.length; i++) { var parent = parents[i]; if (only[parent.id] === undefined) { - only[parent.id] = graph.entity(parent.id); + only[parent.id] = parent; addParents(graph.parentRelations(parent)); } } diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js index 06be536ec..ba28c968a 100644 --- a/js/id/svg/vertices.js +++ b/js/id/svg/vertices.js @@ -36,7 +36,7 @@ iD.svg.Vertices = function(projection) { groups.attr('transform', iD.svg.PointTransform(projection)) .call(iD.svg.TagClasses()) .call(iD.svg.MemberClasses(graph)) - .classed('shared', function(entity) { return graph.parentWays(entity).length > 1; }); + .classed('shared', function(entity) { return graph.isShared(entity); }); // Selecting the following implicitly // sets the data (vertix entity) on the elements From dbac370c71979dc7992619d2634cf01c19d812ac Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 14:07:52 -0500 Subject: [PATCH 13/28] More draw refactoring --- js/id/behavior/add_way.js | 28 ++++++--------------- js/id/behavior/draw.js | 17 ++++++++++--- js/id/behavior/draw_way.js | 51 +++++++++++++++----------------------- js/id/modes/add_point.js | 18 +++++++++++--- js/id/modes/draw_area.js | 34 ++++++++----------------- js/id/modes/draw_line.js | 41 ++++++++---------------------- 6 files changed, 76 insertions(+), 113 deletions(-) diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 021377890..8eacd9372 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -1,25 +1,16 @@ iD.behavior.AddWay = function(mode) { var map = mode.map, - history = mode.history, controller = mode.controller, event = d3.dispatch('startFromNode', 'startFromWay', 'start'), - draw; - - function add(datum) { - if (datum.type === 'node') { - event.startFromNode(datum); - } else if (datum.type === 'way') { - var choice = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map); - event.startFromWay(datum, choice.loc, choice.index); - } else if (datum.midpoint) { - var way = history.graph().entity(datum.way); - event.startFromWay(way, datum.loc, datum.index); - } else { - event.start(map.mouseCoordinates()); - } - } + draw = iD.behavior.Draw(map); var addWay = function(surface) { + draw.on('click', event.start) + .on('clickNode', event.startFromNode) + .on('clickWay', event.startFromWay) + .on('cancel', addWay.cancel) + .on('finish', addWay.cancel); + map.fastEnable(false) .minzoom(16) .dblclickEnable(false); @@ -43,10 +34,5 @@ iD.behavior.AddWay = function(mode) { controller.exit(); }; - draw = iD.behavior.Draw() - .on('add', add) - .on('cancel', addWay.cancel) - .on('finish', addWay.cancel); - return d3.rebind(addWay, event, 'on'); }; diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 4d0153505..41e7fe2a9 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,5 +1,5 @@ -iD.behavior.Draw = function () { - var event = d3.dispatch('move', 'add', 'undo', 'cancel', 'finish'), +iD.behavior.Draw = function(map) { + var event = d3.dispatch('move', 'click', 'clickNode', 'clickWay', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), down, surface, hover; @@ -26,7 +26,18 @@ iD.behavior.Draw = function () { } function click() { - event.add(datum()); + var d = datum(); + if (d.type === 'node') { + event.clickNode(d); + } else if (d.type === 'way') { + var choice = iD.geo.chooseIndex(d, d3.mouse(map.surface.node()), map); + event.clickWay(d, choice.loc, choice.index); + } else if (d.midpoint) { + var way = history.graph().entity(d.way); + event.clickWay(way, d.loc, d.index); + } else { + event.click(map.mouseCoordinates()); + } } function keydown() { diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 63526df7a..3bcef869d 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -1,11 +1,11 @@ -iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { +iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { var map = mode.map, history = mode.history, controller = mode.controller, - event = d3.dispatch('add', 'addHead', 'addTail', 'addNode', 'addWay'), way = mode.history.graph().entity(wayId), finished = false, - draw; + annotation = 'added to a way', + draw = iD.behavior.Draw(map); var node = iD.Node({loc: map.mouseCoordinates()}), nodeId = node.id; @@ -26,29 +26,19 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { history.replace(iD.actions.MoveNode(nodeId, loc)); } - function add(datum) { - if (datum.id === headId) { - event.addHead(datum); - } else if (datum.id === tailId) { - event.addTail(datum); - } else if (datum.type === 'node' && datum.id !== nodeId) { - event.addNode(datum); - } else if (datum.type === 'way') { - var choice = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map); - event.addWay(datum, choice.loc, choice.index); - } else if (datum.midpoint) { - var way = history.graph().entity(datum.way); - event.addWay(way, datum.loc, datum.index); - } else { - event.add(map.mouseCoordinates()); - } - } - function undone() { controller.enter(iD.modes.Browse()); } var drawWay = function(surface) { + draw.on('move', move) + .on('click', drawWay.add) + .on('clickNode', drawWay.addNode) + .on('clickWay', drawWay.addWay) + .on('undo', history.undo) + .on('cancel', drawWay.cancel) + .on('finish', drawWay.finish); + map.fastEnable(false) .minzoom(16) .dblclickEnable(false); @@ -80,6 +70,12 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { history.on('undone.draw', null); }; + drawWay.annotation = function(_) { + if (!arguments.length) return annotation; + annotation = _; + return drawWay; + }; + function ReplaceTemporaryNode(newNode) { return function(graph) { return graph @@ -89,7 +85,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { } // Connect the way to an existing node and continue drawing. - drawWay.addNode = function(node, annotation) { + drawWay.addNode = function(node) { history.perform( ReplaceTemporaryNode(node), annotation); @@ -99,7 +95,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { }; // Connect the way to an existing way. - drawWay.addWay = function(way, loc, wayIndex, annotation) { + drawWay.addWay = function(way, loc, wayIndex) { var newNode = iD.Node({loc: loc}); history.perform( @@ -113,7 +109,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { }; // Accept the current position of the temporary node and continue drawing. - drawWay.add = function(loc, annotation) { + drawWay.add = function(loc) { var newNode = iD.Node({loc: loc}); history.replace( @@ -149,12 +145,5 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { controller.enter(iD.modes.Browse()); }; - draw = iD.behavior.Draw() - .on('move', move) - .on('add', add) - .on('undo', history.undo) - .on('cancel', drawWay.cancel) - .on('finish', drawWay.finish); - return d3.rebind(drawWay, event, 'on'); }; diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 1a51c314d..1bcfc5d04 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -16,8 +16,8 @@ iD.modes.AddPoint = function() { map.tail('Click on the map to add a point.', true); - function add() { - var node = iD.Node({loc: map.mouseCoordinates()}); + function add(loc) { + var node = iD.Node({loc: loc}); history.perform( iD.actions.AddNode(node), @@ -26,12 +26,22 @@ iD.modes.AddPoint = function() { controller.enter(iD.modes.Select(node, true)); } + function addWay(way, loc, index) { + add(loc); + } + + function addNode(node) { + add(node.loc); + } + function cancel() { controller.exit(); } - behavior = iD.behavior.Draw() - .on('add', add) + behavior = iD.behavior.Draw(map) + .on('click', add) + .on('clickWay', addWay) + .on('clickNode', addNode) .on('cancel', cancel) .on('finish', cancel) (surface); diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 721452bae..8423ff436 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -8,33 +8,21 @@ iD.modes.DrawArea = function(wayId, baseGraph) { mode.enter = function() { var way = mode.history.graph().entity(wayId), - index = -1, headId = way.nodes[way.nodes.length - 2], - tailId = way.first(), - annotation = way.isDegenerate() ? 'started an area' : 'continued an area'; + tailId = way.first(); - function addHeadTail() { - behavior.finish(); - } + behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph) + .annotation(way.isDegenerate() ? 'started an area' : 'continued an area'); - function addNode(node) { - behavior.addNode(node, annotation); - } + var addNode = behavior.addNode; - function addWay(way, loc, index) { - behavior.addWay(way, loc, index, annotation); - } - - function add(loc) { - behavior.add(loc, annotation); - } - - behavior = iD.behavior.DrawWay(wayId, headId, tailId, index, mode, baseGraph) - .on('addHead', addHeadTail) - .on('addTail', addHeadTail) - .on('addNode', addNode) - .on('addWay', addWay) - .on('add', add); + behavior.addNode = function(node) { + if (node.id === headId || node.id === tailId) { + behavior.finish(); + } else { + addNode(node); + } + }; mode.map.surface.call(behavior); mode.map.tail('Click to add points to your area. Click the first point to finish the area.', true); diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 289d91c14..5dee157b3 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -9,41 +9,20 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { mode.enter = function() { var way = mode.history.graph().entity(wayId), index = (direction === 'forward') ? undefined : 0, - headId = (direction === 'forward') ? way.last() : way.first(), - tailId = (direction === 'forward') ? way.first() : way.last(), - annotation = way.isDegenerate() ? 'started a line' : 'continued a line'; + headId = (direction === 'forward') ? way.last() : way.first(); - function addHead() { - behavior.finish(); - } + behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph) + .annotation(way.isDegenerate() ? 'started a line' : 'continued a line'); - function addTail(node) { - // connect the way in a loop - if (way.nodes.length > 2) { - behavior.addNode(node, annotation); + var addNode = behavior.addNode; + + behavior.addNode = function(node) { + if (node.id === headId) { + behavior.finish(); } else { - behavior.cancel(); + addNode(node); } - } - - function addNode(node) { - behavior.addNode(node, annotation); - } - - function addWay(way, loc, index) { - behavior.addWay(way, loc, index, annotation); - } - - function add(loc) { - behavior.add(loc, annotation); - } - - behavior = iD.behavior.DrawWay(wayId, headId, tailId, index, mode, baseGraph) - .on('addHead', addHead) - .on('addTail', addTail) - .on('addNode', addNode) - .on('addWay', addWay) - .on('add', add); + }; mode.map.surface.call(behavior); mode.map.tail('Click to add more points to the line. ' + From 168a3b5e2668daace5a025a951cca785d654bfe7 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 15:59:13 -0500 Subject: [PATCH 14/28] Fix drawing on midpoints (fixes #559) --- js/id/behavior/add_way.js | 5 +++-- js/id/behavior/draw.js | 14 ++++++++------ js/id/behavior/draw_way.js | 16 +++++++++++++++- js/id/modes/add_area.js | 15 +++++++++++++++ js/id/modes/add_line.js | 14 ++++++++++++++ js/id/modes/add_point.js | 1 + 6 files changed, 56 insertions(+), 9 deletions(-) diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 8eacd9372..fa038d201 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -1,13 +1,14 @@ iD.behavior.AddWay = function(mode) { var map = mode.map, controller = mode.controller, - event = d3.dispatch('startFromNode', 'startFromWay', 'start'), + event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'), draw = iD.behavior.Draw(map); var addWay = function(surface) { draw.on('click', event.start) - .on('clickNode', event.startFromNode) .on('clickWay', event.startFromWay) + .on('clickNode', event.startFromNode) + .on('clickMidpoint', event.startFromMidpoint) .on('cancel', addWay.cancel) .on('finish', addWay.cancel); diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 41e7fe2a9..783d9e42c 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,5 +1,5 @@ iD.behavior.Draw = function(map) { - var event = d3.dispatch('move', 'click', 'clickNode', 'clickWay', 'undo', 'cancel', 'finish'), + var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), down, surface, hover; @@ -27,14 +27,16 @@ iD.behavior.Draw = function(map) { function click() { var d = datum(); - if (d.type === 'node') { - event.clickNode(d); - } else if (d.type === 'way') { + if (d.type === 'way') { var choice = iD.geo.chooseIndex(d, d3.mouse(map.surface.node()), map); event.clickWay(d, choice.loc, choice.index); + + } else if (d.type === 'node') { + event.clickNode(d); + } else if (d.midpoint) { - var way = history.graph().entity(d.way); - event.clickWay(way, d.loc, d.index); + event.clickMidpoint(d); + } else { event.click(map.mouseCoordinates()); } diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 3bcef869d..1103bc143 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -33,8 +33,9 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { var drawWay = function(surface) { draw.on('move', move) .on('click', drawWay.add) - .on('clickNode', drawWay.addNode) .on('clickWay', drawWay.addWay) + .on('clickNode', drawWay.addNode) + .on('clickMidpoint', drawWay.addMidpoint) .on('undo', history.undo) .on('cancel', drawWay.cancel) .on('finish', drawWay.finish); @@ -121,6 +122,19 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { controller.enter(mode); }; + // Add a midpoint, connect the way to it, and continue drawing. + drawWay.addMidpoint = function(midpoint) { + var node = iD.Node(); + + history.perform( + iD.actions.AddMidpoint(midpoint, node), + ReplaceTemporaryNode(node), + annotation); + + finished = true; + controller.enter(mode); + }; + // Finish the draw operation, removing the temporary node. If the way has enough // nodes to be valid, it's selected. Otherwise, return to browse mode. drawWay.finish = function() { diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index d1a3efb08..153060b3a 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -15,6 +15,20 @@ iD.modes.AddArea = function() { history = mode.history, controller = mode.controller; + function startFromMidpoint(midpoint) { + var graph = history.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + history.perform( + iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddWay(way), + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(way.id, node.id)); + + controller.enter(iD.modes.DrawArea(way.id, graph)); + } + function startFromNode(node) { var graph = history.graph(), way = iD.Way({tags: defaultTags}); @@ -57,6 +71,7 @@ iD.modes.AddArea = function() { } behavior = iD.behavior.AddWay(mode) + .on('startFromMidpoint', startFromMidpoint) .on('startFromNode', startFromNode) .on('startFromWay', startFromWay) .on('start', start); diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index bf419d2bb..df6f33a57 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -15,6 +15,19 @@ iD.modes.AddLine = function() { history = mode.history, controller = mode.controller; + function startFromMidpoint(midpoint) { + var graph = history.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + history.perform( + iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddWay(way), + iD.actions.AddWayNode(way.id, node.id)); + + controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + } + function startFromNode(node) { var graph = history.graph(), parent = graph.parentWays(node)[0], @@ -65,6 +78,7 @@ iD.modes.AddLine = function() { } behavior = iD.behavior.AddWay(mode) + .on('startFromMidpoint', startFromMidpoint) .on('startFromNode', startFromNode) .on('startFromWay', startFromWay) .on('start', start); diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 1bcfc5d04..723b1935b 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -42,6 +42,7 @@ iD.modes.AddPoint = function() { .on('click', add) .on('clickWay', addWay) .on('clickNode', addNode) + .on('clickMidpoint', addNode) .on('cancel', cancel) .on('finish', cancel) (surface); From 363cd53af14939accb8f124d8f06775513c2ee06 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 16:17:30 -0500 Subject: [PATCH 15/28] Rearrange code --- js/id/behavior/draw_way.js | 22 +++++++++--------- js/id/modes/add_area.js | 42 +++++++++++++++++----------------- js/id/modes/add_line.js | 46 +++++++++++++++++++------------------- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 1103bc143..65231a277 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -85,10 +85,13 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { } } - // Connect the way to an existing node and continue drawing. - drawWay.addNode = function(node) { - history.perform( - ReplaceTemporaryNode(node), + // Accept the current position of the temporary node and continue drawing. + drawWay.add = function(loc) { + var newNode = iD.Node({loc: loc}); + + history.replace( + iD.actions.AddNode(newNode), + ReplaceTemporaryNode(newNode), annotation); finished = true; @@ -109,13 +112,10 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { controller.enter(mode); }; - // Accept the current position of the temporary node and continue drawing. - drawWay.add = function(loc) { - var newNode = iD.Node({loc: loc}); - - history.replace( - iD.actions.AddNode(newNode), - ReplaceTemporaryNode(newNode), + // Connect the way to an existing node and continue drawing. + drawWay.addNode = function(node) { + history.perform( + ReplaceTemporaryNode(node), annotation); finished = true; diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 153060b3a..614c66677 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -15,25 +15,13 @@ iD.modes.AddArea = function() { history = mode.history, controller = mode.controller; - function startFromMidpoint(midpoint) { - var graph = history.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - history.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id), - iD.actions.AddWayNode(way.id, node.id)); - - controller.enter(iD.modes.DrawArea(way.id, graph)); - } - - function startFromNode(node) { + function start(loc) { var graph = history.graph(), + node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); history.perform( + iD.actions.AddNode(node), iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, node.id), iD.actions.AddWayNode(way.id, node.id)); @@ -56,13 +44,25 @@ iD.modes.AddArea = function() { controller.enter(iD.modes.DrawArea(way.id, graph)); } - function start(loc) { + function startFromNode(node) { var graph = history.graph(), - node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(node), + iD.actions.AddWay(way), + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(way.id, node.id)); + + controller.enter(iD.modes.DrawArea(way.id, graph)); + } + + function startFromMidpoint(midpoint) { + var graph = history.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + history.perform( + iD.actions.AddMidpoint(midpoint, node), iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, node.id), iD.actions.AddWayNode(way.id, node.id)); @@ -71,10 +71,10 @@ iD.modes.AddArea = function() { } behavior = iD.behavior.AddWay(mode) - .on('startFromMidpoint', startFromMidpoint) - .on('startFromNode', startFromNode) + .on('start', start) .on('startFromWay', startFromWay) - .on('start', start); + .on('startFromNode', startFromNode) + .on('startFromMidpoint', startFromMidpoint); mode.map.surface.call(behavior); mode.map.tail('Click on the map to start drawing an area, like a park, lake, or building.', true); diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index df6f33a57..90adac738 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -15,19 +15,33 @@ iD.modes.AddLine = function() { history = mode.history, controller = mode.controller; - function startFromMidpoint(midpoint) { + function start(loc) { var graph = history.graph(), - node = iD.Node(), + node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddNode(node), iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, node.id)); controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); } + function startFromWay(other, loc, index) { + var graph = history.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + history.perform( + iD.actions.AddNode(node), + iD.actions.AddWay(way), + iD.actions.AddWayNode(way.id, node.id), + iD.actions.AddWayNode(other.id, node.id, index)); + + controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + } + function startFromNode(node) { var graph = history.graph(), parent = graph.parentWays(node)[0], @@ -50,27 +64,13 @@ iD.modes.AddLine = function() { } } - function startFromWay(other, loc, index) { + function startFromMidpoint(midpoint) { var graph = history.graph(), - node = iD.Node({loc: loc}), + node = iD.Node(), way = iD.Way({tags: defaultTags}); history.perform( - iD.actions.AddNode(node), - iD.actions.AddWay(way), - iD.actions.AddWayNode(way.id, node.id), - iD.actions.AddWayNode(other.id, node.id, index)); - - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); - } - - function start(loc) { - var graph = history.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - history.perform( - iD.actions.AddNode(node), + iD.actions.AddMidpoint(midpoint, node), iD.actions.AddWay(way), iD.actions.AddWayNode(way.id, node.id)); @@ -78,10 +78,10 @@ iD.modes.AddLine = function() { } behavior = iD.behavior.AddWay(mode) - .on('startFromMidpoint', startFromMidpoint) - .on('startFromNode', startFromNode) + .on('start', start) .on('startFromWay', startFromWay) - .on('start', start); + .on('startFromNode', startFromNode) + .on('startFromMidpoint', startFromMidpoint); mode.map.surface.call(behavior); mode.map.tail('Click on the map to start drawing an road, path, or route.', true); From e4c513ae87b235310c77204448cf3f48e92e69b0 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 30 Jan 2013 16:24:33 -0500 Subject: [PATCH 16/28] midpoint = true -> type = 'midpoint' Makes it more consistent with entities. --- js/id/behavior/draw.js | 2 +- js/id/behavior/draw_way.js | 2 +- js/id/renderer/map.js | 2 +- js/id/svg/midpoints.js | 4 +--- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 783d9e42c..bce4470b6 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -34,7 +34,7 @@ iD.behavior.Draw = function(map) { } else if (d.type === 'node') { event.clickNode(d); - } else if (d.midpoint) { + } else if (d.type === 'midpoint') { event.clickMidpoint(d); } else { diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 65231a277..884d72ddb 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -17,7 +17,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { function move(datum) { var loc = map.mouseCoordinates(); - if (datum.type === 'node' || datum.midpoint) { + if (datum.type === 'node' || datum.type === 'midpoint') { loc = datum.loc; } else if (datum.type === 'way') { loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 487ac736a..c7c1ed632 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -92,7 +92,7 @@ iD.Map = function() { all = _.compact(_.values(only)); filter = function(d) { - if (d.midpoint) { + if (d.type === 'midpoint') { for (var i = 0; i < d.ways.length; i++) { if (d.ways[i].id in only) return true; } diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index da4af659c..ee8d2c5e2 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -20,11 +20,9 @@ iD.svg.Midpoints = function(projection) { } else if (iD.geo.dist(projection(a.loc), projection(b.loc)) > 40) { midpoints[id] = { - midpoint: true, + type: 'midpoint', id: id, loc: iD.geo.interp(a.loc, b.loc, 0.5), - a: a.id, - b: b.id, ways: [{id: entity.id, index: j + 1}] }; } From a82b81a3c227ec02d7f36302bd8f4904cb8700b0 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 16:40:00 -0500 Subject: [PATCH 17/28] Add tests for #replace #remove #rebase --- test/spec/graph/graph.js | 65 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 667cfe149..327d66295 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -92,6 +92,14 @@ describe('iD.Graph', function() { expect(graph._parentWays.hasOwnProperty('n')).to.be.false; }); + it("avoids adding duplicate parentWays", function () { + var n = iD.Node({id: 'n'}), + w1 = iD.Way({id: 'w1', nodes: ['n']}), + graph = iD.Graph([n, w1]); + graph.rebase({ 'w1': w1 }); + expect(graph.parentWays(n)).to.eql([w1]); + }); + it("updates parentWays for nodes with modified parentWays", function () { var n = iD.Node({id: 'n'}), w1 = iD.Way({id: 'w1', nodes: ['n']}), @@ -171,6 +179,20 @@ describe('iD.Graph', function() { graph = iD.Graph([node]); expect(graph.remove(node).entity(node.id)).to.be.undefined; }); + + it("removes the entity as a parentWay", function () { + var node = iD.Node({id: 'n' }), + w1 = iD.Way({id: 'w', nodes: ['n']}), + graph = iD.Graph([node, w1]); + expect(graph.remove(w1).parentWays(node)).to.eql([]); + }); + + it("removes the entity as a parentRelation", function () { + var node = iD.Node({id: 'n' }), + r1 = iD.Relation({id: 'w', members: [{id: 'n' }]}), + graph = iD.Graph([node, r1]); + expect(graph.remove(r1).parentRelations(node)).to.eql([]); + }); }); describe("#replace", function () { @@ -193,6 +215,49 @@ describe('iD.Graph', function() { graph = iD.Graph([node1]); expect(graph.replace(node2).entity(node2.id)).to.equal(node2); }); + + it("adds parentWays", function () { + var node = iD.Node({id: 'n' }), + w1 = iD.Way({id: 'w', nodes: ['n']}), + graph = iD.Graph([node]); + expect(graph.replace(w1).parentWays(node)).to.eql([w1]); + }); + + it("removes parentWays", function () { + var node = iD.Node({id: 'n' }), + w1 = iD.Way({id: 'w', nodes: ['n']}), + graph = iD.Graph([node, w1]); + expect(graph.remove(w1).parentWays(node)).to.eql([]); + }); + + it("doesn't add duplicate parentWays", function () { + var node = iD.Node({id: 'n' }), + w1 = iD.Way({id: 'w', nodes: ['n']}), + graph = iD.Graph([node, w1]); + expect(graph.replace(w1).parentWays(node)).to.eql([w1]); + }); + + it("adds parentRels", function () { + var node = iD.Node({id: 'n' }), + r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}), + graph = iD.Graph([node]); + expect(graph.replace(r1).parentRelations(node)).to.eql([r1]); + }); + + it("removes parentRelations", function () { + var node = iD.Node({id: 'n' }), + r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}), + graph = iD.Graph([node, r1]); + expect(graph.remove(r1).parentRelations(node)).to.eql([]); + }); + + it("doesn't add duplicate parentRelations", function () { + var node = iD.Node({id: 'n' }), + r1 = iD.Relation({id: 'w', members: [{id: 'n'}]}), + graph = iD.Graph([node, r1]); + expect(graph.replace(r1).parentRelations(node)).to.eql([r1]); + }); + }); describe("#update", function () { From 9862238a842756b5beb825e35d8222fb2b375576 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 17:04:51 -0500 Subject: [PATCH 18/28] Fix graph.rebase() --- js/id/graph/graph.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index bd5b87f2e..5e865b9ec 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -107,10 +107,12 @@ iD.Graph.prototype = { keys = Object.keys(this._parentWays); for (i = 0; i < keys.length; i++) { child = keys[i]; - for (k = 0; k < base.parentWays[child].length; k++) { - id = base.parentWays[child][k]; - if (this.entity(id) && !_.contains(this._parentWays[child], id)) { - this._parentWays[child].push(id); + if (base.parentWays[child]) { + for (k = 0; k < base.parentWays[child].length; k++) { + id = base.parentWays[child][k]; + if (this.entity(id) && !_.contains(this._parentWays[child], id)) { + this._parentWays[child].push(id); + } } } } @@ -118,10 +120,12 @@ iD.Graph.prototype = { keys = Object.keys(this._parentRels); for (i = 0; i < keys.length; i++) { child = keys[i]; - for (k = 0; k < base.parentRels[child].length; k++) { - id = base.parentRels[child][k]; - if (this.entity(id) && !_.contains(this._parentRels[child], id)) { - this._parentRels[child].push(id); + if (base.parentRels[child]) { + for (k = 0; k < base.parentRels[child].length; k++) { + id = base.parentRels[child][k]; + if (this.entity(id) && !_.contains(this._parentRels[child], id)) { + this._parentRels[child].push(id); + } } } } From 792531d48a4f49933034a922e2bf45d1beaec252 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Wed, 30 Jan 2013 17:27:28 -0500 Subject: [PATCH 19/28] fixed tooltip sizing / positioning/ --- css/app.css | 109 ++++++++++++++++++++++++++-------------------------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/css/app.css b/css/app.css index 054863230..4ec4bd91d 100644 --- a/css/app.css +++ b/css/app.css @@ -246,7 +246,6 @@ form.hide { button { line-height:20px; - position: relative; border:0; color:#222; background: white; @@ -346,16 +345,16 @@ button.save .count { button.save.has-count .count { display: block; position: absolute; - left: 115%; top: 0; bottom: 0; - background: rgba(255,255,255,.5); + background: rgba(255, 255, 255, .5); color: #333; padding: 10px; height: 30px; line-height: 12px; border-radius: 4px; margin: auto; + margin-left: 8.3333%; } button.save.has-count .count::before { @@ -1072,12 +1071,10 @@ a.success-action { ------------------------------------------------------- */ .tooltip { - white-space: normal; + width: 200px; position: absolute; - left: 0; right: 0; margin: auto; z-index: -1000; height: 0; - padding: 5px; opacity: 0; display: block; } @@ -1089,77 +1086,81 @@ a.success-action { } .tooltip.top { - margin-top: -5px; + margin-top: -10px; + text-align: center; } .tooltip.right { - margin-left: 5px; + margin-left: 10px; + text-align: left; } .tooltip.bottom { - margin-top: 5px; + margin-top: 10px; + text-align: center; } .tooltip.left { - margin-left: -5px; + margin-left: -10px; + text-align: right; } .tooltip-inner { - text-align: left; - width: 200px; - font-size: 11px; - font-weight: bold; - line-height: 20px; - padding: 5px 10px; - color: #333; - background-color: white; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; + color: #333; + display: inline-block; + padding: 5px 10px; + font-size: 11px; + font-weight: bold; + line-height: 20px; + background-color: white; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } .tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; } .tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-top-color: white; - border-width: 5px 5px 0; + bottom: -5px; + left: 50%; + margin-left: -5px; + border-top-color: white; + border-width: 5px 5px 0; } .tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-right-color: white; - border-width: 5px 5px 5px 0; + top: 50%; + left: -5px; + margin-top: -5px; + border-right-color: white; + border-width: 5px 5px 5px 0; } .tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-left-color: white; - border-width: 5px 0 5px 5px; + top: 50%; + right: 5px; + margin-top: -5px; + border-left-color: white; + border-width: 5px 0 5px 5px; } .tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-bottom-color: white; - border-width: 0 5px 5px; + top: -5px; + left: 50%; + margin-left: -5px; + border-bottom-color: white; + border-width: 0 5px 5px; } .Browse .tooltip .tooltip-arrow { - left: 30px; + left: 30px; } .tooltip .keyhint { @@ -1174,13 +1175,13 @@ a.success-action { } .tail { - pointer-events:none; - position: absolute; - background: rgba(255, 255, 255, 0.7); - max-width: 250px; - margin-top: -15px; - padding: 5px; - -webkit-border-radius: 4px; + pointer-events:none; + position: absolute; + background: rgba(255, 255, 255, 0.7); + max-width: 250px; + margin-top: -15px; + padding: 5px; + -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } From 35cefceba67fcf2ee1bfa63bb1511a51f4eeee2d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 30 Jan 2013 17:43:14 -0500 Subject: [PATCH 20/28] Fix partial redraws --- js/id/renderer/map.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 79fcd3240..0d1532948 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -165,7 +165,8 @@ iD.Map = function() { } function resetTransform() { - if (!surface.style(transformProp)) return false; + var prop = surface.style(transformProp); + if (!prop || prop === 'none') return false; surface.style(transformProp, ''); tilegroup.style(transformProp, ''); return true; From eb504a3e23ad1697eb942ae1bd1ba7b1c81aebf5 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 30 Jan 2013 18:18:19 -0500 Subject: [PATCH 21/28] Correctly rewrite users --- js/id/ui/contributors.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index c0036a677..c64d8a2ac 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -16,7 +16,7 @@ iD.ui.contributors = function(map) { var l = selection .select('.contributor-list') .selectAll('a.user-link') - .data(subset); + .data(subset, function(d) { return d; }); l.enter().append('a') @@ -43,6 +43,10 @@ iD.ui.contributors = function(map) { ext[1][0], ext[1][1]]; }) .text(' and ' + (u.length - limit) + ' others'); + } else { + selection + .select('.contributor-count') + .html(''); } if (!u.length) { From 1b495870c0424b53b6f9cbbb12522316497158c8 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Wed, 30 Jan 2013 19:09:10 -0500 Subject: [PATCH 22/28] grammar for tooltips, keybinding hints, other tooltip fixes. --- css/app.css | 42 +++++++++++++++++++++++++++------ js/id/behavior/drag_midpoint.js | 2 +- js/id/behavior/draw_way.js | 4 ++-- js/id/id.js | 8 +++---- js/id/modes/add_point.js | 2 +- js/id/modes/browse.js | 2 +- js/id/modes/draw_line.js | 2 +- js/id/modes/move_way.js | 4 ++-- js/id/modes/select.js | 4 ++-- js/id/operations/circularize.js | 4 ++-- js/id/operations/delete.js | 10 ++++---- js/id/operations/reverse.js | 4 ++-- js/id/operations/split.js | 4 ++-- js/id/operations/unjoin.js | 4 ++-- 14 files changed, 62 insertions(+), 34 deletions(-) diff --git a/css/app.css b/css/app.css index 4ec4bd91d..98ba49e57 100644 --- a/css/app.css +++ b/css/app.css @@ -1159,19 +1159,47 @@ a.success-action { border-width: 0 5px 5px; } +.Browse .tooltip { + left: -20px !important; } .Browse .tooltip .tooltip-arrow { - left: 30px; + left: 60px; +} + +.tooltip .keyhint-wrap { + padding: 5px 0 5px 0; } .tooltip .keyhint { - float: right; - background: #eee; + display: block; + color: #222; font-size: 10px; - padding: 0 4px; - background:#aaa; - color:#fff; + padding: 0px 7px; + text-transform: uppercase; + font-weight: bold; + display: inline-block; border-radius: 2px; - margin-left: 4px; + border: 1px solid #CCC; + position: relative; + z-index: 1; + text-align: left; + clear: both; +} + +.tooltip .keyhint .keyhint-label{ + display: inline-block; +} + +.tooltip .keyhint::after { + content: ""; + position: absolute; + border-radius: 2px; + height: 10px; + width: 100%; + z-index: 0; + bottom: -4px; + left: -1px; + border: 1px solid #CCC; + border-top: 0; } .tail { diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index 72691a512..bd1a40bd9 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -25,7 +25,7 @@ iD.behavior.DragMidpoint = function(mode) { .on('end', function() { history.replace( iD.actions.Noop(), - 'added a node to a way'); + 'Added a node to a way.'); }); return behavior; diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 884d72ddb..21c13d2fa 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -4,7 +4,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { controller = mode.controller, way = mode.history.graph().entity(wayId), finished = false, - annotation = 'added to a way', + annotation = 'Added to a way.', draw = iD.behavior.Draw(map); var node = iD.Node({loc: map.mouseCoordinates()}), @@ -153,7 +153,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { drawWay.cancel = function() { history.perform( d3.functor(baseGraph), - 'cancelled drawing'); + 'Cancelled drawing.'); finished = true; controller.enter(iD.modes.Browse()); diff --git a/js/id/id.js b/js/id/id.js index ab6dd37f2..69e64e5fe 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -22,7 +22,7 @@ window.iD = function(container) { } function hintprefix(x, y) { - return '' + x + ' ' + y; + return '' + y + '' + '

' + x + '
'; } var m = container.append('div') @@ -44,10 +44,10 @@ window.iD = function(container) { .enter().append('button') .attr('tabindex', -1) .attr('class', function (mode) { return mode.title + ' add-button col3'; }) + .call(bootstrap.tooltip().placement('bottom').html(true)) .attr('data-original-title', function (mode) { return hintprefix(mode.key, mode.description); }) - .call(bootstrap.tooltip().placement('bottom').html(true)) .on('click.editor', function (mode) { controller.enter(mode); }); function disableTooHigh() { @@ -207,12 +207,12 @@ window.iD = function(container) { limiter.select('#undo') .property('disabled', !undo) - .attr('data-original-title', hintprefix('⌘Z', undo)) + .attr('data-original-title', hintprefix('⌘ + Z', undo)) .call(refreshTooltip); limiter.select('#redo') .property('disabled', !redo) - .attr('data-original-title', hintprefix('⌘⇧Z', redo)) + .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo)) .call(refreshTooltip); }); diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 723b1935b..1b9781a9d 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -21,7 +21,7 @@ iD.modes.AddPoint = function() { history.perform( iD.actions.AddNode(node), - 'added a point'); + 'Added a point.'); controller.enter(iD.modes.Select(node, true)); } diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 9782bac27..ab6bc80cb 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -3,7 +3,7 @@ iD.modes.Browse = function() { button: 'browse', id: 'browse', title: 'Browse', - description: 'Pan and zoom the map', + description: 'Pan and zoom the map.', key: 'b' }; diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 5dee157b3..ae54ab2e1 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -12,7 +12,7 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { headId = (direction === 'forward') ? way.last() : way.first(); behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph) - .annotation(way.isDegenerate() ? 'started a line' : 'continued a line'); + .annotation(way.isDegenerate() ? 'Started a line.' : 'Continued a line.'); var addNode = behavior.addNode; diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 1f559da82..18c6ee619 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -18,7 +18,7 @@ iD.modes.MoveWay = function(wayId) { history.perform( iD.actions.Noop(), - 'moved a way'); + 'Moved a way.'); function move() { var p = d3.mouse(selection.node()), @@ -29,7 +29,7 @@ iD.modes.MoveWay = function(wayId) { history.replace( iD.actions.MoveWay(wayId, delta, projection), - 'moved a way'); + 'Moved a way.'); } function finish() { diff --git a/js/id/modes/select.js b/js/id/modes/select.js index ad06bda35..1ddd4ffc4 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -14,7 +14,7 @@ iD.modes.Select = function(entity, initial) { if (!_.isEqual(entity.tags, tags)) { mode.history.perform( iD.actions.ChangeEntityTags(d.id, tags), - 'changed tags'); + 'Changed tags.'); } } @@ -114,7 +114,7 @@ iD.modes.Select = function(entity, initial) { history.perform( iD.actions.AddNode(node), iD.actions.AddWayNode(datum.id, node.id, choice.index), - 'added a point to a road'); + 'Added a point to a road.'); d3.event.preventDefault(); d3.event.stopPropagation(); diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index 49956430e..ce161aef4 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -10,12 +10,12 @@ iD.operations.Circularize = function(entityId, mode) { if (geometry === 'line') { history.perform( action, - 'made a line circular'); + 'Made a line circular.'); } else if (geometry === 'area') { history.perform( action, - 'made an area circular'); + 'Made an area circular.'); } }; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 03d788261..cc3750360 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -9,22 +9,22 @@ iD.operations.Delete = function(entityId, mode) { if (geometry === 'vertex') { history.perform( iD.actions.DeleteNode(entityId), - 'deleted a vertex'); + 'Deleted a vertex.'); } else if (geometry === 'point') { history.perform( iD.actions.DeleteNode(entityId), - 'deleted a point'); + 'Deleted a point.'); } else if (geometry === 'line') { history.perform( iD.actions.DeleteWay(entityId), - 'deleted a line'); + 'Deleted a line.'); } else if (geometry === 'area') { history.perform( iD.actions.DeleteWay(entityId), - 'deleted an area'); + 'Deleted an area.'); } }; @@ -41,7 +41,7 @@ iD.operations.Delete = function(entityId, mode) { operation.id = "delete"; operation.key = "⌫"; operation.title = "Delete"; - operation.description = "Remove this from the map"; + operation.description = "Remove this from the map."; return operation; }; diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js index b36dfd60c..62fd8810b 100644 --- a/js/id/operations/reverse.js +++ b/js/id/operations/reverse.js @@ -4,7 +4,7 @@ iD.operations.Reverse = function(entityId, mode) { var operation = function() { history.perform( iD.actions.ReverseWay(entityId), - 'reversed a line'); + 'Reversed a line.'); }; operation.available = function() { @@ -20,7 +20,7 @@ iD.operations.Reverse = function(entityId, mode) { operation.id = "reverse"; operation.key = "V"; operation.title = "Reverse"; - operation.description = "Make this way go in the opposite direction"; + operation.description = "Make this way go in the opposite direction."; return operation; }; diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 3dcab7481..40aa55fd0 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -3,7 +3,7 @@ iD.operations.Split = function(entityId, mode) { action = iD.actions.SplitWay(entityId); var operation = function() { - history.perform(action, 'split a way'); + history.perform(action, 'Split a way.'); }; operation.available = function() { @@ -20,7 +20,7 @@ iD.operations.Split = function(entityId, mode) { operation.id = "split"; operation.key = "X"; operation.title = "Split"; - operation.description = "Split this into two ways at this point"; + operation.description = "Split this into two ways at this point."; return operation; }; diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js index c18de3029..f38333bb4 100644 --- a/js/id/operations/unjoin.js +++ b/js/id/operations/unjoin.js @@ -3,7 +3,7 @@ iD.operations.Unjoin = function(entityId, mode) { action = iD.actions.UnjoinNode(entityId); var operation = function() { - history.perform(action, 'unjoined lines'); + history.perform(action, 'Unjoined lines.'); }; operation.available = function() { @@ -20,7 +20,7 @@ iD.operations.Unjoin = function(entityId, mode) { operation.id = "unjoin"; operation.key = "⇧-J"; operation.title = "Unjoin"; - operation.description = "Disconnect these ways from each other"; + operation.description = "Disconnect these ways from each other."; return operation; }; From 6ae360a533115cefc2edcbec9475f46598145eda Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 08:28:10 -0500 Subject: [PATCH 23/28] These tests now pass --- test/spec/actions/delete_way.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/spec/actions/delete_way.js b/test/spec/actions/delete_way.js index 863b0fa2f..d550803fa 100644 --- a/test/spec/actions/delete_way.js +++ b/test/spec/actions/delete_way.js @@ -31,8 +31,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(node.id)).not.to.be.undefined; }); - // See #508 - xit("deletes multiple member nodes", function () { + it("deletes multiple member nodes", function () { var a = iD.Node(), b = iD.Node(), way = iD.Way({nodes: [a.id, b.id]}), @@ -42,7 +41,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(b.id)).to.be.undefined; }); - xit("deletes a circular way's start/end node", function () { + it("deletes a circular way's start/end node", function () { var a = iD.Node(), b = iD.Node(), c = iD.Node(), From 208ed6a2ed9425a8c2be4c0e8d6a179ede9c4bbe Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 08:30:46 -0500 Subject: [PATCH 24/28] Move iD.geo tests to own file --- test/index.html | 1 + test/index_packaged.html | 1 + test/spec/geo.js | 96 +++++++++++++++++++++++++++++++++++ test/spec/util.js | 107 ++------------------------------------- 4 files changed, 102 insertions(+), 103 deletions(-) create mode 100644 test/spec/geo.js diff --git a/test/index.html b/test/index.html index 731800f62..d8d3f8f6f 100644 --- a/test/index.html +++ b/test/index.html @@ -185,6 +185,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index aba99ba04..719a5a999 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -79,6 +79,7 @@ + diff --git a/test/spec/geo.js b/test/spec/geo.js new file mode 100644 index 000000000..113d4795d --- /dev/null +++ b/test/spec/geo.js @@ -0,0 +1,96 @@ +describe('iD.geo', function() { + describe('.roundCoords', function() { + expect(iD.geo.roundCoords([0.1, 1])).to.eql([0, 1]); + expect(iD.geo.roundCoords([0, 1])).to.eql([0, 1]); + expect(iD.geo.roundCoords([0, 1.1])).to.eql([0, 1]); + }); + + describe('.interp', function() { + it('interpolates halfway', function() { + var a = [0, 0], + b = [10, 10]; + expect(iD.geo.interp(a, b, 0.5)).to.eql([5, 5]); + }); + it('interpolates to one side', function() { + var a = [0, 0], + b = [10, 10]; + expect(iD.geo.interp(a, b, 0)).to.eql([0, 0]); + }); + }); + + describe('.dist', function() { + it('distance between two same points is zero', function() { + var a = [0, 0], + b = [0, 0]; + expect(iD.geo.dist(a, b)).to.eql(0); + }); + it('a straight 10 unit line is 10', function() { + var a = [0, 0], + b = [10, 0]; + expect(iD.geo.dist(a, b)).to.eql(10); + }); + it('a pythagorean triangle is right', function() { + var a = [0, 0], + b = [4, 3]; + expect(iD.geo.dist(a, b)).to.eql(5); + }); + }); + + describe('.pointInPolygon', function() { + it('says a point in a polygon is on a polygon', function() { + var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]; + var point = [0.5, 0.5]; + expect(iD.geo.pointInPolygon(point, poly)).to.be.true; + }); + it('says a point outside of a polygon is outside', function() { + var poly = [ + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0]]; + var point = [0.5, 1.5]; + expect(iD.geo.pointInPolygon(point, poly)).to.be.false; + }); + }); + + describe('.polygonContainsPolygon', function() { + it('says a polygon in a polygon is in', function() { + var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]; + expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.true; + }); + it('says a polygon outside of a polygon is out', function() { + var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]]; + expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.false; + }); + }); + + describe('.polygonIntersectsPolygon', function() { + it('says a polygon in a polygon intersects it', function() { + var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]; + expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true; + }); + + it('says a polygon that partially intersects does', function() { + var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]]; + expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true; + }); + + it('says totally disjoint polygons do not intersect', function() { + var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; + var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]]; + expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.false; + }); + }); + + describe('.pathLength', function() { + it('calculates a simple path length', function() { + var path = [[0, 0], [0, 1], [3, 5]]; + expect(iD.geo.pathLength(path)).to.eql(6); + }); + }); +}); diff --git a/test/spec/util.js b/test/spec/util.js index 9d5cf082b..de3fab735 100644 --- a/test/spec/util.js +++ b/test/spec/util.js @@ -1,123 +1,24 @@ describe('iD.Util', function() { - var util; - - it('#trueObj', function() { + it('.trueObj', function() { expect(iD.util.trueObj(['a', 'b', 'c'])).to.eql({ a: true, b: true, c: true }); expect(iD.util.trueObj([])).to.eql({}); }); - it('#tagText', function() { + it('.tagText', function() { expect(iD.util.tagText({})).to.eql(''); expect(iD.util.tagText({tags:{foo:'bar'}})).to.eql('foo: bar'); expect(iD.util.tagText({tags:{foo:'bar',two:'three'}})).to.eql('foo: bar\ntwo: three'); }); - it('#stringQs', function() { + it('.stringQs', function() { expect(iD.util.stringQs('foo=bar')).to.eql({foo: 'bar'}); expect(iD.util.stringQs('foo=bar&one=2')).to.eql({foo: 'bar', one: '2' }); expect(iD.util.stringQs('')).to.eql({}); }); - it('#qsString', function() { + it('.qsString', function() { expect(iD.util.qsString({ foo: 'bar' })).to.eql('foo=bar'); expect(iD.util.qsString({ foo: 'bar', one: 2 })).to.eql('foo=bar&one=2'); expect(iD.util.qsString({})).to.eql(''); }); - - describe('geo', function() { - describe('#roundCoords', function() { - expect(iD.geo.roundCoords([0.1, 1])).to.eql([0, 1]); - expect(iD.geo.roundCoords([0, 1])).to.eql([0, 1]); - expect(iD.geo.roundCoords([0, 1.1])).to.eql([0, 1]); - }); - - describe('#interp', function() { - it('interpolates halfway', function() { - var a = [0, 0], - b = [10, 10]; - expect(iD.geo.interp(a, b, 0.5)).to.eql([5, 5]); - }); - it('interpolates to one side', function() { - var a = [0, 0], - b = [10, 10]; - expect(iD.geo.interp(a, b, 0)).to.eql([0, 0]); - }); - }); - - describe('#dist', function() { - it('distance between two same points is zero', function() { - var a = [0, 0], - b = [0, 0]; - expect(iD.geo.dist(a, b)).to.eql(0); - }); - it('a straight 10 unit line is 10', function() { - var a = [0, 0], - b = [10, 0]; - expect(iD.geo.dist(a, b)).to.eql(10); - }); - it('a pythagorean triangle is right', function() { - var a = [0, 0], - b = [4, 3]; - expect(iD.geo.dist(a, b)).to.eql(5); - }); - }); - - describe('#pointInPolygon', function() { - it('says a point in a polygon is on a polygon', function() { - var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]; - var point = [0.5, 0.5]; - expect(iD.geo.pointInPolygon(point, poly)).to.be.true; - }); - it('says a point outside of a polygon is outside', function() { - var poly = [ - [0, 0], - [0, 1], - [1, 1], - [1, 0], - [0, 0]]; - var point = [0.5, 1.5]; - expect(iD.geo.pointInPolygon(point, poly)).to.be.false; - }); - }); - - describe('#polygonContainsPolygon', function() { - it('says a polygon in a polygon is in', function() { - var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; - var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]; - expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.true; - }); - it('says a polygon outside of a polygon is out', function() { - var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; - var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]]; - expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.false; - }); - }); - - describe('#polygonIntersectsPolygon', function() { - it('says a polygon in a polygon intersects it', function() { - var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; - var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]]; - expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true; - }); - - it('says a polygon that partially intersects does', function() { - var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; - var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]]; - expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true; - }); - - it('says totally disjoint polygons do not intersect', function() { - var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]]; - var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]]; - expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.false; - }); - }); - - describe('#pathLength', function() { - it('calculates a simple path length', function() { - var path = [[0, 0], [0, 1], [3, 5]]; - expect(iD.geo.pathLength(path)).to.eql(6); - }); - }); - }); }); From edb3a3b68210e26b3eb0548dfae2dceb4ee00180 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 31 Jan 2013 10:03:43 -0500 Subject: [PATCH 25/28] Entire notice is clickable. Fixes #262 --- css/app.css | 16 ++++++++++++---- js/id/ui/notice.js | 21 +++++++++------------ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/css/app.css b/css/app.css index 98ba49e57..4c149843d 100644 --- a/css/app.css +++ b/css/app.css @@ -1052,7 +1052,8 @@ a.success-action { text-align:center; } -.notice .notice-inner { +.notice .zoom-to { + width:100%; height: 40px; border-radius: 5px; line-height: 40px; @@ -1061,12 +1062,19 @@ a.success-action { opacity: 0.9; } -.notice .notice-inner .zoom-to { - width:40px; - height:40px; +.notice .zoom-to:hover { + background: #bde5aa; +} + +.notice .zoom-to .icon { + margin-top:10px; margin-right:10px; } +.icon.zoom-in-invert { + background-position: -240px -40px; +} + /* Tooltips ------------------------------------------------------- */ diff --git a/js/id/ui/notice.js b/js/id/ui/notice.js index bcf44b7c9..12b1a2c09 100644 --- a/js/id/ui/notice.js +++ b/js/id/ui/notice.js @@ -4,20 +4,17 @@ iD.ui.notice = function(selection) { notice = {}; var div = selection.append('div') - .attr('class', 'notice') - .append('div') - .attr('class', 'notice-inner'); + .attr('class', 'notice'); - div.append('button') - .attr('class', 'zoom-to') - .on('click', function() { - event.zoom(); - }) - .append('span') - .attr('class', 'icon invert zoom-in'); + var button = div.append('button') + .attr('class', 'zoom-to notice') + .on('click', event.zoom); - div.append('span') - .attr('class', 'notice-text') + button.append('span') + .attr('class', 'icon zoom-in-invert'); + + button.append('span') + .attr('class', 'label') .text(t('zoom_in_edit')); notice.message = function(_) { From 5c1d3cc18e41b4aba60d6f4058da47da0a818489 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 10:34:57 -0500 Subject: [PATCH 26/28] Preserve label tree between partial redraws --- js/id/renderer/map.js | 2 +- js/id/svg/labels.js | 29 ++++++++++++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 0d1532948..08c18e915 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -112,7 +112,7 @@ iD.Map = function() { .call(areas, graph, all, filter) .call(multipolygons, graph, all, filter) .call(midpoints, graph, all, filter) - .call(labels, graph, all, filter, dimensions); + .call(labels, graph, all, filter, dimensions, !difference); } dispatch.drawn(map); } diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 2fcc2fcc1..5f300f147 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -183,14 +183,26 @@ iD.svg.Labels = function(projection) { } - return function drawLabels(surface, graph, entities, filter, dimensions) { + var rtree = new RTree(), + rectangles = {}; + + return function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) { + - var rtree = new RTree(); var hidePoints = !d3.select('.node.point').node(); var labelable = [], i, k, entity; for (i = 0; i < label_stack.length; i++) labelable.push([]); + if (fullRedraw) { + rtree = new RTree(); + rectangles = {}; + } else { + for (i = 0; i < entities.length; i++) { + rtree.remove(rectangles[entities[i].id], entities[i].id); + } + } + // Split entities into groups specified by label_stack for (i = 0; i < entities.length; i++) { entity = entities[i]; @@ -252,7 +264,7 @@ iD.svg.Labels = function(projection) { textAnchor: offset[2] }; var rect = new RTree.Rectangle(p.x - m, p.y - m, width + 2*m, height + 2*m); - if (tryInsert(rect)) return p; + if (tryInsert(rect, entity.id)) return p; } @@ -275,7 +287,7 @@ iD.svg.Labels = function(projection) { Math.abs(sub[0][1] - sub[sub.length - 1][1]) + 30 ); if (rev) sub = sub.reverse(); - if (tryInsert(rect)) return { + if (tryInsert(rect, entity.id)) return { 'font-size': height + 2, lineString: lineString(sub), startOffset: offset + '%' @@ -298,16 +310,19 @@ iD.svg.Labels = function(projection) { height: height }; var rect = new RTree.Rectangle(p.x - width/2, p.y, width, height); - if (tryInsert(rect)) return p; + if (tryInsert(rect, entity.id)) return p; } - function tryInsert(rect) { + function tryInsert(rect, id) { // Check that label is visible if (rect.x1 < 0 || rect.y1 < 0 || rect.x2 > dimensions[0] || rect.y2 > dimensions[1]) return false; var v = rtree.search(rect, true).length === 0; - if (v) rtree.insert(rect); + if (v) { + rtree.insert(rect, id); + rectangles[id] = rect; + } return v; } From c69345e0398cf7c94b633dfcc5f0db621bf3a63d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 31 Jan 2013 10:50:14 -0500 Subject: [PATCH 27/28] Fix comparisons in `subpath` --- js/id/svg/labels.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 5f300f147..3962c4915 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -156,7 +156,7 @@ iD.svg.Labels = function(projection) { for (var i = 0; i < nodes.length - 1; i++) { var current = segmentLength(i); var portion; - if (!start && sofar + current > from) { + if (!start && sofar + current >= from) { portion = (from - sofar) / current; start = [ nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]), @@ -164,7 +164,7 @@ iD.svg.Labels = function(projection) { ]; i0 = i + 1; } - if (!end && sofar + current > to) { + if (!end && sofar + current >= to) { portion = (to - sofar) / current; end = [ nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]), From 2c2efe3b65ea3ccb37a4c113defc9940285c1962 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 31 Jan 2013 11:02:41 -0500 Subject: [PATCH 28/28] Background refinements. Fixes #561, fixes #456 --- js/id/renderer/background.js | 136 ++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 6476e7a28..752853e6b 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -1,4 +1,10 @@ iD.Background = function() { + + var deviceRatio = (window.devicePixelRatio && + window.devicePixelRatio === 2) ? 0.5 : 1; + // tileSize = (deviceRatio === 0.5) ? [128,128] : [256,256]; + tileSize = [256, 256]; + var tile = d3.geo.tile(), projection, cache = {}, @@ -13,7 +19,12 @@ iD.Background = function() { '-o-transform-origin:0 0;' + '-webkit-user-select: none;' + '-webkit-user-drag: none;' + - '-moz-user-drag: none;'; + '-moz-user-drag: none;' + + 'opacity:0;'; + + function tileSizeAtZoom(d, z) { + return Math.ceil(tileSize[0] * Math.pow(2, z - d[2])) / tileSize[0]; + } function atZoom(t, distance) { var power = Math.pow(2, distance); @@ -21,108 +32,101 @@ iD.Background = function() { Math.floor(t[0] * power), Math.floor(t[1] * power), t[2] + distance]; - az.push(source(az)); return az; } - function upZoom(t, distance) { - var az = atZoom(t, distance), tiles = []; - for (var x = 0; x < 2; x++) { - for (var y = 0; y < 2; y++) { - var up = [az[0] + x, az[1] + y, az[2]]; - up.push(source(up)); - tiles.push(up); - } - } - return tiles; - } - - function tileSize(d, z) { - return Math.ceil(256 * Math.pow(2, z - d[2])) / 256; - } - function lookUp(d) { for (var up = -1; up > -d[2]; up--) { if (cache[atZoom(d, up)] !== false) return atZoom(d, up); } } + function uniqueBy(a, n) { + var o = [], seen = {}; + for (var i = 0; i < a.length; i++) { + if (seen[a[i][n]] === undefined) { + o.push(a[i]); + seen[a[i][n]] = true; + } + } + return o; + } + + function addSource(d) { + d.push(source(d)); + return d; + } + // derive the tiles onscreen, remove those offscreen and position tiles // correctly for the currentstate of `projection` function background() { - var tiles = tile + var sel = this, + tiles = tile .scale(projection.scale()) .scaleExtent(source.scaleExtent || [1, 17]) .translate(projection.translate())(), + requests = [], scaleExtent = tile.scaleExtent(), z = Math.max(Math.log(projection.scale()) / Math.log(2) - 8, 0), - rz = Math.max(scaleExtent[0], Math.min(scaleExtent[1], Math.floor(z))), - ts = 256 * Math.pow(2, z - rz), + rz = Math.max(scaleExtent[0], + Math.min(scaleExtent[1], Math.floor(z))), + ts = tileSize[0] * Math.pow(2, z - rz), tile_origin = [ projection.scale() / 2 - projection.translate()[0], - projection.scale() / 2 - projection.translate()[1]], - ups = {}; + projection.scale() / 2 - projection.translate()[1]]; tiles.forEach(function(d) { - - if (cache[d] === true) { - d.push(source(d)); - } else if (cache[d] === false && - cache[atZoom(d, -1)] !== false && - !ups[atZoom(d, -1)]) { - - ups[atZoom(d, -1)] = true; - tiles.push(atZoom(d, -1)); - - } else if (cache[d] === undefined && - lookUp(d)) { - - var upTile = lookUp(d); - if (!ups[upTile]) { - ups[upTile] = true; - tiles.push(upTile); - } - - } else if (cache[d] === undefined || - cache[d] === false) { - upZoom(d, 1).forEach(function(u) { - if (cache[u] && !ups[u]) { - ups[u] = true; - tiles.push(u); - } - }); + addSource(d); + requests.push(d); + if (!cache[d[3]] && lookUp(d)) { + requests.push(addSource(lookUp(d))); } }); - var image = this - .selectAll('img') - .data(tiles, function(d) { return d; }); + requests = uniqueBy(requests, 3); function load(d) { - cache[d.slice(0, 3)] = true; - d3.select(this).on('load', null); + cache[d[3]] = true; + d3.select(this) + .on('load', null) + .transition() + .style('opacity', 1); + background.apply(sel); } function error(d) { - cache[d.slice(0, 3)] = false; + cache[d[3]] = false; + d3.select(this).on('load', null); d3.select(this).remove(); + background.apply(sel); } + function imageTransform(d) { + var _ts = tileSize[0] * Math.pow(2, z - d[2]); + var scale = tileSizeAtZoom(d, z); + return 'translate(' + + (Math.round((d[0] * _ts) - tile_origin[0]) + offset[0]) + 'px,' + + (Math.round((d[1] * _ts) - tile_origin[1]) + offset[1]) + 'px)' + + 'scale(' + scale + ',' + scale + ')'; + } + + var image = this + .selectAll('img') + .data(requests, function(d) { return d[3]; }); + + image.exit() + .style(transformProp, imageTransform) + .transition() + .style('opacity', 0) + .remove(); + image.enter().append('img') .attr('style', imgstyle) .attr('src', function(d) { return d[3]; }) .on('error', error) .on('load', load); - - image.exit().remove(); - - image.style(transformProp, function(d) { - var _ts = 256 * Math.pow(2, z - d[2]); - var scale = tileSize(d, z); - return 'translate(' + - (Math.round((d[0] * _ts) - tile_origin[0]) + offset[0]) + 'px,' + - (Math.round((d[1] * _ts) - tile_origin[1]) + offset[1]) + 'px) scale(' + scale + ',' + scale + ')'; - }); + + image.style(transformProp, imageTransform); if (Object.keys(cache).length > 100) cache = {}; }