diff --git a/modules/validations/almost_junction.js b/modules/validations/almost_junction.js index daedb3829..44a6051a0 100644 --- a/modules/validations/almost_junction.js +++ b/modules/validations/almost_junction.js @@ -28,95 +28,6 @@ export function validationAlmostJunction() { return node.tags.noexit && node.tags.noexit === 'yes'; } - function isExtendableCandidate(node, way, graph) { - if (isNoexit(node) || graph.parentWays(node).length !== 1) { - return false; - } - var occurences = 0; - for (var index in way.nodes) { - if (way.nodes[index] === node.id) { - occurences += 1; - if (occurences > 1) { - return false; - } - } - } - return true; - } - - - function findConnectableEndNodesByExtension(way, graph, tree) { - var results = []; - if (way.isClosed()) return results; - - var testNodes; - var endpointIndicies = [0, way.nodes.length - 1]; - endpointIndicies.forEach(function(nodeIndex) { - - var nodeID = way.nodes[nodeIndex]; - var node = graph.entity(nodeID); - - if (!isExtendableCandidate(node, way, graph)) return; - - var connectionInfo = canConnectByExtend(way, nodeIndex, graph, tree); - if (!connectionInfo) return; - - testNodes = graph.childNodes(way).slice(); // shallow copy - testNodes[nodeIndex] = testNodes[nodeIndex].move(connectionInfo.cross_loc); - - // don't flag issue if connecting the ways would cause self-intersection - if (geoHasSelfIntersections(testNodes, nodeID)) return; - - results.push(connectionInfo); - }); - - return results; - } - - - function canConnectByExtend(way, endNodeIdx, graph, tree) { - var EXTEND_TH_METERS = 5; - var tipNid = way.nodes[endNodeIdx]; // the 'tip' node for extension point - var midNid = endNodeIdx === 0 ? way.nodes[1] : way.nodes[way.nodes.length - 2]; // the other node of the edge - var tipNode = graph.entity(tipNid); - var midNode = graph.entity(midNid); - var lon = tipNode.loc[0]; - var lat = tipNode.loc[1]; - var lon_range = geoMetersToLon(EXTEND_TH_METERS, lat) / 2; - var lat_range = geoMetersToLat(EXTEND_TH_METERS) / 2; - var queryExtent = geoExtent([ - [lon - lon_range, lat - lat_range], - [lon + lon_range, lat + lat_range] - ]); - - // first, extend the edge of [midNode -> tipNode] by EXTEND_TH_METERS and find the "extended tip" location - var edgeLen = geoSphericalDistance(midNode.loc, tipNode.loc); - var t = EXTEND_TH_METERS / edgeLen + 1.0; - var extTipLoc = geoVecInterp(midNode.loc, tipNode.loc, t); - - // then, check if the extension part [tipNode.loc -> extTipLoc] intersects any other ways - var intersected = tree.intersects(queryExtent, graph); - for (var i = 0; i < intersected.length; i++) { - if (!isHighway(intersected[i]) || intersected[i].id === way.id) continue; - - var way2 = intersected[i]; - for (var j = 0; j < way2.nodes.length - 1; j++) { - var nA = graph.entity(way2.nodes[j]); - var nB = graph.entity(way2.nodes[j + 1]); - var crossLoc = geoLineIntersection([tipNode.loc, extTipLoc], [nA.loc, nB.loc]); - if (crossLoc) { - return { - node: tipNode, - wid: way2.id, - edge: [nA.id, nB.id], - cross_loc: crossLoc - }; - } - } - } - return null; - } - var validation = function checkAlmostJunction(entity, context) { if (!isHighway(entity)) return []; @@ -126,7 +37,7 @@ export function validationAlmostJunction() { var tree = context.history().tree(); var issues = []; - var extendableNodeInfos = findConnectableEndNodesByExtension(entity, graph, tree); + var extendableNodeInfos = findConnectableEndNodesByExtension(entity); extendableNodeInfos.forEach(function(extendableNodeInfo) { var node = extendableNodeInfo.node; var edgeHighway = graph.entity(extendableNodeInfo.wid); @@ -192,6 +103,102 @@ export function validationAlmostJunction() { }); return issues; + + + + function isExtendableCandidate(node, way) { + // can not accurately test vertices on tiles not downloaded from osm - #5938 + var osm = context.connection(); + if (osm && !osm.isDataLoaded(node.loc)) { + return false; + } + if (isNoexit(node) || graph.parentWays(node).length !== 1) { + return false; + } + + var occurences = 0; + for (var index in way.nodes) { + if (way.nodes[index] === node.id) { + occurences += 1; + if (occurences > 1) { + return false; + } + } + } + return true; + } + + + function findConnectableEndNodesByExtension(way) { + var results = []; + if (way.isClosed()) return results; + + var testNodes; + var indices = [0, way.nodes.length - 1]; + indices.forEach(function(nodeIndex) { + var nodeID = way.nodes[nodeIndex]; + var node = graph.entity(nodeID); + + if (!isExtendableCandidate(node, way)) return; + + var connectionInfo = canConnectByExtend(way, nodeIndex); + if (!connectionInfo) return; + + testNodes = graph.childNodes(way).slice(); // shallow copy + testNodes[nodeIndex] = testNodes[nodeIndex].move(connectionInfo.cross_loc); + + // don't flag issue if connecting the ways would cause self-intersection + if (geoHasSelfIntersections(testNodes, nodeID)) return; + + results.push(connectionInfo); + }); + + return results; + } + + + function canConnectByExtend(way, endNodeIdx) { + var EXTEND_TH_METERS = 5; + var tipNid = way.nodes[endNodeIdx]; // the 'tip' node for extension point + var midNid = endNodeIdx === 0 ? way.nodes[1] : way.nodes[way.nodes.length - 2]; // the other node of the edge + var tipNode = graph.entity(tipNid); + var midNode = graph.entity(midNid); + var lon = tipNode.loc[0]; + var lat = tipNode.loc[1]; + var lon_range = geoMetersToLon(EXTEND_TH_METERS, lat) / 2; + var lat_range = geoMetersToLat(EXTEND_TH_METERS) / 2; + var queryExtent = geoExtent([ + [lon - lon_range, lat - lat_range], + [lon + lon_range, lat + lat_range] + ]); + + // first, extend the edge of [midNode -> tipNode] by EXTEND_TH_METERS and find the "extended tip" location + var edgeLen = geoSphericalDistance(midNode.loc, tipNode.loc); + var t = EXTEND_TH_METERS / edgeLen + 1.0; + var extTipLoc = geoVecInterp(midNode.loc, tipNode.loc, t); + + // then, check if the extension part [tipNode.loc -> extTipLoc] intersects any other ways + var intersected = tree.intersects(queryExtent, graph); + for (var i = 0; i < intersected.length; i++) { + if (!isHighway(intersected[i]) || intersected[i].id === way.id) continue; + + var way2 = intersected[i]; + for (var j = 0; j < way2.nodes.length - 1; j++) { + var nA = graph.entity(way2.nodes[j]); + var nB = graph.entity(way2.nodes[j + 1]); + var crossLoc = geoLineIntersection([tipNode.loc, extTipLoc], [nA.loc, nB.loc]); + if (crossLoc) { + return { + node: tipNode, + wid: way2.id, + edge: [nA.id, nB.id], + cross_loc: crossLoc + }; + } + } + } + return null; + } }; validation.type = type; diff --git a/modules/validations/disconnected_way.js b/modules/validations/disconnected_way.js index 23e3c1703..0e425321a 100644 --- a/modules/validations/disconnected_way.js +++ b/modules/validations/disconnected_way.js @@ -22,59 +22,11 @@ export function validationDisconnectedWay() { } - 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 checkDisconnectedWay(entity, context) { var graph = context.graph(); + if (!isTaggedAsHighway(entity)) return []; - if (!isDisconnectedWay(entity, graph) && !isDisconnectedMultipolygon(entity, graph)) return []; + if (!isDisconnectedWay(entity) && !isDisconnectedMultipolygon(entity)) return []; var entityLabel = utilDisplayLabel(entity, context); var fixes = []; @@ -131,6 +83,58 @@ export function validationDisconnectedWay() { })]; + function vertexIsDisconnected(way, vertex, relation) { + // can not accurately test vertices on tiles not downloaded from osm - #5938 + var osm = context.connection(); + if (osm && !osm.isDataLoaded(vertex.loc)) { + return false; + } + + 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) { + if (parentWay === way) return false; // ignore the way we're testing + 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) { + if (entity.type !== 'way') return false; + return graph.childNodes(entity).every(function(vertex) { + return vertexIsDisconnected(entity, vertex); + }); + } + + + 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 graph.childNodes(way).every(function(vertex) { + return vertexIsDisconnected(way, vertex, entity); + }); + }); + } + function continueDrawing(way, vertex) { // make sure the vertex is actually visible and editable var map = context.map();