mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 01:02:58 +00:00
15
css/map.css
15
css/map.css
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
js/id/actions/connect.js
Normal file
54
js/id/actions/connect.js
Normal 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;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -86,6 +86,14 @@ locale.en = {
|
||||
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.",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
test/spec/actions/connect.js
Normal file
110
test/spec/actions/connect.js
Normal 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'}]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user