mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-16 13:59:27 +02:00
Merge branch 'node_drag_relation_checks'
This commit is contained in:
+2
-1
@@ -116,6 +116,7 @@ en:
|
||||
line: Connected a way to a line.
|
||||
area: Connected a way to an area.
|
||||
relation: These features can't be connected because they have conflicting relation roles.
|
||||
restriction: "These features can't be connected because it would damage a \"{relation}\" relation."
|
||||
disconnect:
|
||||
title: Disconnect
|
||||
description: Disconnect these lines/areas from each other.
|
||||
@@ -131,7 +132,7 @@ en:
|
||||
annotation: "Merged {n} features."
|
||||
not_eligible: These features can't be merged.
|
||||
not_adjacent: These features can't be merged because their endpoints aren't connected.
|
||||
restriction: These features can't be merged because at least one is a member of a "{relation}" relation.
|
||||
restriction: "These features can't be merged because at least one is a member of a \"{relation}\" relation."
|
||||
incomplete_relation: These features can't be merged because at least one hasn't been fully downloaded.
|
||||
conflicting_tags: These features can't be merged because some of their tags have conflicting values.
|
||||
move:
|
||||
|
||||
Vendored
+2
-1
@@ -151,7 +151,8 @@
|
||||
"line": "Connected a way to a line.",
|
||||
"area": "Connected a way to an area."
|
||||
},
|
||||
"relation": "These features can't be connected because they have conflicting relation roles."
|
||||
"relation": "These features can't be connected because they have conflicting relation roles.",
|
||||
"restriction": "These features can't be connected because it would damage a \"{relation}\" relation."
|
||||
},
|
||||
"disconnect": {
|
||||
"title": "Disconnect",
|
||||
|
||||
+185
-18
@@ -1,3 +1,5 @@
|
||||
import _uniq from 'lodash-es/uniq';
|
||||
|
||||
import { actionDeleteNode } from './delete_node';
|
||||
|
||||
|
||||
@@ -15,7 +17,7 @@ import { actionDeleteNode } from './delete_node';
|
||||
// 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
|
||||
//
|
||||
export function actionConnect(nodeIds) {
|
||||
export function actionConnect(nodeIDs) {
|
||||
var action = function(graph) {
|
||||
var survivor;
|
||||
var node;
|
||||
@@ -23,14 +25,14 @@ export function actionConnect(nodeIds) {
|
||||
var i, j;
|
||||
|
||||
// Choose a survivor node, prefer an existing (not new) node - #4974
|
||||
for (i = 0; i < nodeIds.length; i++) {
|
||||
survivor = graph.entity(nodeIds[i]);
|
||||
for (i = 0; i < nodeIDs.length; i++) {
|
||||
survivor = graph.entity(nodeIDs[i]);
|
||||
if (survivor.version) break; // found one
|
||||
}
|
||||
|
||||
// Replace all non-surviving nodes with the survivor and merge tags.
|
||||
for (i = 0; i < nodeIds.length; i++) {
|
||||
node = graph.entity(nodeIds[i]);
|
||||
for (i = 0; i < nodeIDs.length; i++) {
|
||||
node = graph.entity(nodeIDs[i]);
|
||||
if (node.id === survivor.id) continue;
|
||||
|
||||
parents = graph.parentWays(node);
|
||||
@@ -57,27 +59,192 @@ export function actionConnect(nodeIds) {
|
||||
|
||||
action.disabled = function(graph) {
|
||||
var seen = {};
|
||||
var restrictionIDs = [];
|
||||
var survivor;
|
||||
var node, way;
|
||||
var relations, relation, role;
|
||||
var i, j, k;
|
||||
|
||||
for (var i = 0; i < nodeIds.length; i++) {
|
||||
var node = graph.entity(nodeIds[i]);
|
||||
// Choose a survivor node, prefer an existing (not new) node - #4974
|
||||
for (i = 0; i < nodeIDs.length; i++) {
|
||||
survivor = graph.entity(nodeIDs[i]);
|
||||
if (survivor.version) break; // found one
|
||||
}
|
||||
|
||||
var toCheck = [node].concat(graph.parentWays(node));
|
||||
for (var j = 0; j < toCheck.length; j++) {
|
||||
var entity = toCheck[j];
|
||||
// 1. disable if the nodes being connected have conflicting relation roles
|
||||
for (i = 0; i < nodeIDs.length; i++) {
|
||||
node = graph.entity(nodeIDs[i]);
|
||||
relations = graph.parentRelations(node);
|
||||
|
||||
var relations = graph.parentRelations(entity);
|
||||
for (var k = 0; k < relations.length; k++) {
|
||||
var relation = relations[k];
|
||||
var role = relation.memberById(entity.id).role || '';
|
||||
for (j = 0; j < relations.length; j++) {
|
||||
relation = relations[j];
|
||||
role = relation.memberById(node.id).role || '';
|
||||
|
||||
if (seen[relation.id] !== undefined && seen[relation.id] !== role) {
|
||||
return 'relation';
|
||||
} else {
|
||||
seen[relation.id] = role;
|
||||
// if this node is a via node in a restriction, remember for later
|
||||
if (relation.isRestriction()) {
|
||||
restrictionIDs.push(relation.id);
|
||||
}
|
||||
|
||||
if (seen[relation.id] !== undefined && seen[relation.id] !== role) {
|
||||
return 'relation';
|
||||
} else {
|
||||
seen[relation.id] = role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gather restrictions for parent ways
|
||||
for (i = 0; i < nodeIDs.length; i++) {
|
||||
node = graph.entity(nodeIDs[i]);
|
||||
|
||||
var parents = graph.parentWays(node);
|
||||
for (j = 0; j < parents.length; j++) {
|
||||
var parent = parents[j];
|
||||
relations = graph.parentRelations(parent);
|
||||
|
||||
for (k = 0; k < relations.length; k++) {
|
||||
relation = relations[k];
|
||||
if (relation.isRestriction()) {
|
||||
restrictionIDs.push(relation.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// test restrictions
|
||||
restrictionIDs = _uniq(restrictionIDs);
|
||||
for (i = 0; i < restrictionIDs.length; i++) {
|
||||
relation = graph.entity(restrictionIDs[i]);
|
||||
if (!relation.isComplete(graph)) continue;
|
||||
|
||||
var memberWays = relation.members
|
||||
.filter(function(m) { return m.type === 'way'; })
|
||||
.map(function(m) { return graph.entity(m.id); });
|
||||
|
||||
// 2a. disable if connection would damage a restriction
|
||||
// (a key node is a node at the junction of ways)
|
||||
var nodes = { from: [], via: [], to: [], keyfrom: [], keyto: [] };
|
||||
for (j = 0; j < relation.members.length; j++) {
|
||||
collectNodes(relation.members[j], nodes);
|
||||
}
|
||||
|
||||
nodes.keyfrom = _uniq(nodes.keyfrom.filter(hasDuplicates));
|
||||
nodes.keyto = _uniq(nodes.keyto.filter(hasDuplicates));
|
||||
|
||||
var f = keyNodeFilter(nodes.keyfrom, nodes.keyto);
|
||||
nodes.from = nodes.from.filter(f);
|
||||
nodes.via = nodes.via.filter(f);
|
||||
nodes.to = nodes.to.filter(f);
|
||||
|
||||
var connectFrom = false;
|
||||
var connectVia = false;
|
||||
var connectTo = false;
|
||||
var connectKeyFrom = false;
|
||||
var connectKeyTo = false;
|
||||
|
||||
for (j = 0; j < nodeIDs.length; j++) {
|
||||
var n = nodeIDs[j];
|
||||
if (nodes.from.indexOf(n) !== -1) { connectFrom = true; }
|
||||
if (nodes.via.indexOf(n) !== -1) { connectVia = true; }
|
||||
if (nodes.to.indexOf(n) !== -1) { connectTo = true; }
|
||||
if (nodes.keyfrom.indexOf(n) !== -1) { connectKeyFrom = true; }
|
||||
if (nodes.keyto.indexOf(n) !== -1) { connectKeyTo = true; }
|
||||
}
|
||||
if (connectFrom && connectTo) { return 'restriction'; }
|
||||
if (connectFrom && connectVia) { return 'restriction'; }
|
||||
if (connectTo && connectVia) { return 'restriction'; }
|
||||
|
||||
// connecting to a key node -
|
||||
// if both nodes are on a member way (i.e. part of the turn restriction),
|
||||
// the connecting node must be adjacent to the key node.
|
||||
if (connectKeyFrom || connectKeyTo) {
|
||||
if (nodeIDs.length !== 2) { return 'restriction'; }
|
||||
|
||||
var n0 = null;
|
||||
var n1 = null;
|
||||
for (j = 0; j < memberWays.length; j++) {
|
||||
way = memberWays[j];
|
||||
if (way.contains(nodeIDs[0])) { n0 = nodeIDs[0]; }
|
||||
if (way.contains(nodeIDs[1])) { n1 = nodeIDs[1]; }
|
||||
}
|
||||
|
||||
if (n0 && n1) { // both nodes are part of the restriction
|
||||
var ok = false;
|
||||
for (j = 0; j < memberWays.length; j++) {
|
||||
way = memberWays[j];
|
||||
if (way.areAdjacent(n0, n1)) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
return 'restriction';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2b. disable if nodes being connected will destroy a member way in a restriction
|
||||
// (to test, make a copy and try actually connecting the nodes)
|
||||
for (j = 0; j < memberWays.length; j++) {
|
||||
way = memberWays[j].update({}); // make copy
|
||||
for (k = 0; k < nodeIDs.length; k++) {
|
||||
if (nodeIDs[k] === survivor.id) continue;
|
||||
|
||||
if (way.areAdjacent(nodeIDs[k], survivor.id)) {
|
||||
way = way.removeNode(nodeIDs[k]);
|
||||
} else {
|
||||
way = way.replaceNode(nodeIDs[k], survivor.id);
|
||||
}
|
||||
}
|
||||
if (way.isDegenerate()) {
|
||||
return 'restriction';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
|
||||
// if a key node appears multiple times (indexOf !== lastIndexOf) it's a FROM-VIA or TO-VIA junction
|
||||
function hasDuplicates(n, i, arr) {
|
||||
return arr.indexOf(n) !== arr.lastIndexOf(n);
|
||||
}
|
||||
|
||||
function keyNodeFilter(froms, tos) {
|
||||
return function(n) {
|
||||
return froms.indexOf(n) === -1 && tos.indexOf(n) === -1;
|
||||
};
|
||||
}
|
||||
|
||||
function collectNodes(member, collection) {
|
||||
var entity = graph.hasEntity(member.id);
|
||||
if (!entity) return;
|
||||
|
||||
var role = member.role || '';
|
||||
if (!collection[role]) {
|
||||
collection[role] = [];
|
||||
}
|
||||
|
||||
if (member.type === 'node') {
|
||||
collection[role].push(member.id);
|
||||
if (role === 'via') {
|
||||
collection.keyfrom.push(member.id);
|
||||
collection.keyto.push(member.id);
|
||||
}
|
||||
|
||||
} else if (member.type === 'way') {
|
||||
collection[role].push.apply(collection[role], entity.nodes);
|
||||
if (role === 'from' || role === 'via') {
|
||||
collection.keyfrom.push(entity.first());
|
||||
collection.keyfrom.push(entity.last());
|
||||
}
|
||||
if (role === 'to' || role === 'via') {
|
||||
collection.keyto.push(entity.first());
|
||||
collection.keyto.push(entity.last());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
+45
-36
@@ -119,7 +119,9 @@ export function modeDragNode(context) {
|
||||
|
||||
if (_isCancelled) {
|
||||
if (hasHidden) {
|
||||
uiFlash().text(t('modes.drag_node.connected_to_hidden'))();
|
||||
uiFlash()
|
||||
.duration(4000)
|
||||
.text(t('modes.drag_node.connected_to_hidden'))();
|
||||
}
|
||||
return drag.cancel();
|
||||
}
|
||||
@@ -178,14 +180,15 @@ export function modeDragNode(context) {
|
||||
var target = d && d.properties && d.properties.entity;
|
||||
var targetLoc = target && target.loc;
|
||||
var targetNodes = d && d.properties && d.properties.nodes;
|
||||
var edge;
|
||||
|
||||
if (targetLoc) { // snap to node/vertex - a point target with `.loc`
|
||||
loc = targetLoc;
|
||||
|
||||
} else if (targetNodes) { // snap to way - a line target with `.nodes`
|
||||
var choice = geoChooseEdge(targetNodes, context.mouse(), context.projection, end.id);
|
||||
if (choice) {
|
||||
loc = choice.loc;
|
||||
edge = geoChooseEdge(targetNodes, context.mouse(), context.projection, end.id);
|
||||
if (edge) {
|
||||
loc = edge.loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,17 +203,33 @@ export function modeDragNode(context) {
|
||||
|
||||
// Check if this connection to `target` could cause relations to break..
|
||||
if (target) {
|
||||
isInvalid = isRelationConflict([entity.id, target.id], context.graph());
|
||||
if (isInvalid && !context.surface().classed('nope')) {
|
||||
uiFlash().text(t('operations.connect.relation'))();
|
||||
}
|
||||
isInvalid = hasRelationConflict(entity, target, edge, context.graph());
|
||||
}
|
||||
|
||||
// Check if this drag causes the geometry to break..
|
||||
if (!isInvalid) {
|
||||
isInvalid = isInvalidGeometry(entity, context.graph());
|
||||
isInvalid = hasInvalidGeometry(entity, context.graph());
|
||||
}
|
||||
|
||||
|
||||
var nope = context.surface().classed('nope');
|
||||
if (isInvalid === 'relation' || isInvalid === 'restriction') {
|
||||
if (!nope) { // about to nope - show hint
|
||||
uiFlash()
|
||||
.duration(4000)
|
||||
.text(t('operations.connect.' + isInvalid,
|
||||
{ relation: context.presets().item('type/restriction').name() }
|
||||
))();
|
||||
}
|
||||
} else {
|
||||
if (nope) { // about to un-nope, remove hint
|
||||
uiFlash()
|
||||
.duration(1)
|
||||
.text('')();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var nopeDisabled = context.surface().classed('nope-disabled');
|
||||
if (nopeDisabled) {
|
||||
context.surface()
|
||||
@@ -226,39 +245,29 @@ export function modeDragNode(context) {
|
||||
}
|
||||
|
||||
|
||||
// See also actionConnect.disabled()
|
||||
// (similar, but this code can also check node-way)
|
||||
function isRelationConflict(ids, graph) {
|
||||
var seen = {};
|
||||
// Uses `actionConnect.disabled()` to know whether this connection is ok..
|
||||
function hasRelationConflict(entity, target, edge, graph) {
|
||||
var testGraph = graph.update(); // copy
|
||||
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var ent1 = graph.entity(ids[i]);
|
||||
// if snapping to way - add midpoint there and consider that the target..
|
||||
if (edge) {
|
||||
var midpoint = osmNode();
|
||||
var action = actionAddMidpoint({
|
||||
loc: edge.loc,
|
||||
edge: [target.nodes[edge.index - 1], target.nodes[edge.index]]
|
||||
}, midpoint);
|
||||
|
||||
var toCheck = [ent1];
|
||||
if (ent1.type === 'node') {
|
||||
toCheck = toCheck.concat(graph.parentWays(ent1));
|
||||
}
|
||||
for (var j = 0; j < toCheck.length; j++) {
|
||||
var ent2 = toCheck[j];
|
||||
|
||||
var relations = graph.parentRelations(ent2);
|
||||
for (var k = 0; k < relations.length; k++) {
|
||||
var relation = relations[k];
|
||||
var role = relation.memberById(ent2.id).role || '';
|
||||
|
||||
if (seen[relation.id] !== undefined && seen[relation.id] !== role) {
|
||||
return 'relation';
|
||||
} else {
|
||||
seen[relation.id] = role;
|
||||
}
|
||||
}
|
||||
}
|
||||
testGraph = action(testGraph);
|
||||
target = midpoint;
|
||||
}
|
||||
return false;
|
||||
|
||||
// can we connect to it?
|
||||
var ids = [entity.id, target.id];
|
||||
return actionConnect(ids).disabled(testGraph);
|
||||
}
|
||||
|
||||
|
||||
function isInvalidGeometry(entity, graph) {
|
||||
function hasInvalidGeometry(entity, graph) {
|
||||
var parents = graph.parentWays(entity);
|
||||
var i, j, k;
|
||||
|
||||
|
||||
+212
-51
@@ -174,98 +174,259 @@ describe('iD.actionConnect', function() {
|
||||
|
||||
|
||||
describe('#disabled', function () {
|
||||
it('returns falsy when connecting members of the same relation and same roles (different ways)', function () {
|
||||
//
|
||||
// "Route 1" "Route 1"
|
||||
// 'forward' 'forward'
|
||||
// a ----- b c ===== d
|
||||
//
|
||||
it('returns falsy when connecting members of the same relation and same roles', function () {
|
||||
var graph = iD.coreGraph([
|
||||
iD.osmNode({id: 'a'}),
|
||||
iD.osmNode({id: 'b'}),
|
||||
iD.osmNode({id: 'c'}),
|
||||
iD.osmNode({id: 'd'}),
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b']}),
|
||||
iD.osmWay({id: '=', nodes: ['c', 'd']}),
|
||||
iD.osmRelation({id: 'r1', tags: { name: 'Route 1'}, members: [
|
||||
{ id: '-', type: 'way', role: 'forward' },
|
||||
{ id: '=', type: 'way', role: 'forward' }
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
|
||||
iD.osmRelation({id: 'r1', members: [
|
||||
{ id: 'b', type: 'node', role: 'foo' },
|
||||
{ id: 'c', type: 'node', role: 'foo' }
|
||||
]})
|
||||
]);
|
||||
|
||||
expect(iD.actionConnect(['b', 'c']).disabled(graph)).to.be.not.ok;
|
||||
});
|
||||
|
||||
it('returns falsy when connecting members of the different relation and different roles (different ways)', function () {
|
||||
//
|
||||
// "Route 1" "Route 2"
|
||||
// 'forward' 'backward'
|
||||
// a ----- b c ===== d
|
||||
//
|
||||
it('returns falsy when connecting members of the different relation and different roles', function () {
|
||||
var graph = iD.coreGraph([
|
||||
iD.osmNode({id: 'a'}),
|
||||
iD.osmNode({id: 'b'}),
|
||||
iD.osmNode({id: 'c'}),
|
||||
iD.osmNode({id: 'd'}),
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b']}),
|
||||
iD.osmWay({id: '=', nodes: ['c', 'd']}),
|
||||
iD.osmRelation({id: 'r1', tags: { name: 'Route 1'}, members: [
|
||||
{ id: '-', type: 'way', role: 'forward' }
|
||||
]}),
|
||||
iD.osmRelation({id: 'r2', tags: { name: 'Route 2'}, members: [
|
||||
{ id: '=', type: 'way', role: 'backward' }
|
||||
]})
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
|
||||
iD.osmRelation({id: 'r1', members: [{ id: 'b', type: 'node', role: 'foo' } ]}),
|
||||
iD.osmRelation({id: 'r2', members: [{ id: 'c', type: 'node', role: 'bar' } ]})
|
||||
]);
|
||||
|
||||
expect(iD.actionConnect(['b', 'c']).disabled(graph)).to.be.not.ok;
|
||||
});
|
||||
|
||||
it('returns \'relation\' when connecting members of the same relation but different roles (different ways)', function () {
|
||||
//
|
||||
// "Route 1" "Route 1"
|
||||
// 'forward' 'backward'
|
||||
// a ----- b c ===== d
|
||||
//
|
||||
it('returns \'relation\' when connecting members of the same relation but different roles', function () {
|
||||
var graph = iD.coreGraph([
|
||||
iD.osmNode({id: 'a'}),
|
||||
iD.osmNode({id: 'b'}),
|
||||
iD.osmNode({id: 'c'}),
|
||||
iD.osmNode({id: 'd'}),
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b']}),
|
||||
iD.osmWay({id: '=', nodes: ['c', 'd']}),
|
||||
iD.osmRelation({id: 'r1', tags: { name: 'Route 1'}, members: [
|
||||
{ id: '-', type: 'way', role: 'forward' },
|
||||
{ id: '=', type: 'way', role: 'backward' }
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
|
||||
iD.osmRelation({id: 'r1', members: [
|
||||
{ id: 'b', type: 'node', role: 'foo' },
|
||||
{ id: 'c', type: 'node', role: 'bar' }
|
||||
]})
|
||||
]);
|
||||
|
||||
expect(iD.actionConnect(['b', 'c']).disabled(graph)).to.eql('relation');
|
||||
});
|
||||
|
||||
it('returns \'relation\' when connecting members of the same relation but different roles (joined way)', function () {
|
||||
it('returns falsy when connecting a node unrelated to the restriction', function () {
|
||||
//
|
||||
// from via to
|
||||
// a --- b === c ~~~ d
|
||||
// a --- b d ~~~ e r1: `no_right_turn`
|
||||
// | FROM '-'
|
||||
// | VIA 'b'
|
||||
// c TO '|'
|
||||
//
|
||||
var graph = iD.coreGraph([
|
||||
iD.osmNode({id: 'a'}),
|
||||
iD.osmNode({id: 'b'}),
|
||||
iD.osmNode({id: 'c'}),
|
||||
iD.osmNode({id: 'd'}),
|
||||
iD.osmNode({id: 'e'}),
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b']}),
|
||||
iD.osmWay({id: '=', nodes: ['b', 'c']}),
|
||||
iD.osmWay({id: '~', nodes: ['c', 'd']}),
|
||||
iD.osmRelation({id: 'r', members: [
|
||||
iD.osmWay({id: '|', nodes: ['b', 'c']}),
|
||||
iD.osmWay({id: '~', nodes: ['d', 'e']}),
|
||||
iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_right_turn' }, members: [
|
||||
{ id: '-', type: 'way', role: 'from' },
|
||||
{ id: '=', type: 'way', role: 'via' },
|
||||
{ id: '~', type: 'way', role: 'to' }
|
||||
{ id: 'b', type: 'node', role: 'via' },
|
||||
{ id: '|', type: 'way', role: 'to' }
|
||||
]})
|
||||
]);
|
||||
|
||||
expect(iD.actionConnect(['a', 'b']).disabled(graph)).to.eql('relation');
|
||||
expect(iD.actionConnect(['b', 'c']).disabled(graph)).to.eql('relation');
|
||||
expect(iD.actionConnect(['c', 'd']).disabled(graph)).to.eql('relation');
|
||||
expect(iD.actionConnect(['a', 'd']).disabled(graph)).to.be.not.ok;
|
||||
expect(iD.actionConnect(['b', 'd']).disabled(graph)).to.be.not.ok;
|
||||
expect(iD.actionConnect(['c', 'd']).disabled(graph)).to.be.not.ok;
|
||||
});
|
||||
});
|
||||
|
||||
it('returns falsy when connecting nodes that would not break a via-node restriction', function () {
|
||||
//
|
||||
// a --- b --- c r1: `no_right_turn`
|
||||
// | FROM '-'
|
||||
// d VIA 'c'
|
||||
// | TO '|'
|
||||
// e
|
||||
//
|
||||
var graph = iD.coreGraph([
|
||||
iD.osmNode({id: 'a'}),
|
||||
iD.osmNode({id: 'b'}),
|
||||
iD.osmNode({id: 'c'}),
|
||||
iD.osmNode({id: 'd'}),
|
||||
iD.osmNode({id: 'e'}),
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
|
||||
iD.osmWay({id: '|', nodes: ['c', 'd', 'e']}),
|
||||
iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_right_turn' }, members: [
|
||||
{ id: '-', type: 'way', role: 'from' },
|
||||
{ id: 'c', type: 'node', role: 'via' },
|
||||
{ id: '|', type: 'way', role: 'to' }
|
||||
]})
|
||||
]);
|
||||
|
||||
// allowed: adjacent connections that don't destroy a way
|
||||
expect(iD.actionConnect(['a', 'b']).disabled(graph)).to.be.not.ok;
|
||||
expect(iD.actionConnect(['b', 'c']).disabled(graph)).to.be.not.ok;
|
||||
expect(iD.actionConnect(['c', 'd']).disabled(graph)).to.be.not.ok;
|
||||
expect(iD.actionConnect(['d', 'e']).disabled(graph)).to.be.not.ok;
|
||||
});
|
||||
|
||||
it('returns falsy when connecting nodes that would not break a via-way restriction', function () {
|
||||
//
|
||||
// a --- b --- c r1: `no_u_turn`
|
||||
// | FROM '='
|
||||
// d VIA '|'
|
||||
// | TO '-'
|
||||
// g === f === e
|
||||
//
|
||||
var graph = iD.coreGraph([
|
||||
iD.osmNode({id: 'a'}),
|
||||
iD.osmNode({id: 'b'}),
|
||||
iD.osmNode({id: 'c'}),
|
||||
iD.osmNode({id: 'd'}),
|
||||
iD.osmNode({id: 'e'}),
|
||||
iD.osmNode({id: 'f'}),
|
||||
iD.osmNode({id: 'g'}),
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
|
||||
iD.osmWay({id: '|', nodes: ['c', 'd', 'e']}),
|
||||
iD.osmWay({id: '=', nodes: ['e', 'f', 'g']}),
|
||||
iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_u_turn' }, members: [
|
||||
{ id: '=', type: 'way', role: 'from' },
|
||||
{ id: '|', type: 'way', role: 'via' },
|
||||
{ id: '-', type: 'way', role: 'to' }
|
||||
]})
|
||||
]);
|
||||
|
||||
// allowed: adjacent connections that don't destroy a way
|
||||
expect(iD.actionConnect(['a', 'b']).disabled(graph)).to.be.not.ok;
|
||||
expect(iD.actionConnect(['b', 'c']).disabled(graph)).to.be.not.ok;
|
||||
expect(iD.actionConnect(['c', 'd']).disabled(graph)).to.be.not.ok;
|
||||
expect(iD.actionConnect(['d', 'e']).disabled(graph)).to.be.not.ok;
|
||||
expect(iD.actionConnect(['e', 'f']).disabled(graph)).to.be.not.ok;
|
||||
expect(iD.actionConnect(['f', 'g']).disabled(graph)).to.be.not.ok;
|
||||
});
|
||||
|
||||
it('returns \'restriction\' when connecting nodes that would break a via-node restriction', function () {
|
||||
//
|
||||
// a --- b --- c r1: `no_right_turn`
|
||||
// | FROM '-'
|
||||
// d VIA 'c'
|
||||
// | TO '|'
|
||||
// e
|
||||
//
|
||||
var graph = iD.coreGraph([
|
||||
iD.osmNode({id: 'a'}),
|
||||
iD.osmNode({id: 'b'}),
|
||||
iD.osmNode({id: 'c'}),
|
||||
iD.osmNode({id: 'd'}),
|
||||
iD.osmNode({id: 'e'}),
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
|
||||
iD.osmWay({id: '|', nodes: ['c', 'd', 'e']}),
|
||||
iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_right_turn' }, members: [
|
||||
{ id: '-', type: 'way', role: 'from' },
|
||||
{ id: 'c', type: 'node', role: 'via' },
|
||||
{ id: '|', type: 'way', role: 'to' }
|
||||
]})
|
||||
]);
|
||||
|
||||
// prevented:
|
||||
// extra connections to the VIA node, or any connections between FROM and TO
|
||||
expect(iD.actionConnect(['a', 'c']).disabled(graph)).to.eql('restriction', 'extra connection FROM-VIA');
|
||||
expect(iD.actionConnect(['e', 'c']).disabled(graph)).to.eql('restriction', 'extra connection TO-VIA');
|
||||
expect(iD.actionConnect(['b', 'd']).disabled(graph)).to.eql('restriction', 'extra connection FROM-TO');
|
||||
});
|
||||
|
||||
it('returns \'restriction\' when connecting nodes that would break a via-way restriction', function () {
|
||||
//
|
||||
// a --- b --- c r1: `no_u_turn`
|
||||
// | FROM '='
|
||||
// d VIA '|'
|
||||
// | TO '-'
|
||||
// g === f === e
|
||||
//
|
||||
var graph = iD.coreGraph([
|
||||
iD.osmNode({id: 'a'}),
|
||||
iD.osmNode({id: 'b'}),
|
||||
iD.osmNode({id: 'c'}),
|
||||
iD.osmNode({id: 'd'}),
|
||||
iD.osmNode({id: 'e'}),
|
||||
iD.osmNode({id: 'f'}),
|
||||
iD.osmNode({id: 'g'}),
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
|
||||
iD.osmWay({id: '|', nodes: ['c', 'd', 'e']}),
|
||||
iD.osmWay({id: '=', nodes: ['e', 'f', 'g']}),
|
||||
iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_u_turn' }, members: [
|
||||
{ id: '=', type: 'way', role: 'from' },
|
||||
{ id: '|', type: 'way', role: 'via' },
|
||||
{ id: '-', type: 'way', role: 'to' }
|
||||
]})
|
||||
]);
|
||||
|
||||
// prevented:
|
||||
// extra connections to any node along VIA way
|
||||
expect(iD.actionConnect(['a', 'c']).disabled(graph)).to.eql('restriction', 'extra connection TO-VIA c');
|
||||
expect(iD.actionConnect(['b', 'd']).disabled(graph)).to.eql('restriction', 'extra connection TO-VIA d');
|
||||
expect(iD.actionConnect(['b', 'e']).disabled(graph)).to.eql('restriction', 'extra connection TO-VIA e');
|
||||
expect(iD.actionConnect(['c', 'e']).disabled(graph)).to.eql('restriction', 'extra connection VIA-VIA');
|
||||
expect(iD.actionConnect(['f', 'c']).disabled(graph)).to.eql('restriction', 'extra connection FROM-VIA c');
|
||||
expect(iD.actionConnect(['f', 'd']).disabled(graph)).to.eql('restriction', 'extra connection FROM-VIA d');
|
||||
expect(iD.actionConnect(['g', 'e']).disabled(graph)).to.eql('restriction', 'extra connection FROM-VIA e');
|
||||
});
|
||||
|
||||
it('returns \'restriction\' when connecting would destroy a way in a via-node restriction', function () {
|
||||
//
|
||||
// a --- b r1: `no_right_turn`
|
||||
// | FROM '-'
|
||||
// | VIA 'b'
|
||||
// c TO '|'
|
||||
//
|
||||
var graph = iD.coreGraph([
|
||||
iD.osmNode({id: 'a'}),
|
||||
iD.osmNode({id: 'b'}),
|
||||
iD.osmNode({id: 'c'}),
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b']}),
|
||||
iD.osmWay({id: '|', nodes: ['b', 'c']}),
|
||||
iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_right_turn' }, members: [
|
||||
{ id: '-', type: 'way', role: 'from' },
|
||||
{ id: 'b', type: 'node', role: 'via' },
|
||||
{ id: '|', type: 'way', role: 'to' }
|
||||
]})
|
||||
]);
|
||||
|
||||
expect(iD.actionConnect(['a', 'b']).disabled(graph)).to.eql('restriction', 'destroy FROM');
|
||||
expect(iD.actionConnect(['b', 'c']).disabled(graph)).to.eql('restriction', 'destroy TO');
|
||||
});
|
||||
|
||||
it('returns \'restriction\' when connecting would destroy a way in via-way restriction', function () {
|
||||
//
|
||||
// a --- b r1: `no_u_turn`
|
||||
// | FROM '='
|
||||
// | VIA '|'
|
||||
// d === c TO '-'
|
||||
//
|
||||
var graph = iD.coreGraph([
|
||||
iD.osmNode({id: 'a'}),
|
||||
iD.osmNode({id: 'b'}),
|
||||
iD.osmNode({id: 'c'}),
|
||||
iD.osmNode({id: 'd'}),
|
||||
iD.osmWay({id: '-', nodes: ['a', 'b']}),
|
||||
iD.osmWay({id: '|', nodes: ['b', 'c']}),
|
||||
iD.osmWay({id: '=', nodes: ['c', 'd']}),
|
||||
iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_u_turn' }, members: [
|
||||
{ id: '=', type: 'way', role: 'from' },
|
||||
{ id: '|', type: 'way', role: 'via' },
|
||||
{ id: '-', type: 'way', role: 'to' }
|
||||
]})
|
||||
]);
|
||||
|
||||
expect(iD.actionConnect(['a', 'b']).disabled(graph)).to.eql('restriction', 'destroy TO');
|
||||
expect(iD.actionConnect(['b', 'c']).disabled(graph)).to.eql('restriction', 'destroy VIA');
|
||||
expect(iD.actionConnect(['c', 'd']).disabled(graph)).to.eql('restriction', 'destroy FROM');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user