diff --git a/data/core.yaml b/data/core.yaml index 95ad8d4e2..14a181da9 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1353,7 +1353,9 @@ en: tip: "Find unconnected highways and paths" highway: message: "{highway} is disconnected from other roads and paths" + message_new_road: "New {highway} is disconnected from existing road network" reference: "Highways should connect to other highways or building entrances." + reference_new_road: "New roads should connect to existing road network or building entrances." fixme_tag: title: '"Fix Me" Requests' message: '{feature} has a "Fix Me" request' diff --git a/dist/locales/en.json b/dist/locales/en.json index 6f6e45291..e03abe352 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -1669,7 +1669,9 @@ "tip": "Find unconnected highways and paths", "highway": { "message": "{highway} is disconnected from other roads and paths", - "reference": "Highways should connect to other highways or building entrances." + "message_new_road": "New {highway} is disconnected from existing road network", + "reference": "Highways should connect to other highways or building entrances.", + "reference_new_road": "New roads should connect to existing road network or building entrances." } }, "fixme_tag": { diff --git a/modules/validations/disconnected_way.js b/modules/validations/disconnected_way.js index 3f4e147ee..8b720d89b 100644 --- a/modules/validations/disconnected_way.js +++ b/modules/validations/disconnected_way.js @@ -20,13 +20,20 @@ export function validationDisconnectedWay() { function isTaggedAsHighway(entity) { return highways[entity.tags.highway]; } - + + function isNewRoad(entityId) { + return entityId[0] === 'w' && entityId[1] === '-'; + } var validation = function checkDisconnectedWay(entity, context) { var graph = context.graph(); if (!isTaggedAsHighway(entity)) return []; - if (!isDisconnectedWay(entity) && !isDisconnectedMultipolygon(entity)) return []; + + if (!isDisconnectedWay(entity) && !isDisconnectedMultipolygon(entity) + && !isNewRoadUnreachableFromExistingRoads(entity, graph)) { + return []; + } var entityLabel = utilDisplayLabel(entity, context); var fixes = []; @@ -81,7 +88,14 @@ export function validationDisconnectedWay() { return [new validationIssue({ type: type, severity: 'warning', - message: t('issues.disconnected_way.highway.message', { highway: entityLabel }), + message: (isNewRoad(entity.id) + ? t('issues.disconnected_way.highway.message_new_road', { highway: entityLabel }) + : t('issues.disconnected_way.highway.message', { highway: entityLabel }) + ), + tooltip: (isNewRoad(entity.id) + ? t('issues.disconnected_way.highway.reference_new_road') + : t('issues.disconnected_way.highway.reference') + ), reference: showReference, entities: [entity], fixes: fixes @@ -135,6 +149,34 @@ export function validationDisconnectedWay() { } + // check if entity is a new road that cannot eventually connect to any + // existing roads + function isNewRoadUnreachableFromExistingRoads(entity) { + if (!isNewRoad(entity.id) || !isTaggedAsHighway(entity)) return false; + + var visitedWids = new Set(); + return !connectToExistingRoadOrEntrance(entity, visitedWids); + } + + + function connectToExistingRoadOrEntrance(way, visitedWids) { + visitedWids.add(way.id); + for (var i = 0; i < way.nodes.length; i++) { + var vertex = graph.entity(way.nodes[i]); + if (vertex.tags.entrance && vertex.tags.entrance !== 'no') return true; + + var parentWays = graph.parentWays(vertex); + for (var j = 0; j < parentWays.length; j++) { + var parentWay = parentWays[j]; + if (visitedWids.has(parentWay.id)) continue; + if (isTaggedAsHighway(parentWay) && !isNewRoad(parentWay.id)) return true; + if (connectToExistingRoadOrEntrance(parentWay, visitedWids)) return true; + } + } + return false; + } + + function isDisconnectedMultipolygon(entity) { if (entity.type !== 'relation' || !entity.isMultipolygon()) return false; diff --git a/test/spec/validations/disconnected_way.js b/test/spec/validations/disconnected_way.js index 59735a4da..f0d392ed2 100644 --- a/test/spec/validations/disconnected_way.js +++ b/test/spec/validations/disconnected_way.js @@ -33,6 +33,22 @@ describe('iD.validations.disconnected_way', function () { ); } + function createWayAtEndOfExistingOne(tags1, tags2) { + var n1 = iD.osmNode({id: 'n1', loc: [4,4]}); + var n2 = iD.osmNode({id: 'n2', loc: [4,5]}); + var n3 = iD.osmNode({id: 'n-3', loc: [5,5]}); + var w = iD.osmWay({id: 'w1', nodes: ['n1', 'n2'], tags: tags1}); + var w2 = iD.osmWay({id: 'w-2', nodes: ['n1', 'n-3'], tags: tags2}); + + context.perform( + iD.actionAddEntity(n1), + iD.actionAddEntity(n2), + iD.actionAddEntity(n3), + iD.actionAddEntity(w), + iD.actionAddEntity(w2) + ); + } + function validate() { var validator = iD.validationDisconnectedWay(); var changes = context.history().changes(); @@ -91,8 +107,9 @@ describe('iD.validations.disconnected_way', function () { expect(issue.entities[0].id).to.eql('w-1'); }); - it('ignores highways that are connected', function() { - createConnectingWays({'highway': 'unclassified'}, {'highway': 'unclassified'}); + it('ignores highways that are connected to existing highways', function() { + createWayAtEndOfExistingOne({'highway': 'secondary'}, {'highway': 'secondary'}); + var issues = validate(); expect(issues).to.have.lengthOf(0); });