Merge branch 'master' of github.com:systemed/iD

This commit is contained in:
Saman Bemel-Benrud
2013-02-05 11:00:02 -05:00
16 changed files with 296 additions and 49 deletions
+3 -1
View File
@@ -50,7 +50,9 @@ all: \
js/id/validate.js \
js/id/end.js \
locale/locale.js \
locale/en.js
locale/en.js \
data/data.js \
data/deprecated.js
iD.js: Makefile
@rm -f $@
+20 -15
View File
@@ -62,41 +62,41 @@ svg[data-zoom="17"] g.vertex .fill {
transform:scale(0.7, 0.7);
}
g.vertex.sha#f6634f .shadow {
g.vertex.shared .shadow {
-webkit-transform:scale(1.2, 1.2);
-moz-transform:scale(1.2, 1.2);
transform:scale(1.2, 1.2);
}
g.vertex.sha#f6634f .fill,
g.vertex.sha#f6634f .stroke {
g.vertex.shared .fill,
g.vertex.shared .stroke {
-webkit-transform:scale(1.1, 1.1);
-moz-transform:scale(1.1, 1.1);
transform:scale(1.1, 1.1);
}
svg[data-zoom="16"] g.vertex.sha#f6634f .shadow {
svg[data-zoom="16"] g.vertex.shared .shadow {
-webkit-transform:scale(0.9, 0.9);
-moz-transform:scale(0.9, 0.9);
transform:scale(0.9, 0.9);
}
svg[data-zoom="16"] g.vertex.sha#f6634f .fill,
svg[data-zoom="16"] g.vertex.sha#f6634f .stroke {
svg[data-zoom="16"] g.vertex.shared .fill,
svg[data-zoom="16"] g.vertex.shared .stroke {
-webkit-transform:scale(0.8, 0.8);
-moz-transform:scale(0.8, 0.8);
transform:scale(0.8, 0.8);
}
svg[data-zoom="17"] g.vertex.sha#f6634f .shadow {
svg[data-zoom="17"] g.vertex.shared .shadow {
-webkit-transform:scale(1, 1);
-moz-transform:scale(1, 1);
transform:scale(1, 1);
}
svg[data-zoom="17"] g.vertex.sha#f6634f .fill,
svg[data-zoom="17"] g.vertex.sha#f6634f .stroke {
svg[data-zoom="17"] g.vertex.shared .fill,
svg[data-zoom="17"] g.vertex.shared .stroke {
-webkit-transform:scale(0.9, 0.9);
-moz-transform:scale(0.9, 0.9);
transform:scale(0.9, 0.9);
}
g.vertex.sha#f6634f .fill {
g.vertex.shared .fill {
fill:#aaa;
}
@@ -123,7 +123,8 @@ g.vertex.selected .shadow {
.mode-draw-line g.midpoint,
.mode-add-area g.midpoint,
.mode-add-line g.midpoint,
.mode-add-point g.midpoint {
.mode-add-point g.midpoint,
.behavior-drag-node g.midpoint {
display: none;
}
@@ -716,14 +717,16 @@ text.point {
.mode-draw-line .behavior-hover .way,
.mode-draw-area .behavior-hover .way,
.mode-add-line .behavior-hover .way,
.mode-add-area .behavior-hover .way {
.mode-add-area .behavior-hover .way,
.behavior-drag-node.behavior-hover .way {
cursor:url(../img/cursor-draw-connect-line.png) 9 9, auto;
}
.mode-draw-line .behavior-hover .vertex,
.mode-draw-area .behavior-hover .vertex,
.mode-add-line .behavior-hover .vertex,
.mode-add-area .behavior-hover .vertex {
.mode-add-area .behavior-hover .vertex,
.behavior-drag-node.behavior-hover .vertex {
cursor:url(../img/cursor-draw-connect-vertex.png) 9 9, auto;
}
@@ -734,12 +737,14 @@ text.point {
/* Modes */
.mode-draw-line .vertex.active,
.mode-draw-area .vertex.active {
.mode-draw-area .vertex.active,
.behavior-drag-node .vertex.active {
display: none;
}
.mode-draw-line .way.active,
.mode-draw-area .way.active {
.mode-draw-area .way.active,
.behavior-drag-node .active {
pointer-events: none;
}
+1
View File
@@ -76,6 +76,7 @@
<script src='js/id/actions/add_entity.js'></script>
<script src='js/id/actions/add_vertex.js'></script>
<script src='js/id/actions/change_tags.js'></script>
<script src='js/id/actions/connect.js'></script>
<script src='js/id/actions/delete_multiple.js'></script>
<script src='js/id/actions/delete_node.js'></script>
<script src="js/id/actions/delete_relation.js"></script>
+54
View File
@@ -0,0 +1,54 @@
// Connect the ways at the given nodes.
//
// The last node will survive. All other nodes will be replaced with
// the surviving node in parent ways, and then removed.
//
// Tags and relation memberships of of non-surviving nodes are merged
// to the survivor.
//
// This is the inverse of `iD.actions.Disconnect`.
//
// Reference:
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeNodesAction.as
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/MergeNodesAction.java
//
iD.actions.Connect = function(nodeIds) {
var action = function(graph) {
var survivor = graph.entity(_.last(nodeIds));
for (var i = 0; i < nodeIds.length - 1; i++) {
var node = graph.entity(nodeIds[i]), index;
graph.parentWays(node).forEach(function (parent) {
while (true) {
index = parent.nodes.indexOf(node.id);
if (index < 0)
break;
parent = parent.updateNode(survivor.id, index);
}
graph = graph.replace(parent);
});
graph.parentRelations(node).forEach(function (parent) {
var memberA = parent.memberById(survivor.id),
memberB = parent.memberById(node.id);
if (!memberA) {
graph = graph.replace(parent.addMember({id: survivor.id, role: memberB.role, type: 'node'}));
}
});
survivor = survivor.mergeTags(node.tags);
graph = iD.actions.DeleteNode(node.id)(graph);
}
graph = graph.replace(survivor);
return graph;
};
action.enabled = function(graph) {
return nodeIds.length > 1;
};
return action;
};
+2
View File
@@ -4,6 +4,8 @@
// Normally, this will be undefined and the way will automatically
// be assigned a new ID.
//
// This is the inverse of `iD.actions.Connect`.
//
// Reference:
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java
+1 -1
View File
@@ -42,7 +42,7 @@ iD.actions.Join = function(idA, idB) {
var memberA = parent.memberById(idA),
memberB = parent.memberById(idB);
if (!memberA) {
graph = graph.replace(parent.addMember({id: idA, role: memberB.role}));
graph = graph.replace(parent.addMember({id: idA, role: memberB.role, type: 'way'}));
}
});
+80 -23
View File
@@ -17,39 +17,96 @@ iD.behavior.DragNode = function(context) {
}, 50);
}
function stopNudge(nudge) {
function stopNudge() {
if (nudgeInterval) window.clearInterval(nudgeInterval);
nudgeInterval = null;
}
function annotation(entity) {
function moveAnnotation(entity) {
return t('operations.move.annotation.' + entity.geometry(context.graph()));
}
return iD.behavior.drag()
.delegate(".node")
.origin(function(entity) {
return context.projection(entity.loc);
})
.on('start', function() {
context.perform(
iD.actions.Noop());
})
.on('move', function(entity) {
d3.event.sourceEvent.stopPropagation();
function connectAnnotation(datum) {
return t('operations.connect.annotation.' + datum.geometry(context.graph()));
}
var nudge = edge(d3.event.point, context.map().size());
if (nudge) startNudge(nudge);
else stopNudge();
function origin(entity) {
return context.projection(entity.loc);
}
function start(entity) {
var activeIDs = _.pluck(context.graph().parentWays(entity), 'id');
activeIDs.push(entity.id);
context.surface()
.classed('behavior-drag-node', true)
.selectAll('.node, .way')
.filter(function (d) { return activeIDs.indexOf(d.id) >= 0; })
.classed('active', true);
context.perform(
iD.actions.Noop());
}
function datum() {
if (d3.event.sourceEvent.altKey) {
return {};
} else {
return d3.event.sourceEvent.target.__data__ || {};
}
}
function move(entity) {
d3.event.sourceEvent.stopPropagation();
var nudge = edge(d3.event.point, context.map().size());
if (nudge) startNudge(nudge);
else stopNudge();
var loc = context.map().mouseCoordinates();
var d = datum();
if (d.type === 'node') {
loc = d.loc;
} else if (d.type === 'way') {
loc = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context).loc;
}
context.replace(iD.actions.MoveNode(entity.id, loc));
}
function end(entity) {
context.surface()
.classed('behavior-drag-node', false)
.selectAll('.active')
.classed('active', false);
stopNudge();
var d = datum();
if (d.type === 'way') {
var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context);
context.replace(
iD.actions.MoveNode(entity.id, context.projection.invert(d3.event.point)),
annotation(entity));
})
.on('end', function(entity) {
stopNudge();
iD.actions.MoveNode(entity.id, choice.loc),
iD.actions.AddVertex(d.id, entity.id, choice.index),
connectAnnotation(d));
} else if (d.type === 'node' && d.id !== entity.id) {
context.replace(
iD.actions.Connect([entity.id, d.id]),
connectAnnotation(d));
} else {
context.replace(
iD.actions.Noop(),
annotation(entity));
});
moveAnnotation(entity));
}
}
return iD.behavior.drag()
.delegate("g.node")
.origin(origin)
.on('start', start)
.on('move', move)
.on('end', end);
};
+1 -1
View File
@@ -10,7 +10,7 @@ iD.modes.AddArea = function(context) {
var behavior = iD.behavior.AddWay(context)
.on('start', start)
.on('startFromWay', startFromWay)
.on('startFromNode', startFromNode)
.on('startFromNode', startFromNode),
defaultTags = {area: 'yes'};
function start(loc) {
+1 -1
View File
@@ -10,7 +10,7 @@ iD.modes.AddLine = function(context) {
var behavior = iD.behavior.AddWay(context)
.on('start', start)
.on('startFromWay', startFromWay)
.on('startFromNode', startFromNode)
.on('startFromNode', startFromNode),
defaultTags = {highway: 'residential'};
function start(loc) {
+3 -3
View File
@@ -23,15 +23,15 @@ iD.svg.Vertices = function(projection) {
group.append('circle')
.attr('r', 10)
.attr('class', 'shadow');
.attr('class', 'node vertex shadow');
group.append('circle')
.attr('r', 4)
.attr('class', 'stroke');
.attr('class', 'node vertex stroke');
group.append('circle')
.attr('r', 3)
.attr('class', 'fill');
.attr('class', 'node vertex fill');
groups.attr('transform', iD.svg.PointTransform(projection))
.call(iD.svg.TagClasses())
+9
View File
@@ -83,9 +83,18 @@ locale.en = {
vertex: "Deleted a node from a way.",
line: "Deleted a line.",
area: "Deleted an area.",
relation: "Deleted a relation.",
multiple: "Deleted {n} objects."
}
},
connect: {
annotation: {
point: "Connected a way to a point.",
vertex: "Connected a way to another.",
line: "Connected a way to a line.",
area: "Connected a way to an area."
}
},
disconnect: {
title: "Disconnect",
description: "Disconnect these ways from each other.",
+2
View File
@@ -72,6 +72,7 @@
<script src='../js/id/actions/add_entity.js'></script>
<script src='../js/id/actions/add_vertex.js'></script>
<script src='../js/id/actions/change_tags.js'></script>
<script src='../js/id/actions/connect.js'></script>
<script src='../js/id/actions/circularize.js'></script>
<script src='../js/id/actions/orthogonalize.js'></script>
<script src="../js/id/actions/delete_multiple.js"></script>
@@ -151,6 +152,7 @@
<script src="spec/actions/add_midpoint.js"></script>
<script src="spec/actions/add_entity.js"></script>
<script src="spec/actions/change_tags.js"></script>
<script src='spec/actions/connect.js'></script>
<script src="spec/actions/delete_multiple.js"></script>
<script src="spec/actions/delete_node.js"></script>
<script src="spec/actions/delete_relation.js"></script>
+1
View File
@@ -35,6 +35,7 @@
<script src="spec/actions/add_midpoint.js"></script>
<script src="spec/actions/add_entity.js"></script>
<script src="spec/actions/change_tags.js"></script>
<script src='spec/actions/connect.js'></script>
<script src="spec/actions/delete_multiple.js"></script>
<script src="spec/actions/delete_node.js"></script>
<script src="spec/actions/delete_relation.js"></script>
+110
View File
@@ -0,0 +1,110 @@
describe("iD.actions.Connect", function() {
describe("#enabled", function () {
it("returns true for two or more nodes", function () {
expect(iD.actions.Connect(['a', 'b']).enabled()).to.be.true;
});
it("returns false for less than two nodes", function () {
expect(iD.actions.Connect(['a']).enabled()).to.be.false;
});
});
it("removes all but the final node", function() {
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'})
});
graph = iD.actions.Connect(['a', 'b', 'c'])(graph);
expect(graph.entity('a')).to.be.undefined;
expect(graph.entity('b')).to.be.undefined;
expect(graph.entity('c')).not.to.be.undefined;
});
it("replaces non-surviving nodes in parent ways", function() {
// a --- b --- c
//
// e
// |
// d
//
// Connect [e, b].
//
// Expected result:
//
// 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'}),
'e': iD.Node({id: 'e'}),
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}),
'|': iD.Way({id: '|', nodes: ['d', 'e']})
});
graph = iD.actions.Connect(['e', 'b'])(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.entity('|').nodes).to.eql(['d', 'b']);
});
it("handles circular ways", function() {
// c -- a d === e
// | /
// | /
// | /
// b
//
// Connect [a, 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'}),
'e': iD.Node({id: 'e'}),
'-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'a']}),
'=': iD.Way({id: '=', nodes: ['d', 'e']})
});
graph = iD.actions.Connect(['a', 'd'])(graph);
expect(graph.entity('-').nodes).to.eql(['d', 'b', 'c', 'd']);
});
it("merges tags to the surviving node", function() {
var graph = iD.Graph({
'a': iD.Node({id: 'a', tags: {a: 'a'}}),
'b': iD.Node({id: 'b', tags: {b: 'b'}}),
'c': iD.Node({id: 'c', tags: {c: 'c'}})
});
graph = iD.actions.Connect(['a', 'b', 'c'])(graph);
expect(graph.entity('c').tags).to.eql({a: 'a', b: 'b', c: 'c'});
});
it("merges memberships to the surviving node", function() {
var graph = iD.Graph({
'a': iD.Node({id: 'a'}),
'b': iD.Node({id: 'b'}),
'c': iD.Node({id: 'c'}),
'd': iD.Node({id: 'c'}),
'-': iD.Way({id: '-', nodes: ['a', 'b']}),
'=': iD.Way({id: '=', nodes: ['c', 'd']}),
'r1': iD.Relation({id: 'r1', members: [{id: 'b', role: 'r1', type: 'node'}]}),
'r2': iD.Relation({id: 'r2', members: [{id: 'b', role: 'r1', type: 'node'}, {id: 'c', role: 'r2', type: 'node'}]})
});
graph = iD.actions.Connect(['b', 'c'])(graph);
expect(graph.entity('r1').members).to.eql([{id: 'c', role: 'r1', type: 'node'}]);
expect(graph.entity('r2').members).to.eql([{id: 'c', role: 'r2', type: 'node'}]);
});
});
+4 -4
View File
@@ -160,13 +160,13 @@ describe("iD.actions.Join", function () {
'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'}]})
'r1': iD.Relation({id: 'r1', members: [{id: '=', role: 'r1', type: 'way'}]}),
'r2': iD.Relation({id: 'r2', members: [{id: '=', role: 'r1', type: 'way'}, {id: '-', role: 'r2', type: 'way'}]})
});
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'}]);
expect(graph.entity('r1').members).to.eql([{id: '-', role: 'r1', type: 'way'}]);
expect(graph.entity('r2').members).to.eql([{id: '-', role: 'r2', type: 'way'}]);
});
});
+4
View File
@@ -14,6 +14,8 @@ describe("iD.svg.Midpoints", function () {
line = iD.Way({nodes: [a.id, b.id]}),
graph = iD.Graph([a, b, line]);
// If no vertices are drawn, no midpoints are drawn. This dependence needs to be removed
surface.call(iD.svg.Vertices(projection), graph, [a], filter);
surface.call(iD.svg.Midpoints(projection), graph, [line], filter);
expect(surface.select('.midpoint').datum().loc).to.eql([25, 0]);
@@ -42,6 +44,8 @@ describe("iD.svg.Midpoints", function () {
graph = iD.Graph([a, b, c, d, l1, l2, l3, l4]),
ab = function (d) { return d.id === [a.id, b.id].sort().join("-"); };
// If no vertices are drawn, no midpoints are drawn. This dependence needs to be removed
surface.call(iD.svg.Vertices(projection), graph, [a], filter);
surface.call(iD.svg.Midpoints(projection), graph, [l1, l2, l3, l4], filter);
expect(surface.selectAll('.midpoint').filter(ab).datum().ways).to.eql([