Flag disconnected ferry routes

Flag disconnected highway multipolygon outer member ways
Let routing islands cross ferry routes
This commit is contained in:
Quincy Morgan
2019-05-17 00:15:53 -04:00
parent 07bc2821cd
commit e697bdbeb1
6 changed files with 50 additions and 95 deletions
+2
View File
@@ -128,7 +128,9 @@ _Breaking changes, which may affect downstream projects or sites that embed iD,
- Flag unreachable one-way highways and waterways flowing against each other ([#6216])
- Flag disconnected area and multipolygon highways ([#6075])
- Flag new highways disconnected from the larger road network ([#6284], thanks [@Bonkles], [@gaoxm])
- Flag routing islands
- Don't flag highways connected to ferry routes as disconnected ([#6287])
- Flag disconnected ferry routes
- Flag deprecated values among semicolon-delimited tags ([#6038])
- Add quick fixes for setting the `layer` to resolve certain Crossing Ways issues ([#5943])
- Rename "Generic Names" validation rule to "Suspicious Names"
+1 -1
View File
@@ -1365,7 +1365,7 @@ en:
reference: "Crossing indoor features should be connected or use different levels."
disconnected_way:
title: Disconnected Ways
tip: "Find unconnected highways and paths"
tip: "Find unroutable roads, paths, and ferry routes"
routable:
message:
multiple: "{count} routable features are connected only to each other."
+1 -1
View File
@@ -1684,7 +1684,7 @@
},
"disconnected_way": {
"title": "Disconnected Ways",
"tip": "Find unconnected highways and paths",
"tip": "Find unroutable roads, paths, and ferry routes",
"routable": {
"message": {
"multiple": "{count} routable features are connected only to each other."
+45 -65
View File
@@ -16,9 +16,7 @@ export function validationDisconnectedWay() {
var validation = function checkDisconnectedWay(entity, context) {
var graph = context.graph();
if (!isTaggedAsHighway(entity)) return [];
var routingIslandWays = routingIslandForWay(entity);
var routingIslandWays = routingIslandForEntity(entity);
if (!routingIslandWays) return [];
var fixes = [];
@@ -113,9 +111,40 @@ export function validationDisconnectedWay() {
.text(t('issues.disconnected_way.routable.reference'));
}
function routingIslandForEntity(entity) {
function isConnectedVertex(vertex, way, relation, routingIslandSet) {
// can not accurately test vertices on tiles not downloaded from osm - #5938
if (entity.type !== 'way') return null;
if (!isRoutableWay(entity, true)) return null;
var waysToCheck = [entity];
var routingIsland = new Set([entity]);
while (waysToCheck.length) {
var wayToCheck = waysToCheck.pop();
var childNodes = graph.childNodes(wayToCheck);
for (var i in childNodes) {
var vertex = childNodes[i];
var result = isConnectedVertex(vertex, routingIsland);
if (result === true) {
return null;
} else if (result === false) {
continue;
}
result.forEach(function(connectedWay) {
if (!routingIsland.has(connectedWay)) {
routingIsland.add(connectedWay);
waysToCheck.push(connectedWay);
}
});
}
}
return routingIsland;
}
function isConnectedVertex(vertex, routingIslandWays) {
// assume ways overlapping unloaded tiles are connected to the wider road network - #5938
var osm = context.connection();
if (osm && !osm.isDataLoaded(vertex.loc)) return true;
@@ -135,27 +164,9 @@ export function validationDisconnectedWay() {
var parentWay = parentsWays[i];
// ignore any way we've already accounted for
if (routingIslandSet.has(parentWay)) continue;
if (routingIslandWays.has(parentWay)) continue;
// count connections to ferry routes as connected
if (parentWay.tags.route === 'ferry') return true;
if (isTaggedAsHighway(parentWay)) connectedWays.add(parentWay);
var parentRelations = graph.parentRelations(parentWay);
for (var j in parentRelations) {
var parentRelation = parentRelations[j];
// ignore the relation we're testing, if any
if (relation && parentRelation === relation) continue;
if (parentRelation.tags.type === 'route' &&
parentRelation.tags.route === 'ferry') return true;
if (parentRelation.isMultipolygon() &&
isTaggedAsHighway(parentRelation)) return connectedWays.add(parentWay);
}
if (isRoutableWay(parentWay, false)) connectedWays.add(parentWay);
}
if (connectedWays.size) return connectedWays;
@@ -163,49 +174,18 @@ export function validationDisconnectedWay() {
return false;
}
function isRoutableWay(way, ignoreInnerWays) {
if (isTaggedAsHighway(way) || way.tags.route === 'ferry') return true;
function routingIslandForWay(way, relation) {
if (way.type !== 'way') return null;
return graph.parentRelations(way).some(function(parentRelation) {
if (parentRelation.tags.type === 'route' &&
parentRelation.tags.route === 'ferry') return true;
var waysToCheck = [way];
var routingIsland = new Set([way]);
while (waysToCheck.length) {
var wayToCheck = waysToCheck.pop();
var childNodes = graph.childNodes(wayToCheck);
for (var i in childNodes) {
var vertex = childNodes[i];
var result = isConnectedVertex(vertex, entity, relation, routingIsland);
if (result === true) {
return null;
} else if (result === false) {
continue;
}
result.forEach(function(connectedWay) {
if (!routingIsland.has(connectedWay)) {
routingIsland.add(connectedWay);
waysToCheck.push(connectedWay);
}
});
}
}
return routingIsland;
}
/*function isDisconnectedMultipolygon(entity) {
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 isDisconnectedWay(way, entity);
if (parentRelation.isMultipolygon() &&
isTaggedAsHighway(parentRelation) &&
(!ignoreInnerWays || parentRelation.memberById(way.id).role !== 'inner')) return true;
});
}*/
}
function continueDrawing(way, vertex) {
// make sure the vertex is actually visible and editable
+1 -1
View File
@@ -207,7 +207,7 @@ export function validationImpossibleOneway() {
}
}
var validation = function checkDisconnectedWay(entity, context) {
var validation = function checkImpossibleOneway(entity, context) {
if (entity.type !== 'way' || entity.geometry(context.graph()) !== 'line') return [];
-27
View File
@@ -87,33 +87,6 @@ describe('iD.validations.disconnected_way', function () {
expect(issue.entityIds[0]).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.entityIds).to.have.lengthOf(1);
expect(issue.entityIds[0]).to.eql('w-1');
});
it('ignores highways that are connected to existing highways', function() {
createWayAtEndOfExistingOne({'highway': 'secondary'}, {'highway': 'secondary'});
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'}});