Skip to content

Commit

Permalink
Flag disconnected highway areas and multipolygons (close #6075)
Browse files Browse the repository at this point in the history
Don't flag highways connected only to highway multipolygons
  • Loading branch information
quincylvania committed Mar 21, 2019
1 parent e585090 commit f114945
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 24 deletions.
81 changes: 62 additions & 19 deletions modules/validations/disconnected_way.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,80 @@ import { validationIssue, validationIssueFix } from '../core/validator';
export function validationDisconnectedWay() {
var type = 'disconnected_way';

var highways = {
residential: true, service: true, track: true, unclassified: true, footway: true,
path: true, tertiary: true, secondary: true, primary: true, living_street: true,
cycleway: true, trunk: true, steps: true, motorway: true, motorway_link: true,
pedestrian: true, trunk_link: true, primary_link: true, secondary_link: true,
road: true, tertiary_link: true, bridleway: true, raceway: true, corridor: true,
bus_guideway: true
};

function isDisconnectedHighway(entity, graph) {
if (!entity.tags.highway ||
entity.tags.highway === 'no' ||
entity.tags.highway === 'proposed') return false;
if (entity.geometry(graph) !== 'line') return false;

return graph.childNodes(entity)
.every(function(vertex) {
var parents = graph.parentWays(vertex);
if (parents.length === 1) { // standalone vertex
return true;
} else { // shared vertex
return !vertex.tags.entrance &&
parents.filter(function(parent) {
return parent.tags.highway && parent !== entity;
}).length === 0;
}
function isTaggedAsHighway(entity) {
return highways[entity.tags.highway];
}

function vertexIsDisconnected(way, vertex, graph, relation) {
var parents = graph.parentWays(vertex);

// standalone vertex
if (parents.length === 1) return true;

// entrances are considered connected
if (vertex.tags.entrance && vertex.tags.entrance !== 'no') return false;

return !parents.some(function(parentWay) {
// ignore the way we're testing
if (parentWay === way) return false;

if (isTaggedAsHighway(parentWay)) return true;

return graph.parentMultipolygons(parentWay).some(function(parentRelation) {
// ignore the relation we're testing, if any
if (relation && parentRelation === relation) return false;

return isTaggedAsHighway(parentRelation);
});
});
}

function isDisconnectedWay(entity, graph) {

if (entity.type !== 'way') return false;

return graph.childNodes(entity).every(function(vertex) {
return vertexIsDisconnected(entity, vertex, graph);
});
}

function isDisconnectedMultipolygon(entity, graph) {

if (entity.type !== 'relation' || !entity.isMultipolygon()) return false;

return entity.members.every(function(member) {
if (member.type !== 'way') return true;

var way = graph.hasEntity(member.id);
if (!way) return true;

return graph.childNodes(way).every(function(vertex) {
return vertexIsDisconnected(way, vertex, graph, entity);
});
});
}


var validation = function(entity, context) {
var graph = context.graph();

if (!isDisconnectedHighway(entity, graph)) return [];
if (!isTaggedAsHighway(entity)) return [];

if (!isDisconnectedWay(entity, graph) && !isDisconnectedMultipolygon(entity, graph)) return [];

var entityLabel = utilDisplayLabel(entity, context);
var fixes = [];

if (!entity.isClosed()) {
if (entity.type === 'way' && !entity.isClosed()) {
var first = context.entity(entity.first());
if (first.tags.noexit !== 'yes') {
fixes.push(new validationIssueFix({
Expand Down
61 changes: 56 additions & 5 deletions test/spec/validations/disconnected_way.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ describe('iD.validations.disconnected_way', function () {
);
}

function createConnectingWays() {
function createConnectingWays(tags1, tags2) {
var n1 = iD.osmNode({id: 'n-1', loc: [4,4]});
var n2 = iD.osmNode({id: 'n-2', loc: [4,5]});
var n3 = iD.osmNode({id: 'n-3', loc: [5,5]});
var w = iD.osmWay({id: 'w-1', nodes: ['n-1', 'n-2'], tags: {'highway': 'unclassified'}});
var w2 = iD.osmWay({id: 'w-2', nodes: ['n-1', 'n-3'], tags: {'highway': 'unclassified'}});
var w = iD.osmWay({id: 'w-1', nodes: ['n-1', 'n-2'], tags: tags1});
var w2 = iD.osmWay({id: 'w-2', nodes: ['n-1', 'n-3'], tags: tags2});

context.perform(
iD.actionAddEntity(n1),
Expand Down Expand Up @@ -60,8 +60,59 @@ describe('iD.validations.disconnected_way', function () {
expect(issue.entities[0].id).to.eql('w-1');
});

it('ignores roads that are connected', function() {
createConnectingWays();
it('flags highway connected only to service area', function() {
createConnectingWays({'highway': 'unclassified'}, {'highway': 'services'});
var issues = validate();
expect(issues).to.have.lengthOf(1);
var issue = issues[0];
expect(issue.type).to.eql('disconnected_way');
expect(issue.severity).to.eql('warning');
expect(issue.entities).to.have.lengthOf(1);
expect(issue.entities[0].id).to.eql('w-1');
});

it('flags disconnected highway with disconnected entrance vertex', function() {
var n1 = iD.osmNode({id: 'n-1', loc: [4,4], tags: {'entrance': 'yes'}});
var n2 = iD.osmNode({id: 'n-2', loc: [4,5]});
var w = iD.osmWay({id: 'w-1', nodes: ['n-1', 'n-2'], tags: {'highway': 'unclassified'}});

context.perform(
iD.actionAddEntity(n1),
iD.actionAddEntity(n2),
iD.actionAddEntity(w)
);

var issues = validate();
expect(issues).to.have.lengthOf(1);
var issue = issues[0];
expect(issue.type).to.eql('disconnected_way');
expect(issue.severity).to.eql('warning');
expect(issue.entities).to.have.lengthOf(1);
expect(issue.entities[0].id).to.eql('w-1');
});

it('ignores highways that are connected', function() {
createConnectingWays({'highway': 'unclassified'}, {'highway': 'unclassified'});
var issues = validate();
expect(issues).to.have.lengthOf(0);
});

it('ignores highway with connected entrance vertex', function() {

var n1 = iD.osmNode({id: 'n-1', loc: [4,4], tags: {'entrance': 'yes'}});
var n2 = iD.osmNode({id: 'n-2', loc: [4,5]});
var n3 = iD.osmNode({id: 'n-3', loc: [5,5]});
var w = iD.osmWay({id: 'w-1', nodes: ['n-1', 'n-2'], tags: {'highway': 'unclassified'}});
var w2 = iD.osmWay({id: 'w-2', nodes: ['n-1', 'n-3']});

context.perform(
iD.actionAddEntity(n1),
iD.actionAddEntity(n2),
iD.actionAddEntity(n3),
iD.actionAddEntity(w),
iD.actionAddEntity(w2)
);

var issues = validate();
expect(issues).to.have.lengthOf(0);
});
Expand Down

0 comments on commit f114945

Please sign in to comment.