Merge pull request #4693 from openstreetmap/doubled_back_routes

Fixes for joining and splitting bugs
This commit is contained in:
Bryan Housel
2018-01-18 17:01:38 -05:00
committed by GitHub
10 changed files with 2032 additions and 984 deletions

View File

@@ -1,32 +1,175 @@
import { osmJoinWays } from '../osm';
import _clone from 'lodash-es/clone';
import _groupBy from 'lodash-es/groupBy';
import _omit from 'lodash-es/omit';
import { osmJoinWays, osmWay } from '../osm';
export function actionAddMember(relationId, member, memberIndex) {
return function(graph) {
export function actionAddMember(relationId, member, memberIndex, insertPair) {
return function action(graph) {
var relation = graph.entity(relationId);
if (isNaN(memberIndex) && member.type === 'way') {
var members = relation.indexedMembers();
members.push(member);
if ((isNaN(memberIndex) || insertPair) && member.type === 'way') {
// Try to perform sensible inserts based on how the ways join together
graph = addWayMember(relation, graph);
} else {
graph = graph.replace(relation.addMember(member, memberIndex));
}
var joined = osmJoinWays(members, graph);
for (var i = 0; i < joined.length; i++) {
var segment = joined[i];
for (var j = 0; j < segment.length && segment.length >= 2; j++) {
if (segment[j] !== member)
continue;
return graph;
};
if (j === 0) {
memberIndex = segment[j + 1].index;
} else if (j === segment.length - 1) {
memberIndex = segment[j - 1].index + 1;
// Add a way member into the relation "wherever it makes sense".
// In this situation we were not supplied a memberIndex.
function addWayMember(relation, graph) {
var groups, tempWay, item, i, j, k;
if (insertPair) {
// We're adding a member that must stay paired with an existing member.
// (This feature is used by `actionSplit`)
//
// This is tricky because the members may exist multiple times in the
// member list, and with different A-B/B-A ordering and different roles.
// (e.g. a bus route that loops out and back - #4589).
//
// Replace the existing member with a temporary way,
// so that `osmJoinWays` can treat the pair like a single way.
tempWay = osmWay({ id: 'wTemp', nodes: insertPair.nodes });
graph = graph.replace(tempWay);
var tempMember = { id: tempWay.id, type: 'way', role: member.role };
var tempRelation = relation.replaceMember({id: insertPair.originalID}, tempMember, true);
groups = _groupBy(tempRelation.members, function(m) { return m.type; });
groups.way = groups.way || [];
} else {
// Add the member anywhere, one time. Just push and let `osmJoinWays` decide where to put it.
groups = _groupBy(relation.members, function(m) { return m.type; });
groups.way = groups.way || [];
groups.way.push(member);
}
var members = withIndex(groups.way);
var joined = osmJoinWays(members, graph);
// `joined` might not contain all of the way members,
// But will contain only the completed (downloaded) members
for (i = 0; i < joined.length; i++) {
var segment = joined[i];
var nodes = segment.nodes.slice();
var startIndex = segment[0].index;
// j = array index in `members` where this segment starts
for (j = 0; j < members.length; j++) {
if (members[j].index === startIndex) {
break;
}
}
// k = each member in segment
for (k = 0; k < segment.length; k++) {
item = segment[k];
var way = graph.entity(item.id);
// If this is a paired item, generate members in correct order and role
if (tempWay && item.id === tempWay.id) {
if (nodes[0].id === insertPair.nodes[0]) {
item.pair = [
{ id: insertPair.originalID, type: 'way', role: item.role },
{ id: insertPair.insertedID, type: 'way', role: item.role }
];
} else {
memberIndex = Math.min(segment[j - 1].index + 1, segment[j + 1].index + 1);
item.pair = [
{ id: insertPair.insertedID, type: 'way', role: item.role },
{ id: insertPair.originalID, type: 'way', role: item.role }
];
}
}
// reorder `members` if necessary
if (k > 0) {
if (j+k >= members.length || item.index !== members[j+k].index) {
moveMember(members, item.index, j+k);
}
}
nodes.splice(0, way.nodes.length - 1);
}
}
return graph.replace(relation.addMember(member, memberIndex));
};
if (tempWay) {
graph = graph.remove(tempWay);
}
// Final pass: skip dead items, split pairs, remove index properties
var wayMembers = [];
for (i = 0; i < members.length; i++) {
item = members[i];
if (item.index === -1) continue;
if (item.pair) {
wayMembers.push(item.pair[0]);
wayMembers.push(item.pair[1]);
} else {
wayMembers.push(_omit(item, 'index'));
}
}
// Write members in the order: nodes, ways, relations
// This is reccomended for Public Transport routes:
// see https://wiki.openstreetmap.org/wiki/Public_transport#Service_routes
var newMembers = (groups.node || []).concat(wayMembers, (groups.relation || []));
return graph.replace(relation.update({members: newMembers}));
// `moveMember()` changes the `members` array in place by splicing
// the item with `.index = findIndex` to where it belongs,
// and marking the old position as "dead" with `.index = -1`
//
// j=5, k=0 jk
// segment 5 4 7 6
// members 0 1 2 3 4 5 6 7 8 9 keep 5 in j+k
//
// j=5, k=1 j k
// segment 5 4 7 6
// members 0 1 2 3 4 5 6 7 8 9 move 4 to j+k
// members 0 1 2 3 x 5 4 6 7 8 9 moved
//
// j=5, k=2 j k
// segment 5 4 7 6
// members 0 1 2 3 x 5 4 6 7 8 9 move 7 to j+k
// members 0 1 2 3 x 5 4 7 6 x 8 9 moved
//
// j=5, k=3 j k
// segment 5 4 7 6
// members 0 1 2 3 x 5 4 7 6 x 8 9 keep 6 in j+k
//
function moveMember(arr, findIndex, toIndex) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].index === findIndex) {
break;
}
}
var item = _clone(arr[i]);
arr[i].index = -1; // mark as dead
item.index = toIndex;
arr.splice(toIndex, 0, item);
}
// This is the same as `Relation.indexedMembers`,
// Except we don't want to index all the members, only the ways
function withIndex(arr) {
var result = new Array(arr.length);
for (var i = 0; i < arr.length; i++) {
result[i] = arr[i];
result[i].index = i;
}
return result;
}
}
}

View File

@@ -1,13 +1,8 @@
import _extend from 'lodash-es/extend';
import _groupBy from 'lodash-es/groupBy';
import _map from 'lodash-es/map';
import { actionDeleteWay } from './delete_way';
import {
osmIsInterestingTag,
osmJoinWays
} from '../osm';
import { osmIsInterestingTag, osmJoinWays } from '../osm';
// Join ways at the end node they share.
@@ -27,25 +22,30 @@ export function actionJoin(ids) {
var action = function(graph) {
var ways = ids.map(graph.entity, graph),
survivor = ways[0];
var ways = ids.map(graph.entity, graph);
var survivorID = ways[0].id;
// Prefer to keep an existing way.
for (var i = 0; i < ways.length; i++) {
if (!ways[i].isNew()) {
survivor = ways[i];
survivorID = ways[i].id;
break;
}
}
var joined = osmJoinWays(ways, graph)[0];
var sequences = osmJoinWays(ways, graph);
var joined = sequences[0];
survivor = survivor.update({nodes: _map(joined.nodes, 'id')});
// We might need to reverse some of these ways before joining them. #4688
// `joined.actions` property will contain any actions we need to apply.
graph = sequences.actions.reduce(function(g, action) { return action(g); }, graph);
var survivor = graph.entity(survivorID);
survivor = survivor.update({ nodes: joined.nodes.map(function(n) { return n.id; }) });
graph = graph.replace(survivor);
joined.forEach(function(way) {
if (way.id === survivor.id)
return;
if (way.id === survivorID) return;
graph.parentRelations(way).forEach(function(parent) {
graph = graph.replace(parent.replaceMember(way, survivor));
@@ -70,10 +70,10 @@ export function actionJoin(ids) {
if (joined.length > 1)
return 'not_adjacent';
var nodeIds = _map(joined[0].nodes, 'id').slice(1, -1),
relation,
tags = {},
conflicting = false;
var nodeIds = joined[0].nodes.map(function(n) { return n.id; }).slice(1, -1);
var relation;
var tags = {};
var conflicting = false;
joined[0].forEach(function(way) {
var parents = graph.parentRelations(way);

View File

@@ -29,7 +29,7 @@ import { utilWrap } from '../util';
// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as
//
export function actionSplit(nodeId, newWayIds) {
var wayIds;
var _wayIDs;
// if the way is closed, we need to search for a partner node
// to split the way at.
@@ -42,11 +42,11 @@ export function actionSplit(nodeId, newWayIds) {
// For example: bone-shaped areas get split across their waist
// line, circles across the diameter.
function splitArea(nodes, idxA, graph) {
var lengths = new Array(nodes.length),
length,
i,
best = 0,
idxB;
var lengths = new Array(nodes.length);
var length;
var i;
var best = 0;
var idxB;
function wrap(index) {
return utilWrap(index, nodes.length);
@@ -84,16 +84,17 @@ export function actionSplit(nodeId, newWayIds) {
function split(graph, wayA, newWayId) {
var wayB = osmWay({id: newWayId, tags: wayA.tags}),
nodesA,
nodesB,
isArea = wayA.isArea(),
isOuter = osmIsSimpleMultipolygonOuterMember(wayA, graph);
var wayB = osmWay({id: newWayId, tags: wayA.tags});
var origNodes = wayA.nodes.slice();
var nodesA;
var nodesB;
var isArea = wayA.isArea();
var isOuter = osmIsSimpleMultipolygonOuterMember(wayA, graph);
if (wayA.isClosed()) {
var nodes = wayA.nodes.slice(0, -1),
idxA = _indexOf(nodes, nodeId),
idxB = splitArea(nodes, idxA, graph);
var nodes = wayA.nodes.slice(0, -1);
var idxA = _indexOf(nodes, nodeId);
var idxB = splitArea(nodes, idxA, graph);
if (idxB < idxA) {
nodesA = nodes.slice(idxA).concat(nodes.slice(0, idxB + 1));
@@ -134,7 +135,13 @@ export function actionSplit(nodeId, newWayIds) {
role: relation.memberById(wayA.id).role
};
graph = actionAddMember(relation.id, member)(graph);
var insertPair = {
originalID: wayA.id,
insertedID: wayB.id,
nodes: origNodes
};
graph = actionAddMember(relation.id, member, undefined, insertPair)(graph);
}
});
@@ -144,7 +151,8 @@ export function actionSplit(nodeId, newWayIds) {
members: [
{id: wayA.id, role: 'outer', type: 'way'},
{id: wayB.id, role: 'outer', type: 'way'}
]});
]
});
graph = graph.replace(multipolygon);
graph = graph.replace(wayA.update({tags: {}}));
@@ -165,15 +173,15 @@ export function actionSplit(nodeId, newWayIds) {
action.ways = function(graph) {
var node = graph.entity(nodeId),
parents = graph.parentWays(node),
hasLines = _some(parents, function(parent) { return parent.geometry(graph) === 'line'; });
var node = graph.entity(nodeId);
var parents = graph.parentWays(node);
var hasLines = _some(parents, function(parent) { return parent.geometry(graph) === 'line'; });
return parents.filter(function(parent) {
if (wayIds && wayIds.indexOf(parent.id) === -1)
if (_wayIDs && _wayIDs.indexOf(parent.id) === -1)
return false;
if (!wayIds && hasLines && parent.geometry(graph) !== 'line')
if (!_wayIDs && hasLines && parent.geometry(graph) !== 'line')
return false;
if (parent.isClosed()) {
@@ -193,14 +201,14 @@ export function actionSplit(nodeId, newWayIds) {
action.disabled = function(graph) {
var candidates = action.ways(graph);
if (candidates.length === 0 || (wayIds && wayIds.length !== candidates.length))
if (candidates.length === 0 || (_wayIDs && _wayIDs.length !== candidates.length))
return 'not_eligible';
};
action.limitWays = function(_) {
if (!arguments.length) return wayIds;
wayIds = _;
if (!arguments.length) return _wayIDs;
_wayIDs = _;
return action;
};

View File

@@ -1,5 +1,6 @@
import { actionReverse } from '../actions/reverse';
import { osmIsInterestingTag } from './tags';
import { osmWay } from './way';
// For fixing up rendering of multipolygons with tags on the outer member.
@@ -62,87 +63,122 @@ export function osmSimpleMultipolygonOuterMember(entity, graph) {
}
// Join `array` into sequences of connecting ways.
//
// Join `toJoin` array into sequences of connecting ways.
// Segments which share identical start/end nodes will, as much as possible,
// be connected with each other.
//
// The return value is a nested array. Each constituent array contains elements
// of `array` which have been determined to connect. Each consitituent array
// also has a `nodes` property whose value is an ordered array of member nodes,
// with appropriate order reversal and start/end coordinate de-duplication.
// of `toJoin` which have been determined to connect.
//
// Members of `array` must have, at minimum, `type` and `id` properties.
// Thus either an array of `osmWay`s or a relation member array may be
// used.
// Each consitituent array also has a `nodes` property whose value is an
// ordered array of member nodes, with appropriate order reversal and
// start/end coordinate de-duplication.
//
// If an member has a `tags` property, its tags will be reversed via
// Members of `toJoin` must have, at minimum, `type` and `id` properties.
// Thus either an array of `osmWay`s or a relation member array may be used.
//
// If an member is an `osmWay`, its tags and childnodes may be reversed via
// `actionReverse` in the output.
//
// The returned sequences array also has an `actions` array property, containing
// any reversal actions that should be applied to the graph, should the calling
// code attempt to actually join the given ways.
//
// Incomplete members (those for which `graph.hasEntity(element.id)` returns
// false) and non-way members are ignored.
//
export function osmJoinWays(array, graph) {
var joined = [], member, current, nodes, first, last, i, how, what;
array = array.filter(function(member) {
return member.type === 'way' && graph.hasEntity(member.id);
});
export function osmJoinWays(toJoin, graph) {
function resolve(member) {
return graph.childNodes(graph.entity(member.id));
}
function reverse(member) {
return member.tags ? actionReverse(member.id, { reverseOneway: true })(graph).entity(member.id) : member;
function reverse(item) {
var action = actionReverse(item.id, { reverseOneway: true });
sequences.actions.push(action);
return (item instanceof osmWay) ? action(graph).entity(item.id) : item;
}
while (array.length) {
member = array.shift();
current = [member];
current.nodes = nodes = resolve(member).slice();
joined.push(current);
// make a copy containing only the items to join
toJoin = toJoin.filter(function(member) {
return member.type === 'way' && graph.hasEntity(member.id);
});
while (array.length && nodes[0] !== nodes[nodes.length - 1]) {
first = nodes[0];
last = nodes[nodes.length - 1];
for (i = 0; i < array.length; i++) {
member = array[i];
what = resolve(member);
var sequences = [];
sequences.actions = [];
if (last === what[0]) {
how = nodes.push;
what = what.slice(1);
while (toJoin.length) {
// start a new sequence
var item = toJoin.shift();
var currWays = [item];
var currNodes = resolve(item).slice();
var doneSequence = false;
// add to it
while (toJoin.length && !doneSequence) {
var start = currNodes[0];
var end = currNodes[currNodes.length - 1];
var fn = null;
var nodes = null;
var i;
// Find the next way/member to join.
for (i = 0; i < toJoin.length; i++) {
item = toJoin[i];
nodes = resolve(item);
// Strongly prefer to generate a forward path that preserves the order
// of the members array. For multipolygons and most relations, member
// order does not matter - but for routes, it does. If we started this
// sequence backwards (i.e. next member way attaches to the start node
// and not the end node), reverse the initial way before continuing.
if (currWays.length === 1 && nodes[0] !== end && nodes[nodes.length - 1] !== end &&
(nodes[nodes.length - 1] === start || nodes[0] === start)
) {
currWays[0] = reverse(currWays[0]);
currNodes.reverse();
start = currNodes[0];
end = currNodes[currNodes.length - 1];
}
if (nodes[0] === end) {
fn = currNodes.push; // join to end
nodes = nodes.slice(1);
break;
} else if (last === what[what.length - 1]) {
how = nodes.push;
what = what.slice(0, -1).reverse();
member = reverse(member);
} else if (nodes[nodes.length - 1] === end) {
fn = currNodes.push; // join to end
nodes = nodes.slice(0, -1).reverse();
item = reverse(item);
break;
} else if (first === what[what.length - 1]) {
how = nodes.unshift;
what = what.slice(0, -1);
} else if (nodes[nodes.length - 1] === start) {
fn = currNodes.unshift; // join to beginning
nodes = nodes.slice(0, -1);
break;
} else if (first === what[0]) {
how = nodes.unshift;
what = what.slice(1).reverse();
member = reverse(member);
} else if (nodes[0] === start) {
fn = currNodes.unshift; // join to beginning
nodes = nodes.slice(1).reverse();
item = reverse(item);
break;
} else {
what = how = null;
fn = nodes = null;
}
}
if (!what)
break; // No more joinable ways.
if (!nodes) { // couldn't find a joinable way/member
doneSequence = true;
break;
}
how.apply(current, [member]);
how.apply(nodes, what);
fn.apply(currWays, [item]);
fn.apply(currNodes, nodes);
array.splice(i, 1);
toJoin.splice(i, 1);
}
currWays.nodes = currNodes;
sequences.push(currWays);
}
return joined;
return sequences;
}

View File

@@ -161,9 +161,9 @@ _extend(osmRelation.prototype, {
// Wherever a member appears with id `needle.id`, replace it with a member
// with id `replacement.id`, type `replacement.type`, and the original role,
// unless a member already exists with that id and role. Return an updated
// relation.
replaceMember: function(needle, replacement) {
// By default, adding a duplicate member (by id and role) is prevented.
// Return an updated relation.
replaceMember: function(needle, replacement, keepDuplicates) {
if (!this.memberById(needle.id))
return this;
@@ -173,7 +173,7 @@ _extend(osmRelation.prototype, {
var member = this.members[i];
if (member.id !== needle.id) {
members.push(member);
} else if (!this.memberByIdAndRole(replacement.id, member.role)) {
} else if (keepDuplicates || !this.memberByIdAndRole(replacement.id, member.role)) {
members.push({id: replacement.id, type: replacement.type, role: member.role});
}
}

View File

@@ -1,7 +1,7 @@
describe('iD.actionAddMember', function() {
it('adds an member to a relation at the specified index', function() {
var r = iD.Relation({members: [{id: '1'}, {id: '3'}]}),
g = iD.actionAddMember(r.id, {id: '2'}, 1)(iD.Graph([r]));
var r = iD.osmRelation({members: [{id: '1'}, {id: '3'}]});
var g = iD.actionAddMember(r.id, {id: '2'}, 1)(iD.coreGraph([r]));
expect(g.entity(r.id).members).to.eql([{id: '1'}, {id: '2'}, {id: '3'}]);
});
@@ -10,81 +10,197 @@ describe('iD.actionAddMember', function() {
return graph.entity('r').members.map(function (m) { return m.id; });
}
specify('no members', function() {
var graph = iD.Graph([
iD.Node({id: 'a', loc: [0, 0]}),
iD.Node({id: 'b', loc: [0, 0]}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Relation({id: 'r'})
it('handles incomplete relations', function () {
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [0, 0]}),
iD.osmNode({id: 'c', loc: [0, 0]}),
iD.osmNode({id: 'd', loc: [0, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
iD.osmWay({id: '=', nodes: ['c','d']}),
iD.osmRelation({id: 'r', members: [
{id: '~', type: 'way'},
{id: '-', type: 'way'}
]})
]);
graph = iD.actionAddMember('r', {id: '=', type: 'way'})(graph);
expect(members(graph)).to.eql(['~', '-', '=']);
});
it('adds the member to a relation with no members', function() {
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [0, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b']}),
iD.osmRelation({id: 'r'})
]);
graph = iD.actionAddMember('r', {id: '-', type: 'way'})(graph);
expect(members(graph)).to.eql(['-']);
});
specify('not connecting', function() {
// a--->b c===>d
var graph = iD.Graph([
iD.Node({id: 'a', loc: [0, 0]}),
iD.Node({id: 'b', loc: [0, 0]}),
iD.Node({id: 'c', loc: [0, 0]}),
iD.Node({id: 'd', loc: [0, 0]}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['c', 'd']}),
iD.Relation({id: 'r', members: [{id: '-', type: 'way'}]})
it('appends the member if the ways are not connecting', function() {
// Before: a ---> b
// After: a ---> b .. c ===> d
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [0, 0]}),
iD.osmNode({id: 'c', loc: [0, 0]}),
iD.osmNode({id: 'd', loc: [0, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b']}),
iD.osmWay({id: '=', nodes: ['c', 'd']}),
iD.osmRelation({id: 'r', members: [
{id: '-', type: 'way'}
]})
]);
graph = iD.actionAddMember('r', {id: '=', type: 'way'})(graph);
expect(members(graph)).to.eql(['-', '=']);
});
specify('connecting at end', function() {
// a--->b===>c
var graph = iD.Graph([
iD.Node({id: 'a', loc: [0, 0]}),
iD.Node({id: 'b', loc: [0, 0]}),
iD.Node({id: 'c', loc: [0, 0]}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['b', 'c']}),
iD.Relation({id: 'r', members: [{id: '-', type: 'way'}]})
it('appends the member if the way connects at end', function() {
// Before: a ---> b
// After: a ---> b ===> c
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [0, 0]}),
iD.osmNode({id: 'c', loc: [0, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b']}),
iD.osmWay({id: '=', nodes: ['b', 'c']}),
iD.osmRelation({id: 'r', members: [
{id: '-', type: 'way'}
]})
]);
graph = iD.actionAddMember('r', {id: '=', type: 'way'})(graph);
expect(members(graph)).to.eql(['-', '=']);
});
specify('connecting at beginning', function() {
// a===>b--->c~~~>d
var graph = iD.Graph([
iD.Node({id: 'a', loc: [0, 0]}),
iD.Node({id: 'b', loc: [0, 0]}),
iD.Node({id: 'c', loc: [0, 0]}),
iD.Node({id: 'd', loc: [0, 0]}),
iD.Way({id: '=', nodes: ['a', 'b']}),
iD.Way({id: '-', nodes: ['b', 'c']}),
iD.Way({id: '~', nodes: ['c', 'd']}),
iD.Relation({id: 'r', members: [{id: '-', type: 'way'}, {id: '~', type: 'way'}]})
it('inserts the member if the way connects at beginning', function() {
// Before: b ---> c ~~~> d
// After: a ===> b ---> c ~~~> d
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [0, 0]}),
iD.osmNode({id: 'c', loc: [0, 0]}),
iD.osmNode({id: 'd', loc: [0, 0]}),
iD.osmWay({id: '=', nodes: ['a', 'b']}),
iD.osmWay({id: '-', nodes: ['b', 'c']}),
iD.osmWay({id: '~', nodes: ['c', 'd']}),
iD.osmRelation({id: 'r', members: [
{id: '-', type: 'way'},
{id: '~', type: 'way'}
]})
]);
graph = iD.actionAddMember('r', {id: '=', type: 'way'})(graph);
expect(members(graph)).to.eql(['=', '-', '~']);
});
specify('connecting in middle', function() {
// a--->b===>c~~~>d
var graph = iD.Graph([
iD.Node({id: 'a', loc: [0, 0]}),
iD.Node({id: 'b', loc: [0, 0]}),
iD.Node({id: 'c', loc: [0, 0]}),
iD.Node({id: 'd', loc: [0, 0]}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['b', 'c']}),
iD.Way({id: '~', nodes: ['c', 'd']}),
iD.Relation({id: 'r', members: [{id: '-', type: 'way'}, {id: '~', type: 'way'}]})
it('inserts the member if the way connects in middle', function() {
// Before: a ---> b .. c ~~~> d
// After: a ---> b ===> c ~~~> d
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [0, 0]}),
iD.osmNode({id: 'c', loc: [0, 0]}),
iD.osmNode({id: 'd', loc: [0, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b']}),
iD.osmWay({id: '=', nodes: ['b', 'c']}),
iD.osmWay({id: '~', nodes: ['c', 'd']}),
iD.osmRelation({id: 'r', members: [
{id: '-', type: 'way'},
{id: '~', type: 'way'}
]})
]);
graph = iD.actionAddMember('r', {id: '=', type: 'way'})(graph);
expect(members(graph)).to.eql(['-', '=', '~']);
});
it('inserts the member multiple times if insertPair provided (middle)', function() {
// Before: a ---> b .. c ~~~> d <~~~ c .. b <--- a
// After: a ---> b ===> c ~~~> d <~~~ c <=== b <--- a
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [0, 0]}),
iD.osmNode({id: 'c', loc: [0, 0]}),
iD.osmNode({id: 'd', loc: [0, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b']}),
iD.osmWay({id: '=', nodes: ['b', 'c']}),
iD.osmWay({id: '~', nodes: ['c', 'd']}),
iD.osmRelation({id: 'r', members: [
{id: '-', type: 'way'},
{id: '~', type: 'way'},
{id: '~', type: 'way'},
{id: '-', type: 'way'}
]})
]);
var member = { id: '=', type: 'way' };
var insertPair = {
originalID: '-',
insertedID: '=',
nodes: ['a','b','c']
};
graph = iD.actionAddMember('r', member, undefined, insertPair)(graph);
expect(members(graph)).to.eql(['-', '=', '~', '~', '=', '-']);
});
it('inserts the member multiple times if insertPair provided (beginning/end)', function() {
// Before: b <=== c ~~~> d <~~~ c ===> b
// After: a <--- b <=== c ~~~> d <~~~ c ===> b ---> a
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [0, 0]}),
iD.osmNode({id: 'c', loc: [0, 0]}),
iD.osmNode({id: 'd', loc: [0, 0]}),
iD.osmWay({id: '-', nodes: ['b', 'a']}),
iD.osmWay({id: '=', nodes: ['c', 'b']}),
iD.osmWay({id: '~', nodes: ['c', 'd']}),
iD.osmRelation({id: 'r', members: [
{id: '=', type: 'way'},
{id: '~', type: 'way'},
{id: '~', type: 'way'},
{id: '=', type: 'way'}
]})
]);
var member = { id: '-', type: 'way' };
var insertPair = {
originalID: '=',
insertedID: '-',
nodes: ['c','b','a']
};
graph = iD.actionAddMember('r', member, undefined, insertPair)(graph);
expect(members(graph)).to.eql(['-', '=', '~', '~', '=', '-']);
});
it('reorders members as node, way, relation (for Public Transport routing)', function() {
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [0, 0]}),
iD.osmNode({id: 'c', loc: [0, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b']}),
iD.osmWay({id: '=', nodes: ['b', 'c']}),
iD.osmRelation({id: 'r', members: [
{ id: 'n1', type: 'node', role: 'forward' },
{ id: '-', type: 'way', role: 'forward' },
{ id: 'r1', type: 'relation', role: 'forward' },
{ id: 'n2', type: 'node', role: 'forward' }
]})
]);
graph = iD.actionAddMember('r', { id: '=', type: 'way', role: 'forward' })(graph);
expect(graph.entity('r').members).to.eql([
{ id: 'n1', type: 'node', role: 'forward' },
{ id: 'n2', type: 'node', role: 'forward' },
{ id: '-', type: 'way', role: 'forward' },
{ id: '=', type: 'way', role: 'forward' },
{ id: 'r1', type: 'relation', role: 'forward' }
]);
});
});
});

View File

@@ -2,67 +2,67 @@ describe('iD.actionJoin', function () {
describe('#disabled', function () {
it('returns falsy for ways that share an end/start node', function () {
// a --> b ==> c
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['b', 'c']})
]);
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']})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).not.to.be.ok;
});
it('returns falsy for ways that share a start/end node', function () {
// a <-- b <== c
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['b', 'a']}),
iD.Way({id: '=', nodes: ['c', 'b']})
]);
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmWay({id: '-', nodes: ['b', 'a']}),
iD.osmWay({id: '=', nodes: ['c', 'b']})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).not.to.be.ok;
});
it('returns falsy for ways that share a start/start node', function () {
// a <-- b ==> c
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['b', 'a']}),
iD.Way({id: '=', nodes: ['b', 'c']})
]);
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmWay({id: '-', nodes: ['b', 'a']}),
iD.osmWay({id: '=', nodes: ['b', 'c']})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).not.to.be.ok;
});
it('returns falsy for ways that share an end/end node', function () {
// a --> b <== c
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['c', 'b']})
]);
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: ['c', 'b']})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).not.to.be.ok;
});
it('returns falsy for more than two ways when connected, regardless of order', function () {
// a --> b ==> c ~~> d
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: 'd'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['b', 'c']}),
iD.Way({id: '~', nodes: ['c', 'd']})
]);
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']})
]);
expect(iD.actionJoin(['-', '=', '~']).disabled(graph)).not.to.be.ok;
expect(iD.actionJoin(['-', '~', '=']).disabled(graph)).not.to.be.ok;
@@ -73,9 +73,9 @@ describe('iD.actionJoin', function () {
});
it('returns \'not_eligible\' for non-line geometries', function () {
var graph = iD.Graph([
iD.Node({id: 'a'})
]);
var graph = iD.coreGraph([
iD.osmNode({id: 'a'})
]);
expect(iD.actionJoin(['a']).disabled(graph)).to.equal('not_eligible');
});
@@ -84,14 +84,14 @@ describe('iD.actionJoin', function () {
// a -- b -- c
// |
// d
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: 'd'}),
iD.Way({id: '-', nodes: ['a', 'b', 'c']}),
iD.Way({id: '=', nodes: ['b', 'd']})
]);
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', 'c']}),
iD.osmWay({id: '=', nodes: ['b', 'd']})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).to.equal('not_adjacent');
});
@@ -101,18 +101,18 @@ describe('iD.actionJoin', function () {
// from: -
// to: =
// via: b
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['b', 'c']}),
iD.Relation({id: 'r', tags: {type: 'restriction'}, members: [
{type: 'way', id: '-', role: 'from'},
{type: 'way', id: '=', role: 'to'},
{type: 'node', id: 'b', role: 'via'}
]})
]);
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: 'r', tags: {type: 'restriction'}, members: [
{type: 'way', id: '-', role: 'from'},
{type: 'way', id: '=', role: 'to'},
{type: 'node', id: 'b', role: 'via'}
]})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).to.equal('restriction');
});
@@ -124,20 +124,20 @@ describe('iD.actionJoin', function () {
// from: -
// to: |
// via: b
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: 'd'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['b', 'c']}),
iD.Way({id: '|', nodes: ['b', 'd']}),
iD.Relation({id: 'r', tags: {type: 'restriction'}, members: [
{type: 'way', id: '-', role: 'from'},
{type: 'way', id: '|', role: 'to'},
{type: 'node', id: 'b', role: 'via'}
]})
]);
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: ['b', 'd']}),
iD.osmRelation({id: 'r', tags: {type: 'restriction'}, members: [
{type: 'way', id: '-', role: 'from'},
{type: 'way', id: '|', role: 'to'},
{type: 'node', id: 'b', role: 'via'}
]})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).to.equal('restriction');
});
@@ -149,20 +149,20 @@ describe('iD.actionJoin', function () {
// from: -
// to: |
// via: a
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: 'd'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['b', 'c']}),
iD.Way({id: '|', nodes: ['a', 'd']}),
iD.Relation({id: 'r', tags: {type: 'restriction'}, members: [
{type: 'way', id: '-', role: 'from'},
{type: 'way', id: '|', role: 'to'},
{type: 'node', id: 'a', role: 'via'}
]})
]);
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: ['a', 'd']}),
iD.osmRelation({id: 'r', tags: {type: 'restriction'}, members: [
{type: 'way', id: '-', role: 'from'},
{type: 'way', id: '|', role: 'to'},
{type: 'node', id: 'a', role: 'via'}
]})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).not.to.be.ok;
});
@@ -176,68 +176,68 @@ describe('iD.actionJoin', function () {
// from: |
// to: \
// via: b
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: 'd'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['b', 'c']}),
iD.Way({id: '|', nodes: ['d', 'b']}),
iD.Way({id: '\\', nodes: ['b', 'e']}),
iD.Relation({id: 'r', tags: {type: 'restriction'}, members: [
{type: 'way', id: '|', role: 'from'},
{type: 'way', id: '\\', role: 'to'},
{type: 'node', id: 'b', role: 'via'}
]})
]);
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: ['d', 'b']}),
iD.osmWay({id: '\\', nodes: ['b', 'e']}),
iD.osmRelation({id: 'r', tags: {type: 'restriction'}, members: [
{type: 'way', id: '|', role: 'from'},
{type: 'way', id: '\\', role: 'to'},
{type: 'node', id: 'b', role: 'via'}
]})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).not.to.be.ok;
});
it('returns \'conflicting_tags\' for two entities that have conflicting tags', function () {
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b'], tags: {highway: 'primary'}}),
iD.Way({id: '=', nodes: ['b', 'c'], tags: {highway: 'secondary'}})
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmWay({id: '-', nodes: ['a', 'b'], tags: {highway: 'primary'}}),
iD.osmWay({id: '=', nodes: ['b', 'c'], tags: {highway: 'secondary'}})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).to.equal('conflicting_tags');
});
it('takes tag reversals into account when calculating conflicts', function () {
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b'], tags: {'oneway': 'yes'}}),
iD.Way({id: '=', nodes: ['c', 'b'], tags: {'oneway': '-1'}})
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmWay({id: '-', nodes: ['a', 'b'], tags: {'oneway': 'yes'}}),
iD.osmWay({id: '=', nodes: ['c', 'b'], tags: {'oneway': '-1'}})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).not.to.be.ok;
});
it('returns falsy for exceptions to tag conflicts: missing tag', function () {
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b'], tags: {highway: 'primary'}}),
iD.Way({id: '=', nodes: ['b', 'c'], tags: {}})
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmWay({id: '-', nodes: ['a', 'b'], tags: {highway: 'primary'}}),
iD.osmWay({id: '=', nodes: ['b', 'c'], tags: {}})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).not.to.be.ok;
});
it('returns falsy for exceptions to tag conflicts: uninteresting tag', function () {
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b'], tags: {'tiger:cfcc': 'A41'}}),
iD.Way({id: '=', nodes: ['b', 'c'], tags: {'tiger:cfcc': 'A42'}})
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmWay({id: '-', nodes: ['a', 'b'], tags: {'tiger:cfcc': 'A41'}}),
iD.osmWay({id: '=', nodes: ['b', 'c'], tags: {'tiger:cfcc': 'A42'}})
]);
expect(iD.actionJoin(['-', '=']).disabled(graph)).not.to.be.ok;
@@ -247,13 +247,13 @@ describe('iD.actionJoin', function () {
it('joins a --> b ==> c', function () {
// Expected result:
// a --> b --> c
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['b', 'c']})
]);
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']})
]);
graph = iD.actionJoin(['-', '='])(graph);
@@ -264,35 +264,35 @@ describe('iD.actionJoin', function () {
it('joins a <-- b <== c', function () {
// Expected result:
// a <-- b <-- c
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['b', 'a']}),
iD.Way({id: '=', nodes: ['c', 'b']})
]);
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmWay({id: '-', nodes: ['b', 'a']}),
iD.osmWay({id: '=', nodes: ['c', 'b']})
]);
graph = iD.actionJoin(['-', '='])(graph);
expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.hasEntity('=')).to.be.undefined;
});
it('joins a <-- b ==> c', function () {
// Expected result:
// a <-- b <-- c
// tags on === reversed
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['b', 'a']}),
iD.Way({id: '=', nodes: ['b', 'c'], tags: {'lanes:forward': 2}})
]);
// a --> b --> c
// tags on --- reversed
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmWay({id: '-', nodes: ['b', 'a'], tags: {'lanes:forward': 2}}),
iD.osmWay({id: '=', nodes: ['b', 'c']})
]);
graph = iD.actionJoin(['-', '='])(graph);
expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.hasEntity('=')).to.be.undefined;
expect(graph.entity('-').tags).to.eql({'lanes:backward': 2});
});
@@ -301,13 +301,13 @@ describe('iD.actionJoin', function () {
// Expected result:
// a --> b --> c
// tags on === reversed
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['c', 'b'], tags: {'lanes:forward': 2}})
]);
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: ['c', 'b'], tags: {'lanes:forward': 2}})
]);
graph = iD.actionJoin(['-', '='])(graph);
@@ -320,17 +320,17 @@ describe('iD.actionJoin', function () {
// Expected result:
// a --> b --> c --> d --> e
// tags on === reversed
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: 'd'}),
iD.Node({id: 'e'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['c', 'b'], tags: {'lanes:forward': 2}}),
iD.Way({id: '+', nodes: ['d', 'c']}),
iD.Way({id: '*', nodes: ['d', 'e'], tags: {'lanes:backward': 2}})
]);
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: ['c', 'b'], tags: {'lanes:forward': 2}}),
iD.osmWay({id: '+', nodes: ['d', 'c']}),
iD.osmWay({id: '*', nodes: ['d', 'e'], tags: {'lanes:backward': 2}})
]);
graph = iD.actionJoin(['-', '=', '+', '*'])(graph);
@@ -346,15 +346,15 @@ describe('iD.actionJoin', function () {
// --- is new, === is existing, +++ is new
// Expected result:
// a ==> b ==> c ==> d
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: 'd'}),
iD.Way({id: 'w-1', nodes: ['a', 'b']}),
iD.Way({id: 'w1', nodes: ['b', 'c']}),
iD.Way({id: 'w-2', nodes: ['c', 'd']})
]);
var graph = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmNode({id: 'd'}),
iD.osmWay({id: 'w-1', nodes: ['a', 'b']}),
iD.osmWay({id: 'w1', nodes: ['b', 'c']}),
iD.osmWay({id: 'w-2', nodes: ['c', 'd']})
]);
graph = iD.actionJoin(['w-1', 'w1', 'w-2'])(graph);
@@ -364,15 +364,15 @@ describe('iD.actionJoin', function () {
});
it('merges tags', function () {
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Node({id: 'd'}),
iD.Way({id: '-', nodes: ['a', 'b'], tags: {a: 'a', b: '-', c: 'c'}}),
iD.Way({id: '=', nodes: ['b', 'c'], tags: {a: 'a', b: '=', d: 'd'}}),
iD.Way({id: '+', nodes: ['c', 'd'], tags: {a: 'a', b: '=', e: 'e'}})
]);
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'], tags: {a: 'a', b: '-', c: 'c'}}),
iD.osmWay({id: '=', nodes: ['b', 'c'], tags: {a: 'a', b: '=', d: 'd'}}),
iD.osmWay({id: '+', nodes: ['c', 'd'], tags: {a: 'a', b: '=', e: 'e'}})
]);
graph = iD.actionJoin(['-', '=', '+'])(graph);
@@ -380,19 +380,65 @@ describe('iD.actionJoin', function () {
});
it('merges relations', function () {
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['b', 'c']}),
iD.Relation({id: 'r1', members: [{id: '=', role: 'r1', type: 'way'}]}),
iD.Relation({id: 'r2', members: [{id: '=', role: 'r2', type: 'way'}, {id: '-', role: 'r2', type: 'way'}]})
]);
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', members: [
{id: '=', role: 'r1', type: 'way'}
]}),
iD.osmRelation({id: 'r2', members: [
{id: '=', role: 'r2', type: 'way'},
{id: '-', role: 'r2', type: 'way'}
]})
]);
graph = iD.actionJoin(['-', '='])(graph);
expect(graph.entity('r1').members).to.eql([{id: '-', role: 'r1', type: 'way'}]);
expect(graph.entity('r2').members).to.eql([{id: '-', role: 'r2', type: 'way'}]);
});
it('preserves duplicate route segments in relations', function () {
//
// Situation:
// a ---> b ===> c ~~~~> d join '-' and '='
// Relation: ['-', '=', '~', '~', '=', '-']
//
// Expected result:
// a ---> b ---> c ~~~~> d
// Relation: ['-', '~', '~', '-']
//
var graph = iD.coreGraph([
iD.osmNode({ id: 'a', loc: [0, 0] }),
iD.osmNode({ id: 'b', loc: [1, 0] }),
iD.osmNode({ id: 'c', loc: [2, 0] }),
iD.osmNode({ id: 'd', loc: [3, 0] }),
iD.osmWay({ id: '-', nodes: ['a', 'b'] }),
iD.osmWay({ id: '=', nodes: ['b', 'c'] }),
iD.osmWay({ id: '~', nodes: ['c', 'd'] }),
iD.osmRelation({id: 'r', members: [
{id: '-', role: 'forward', type: 'way'},
{id: '=', role: 'forward', type: 'way'},
{id: '~', role: 'forward', type: 'way'},
{id: '~', role: 'forward', type: 'way'},
{id: '=', role: 'forward', type: 'way'},
{id: '-', role: 'forward', type: 'way'}
]})
]);
graph = iD.actionJoin(['-', '='])(graph);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']);
expect(graph.entity('~').nodes).to.eql(['c', 'd']);
expect(graph.entity('r').members).to.eql([
{id: '-', role: 'forward', type: 'way'},
{id: '~', role: 'forward', type: 'way'},
{id: '~', role: 'forward', type: 'way'},
{id: '-', role: 'forward', type: 'way'}
]);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,91 +1,101 @@
describe('iD.osmIsSimpleMultipolygonOuterMember', function() {
it('returns the parent relation of a simple multipolygon outer', function() {
var outer = iD.Way({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {type: 'multipolygon'},
members: [{id: outer.id, role: 'outer'}]}),
graph = iD.Graph([outer, relation]);
var outer = iD.osmWay({tags: {'natural':'wood'}});
var relation = iD.osmRelation(
{tags: {type: 'multipolygon'}, members: [{id: outer.id, role: 'outer'}]}
);
var graph = iD.coreGraph([outer, relation]);
expect(iD.osmIsSimpleMultipolygonOuterMember(outer, graph)).to.equal(relation);
});
it('returns the parent relation of a simple multipolygon outer, assuming role outer if unspecified', function() {
var outer = iD.Way({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {type: 'multipolygon'},
members: [{id: outer.id}]}),
graph = iD.Graph([outer, relation]);
var outer = iD.osmWay({tags: {'natural':'wood'}});
var relation = iD.osmRelation(
{tags: {type: 'multipolygon'}, members: [{id: outer.id}]}
);
var graph = iD.coreGraph([outer, relation]);
expect(iD.osmIsSimpleMultipolygonOuterMember(outer, graph)).to.equal(relation);
});
it('returns false if entity is not a way', function() {
var outer = iD.Node({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {type: 'multipolygon'},
members: [{id: outer.id, role: 'outer'}]}),
graph = iD.Graph([outer, relation]);
var outer = iD.osmNode({tags: {'natural':'wood'}});
var relation = iD.osmRelation(
{tags: {type: 'multipolygon'}, members: [{id: outer.id, role: 'outer'}]}
);
var graph = iD.coreGraph([outer, relation]);
expect(iD.osmIsSimpleMultipolygonOuterMember(outer, graph)).to.be.false;
});
it('returns false if entity does not have interesting tags', function() {
var outer = iD.Way({tags: {'tiger:reviewed':'no'}}),
relation = iD.Relation({tags: {type: 'multipolygon'},
members: [{id: outer.id, role: 'outer'}]}),
graph = iD.Graph([outer, relation]);
var outer = iD.osmWay({tags: {'tiger:reviewed':'no'}});
var relation = iD.osmRelation(
{tags: {type: 'multipolygon'}, members: [{id: outer.id, role: 'outer'}]}
);
var graph = iD.coreGraph([outer, relation]);
expect(iD.osmIsSimpleMultipolygonOuterMember(outer, graph)).to.be.false;
});
it('returns false if entity does not have a parent relation', function() {
var outer = iD.Way({tags: {'natural':'wood'}}),
graph = iD.Graph([outer]);
var outer = iD.osmWay({tags: {'natural':'wood'}});
var graph = iD.coreGraph([outer]);
expect(iD.osmIsSimpleMultipolygonOuterMember(outer, graph)).to.be.false;
});
it('returns false if the parent is not a multipolygon', function() {
var outer = iD.Way({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {type: 'route'},
members: [{id: outer.id, role: 'outer'}]}),
graph = iD.Graph([outer, relation]);
var outer = iD.osmWay({tags: {'natural':'wood'}});
var relation = iD.osmRelation(
{tags: {type: 'route'}, members: [{id: outer.id, role: 'outer'}]}
);
var graph = iD.coreGraph([outer, relation]);
expect(iD.osmIsSimpleMultipolygonOuterMember(outer, graph)).to.be.false;
});
it('returns false if the parent has interesting tags', function() {
var outer = iD.Way({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {natural: 'wood', type: 'multipolygon'},
members: [{id: outer.id, role: 'outer'}]}),
graph = iD.Graph([outer, relation]);
var outer = iD.osmWay({tags: {'natural':'wood'}});
var relation = iD.osmRelation(
{tags: {natural: 'wood', type: 'multipolygon'}, members: [{id: outer.id, role: 'outer'}]}
);
var graph = iD.coreGraph([outer, relation]);
expect(iD.osmIsSimpleMultipolygonOuterMember(outer, graph)).to.be.false;
});
it('returns the parent relation of a simple multipolygon outer, ignoring uninteresting parent tags', function() {
var outer = iD.Way({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {'tiger:reviewed':'no', type: 'multipolygon'},
members: [{id: outer.id, role: 'outer'}]}),
graph = iD.Graph([outer, relation]);
var outer = iD.osmWay({tags: {'natural':'wood'}});
var relation = iD.osmRelation(
{tags: {'tiger:reviewed':'no', type: 'multipolygon'}, members: [{id: outer.id, role: 'outer'}]}
);
var graph = iD.coreGraph([outer, relation]);
expect(iD.osmIsSimpleMultipolygonOuterMember(outer, graph)).to.equal(relation);
});
it('returns false if the parent has multiple outer ways', function() {
var outer1 = iD.Way({tags: {'natural':'wood'}}),
outer2 = iD.Way({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {type: 'multipolygon'},
members: [{id: outer1.id, role: 'outer'}, {id: outer2.id, role: 'outer'}]}),
graph = iD.Graph([outer1, outer2, relation]);
var outer1 = iD.osmWay({tags: {'natural':'wood'}});
var outer2 = iD.osmWay({tags: {'natural':'wood'}});
var relation = iD.osmRelation(
{tags: {type: 'multipolygon'}, members: [{id: outer1.id, role: 'outer'}, {id: outer2.id, role: 'outer'}]}
);
var graph = iD.coreGraph([outer1, outer2, relation]);
expect(iD.osmIsSimpleMultipolygonOuterMember(outer1, graph)).to.be.false;
expect(iD.osmIsSimpleMultipolygonOuterMember(outer2, graph)).to.be.false;
});
it('returns false if the parent has multiple outer ways, assuming role outer if unspecified', function() {
var outer1 = iD.Way({tags: {'natural':'wood'}}),
outer2 = iD.Way({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {type: 'multipolygon'},
members: [{id: outer1.id}, {id: outer2.id}]}),
graph = iD.Graph([outer1, outer2, relation]);
var outer1 = iD.osmWay({tags: {'natural':'wood'}});
var outer2 = iD.osmWay({tags: {'natural':'wood'}});
var relation = iD.osmRelation(
{tags: {type: 'multipolygon'}, members: [{id: outer1.id}, {id: outer2.id}]}
);
var graph = iD.coreGraph([outer1, outer2, relation]);
expect(iD.osmIsSimpleMultipolygonOuterMember(outer1, graph)).to.be.false;
expect(iD.osmIsSimpleMultipolygonOuterMember(outer2, graph)).to.be.false;
});
it('returns false if the entity is not an outer', function() {
var inner = iD.Way({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {type: 'multipolygon'},
members: [{id: inner.id, role: 'inner'}]}),
graph = iD.Graph([inner, relation]);
var inner = iD.osmWay({tags: {'natural':'wood'}});
var relation = iD.osmRelation(
{tags: {type: 'multipolygon'}, members: [{id: inner.id, role: 'inner'}]}
);
var graph = iD.coreGraph([inner, relation]);
expect(iD.osmIsSimpleMultipolygonOuterMember(inner, graph)).to.be.false;
});
});
@@ -93,28 +103,28 @@ describe('iD.osmIsSimpleMultipolygonOuterMember', function() {
describe('iD.osmSimpleMultipolygonOuterMember', function() {
it('returns the outer member of a simple multipolygon', function() {
var inner = iD.Way(),
outer = iD.Way({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {type: 'multipolygon'}, members: [
{id: outer.id, role: 'outer'},
{id: inner.id, role: 'inner'}]
}),
graph = iD.Graph([inner, outer, relation]);
var inner = iD.osmWay();
var outer = iD.osmWay({tags: {'natural':'wood'}});
var relation = iD.osmRelation({tags: {type: 'multipolygon'}, members: [
{id: outer.id, role: 'outer'},
{id: inner.id, role: 'inner'}]
});
var graph = iD.coreGraph([inner, outer, relation]);
expect(iD.osmSimpleMultipolygonOuterMember(inner, graph)).to.equal(outer);
expect(iD.osmSimpleMultipolygonOuterMember(outer, graph)).to.equal(outer);
});
it('returns falsy for a complex multipolygon', function() {
var inner = iD.Way(),
outer1 = iD.Way({tags: {'natural':'wood'}}),
outer2 = iD.Way({tags: {'natural':'wood'}}),
relation = iD.Relation({tags: {type: 'multipolygon'}, members: [
{id: outer1.id, role: 'outer'},
{id: outer2.id, role: 'outer'},
{id: inner.id, role: 'inner'}]
}),
graph = iD.Graph([inner, outer1, outer2, relation]);
var inner = iD.osmWay();
var outer1 = iD.osmWay({tags: {'natural':'wood'}});
var outer2 = iD.osmWay({tags: {'natural':'wood'}});
var relation = iD.osmRelation({tags: {type: 'multipolygon'}, members: [
{id: outer1.id, role: 'outer'},
{id: outer2.id, role: 'outer'},
{id: inner.id, role: 'inner'}]
});
var graph = iD.coreGraph([inner, outer1, outer2, relation]);
expect(iD.osmSimpleMultipolygonOuterMember(inner, graph)).not.to.be.ok;
expect(iD.osmSimpleMultipolygonOuterMember(outer1, graph)).not.to.be.ok;
@@ -122,12 +132,12 @@ describe('iD.osmSimpleMultipolygonOuterMember', function() {
});
it('handles incomplete relations', function() {
var way = iD.Way({id: 'w'}),
relation = iD.Relation({id: 'r', tags: {type: 'multipolygon'}, members: [
{id: 'o', role: 'outer'},
{id: 'w', role: 'inner'}]
}),
graph = iD.Graph([way, relation]);
var way = iD.osmWay({id: 'w'});
var relation = iD.osmRelation({id: 'r', tags: {type: 'multipolygon'}, members: [
{id: 'o', role: 'outer'},
{id: 'w', role: 'inner'}]
});
var graph = iD.coreGraph([way, relation]);
expect(iD.osmSimpleMultipolygonOuterMember(way, graph)).not.to.be.ok;
});
@@ -135,69 +145,280 @@ describe('iD.osmSimpleMultipolygonOuterMember', function() {
describe('iD.osmJoinWays', function() {
function getIDs(objects) {
return objects.map(function(node) { return node.id; });
}
it('returns an array of members with nodes properties', function() {
var node = iD.Node({loc: [0, 0]}),
way = iD.Way({nodes: [node.id]}),
member = {id: way.id, type: 'way'},
graph = iD.Graph([node, way]),
result = iD.osmJoinWays([member], graph);
var node = iD.osmNode({id: 'a', loc: [0, 0]});
var way = iD.osmWay({id: '-', nodes: ['a']});
var member = {id: '-', type: 'way'};
var graph = iD.coreGraph([node, way]);
var result = iD.osmJoinWays([member], graph);
expect(result.length).to.equal(1);
expect(result[0].nodes.length).to.equal(1);
expect(result[0].nodes[0]).to.equal(node);
expect(result.actions).to.eql([]);
expect(getIDs(result[0].nodes)).to.eql(['a']);
expect(result[0].length).to.equal(1);
expect(result[0][0]).to.equal(member);
expect(result[0][0]).to.eql(member);
});
it('returns the members in the correct order', function() {
// a<===b--->c~~~>d
var graph = iD.Graph([
iD.Node({id: 'a', loc: [0, 0]}),
iD.Node({id: 'b', loc: [0, 0]}),
iD.Node({id: 'c', loc: [0, 0]}),
iD.Node({id: 'd', loc: [0, 0]}),
iD.Way({id: '=', nodes: ['b', 'a']}),
iD.Way({id: '-', nodes: ['b', 'c']}),
iD.Way({id: '~', nodes: ['c', 'd']}),
iD.Relation({id: 'r', members: [
{id: '-', type: 'way'},
{id: '~', type: 'way'},
{id: '=', type: 'way'}
]})
]);
it('joins ways', function() {
//
// a ---> b ===> c
//
var a = iD.osmNode({id: 'a', loc: [0, 0]});
var b = iD.osmNode({id: 'b', loc: [1, 0]});
var c = iD.osmNode({id: 'c', loc: [2, 0]});
var w1 = iD.osmWay({id: '-', nodes: ['a', 'b']});
var w2 = iD.osmWay({id: '=', nodes: ['b', 'c']});
var graph = iD.coreGraph([a, b, c, w1, w2]);
var result = iD.osmJoinWays(graph.entity('r').members, graph);
var ids = result[0].map(function (w) { return w.id; });
expect(ids).to.have.ordered.members(['=', '-', '~']);
var result = iD.osmJoinWays([w1, w2], graph);
expect(result.length).to.equal(1);
expect(result.actions).to.eql([]);
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c']);
expect(result[0].length).to.equal(2);
expect(result[0][0]).to.eql(w1);
expect(result[0][1]).to.eql(w2);
});
it('joins relation members', function() {
//
// a ---> b ===> c
// r: ['-', '=']
//
var a = iD.osmNode({id: 'a', loc: [0, 0]});
var b = iD.osmNode({id: 'b', loc: [1, 0]});
var c = iD.osmNode({id: 'c', loc: [2, 0]});
var w1 = iD.osmWay({id: '-', nodes: ['a', 'b']});
var w2 = iD.osmWay({id: '=', nodes: ['b', 'c']});
var r = iD.osmRelation({id: 'r', members: [
{id: '-', type: 'way'},
{id: '=', type: 'way'}
]});
var graph = iD.coreGraph([a, b, c, w1, w2, r]);
var result = iD.osmJoinWays(r.members, graph);
expect(result.length).to.equal(1);
expect(result.actions).to.eql([]);
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c']);
expect(result[0].length).to.equal(2);
expect(result[0][0]).to.eql({id: '-', type: 'way'});
expect(result[0][1]).to.eql({id: '=', type: 'way'});
});
it('returns joined members in the correct order', function() {
//
// a <=== b ---> c ~~~> d
// r: ['-', '~', '=']
//
var a = iD.osmNode({id: 'a', loc: [0, 0]});
var b = iD.osmNode({id: 'b', loc: [1, 0]});
var c = iD.osmNode({id: 'c', loc: [2, 0]});
var d = iD.osmNode({id: 'd', loc: [3, 0]});
var w1 = iD.osmWay({id: '-', nodes: ['b', 'c']});
var w2 = iD.osmWay({id: '=', nodes: ['b', 'a']});
var w3 = iD.osmWay({id: '~', nodes: ['c', 'd']});
var r = iD.osmRelation({id: 'r', members: [
{id: '-', type: 'way'},
{id: '~', type: 'way'},
{id: '=', type: 'way'}
]});
var graph = iD.coreGraph([a, b, c, d, w1, w2, w3, r]);
var result = iD.osmJoinWays(r.members, graph);
expect(result.length).to.equal(1);
expect(result.actions.length).to.equal(1);
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c', 'd']);
expect(result[0].length).to.equal(3);
expect(result[0][0]).to.eql({id: '=', type: 'way'});
expect(result[0][1]).to.eql({id: '-', type: 'way'});
expect(result[0][2]).to.eql({id: '~', type: 'way'});
});
it('reverses member tags of reversed segements', function() {
// a --> b <== c
// Expected result:
// a --> b --> c
// tags on === reversed
var graph = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: '-', nodes: ['a', 'b']}),
iD.Way({id: '=', nodes: ['c', 'b'], tags: {'oneway': 'yes', 'lanes:forward': 2}})
]);
//
// Source:
// a ---> b <=== c
// Result:
// a ---> b ===> c (and === reversed)
//
var a = iD.osmNode({id: 'a', loc: [0, 0]});
var b = iD.osmNode({id: 'b', loc: [1, 0]});
var c = iD.osmNode({id: 'c', loc: [2, 0]});
var w1 = iD.osmWay({id: '-', nodes: ['a', 'b']});
var w2 = iD.osmWay({id: '=', nodes: ['c', 'b'], tags: {'oneway': 'yes', 'lanes:forward': 2}});
var graph = iD.coreGraph([a, b, c, w1, w2]);
var result = iD.osmJoinWays([graph.entity('-'), graph.entity('=')], graph);
var result = iD.osmJoinWays([w1, w2], graph);
expect(result.length).to.equal(1);
expect(result.actions.length).to.equal(1);
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c']);
expect(result[0].length).to.equal(2);
expect(result[0][0]).to.eql(w1);
expect(result[0][1]).to.be.an.instanceof(iD.osmWay);
expect(result[0][1].nodes).to.eql(['b', 'c']);
expect(result[0][1].tags).to.eql({'oneway': '-1', 'lanes:backward': 2});
});
it('reverses the initial segment to preserve member order', function() {
//
// Source:
// a <--- b ===> c
// Result:
// a ---> b ===> c (and --- reversed)
//
var a = iD.osmNode({id: 'a', loc: [0, 0]});
var b = iD.osmNode({id: 'b', loc: [1, 0]});
var c = iD.osmNode({id: 'c', loc: [2, 0]});
var w1 = iD.osmWay({id: '-', nodes: ['b', 'a'], tags: {'oneway': 'yes', 'lanes:forward': 2}});
var w2 = iD.osmWay({id: '=', nodes: ['b', 'c']});
var graph = iD.coreGraph([a, b, c, w1, w2]);
var result = iD.osmJoinWays([w1, w2], graph);
expect(result.length).to.equal(1);
expect(result.actions.length).to.equal(1);
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c']);
expect(result[0].length).to.equal(2);
expect(result[0][0]).to.be.an.instanceof(iD.osmWay);
expect(result[0][0].nodes).to.eql(['a', 'b']);
expect(result[0][0].tags).to.eql({'oneway': '-1', 'lanes:backward': 2});
expect(result[0][1]).to.eql(w2);
});
it('ignores non-way members', function() {
var node = iD.Node({loc: [0, 0]}),
member = {id: 'n', type: 'node'},
graph = iD.Graph([node]);
var node = iD.osmNode({loc: [0, 0]});
var member = {id: 'n', type: 'node'};
var graph = iD.coreGraph([node]);
expect(iD.osmJoinWays([member], graph)).to.eql([]);
});
it('ignores incomplete members', function() {
var member = {id: 'w', type: 'way'},
graph = iD.Graph();
var member = {id: 'w', type: 'way'};
var graph = iD.coreGraph();
expect(iD.osmJoinWays([member], graph)).to.eql([]);
});
it('returns multiple arrays for disjoint ways', function() {
//
// b
// / \
// a c d ---> e ===> f
//
var a = iD.osmNode({id: 'a', loc: [0, 0]});
var b = iD.osmNode({id: 'b', loc: [1, 1]});
var c = iD.osmNode({id: 'c', loc: [2, 0]});
var d = iD.osmNode({id: 'd', loc: [5, 0]});
var e = iD.osmNode({id: 'e', loc: [6, 0]});
var f = iD.osmNode({id: 'f', loc: [7, 0]});
var w1 = iD.osmWay({id: '/', nodes: ['a', 'b']});
var w2 = iD.osmWay({id: '\\', nodes: ['b', 'c']});
var w3 = iD.osmWay({id: '-', nodes: ['d', 'e']});
var w4 = iD.osmWay({id: '=', nodes: ['e', 'f']});
var graph = iD.coreGraph([a, b, c, d, e, f, w1, w2, w3, w4]);
var result = iD.osmJoinWays([w1, w2, w3, w4], graph);
expect(result.length).to.equal(2);
expect(result.actions).to.eql([]);
expect(result[0].length).to.equal(2);
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c']);
expect(result[0][0]).to.eql(w1);
expect(result[0][1]).to.eql(w2);
expect(result[1].length).to.equal(2);
expect(getIDs(result[1].nodes)).to.eql(['d', 'e', 'f']);
expect(result[1][0]).to.eql(w3);
expect(result[1][1]).to.eql(w4);
});
it('returns multiple arrays for disjoint relations', function() {
//
// b
// / \
// a c d ---> e ===> f
//
// r: ['/', '\', '-', '=']
//
var a = iD.osmNode({id: 'a', loc: [0, 0]});
var b = iD.osmNode({id: 'b', loc: [1, 1]});
var c = iD.osmNode({id: 'c', loc: [2, 0]});
var d = iD.osmNode({id: 'd', loc: [5, 0]});
var e = iD.osmNode({id: 'e', loc: [6, 0]});
var f = iD.osmNode({id: 'f', loc: [7, 0]});
var w1 = iD.osmWay({id: '/', nodes: ['a', 'b']});
var w2 = iD.osmWay({id: '\\', nodes: ['b', 'c']});
var w3 = iD.osmWay({id: '-', nodes: ['d', 'e']});
var w4 = iD.osmWay({id: '=', nodes: ['e', 'f']});
var r = iD.osmRelation({id: 'r', members: [
{id: '/', type: 'way'},
{id: '\\', type: 'way'},
{id: '-', type: 'way'},
{id: '=', type: 'way'}
]});
var graph = iD.coreGraph([a, b, c, d, e, f, w1, w2, w3, w4, r]);
var result = iD.osmJoinWays(r.members, graph);
expect(result.length).to.equal(2);
expect(result.actions).to.eql([]);
expect(result[0].length).to.equal(2);
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c']);
expect(result[0][0]).to.eql({id: '/', type: 'way'});
expect(result[0][1]).to.eql({id: '\\', type: 'way'});
expect(result[1].length).to.equal(2);
expect(getIDs(result[1].nodes)).to.eql(['d', 'e', 'f']);
expect(result[1][0]).to.eql({id: '-', type: 'way'});
expect(result[1][1]).to.eql({id: '=', type: 'way'});
});
it('understands doubled-back relation members', function() {
//
// e
// / \
// a <=== b ---> c ~~~> d
//
// r: ['=', '-', '~', '\', '/', '-', '=']
//
var a = iD.osmNode({id: 'a', loc: [0, 0]});
var b = iD.osmNode({id: 'b', loc: [1, 0]});
var c = iD.osmNode({id: 'c', loc: [2, 0]});
var d = iD.osmNode({id: 'd', loc: [4, 0]});
var e = iD.osmNode({id: 'e', loc: [3, 1]});
var w1 = iD.osmWay({id: '=', nodes: ['b', 'a']});
var w2 = iD.osmWay({id: '-', nodes: ['b', 'c']});
var w3 = iD.osmWay({id: '~', nodes: ['c', 'd']});
var w4 = iD.osmWay({id: '\\', nodes: ['d', 'e']});
var w5 = iD.osmWay({id: '/', nodes: ['c', 'e']});
var r = iD.osmRelation({id: 'r', members: [
{id: '=', type: 'way'},
{id: '-', type: 'way'},
{id: '~', type: 'way'},
{id: '\\', type: 'way'},
{id: '/', type: 'way'},
{id: '-', type: 'way'},
{id: '=', type: 'way'}
]});
var graph = iD.coreGraph([a, b, c, d, e, w1, w2, w3, w4, w5, r]);
var result = iD.osmJoinWays(r.members, graph);
expect(result.length).to.equal(1);
expect(result.actions.length).to.equal(3);
expect(getIDs(result[0].nodes)).to.eql(['a', 'b', 'c', 'd', 'e', 'c', 'b', 'a']);
expect(result[0].length).to.equal(7);
expect(result[0][0]).to.eql({id: '=', type: 'way'});
expect(result[0][1]).to.eql({id: '-', type: 'way'});
expect(result[0][2]).to.eql({id: '~', type: 'way'});
expect(result[0][3]).to.eql({id: '\\', type: 'way'});
expect(result[0][4]).to.eql({id: '/', type: 'way'});
expect(result[0][5]).to.eql({id: '-', type: 'way'});
expect(result[0][6]).to.eql({id: '=', type: 'way'});
});
});

View File

@@ -258,24 +258,37 @@ describe('iD.osmRelation', function () {
it('replaces a member which doesn\'t already exist', function () {
var r = iD.Relation({members: [{id: 'a', role: 'a'}]});
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'node'}).members).to.eql([{id: 'b', role: 'a', type: 'node'}]);
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'node'}).members)
.to.eql([{id: 'b', role: 'a', type: 'node'}]);
});
it('preserves the existing role', function () {
var r = iD.Relation({members: [{id: 'a', role: 'a', type: 'node'}]});
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'node'}).members).to.eql([{id: 'b', role: 'a', type: 'node'}]);
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'node'}).members)
.to.eql([{id: 'b', role: 'a', type: 'node'}]);
});
it('uses the replacement type', function () {
var r = iD.Relation({members: [{id: 'a', role: 'a', type: 'node'}]});
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'way'}).members).to.eql([{id: 'b', role: 'a', type: 'way'}]);
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'way'}).members)
.to.eql([{id: 'b', role: 'a', type: 'way'}]);
});
it('removes members if replacing them would produce duplicates', function () {
var r = iD.Relation({members: [
{id: 'a', role: 'b', type: 'node'},
{id: 'b', role: 'b', type: 'node'}]});
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'node'}).members).to.eql([{id: 'b', role: 'b', type: 'node'}]);
{id: 'b', role: 'b', type: 'node'}
]});
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'node'}).members)
.to.eql([{id: 'b', role: 'b', type: 'node'}]);
});
it('keeps duplicate members if `keepDuplicates = true`', function () {
var r = iD.Relation({members: [
{id: 'a', role: 'b', type: 'node'},
{id: 'b', role: 'b', type: 'node'}
]});
expect(r.replaceMember({id: 'a'}, {id: 'b', type: 'node'}, true).members)
.to.eql([{id: 'b', role: 'b', type: 'node'}, {id: 'b', role: 'b', type: 'node'}]);
});
});