iD.actions.Join

This commit is contained in:
John Firebaugh
2013-02-01 17:24:50 -05:00
parent 20730e5f1a
commit a8410be6eb
6 changed files with 243 additions and 0 deletions

View File

@@ -81,6 +81,7 @@
<script src="js/id/actions/delete_relation.js"></script>
<script src="js/id/actions/delete_way.js"></script>
<script src='js/id/actions/disconnect.js'></script>
<script src='js/id/actions/join.js'></script>
<script src='js/id/actions/move_node.js'></script>
<script src='js/id/actions/move_way.js'></script>
<script src='js/id/actions/circularize.js'></script>

65
js/id/actions/join.js Normal file
View File

@@ -0,0 +1,65 @@
// Join ways at the end node they share.
//
// This is the inverse of `iD.actions.Split`.
//
// Reference:
// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeWaysAction.as
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java
//
iD.actions.Join = function(idA, idB) {
var action = function(graph) {
var a = graph.entity(idA),
b = graph.entity(idB),
nodes, tags;
if (a.first() === b.first()) {
// a <-- b ==> c
// Expected result:
// a <-- b <-- c
nodes = b.nodes.slice().reverse().concat(a.nodes.slice(1));
} else if (a.first() === b.last()) {
// a <-- b <== c
// Expected result:
// a <-- b <-- c
nodes = b.nodes.concat(a.nodes.slice(1));
} else if (a.last() === b.first()) {
// a --> b ==> c
// Expected result:
// a --> b --> c
nodes = a.nodes.concat(b.nodes.slice(1));
} else if (a.last() === b.last()) {
// a --> b <== c
// Expected result:
// a --> b --> c
nodes = a.nodes.concat(b.nodes.slice().reverse().slice(1));
}
graph.parentRelations(b)
.forEach(function (parent) {
var memberA = parent.memberById(idA),
memberB = parent.memberById(idB);
if (!memberA) {
graph = graph.replace(parent.addMember({id: idA, role: memberB.role}));
}
});
graph = graph.replace(a.mergeTags(b.tags).update({nodes: nodes}));
graph = iD.actions.DeleteWay(idB)(graph);
return graph;
};
action.enabled = function(graph) {
var a = graph.entity(idA),
b = graph.entity(idB);
return a.first() === b.first() ||
a.first() === b.last() ||
a.last() === b.first() ||
a.last() === b.last();
};
return action;
};

View File

@@ -1,5 +1,7 @@
// Split a way at the given node.
//
// This is the inverse of `iD.actions.Join`.
//
// For testing convenience, accepts an ID to assign to the new way.
// Normally, this will be undefined and the way will automatically
// be assigned a new ID.

View File

@@ -78,6 +78,7 @@
<script src="../js/id/actions/delete_relation.js"></script>
<script src="../js/id/actions/delete_way.js"></script>
<script src='../js/id/actions/disconnect.js'></script>
<script src='../js/id/actions/join.js'></script>
<script src='../js/id/actions/move_node.js'></script>
<script src='../js/id/actions/move_way.js'></script>
<script src='../js/id/actions/noop.js'></script>
@@ -147,6 +148,7 @@
<script src="spec/actions/delete_relation.js"></script>
<script src="spec/actions/delete_way.js"></script>
<script src='spec/actions/disconnect.js'></script>
<script src="spec/actions/join.js"></script>
<script src="spec/actions/move_node.js"></script>
<script src="spec/actions/move_way.js"></script>
<script src="spec/actions/noop.js"></script>

View File

@@ -40,6 +40,7 @@
<script src="spec/actions/delete_relation.js"></script>
<script src="spec/actions/delete_way.js"></script>
<script src='spec/actions/disconnect.js'></script>
<script src="spec/actions/join.js"></script>
<script src="spec/actions/move_node.js"></script>
<script src="spec/actions/move_way.js"></script>
<script src="spec/actions/noop.js"></script>

172
test/spec/actions/join.js Normal file
View File

@@ -0,0 +1,172 @@
describe("iD.actions.Join", function () {
describe("#enabled", function () {
it("returns true for ways that share an end/start node", function () {
// a --> b ==> c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['a', 'b']}),
'=': iD.Way({id: '=', nodes: ['b', 'c']})
});
expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true;
});
it("returns true for ways that share a start/end node", function () {
// a <-- b <== c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['b', 'a']}),
'=': iD.Way({id: '=', nodes: ['c', 'b']})
});
expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true;
});
it("returns true for ways that share a start/start node", function () {
// a <-- b ==> c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['b', 'a']}),
'=': iD.Way({id: '=', nodes: ['b', 'c']})
});
expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true;
});
it("returns true for ways that share an end/end node", function () {
// a --> b <== c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['a', 'b']}),
'=': iD.Way({id: '=', nodes: ['c', 'b']})
});
expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true;
});
it("returns false for ways that don't share the necessary nodes", function () {
// a -- b -- c
// |
// d
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'd': iD.Node({id: 'd'}),
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}),
'=': iD.Way({id: '=', nodes: ['b', 'd']})
});
expect(iD.actions.Join('-', '=').enabled(graph)).to.be.false;
});
});
it("joins a --> b ==> c", function () {
// Expected result:
// a --> b --> c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['a', 'b']}),
'=': iD.Way({id: '=', nodes: ['b', 'c']})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.entity('=')).to.be.undefined;
});
it("joins a <-- b <== c", function () {
// Expected result:
// a <-- b <-- c
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['b', 'a']}),
'=': iD.Way({id: '=', nodes: ['c', 'b']})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']);
expect(graph.entity('=')).to.be.undefined;
});
it("joins a <-- b ==> c", function () {
// Expected result:
// a <-- b <-- c
// tags on === reversed
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['b', 'a']}),
'=': iD.Way({id: '=', nodes: ['b', 'c']})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']);
expect(graph.entity('=')).to.be.undefined;
});
it("joins a --> b <== c", function () {
// Expected result:
// a --> b --> c
// tags on === reversed
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['a', 'b']}),
'=': iD.Way({id: '=', nodes: ['c', 'b']})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.entity('=')).to.be.undefined;
});
it("merges tags", function () {
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['a', 'b'], tags: {a: 'a', b: '-', c: 'c'}}),
'=': iD.Way({id: '=', nodes: ['b', 'c'], tags: {a: 'a', b: '=', d: 'd'}})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('-').tags).to.eql({a: 'a', b: '-; =', c: 'c', d: 'd'});
});
it("merges relations", function () {
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['a', 'b']}),
'=': iD.Way({id: '=', nodes: ['b', 'c']}),
'r1': iD.Relation({id: 'r1', members: [{id: '=', role: 'r1'}]}),
'r2': iD.Relation({id: 'r2', members: [{id: '=', role: 'r1'}, {id: '-', role: 'r2'}]})
});
graph = iD.actions.Join('-', '=')(graph);
expect(graph.entity('r1').members).to.eql([{id: '-', role: 'r1'}]);
expect(graph.entity('r2').members).to.eql([{id: '-', role: 'r2'}]);
});
});