diff --git a/Makefile b/Makefile index b14e6f989..9869abb14 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ all: \ .INTERMEDIATE iD.js: \ js/lib/d3.v3.js \ js/lib/d3.geo.tile.js \ + js/lib/d3.keybinding.js \ js/lib/d3.one.js \ js/lib/d3.size.js \ js/lib/d3.typeahead.js \ @@ -26,6 +27,7 @@ all: \ js/id/oauth.js \ js/id/taginfo.js \ js/id/util.js \ + js/id/actions.js \ js/id/actions/*.js \ js/id/modes.js \ js/id/modes/*.js \ diff --git a/img/source/design.svg b/img/source/design.svg new file mode 100644 index 000000000..0d245cfc2 --- /dev/null +++ b/img/source/design.svg @@ -0,0 +1,1797 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + Line + + + + + + + Line + + Area + + Commit + + + + + Add a point of interest to the map. Things like restaurants, monuments, and post boxes are points on the map. + + + + i + + + + + + + + + + + + + + + + View + Edit + History + Export + + + + + + + + + + + + + + + + + + + + + Welcome, samanbb + + home + + inbox + + logout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this thing gets bigger + + + + + + + + + + + + + + + + + + + + + + + + + + + Point + Point + + + Contribute to OpenStreetMap by adding points, lines or areas. + Welcome to the iD editor + + + + + + + + + + Point + Point + + + + + Restaurants, monuments, and postal boxes are points. Add a point of interest to the map. + If you don't see an appropriate label your point, make a new one. Did you know? + + Restaurants, monuments, and postal boxes are points. Add a point of interest to the map. + + + + + + + + diff --git a/img/source/renders/basic-ui.png b/img/source/renders/basic-ui.png new file mode 100644 index 000000000..aa6acfccd Binary files /dev/null and b/img/source/renders/basic-ui.png differ diff --git a/index.html b/index.html index ab9e86956..a674a2e7c 100644 --- a/index.html +++ b/index.html @@ -40,7 +40,18 @@ - + + + + + + + + + + + + diff --git a/js/id/actions.js b/js/id/actions.js new file mode 100644 index 000000000..481fa8285 --- /dev/null +++ b/js/id/actions.js @@ -0,0 +1 @@ +iD.actions = {}; diff --git a/js/id/actions/actions.js b/js/id/actions/actions.js deleted file mode 100644 index 593e33d3f..000000000 --- a/js/id/actions/actions.js +++ /dev/null @@ -1,80 +0,0 @@ -iD.actions = {}; - -iD.actions.noop = function() { - return function(graph) { - return graph; - }; -}; - -// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/AddCommand.java -iD.actions.addNode = function(node) { - return function(graph) { - return graph.replace(node, 'added a place'); - }; -}; - -iD.actions.startWay = function(way) { - return function(graph) { - return graph.replace(way, 'started a road'); - }; -}; - -// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as -iD.actions.remove = function(node) { - return function(graph) { - return graph.remove(node, 'removed a feature'); - }; -}; - -// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as -iD.actions.addWayNode = function(way, node, index) { - return function(graph) { - var nodes = way.nodes.slice(); - nodes.splice(index || nodes.length, 0, node.id); - return graph.replace(way.update({nodes: nodes})).replace(node, 'added to a road'); - }; -}; - -iD.actions.removeWayNode = function(way, node) { - return function(graph) { - var nodes = _.without(way.nodes, node.id); - return graph.replace(way.update({nodes: nodes}), 'removed from a road'); - }; -}; - -// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as -iD.actions.changeWayDirection = function(way) { - return function(graph) { - return graph.replace(way.update({ - nodes: way.nodes.slice() - }), 'changed way direction'); - }; -}; - -iD.actions.changeTags = function(node, tags) { - return function(graph) { - return graph.replace(node.update({ - tags: tags - }), 'changed tags'); - }; -}; - -// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java -// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as -iD.actions.move = function(entity, loc) { - return function(graph) { - return graph.replace(entity.update({loc: loc}), 'moved an element'); - }; -}; - -iD.actions.addTemporary = function(node) { - return function(graph) { - return graph.replace(node); - }; -}; - -iD.actions.removeTemporary = function(node) { - return function(graph) { - return graph.remove(node); - }; -}; diff --git a/js/id/actions/add_node.js b/js/id/actions/add_node.js new file mode 100644 index 000000000..291811409 --- /dev/null +++ b/js/id/actions/add_node.js @@ -0,0 +1,6 @@ +// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/AddCommand.java +iD.actions.addNode = function(node) { + return function(graph) { + return graph.replace(node, 'added a place'); + }; +}; diff --git a/js/id/actions/add_way_node.js b/js/id/actions/add_way_node.js new file mode 100644 index 000000000..bc4b7f347 --- /dev/null +++ b/js/id/actions/add_way_node.js @@ -0,0 +1,8 @@ +// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as +iD.actions.addWayNode = function(way, node, index) { + return function(graph) { + var nodes = way.nodes.slice(); + nodes.splice(index || nodes.length, 0, node.id); + return graph.replace(way.update({nodes: nodes})).replace(node, 'added to a road'); + }; +}; diff --git a/js/id/actions/change_tags.js b/js/id/actions/change_tags.js new file mode 100644 index 000000000..81b99760e --- /dev/null +++ b/js/id/actions/change_tags.js @@ -0,0 +1,7 @@ +iD.actions.changeTags = function(node, tags) { + return function(graph) { + return graph.replace(node.update({ + tags: tags + }), 'changed tags'); + }; +}; diff --git a/js/id/actions/change_way_direction.js b/js/id/actions/change_way_direction.js new file mode 100644 index 000000000..cfa637336 --- /dev/null +++ b/js/id/actions/change_way_direction.js @@ -0,0 +1,8 @@ +// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as +iD.actions.changeWayDirection = function(way) { + return function(graph) { + return graph.replace(way.update({ + nodes: way.nodes.slice() + }), 'changed way direction'); + }; +}; diff --git a/js/id/actions/delete_node.js b/js/id/actions/delete_node.js new file mode 100644 index 000000000..1d3737a86 --- /dev/null +++ b/js/id/actions/delete_node.js @@ -0,0 +1,16 @@ +// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as +iD.actions.DeleteNode = function(node) { + return function(graph) { + graph.parentWays(node.id) + .forEach(function(parent) { + graph = iD.actions.removeWayNode(parent, node)(graph); + }); + + graph.parentRelations(node.id) + .forEach(function(parent) { + graph = iD.actions.removeRelationEntity(parent, node)(graph); + }); + + return graph.remove(node, 'removed a node'); + }; +}; diff --git a/js/id/actions/delete_way.js b/js/id/actions/delete_way.js new file mode 100644 index 000000000..157a190a7 --- /dev/null +++ b/js/id/actions/delete_way.js @@ -0,0 +1,21 @@ +// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as +iD.actions.DeleteWay = function(way) { + return function(graph) { + graph.parentRelations(way.id) + .forEach(function(parent) { + graph = iD.actions.removeRelationEntity(parent, way)(graph); + }); + + way.nodes.forEach(function (id) { + var node = graph.entity(id); + + graph = iD.actions.removeWayNode(way, node)(graph); + + if (!graph.parentWays(id).length && !graph.parentRelations(id).length) { + graph = graph.remove(node); + } + }); + + return graph.remove(way, 'removed a way'); + }; +}; diff --git a/js/id/actions/move.js b/js/id/actions/move.js new file mode 100644 index 000000000..d67af763f --- /dev/null +++ b/js/id/actions/move.js @@ -0,0 +1,7 @@ +// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java +// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as +iD.actions.move = function(entity, loc) { + return function(graph) { + return graph.replace(entity.update({loc: loc}), 'moved an element'); + }; +}; diff --git a/js/id/actions/noop.js b/js/id/actions/noop.js new file mode 100644 index 000000000..ddae5dde5 --- /dev/null +++ b/js/id/actions/noop.js @@ -0,0 +1,5 @@ +iD.actions.noop = function() { + return function(graph) { + return graph; + }; +}; diff --git a/js/id/actions/remove_relation_entity.js b/js/id/actions/remove_relation_entity.js new file mode 100644 index 000000000..4a9fd1c70 --- /dev/null +++ b/js/id/actions/remove_relation_entity.js @@ -0,0 +1,6 @@ +iD.actions.removeRelationEntity = function(relation, entity) { + return function(graph) { + var members = _.without(relation.members, entity.id); + return graph.replace(relation.update({members: members}), 'removed from a relation'); + }; +}; diff --git a/js/id/actions/remove_way_node.js b/js/id/actions/remove_way_node.js new file mode 100644 index 000000000..16f9883bb --- /dev/null +++ b/js/id/actions/remove_way_node.js @@ -0,0 +1,6 @@ +iD.actions.removeWayNode = function(way, node) { + return function(graph) { + var nodes = _.without(way.nodes, node.id); + return graph.replace(way.update({nodes: nodes}), 'removed from a road'); + }; +}; diff --git a/js/id/actions/start_way.js b/js/id/actions/start_way.js new file mode 100644 index 000000000..8e7ea7631 --- /dev/null +++ b/js/id/actions/start_way.js @@ -0,0 +1,5 @@ +iD.actions.startWay = function(way) { + return function(graph) { + return graph.replace(way, 'started a road'); + }; +}; diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js index 47487cc91..a10da5d3a 100644 --- a/js/id/graph/entity.js +++ b/js/id/graph/entity.js @@ -12,6 +12,9 @@ iD.Entity = function(a, b, c) { if (iD.debug) { Object.freeze(this); Object.freeze(this.tags); + + if (this.nodes) Object.freeze(this.nodes); + if (this.members) Object.freeze(this.members); } }; @@ -30,11 +33,11 @@ iD.Entity.prototype = { }; iD.Node = function(attrs) { - return iD.Entity({tags: {}}, attrs || {}, {type: 'node'}); + return iD.Entity(attrs || {}, {type: 'node'}); }; iD.Way = function(attrs) { - return iD.Entity({tags: {}, nodes: []}, attrs || {}, {type: 'way'}); + return iD.Entity({nodes: []}, attrs || {}, {type: 'way'}); }; iD.Way.isOneWay = function(d) { @@ -50,5 +53,5 @@ iD.Way.isArea = function(d) { }; iD.Relation = function(attrs) { - return iD.Entity({tags: {}}, attrs || {}, {type: 'relation'}); + return iD.Entity({members: []}, attrs || {}, {type: 'relation'}); }; diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index c829f9a60..a97356ff1 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -1,7 +1,15 @@ iD.Graph = function(entities, annotation) { if (!(this instanceof iD.Graph)) return new iD.Graph(entities, annotation); - this.entities = entities || {}; + if (_.isArray(entities)) { + this.entities = {}; + for (var i = 0; i < entities.length; i++) { + this.entities[entities[i].id] = entities[i]; + } + } else { + this.entities = entities || {}; + } + this.annotation = annotation; if (iD.debug) { @@ -15,11 +23,17 @@ iD.Graph.prototype = { return this.entities[id]; }, - parents: function(id) { + parentWays: function(id) { // This is slow and a bad hack. return _.filter(this.entities, function(e) { - if (e.type !== 'way') return false; - return e.nodes.indexOf(id) !== -1; + return e.type === 'way' && e.nodes.indexOf(id) !== -1; + }); + }, + + parentRelations: function(id) { + // This is slow and a bad hack. + return _.filter(this.entities, function(e) { + return e.type === 'relation' && e.members.indexOf(id) !== -1; }); }, diff --git a/js/id/modes/add_road.js b/js/id/modes/add_road.js index 5887959fd..b2a89cc62 100644 --- a/js/id/modes/add_road.js +++ b/js/id/modes/add_road.js @@ -22,7 +22,7 @@ iD.modes.AddRoad = function() { node = datum; var id = datum.id; - var parents = mode.history.graph().parents(id); + var parents = mode.history.graph().parentWays(id); if (parents.length) { if (parents[0].nodes[0] === id) { way = parents[0]; diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 31c121162..ca1352432 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -27,8 +27,7 @@ iD.modes.DrawArea = function(way_id) { mode.history.replace(iD.actions.addWayNode(way, mode.history.graph().entity(way.nodes[0]))); - delete way.tags.elastic; - mode.history.perform(iD.actions.changeTags(way, way.tags)); + mode.history.perform(iD.actions.changeTags(way, _.omit(way.tags, 'elastic'))); // End by clicking on own tail return mode.controller.enter(iD.modes.Select(way)); diff --git a/js/id/modes/draw_road.js b/js/id/modes/draw_road.js index ec7e942fa..6b31ada42 100644 --- a/js/id/modes/draw_road.js +++ b/js/id/modes/draw_road.js @@ -37,8 +37,7 @@ iD.modes.DrawRoad = function(way_id, direction) { mode.history.graph().entity(lastNode), index)); } - delete way.tags.elastic; - mode.history.perform(iD.actions.changeTags(way, way.tags)); + mode.history.perform(iD.actions.changeTags(way, _.omit(way.tags, 'elastic'))); // End by clicking on own tail return mode.controller.enter(iD.modes.Select(way)); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index b650ac561..82fbd7204 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -15,7 +15,7 @@ iD.modes.Select = function (entity) { if (!dragging) { dragging = iD.util.trueObj([entity.id].concat( - _.pluck(mode.history.graph().parents(entity.id), 'id'))); + _.pluck(mode.history.graph().parentWays(entity.id), 'id'))); mode.history.perform(iD.actions.noop()); } @@ -33,13 +33,13 @@ iD.modes.Select = function (entity) { }); function remove() { - // Remove this node from any ways that is a member of - mode.history.graph().parents(entity.id) - .filter(function(d) { return d.type === 'way'; }) - .forEach(function(parent) { - mode.history.perform(iD.actions.removeWayNode(parent, entity)); - }); - mode.history.perform(iD.actions.remove(entity)); + switch (entity.type) { + case 'way': + mode.history.perform(iD.actions.DeleteWay(entity)); + case 'node': + mode.history.perform(iD.actions.DeleteNode(entity)); + } + mode.controller.exit(); } diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index fd018df45..85f9faaae 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -30,7 +30,7 @@ iD.Map = function() { } dragging = iD.util.trueObj([entity.id].concat( - _.pluck(history.graph().parents(entity.id), 'id'))); + _.pluck(history.graph().parentWays(entity.id), 'id'))); history.perform(iD.actions.noop()); } diff --git a/test/index.html b/test/index.html index 340b68903..8381f45fa 100644 --- a/test/index.html +++ b/test/index.html @@ -42,7 +42,18 @@ - + + + + + + + + + + + + @@ -72,6 +83,8 @@ + + diff --git a/test/index_packaged.html b/test/index_packaged.html index 3cab98faa..f1e70b069 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -26,6 +26,8 @@ + + diff --git a/test/spec/actions/delete_node.js b/test/spec/actions/delete_node.js new file mode 100644 index 000000000..82388b96c --- /dev/null +++ b/test/spec/actions/delete_node.js @@ -0,0 +1,24 @@ +describe("iD.actions.DeleteNode", function () { + it("removes the node from the graph", function () { + var node = iD.Node(), + action = iD.actions.DeleteNode(node), + graph = action(iD.Graph([node])); + expect(graph.entity(node.id)).to.be.undefined; + }); + + it("removes the node from parent ways", function () { + var node = iD.Node(), + way = iD.Way({nodes: [node.id]}), + action = iD.actions.DeleteNode(node), + graph = action(iD.Graph([node, way])); + expect(graph.entity(way.id).nodes).not.to.contain(node.id); + }); + + it("removes the node from parent relations", function () { + var node = iD.Node(), + relation = iD.Relation({members: [node.id]}), + action = iD.actions.DeleteNode(node), + graph = action(iD.Graph([node, relation])); + expect(graph.entity(relation.id).members).not.to.contain(node.id); + }); +}); diff --git a/test/spec/actions/delete_way.js b/test/spec/actions/delete_way.js new file mode 100644 index 000000000..ed86e45a0 --- /dev/null +++ b/test/spec/actions/delete_way.js @@ -0,0 +1,36 @@ +describe("iD.actions.DeleteWay", function () { + it("removes the way from the graph", function () { + var way = iD.Way(), + action = iD.actions.DeleteWay(way), + graph = action(iD.Graph([way])); + expect(graph.entity(way.id)).to.be.undefined; + }); + + it("removes a way from parent relations", function () { + var way = iD.Way(), + relation = iD.Relation({members: [way.id]}), + action = iD.actions.DeleteWay(way), + graph = action(iD.Graph([way, relation])); + expect(graph.entity(relation.id).members).not.to.contain(way.id); + }); + + it("deletes member nodes not referenced by another parent", function () { + var node = iD.Node(), + way = iD.Way({nodes: [node.id]}), + action = iD.actions.DeleteWay(way), + graph = action(iD.Graph([node, way])); + expect(graph.entity(node.id)).to.be.undefined; + }); + + it("does not delete member nodes referenced by another parent", function () { + var node = iD.Node(), + way1 = iD.Way({nodes: [node.id]}), + way2 = iD.Way({nodes: [node.id]}), + action = iD.actions.DeleteWay(way1), + graph = action(iD.Graph([node, way1, way2])); + expect(graph.entity(node.id)).not.to.be.undefined; + }); + + it("does not delete member nodes with interesting tags"); + it("registers member nodes with interesting tags as POIs"); +}); diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js index baa15cc4f..e28c782ef 100644 --- a/test/spec/graph/entity.js +++ b/test/spec/graph/entity.js @@ -131,6 +131,14 @@ describe('Relation', function () { expect(iD.Relation({id: 'r1234'}).modified()).not.to.be.ok; }); + it("defaults members to an empty array", function () { + expect(iD.Relation().members).to.eql([]); + }); + + it("sets members as specified", function () { + expect(iD.Relation({members: ["n-1"]}).members).to.eql(["n-1"]); + }); + it("defaults tags to an empty object", function () { expect(iD.Relation().tags).to.eql({}); }); diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index bd12753e0..31ef038b9 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -1,21 +1,19 @@ -describe('Graph', function() { +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); + }); - describe('Construction and access', function() { - it('entity', function() { - var entities = { 'n-1': { - type: 'node', - loc: [-80, 30], - id: 'n-1' - } - }; - var graph = iD.Graph(entities, 'first graph'); - expect(graph.entity('n-1')).to.equal(entities['n-1']); - }); + 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); + }); - it('annotation', function() { - var graph = iD.Graph({}, 'first graph'); - expect(graph.annotation).to.equal('first graph'); - }); + it('can be constructed with an annotation', function() { + var graph = iD.Graph({}, 'first graph'); + expect(graph.annotation).to.equal('first graph'); }); describe('operations', function() { @@ -50,6 +48,26 @@ describe('Graph', function() { }); }); + describe("#parentWays", function() { + it("returns an array of ways that contain the given node id", function () { + var node = iD.Node({id: "n1"}), + way = iD.Way({id: "w1", nodes: ["n1"]}), + graph = iD.Graph({n1: node, w1: way}); + expect(graph.parentWays("n1")).to.eql([way]); + expect(graph.parentWays("n2")).to.eql([]); + }); + }); + + describe("#parentRelations", function() { + it("returns an array of relations that contain the given entity id", function () { + var node = iD.Node({id: "n1"}), + relation = iD.Relation({id: "r1", members: ["n1"]}), + graph = iD.Graph({n1: node, r1: relation}); + expect(graph.parentRelations("n1")).to.eql([relation]); + expect(graph.parentRelations("n2")).to.eql([]); + }); + }); + describe("#fetch", function () { it("replaces node ids with references", function () { var node = iD.Node({id: "n1"}),