create 'follow' feature for drawing ways

This commit is contained in:
Kyle Hensel
2021-10-05 13:37:18 +13:00
parent a018d276eb
commit c9faf072e6
4 changed files with 111 additions and 2 deletions

View File

@@ -346,6 +346,19 @@ en:
not_downloaded:
single: This feature can't be moved because parts of it have not yet been downloaded.
multiple: These features can't be moved because parts of them have not yet been downloaded.
follow:
key: F
error:
needs_more_initial_nodes: This line can't follow a way because it isn't connected to enough consecutive points along a way. Add another point manually to continue.
intersection_of_mutiple_ways:
line: This line can't follow a way because multiple lines are connected to the line's last two points. Add another vertex manually to continue.
area: This line can't follow a way because multiple areas are connected to the line's last two points. Add another vertex manually to continue.
generic: This line can't follow a way because multiple features are connected to the line's last two points. Add another vertex manually to continue.
intersection_of_different_ways:
line: This line can't follow a way because the line is only connected to the line at a single point. Add another point manually to continue.
area: This line can't follow a way because the line is only connected to the area at a single point. Add another point manually to continue.
generic: This line can't follow a way because the line is only connected to the way at a single point. Add another point manually to continue.
unknown: This line can't follow a way.
reflect:
title:
long: Flip Long
@@ -783,7 +796,7 @@ en:
map_data:
title: Map Data
description: Map Data
key: F
key: U
data_layers: Data Layers
layers:
osm:
@@ -2332,6 +2345,7 @@ en:
split: "Split features at the selected points"
reverse: "Reverse selected features"
move: "Move selected features"
follow: "Follow a line or area"
nudge: "Nudge selected features"
nudge_more: "Nudge selected features by a lot"
scale: "Scale selected features"

View File

@@ -283,6 +283,10 @@
"shortcuts": ["operations.move.key"],
"text": "shortcuts.editing.operations.move"
},
{
"shortcuts": ["operations.follow.key"],
"text": "shortcuts.editing.operations.follow"
},
{
"modifiers": ["⇧"],
"shortcuts": ["↓", "↑", "←", "→"],

File diff suppressed because one or more lines are too long

View File

@@ -18,6 +18,7 @@ import { utilRebind } from '../util/rebind';
import { utilKeybinding } from '../util';
export function behaviorDrawWay(context, wayID, mode, startGraph) {
const keybinding = utilKeybinding('drawWay');
var dispatch = d3_dispatch('rejectedSelfIntersection');
@@ -412,6 +413,96 @@ export function behaviorDrawWay(context, wayID, mode, startGraph) {
});
};
/**
* @param {(typeof osmWay)[]} ways
* @returns {"line" | "area" | "generic"}
*/
function getFeatureType(ways) {
if (ways.every(way => way.isClosed())) return 'area';
if (ways.every(way => !way.isClosed())) return 'line';
return 'generic';
}
/** see PR #8671 */
function followMode() {
if (_didResolveTempEdit) return;
try {
// get the last 2 added nodes.
// check if they are both part of only oneway (the same one)
// check if the ways that they're part of are the same way
// find index of the last two nodes, to determine the direction to travel around the existing way
// add the next node to the way we are drawing
// if we're drawing an area, the first node = last node.
const isDrawingArea = _origWay.nodes[0] === _origWay.nodes.slice(-1)[0];
const [secondLastNodeId, lastNodeId] = _origWay.nodes.slice(isDrawingArea ? -3 : -2);
if (!lastNodeId || !secondLastNodeId || !startGraph.hasEntity(lastNodeId) || !startGraph.hasEntity(secondLastNodeId)) {
context.ui().flash
.duration(4000)
.iconName('#iD-icon-no')
.label(t('operations.follow.error.needs_more_initial_nodes'))();
return;
}
const lastNodesParents = startGraph.parentWays(startGraph.entity(lastNodeId));
const secondLastNodesParents = startGraph.parentWays(startGraph.entity(secondLastNodeId));
const featureType = getFeatureType(lastNodesParents);
if (lastNodesParents.length !== 1 || secondLastNodesParents.length === 0) {
context.ui().flash
.duration(4000)
.iconName('#iD-icon-no')
.label(t(`operations.follow.error.intersection_of_mutiple_ways.${featureType}`))();
return;
}
// Check if the last node's parent is also the parent of the second last node.
// The last node must only have one parent, but the second last node can have
// multiple parents.
if (!secondLastNodesParents.some(n => n.id === lastNodesParents[0].id)) {
context.ui().flash
.duration(4000)
.iconName('#iD-icon-no')
.label(t(`operations.follow.error.intersection_of_different_ways.${featureType}`))();
return;
}
const way = lastNodesParents[0];
const indexOfLast = way.nodes.indexOf(lastNodeId);
const indexOfSecondLast = way.nodes.indexOf(secondLastNodeId);
// for a closed way, the first/last node is the same so it appears twice in the array,
// but indexOf always finds the first occurance. This is only an issue when following a way
// in descending order
const isDescendingPastZero = indexOfLast === way.nodes.length - 2 && indexOfSecondLast === 0;
let nextNodeIndex = indexOfLast + (indexOfLast > indexOfSecondLast && !isDescendingPastZero ? 1 : -1);
// if we're following a closed way and we pass the first/last node, the next index will be -1
if (nextNodeIndex === -1) nextNodeIndex = indexOfSecondLast === 1 ? way.nodes.length - 2 : 1;
const nextNode = startGraph.entity(way.nodes[nextNodeIndex]);
drawWay.addNode(nextNode, {
geometry: { type: 'Point', coordinates: nextNode.loc },
id: nextNode.id,
properties: { target: true, entity: nextNode },
});
} catch (ex) {
context.ui().flash
.duration(4000)
.iconName('#iD-icon-no')
.label(t('operations.follow.error.unknown'))();
}
}
keybinding.on(t('operations.follow.key'), followMode);
d3_select(document).call(keybinding);
// Finish the draw operation, removing the temporary edit.
// If the way has enough nodes to be valid, it's selected.