From 6e0fb616b76268e68fbca9890771eb460cddabf7 Mon Sep 17 00:00:00 2001 From: Kyle Hensel Date: Sat, 2 Nov 2024 19:46:02 +1100 Subject: [PATCH] automatically create pedestrian crossing line when joining sidewalk/road --- data/core.yaml | 1 + modules/validations/crossing_ways.js | 117 +++++++++++++++++++-------- 2 files changed, 84 insertions(+), 34 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 5cdb0658e5..3dd8f6cca1 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1950,6 +1950,7 @@ en: title: Connect the features connect_using_crossing: title: Connect using a crossing + annotation: Connected using a crossing. connect_using_ford: title: Connect using a ford continue_from_start: diff --git a/modules/validations/crossing_ways.js b/modules/validations/crossing_ways.js index 7b03c5fa02..e0a486891e 100644 --- a/modules/validations/crossing_ways.js +++ b/modules/validations/crossing_ways.js @@ -476,6 +476,28 @@ export function validationCrossingWays(context) { if (allowsTunnel(selectedFeatureType) && !skipTunnelFix) { fixes.push(makeAddBridgeOrTunnelFix('add_a_tunnel', 'temaki-tunnel', 'tunnel')); } + + // special case: if + // (1) we're about to join these lines with a highway=crossing node; and + // (2) one of the lines is a sidewalk + // then we will split the sidewalk and create a highway=crossing way + const isSidewalk = ( + entities[0].tags.footway === 'sidewalk' || + entities[1].tags.footway === 'sidewalk' || + entities[0].tags.cycleway === 'sidewalk' || + entities[1].tags.cycleway === 'sidewalk' + ); + if (connectionTags.highway === 'crossing' && isSidewalk) { + const newAction = makeAddBridgeOrTunnelFix( + 'connect_using_crossing', + 'temaki-pedestrian', + 'crossing', + connectionTags + ); + // replace the default action with this action + fixes = fixes.filter(action => action.id !== newAction.id); + fixes.unshift(newAction); + } } // repositioning the features is always an option @@ -498,7 +520,13 @@ export function validationCrossingWays(context) { } } - function makeAddBridgeOrTunnelFix(fixTitleID, iconName, bridgeOrTunnel){ + /** + * @param {string} fixTitleID + * @param {string} iconName + * @param {'bridge' | 'tunnel' | 'crossing'} crossingType + * @param {Tags=} connectionTags + */ + function makeAddBridgeOrTunnelFix(fixTitleID, iconName, crossingType, connectionTags){ return new validationIssueFix({ icon: iconName, title: t.append('issues.fix.' + fixTitleID + '.title'), @@ -678,10 +706,10 @@ export function validationCrossingWays(context) { }); var tags = Object.assign({}, structureWay.tags); // copy tags - if (bridgeOrTunnel === 'bridge'){ + if (crossingType === 'bridge'){ tags.bridge = 'yes'; tags.layer = '1'; - } else { + } else if (crossingType === 'tunnel') { var tunnelValue = 'yes'; if (getFeatureType(structureWay, graph) === 'waterway') { // use `tunnel=culvert` for waterways by default @@ -689,9 +717,24 @@ export function validationCrossingWays(context) { } tags.tunnel = tunnelValue; tags.layer = '-1'; + } else if (crossingType === 'crossing') { + // we know that the line will already have + // `footway=sidewalk` or `cycleway=sidewalk` + tags[tags.footway ? 'footway' : 'cycleway'] = 'crossing'; } + // apply the structure tags to the way graph = actionChangeTags(structureWay.id, tags)(graph); + + // for crossing, we also need to join the two lines + if (crossingType === 'crossing') { + const edgesToJoin = [ + [structEndNode1.id, structEndNode2.id], + crossedEdge, + ]; + graph = actionConnectCrossingWays(crossingLoc, edgesToJoin, connectionTags)(graph); + } + return graph; }; @@ -701,6 +744,42 @@ export function validationCrossingWays(context) { }); } + /** + * @param {[number, number]} loc + * @param {[string, string][]} edges + * @param {Tags} connectionTags + */ + function actionConnectCrossingWays(loc, edges, connectionTags) { + return (graph) => { + // create the new node for the points + var node = osmNode({ loc: loc, tags: connectionTags }); + graph = graph.replace(node); + + var nodesToMerge = [node.id]; + var mergeThresholdInMeters = 0.75; + + edges.forEach(function(edge) { + var edgeNodes = [graph.entity(edge[0]), graph.entity(edge[1])]; + var nearby = geoSphericalClosestNode(edgeNodes, loc); + // if there is already a suitable node nearby, use that + // use the node if node has no interesting tags or if it is a crossing node #8326 + if ((!nearby.node.hasInterestingTags() || nearby.node.isCrossing()) && nearby.distance < mergeThresholdInMeters) { + nodesToMerge.push(nearby.node.id); + // else add the new node to the way + } else { + graph = actionAddMidpoint({loc: loc, edge: edge}, node)(graph); + } + }); + + if (nodesToMerge.length > 1) { + // if we're using nearby nodes, merge them with the new node + graph = actionMergeNodes(nodesToMerge, loc)(graph); + } + + return graph; + }; + } + function makeConnectWaysFix(connectionTags) { var fixTitleID = 'connect_features'; @@ -718,38 +797,8 @@ export function validationCrossingWays(context) { icon: fixIcon, title: t.append('issues.fix.' + fixTitleID + '.title'), onClick: function(context) { - var loc = this.issue.loc; - var edges = this.issue.data.edges; - context.perform( - function actionConnectCrossingWays(graph) { - // create the new node for the points - var node = osmNode({ loc: loc, tags: connectionTags }); - graph = graph.replace(node); - - var nodesToMerge = [node.id]; - var mergeThresholdInMeters = 0.75; - - edges.forEach(function(edge) { - var edgeNodes = [graph.entity(edge[0]), graph.entity(edge[1])]; - var nearby = geoSphericalClosestNode(edgeNodes, loc); - // if there is already a suitable node nearby, use that - // use the node if node has no interesting tags or if it is a crossing node #8326 - if ((!nearby.node.hasInterestingTags() || nearby.node.isCrossing()) && nearby.distance < mergeThresholdInMeters) { - nodesToMerge.push(nearby.node.id); - // else add the new node to the way - } else { - graph = actionAddMidpoint({loc: loc, edge: edge}, node)(graph); - } - }); - - if (nodesToMerge.length > 1) { - // if we're using nearby nodes, merge them with the new node - graph = actionMergeNodes(nodesToMerge, loc)(graph); - } - - return graph; - }, + actionConnectCrossingWays(this.issue.loc, this.issue.data.edges, connectionTags), t('issues.fix.connect_crossing_features.annotation') ); }