diff --git a/index.html b/index.html
index 09d73b8f6..bdc523685 100644
--- a/index.html
+++ b/index.html
@@ -83,6 +83,7 @@
+
diff --git a/js/id/actions/merge.js b/js/id/actions/merge.js
new file mode 100644
index 000000000..8ba2a2318
--- /dev/null
+++ b/js/id/actions/merge.js
@@ -0,0 +1,35 @@
+iD.actions.Merge = function(ids) {
+ function groupEntitiesByGeometry(graph) {
+ var entities = ids.map(function(id) { return graph.entity(id); });
+ return _.extend({point: [], area: []}, _.groupBy(entities, function(entity) { return entity.geometry(graph); }));
+ }
+
+ var action = function(graph) {
+ var geometries = groupEntitiesByGeometry(graph),
+ area = geometries['area'][0],
+ points = geometries['point'];
+
+ points.forEach(function (point) {
+ area = area.mergeTags(point.tags);
+
+ graph.parentRelations(point).forEach(function (parent) {
+ graph = graph.replace(parent.replaceMember(point, area));
+ });
+
+ graph = graph.remove(point);
+ });
+
+ graph = graph.replace(area);
+
+ return graph;
+ };
+
+ action.enabled = function(graph) {
+ var geometries = groupEntitiesByGeometry(graph);
+ return geometries['area'].length === 1 &&
+ geometries['point'].length > 0 &&
+ (geometries['area'].length + geometries['point'].length) === ids.length;
+ };
+
+ return action;
+};
diff --git a/js/id/operations/merge.js b/js/id/operations/merge.js
index b2073db52..6202f1b3f 100644
--- a/js/id/operations/merge.js
+++ b/js/id/operations/merge.js
@@ -1,18 +1,28 @@
iD.operations.Merge = function(selection, context) {
- var action = iD.actions.Join(selection);
+ var join = iD.actions.Join(selection),
+ merge = iD.actions.Merge(selection);
var operation = function() {
var annotation = t('operations.merge.annotation', {n: selection.length}),
- difference = context.perform(action, annotation);
+ action;
+
+ if (join.enabled(context.graph())) {
+ action = join;
+ } else {
+ action = merge;
+ }
+
+ var difference = context.perform(action, annotation);
context.enter(iD.modes.Select(context, difference.extantIDs()));
};
operation.available = function() {
- return selection.length > 1;
+ return selection.length >= 2;
};
operation.enabled = function() {
- return action.enabled(context.graph());
+ return join.enabled(context.graph()) ||
+ merge.enabled(context.graph());
};
operation.id = "merge";
diff --git a/test/index.html b/test/index.html
index 3f2eba72e..ecad5ca75 100644
--- a/test/index.html
+++ b/test/index.html
@@ -80,6 +80,7 @@
+
@@ -157,6 +158,7 @@
+
diff --git a/test/index_packaged.html b/test/index_packaged.html
index 8dd73d666..f85ac1f0b 100644
--- a/test/index_packaged.html
+++ b/test/index_packaged.html
@@ -42,6 +42,7 @@
+
diff --git a/test/spec/actions/merge.js b/test/spec/actions/merge.js
new file mode 100644
index 000000000..5ea0b3dea
--- /dev/null
+++ b/test/spec/actions/merge.js
@@ -0,0 +1,20 @@
+describe("iD.actions.Merge", function () {
+ it("merges multiple points to an area", function () {
+ var graph = iD.Graph({
+ 'a': iD.Node({id: 'a', tags: {a: 'a'}}),
+ 'b': iD.Node({id: 'b', tags: {b: 'b'}}),
+ 'w': iD.Way({id: 'w', tags: {area: 'yes'}}),
+ 'r': iD.Relation({id: 'r', members: [{id: 'a', role: 'r', type: 'node'}]})
+ }),
+ action = iD.actions.Merge(['a', 'b', 'w']);
+
+ expect(action.enabled(graph)).to.be.true;
+
+ graph = action(graph);
+
+ expect(graph.entity('a')).to.be.undefined;
+ expect(graph.entity('b')).to.be.undefined;
+ expect(graph.entity('w').tags).to.eql({a: 'a', b: 'b', area: 'yes'});
+ expect(graph.entity('r').members).to.eql([{id: 'w', role: 'r', type: 'way'}]);
+ });
+});