Skip to content
This repository was archived by the owner on May 12, 2026. It is now read-only.

Commit 46bdf29

Browse files
Untagged node and way validators
1 parent 8c06f1f commit 46bdf29

7 files changed

Lines changed: 106 additions & 13 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace Alidade.Osm.Validators.Warning;
2+
3+
/// <summary>
4+
/// Warns when a locally edited node has no tags and is not a vertex of any way.
5+
/// Way vertices are geometry-only nodes and are expected to be untagged.
6+
/// </summary>
7+
public class UntaggedNodeValidator : ValidatorBase
8+
{
9+
/// <inheritdoc />
10+
public override string ValidatorName => "untagged_node";
11+
12+
/// <inheritdoc />
13+
public override ValidationSeverities Severity => ValidationSeverities.Warning;
14+
15+
/// <inheritdoc />
16+
public override IEnumerable<ValidationIssue> Check(EditBufferSnapshot snap, PresetService presets)
17+
{
18+
HashSet<long> vertexNodeIds = [];
19+
20+
foreach (OsmWay way in snap.Ways.Values)
21+
{
22+
foreach (long nodeId in way.NodeIds)
23+
{
24+
vertexNodeIds.Add(nodeId);
25+
}
26+
}
27+
28+
foreach (OsmNode node in snap.Nodes.Values)
29+
{
30+
if (!snap.EditStates.TryGetValue(node.Ref, out EditState es) || es == EditState.Fetched)
31+
{
32+
continue;
33+
}
34+
35+
if (node.Tags.Count == 0 && !vertexNodeIds.Contains(node.Id))
36+
{
37+
yield return new(Severity, ValidatorName, $"Node {node.Id} has no tags", node.Ref, false);
38+
}
39+
}
40+
}
41+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
namespace Alidade.Osm.Validators.Warning;
2+
3+
/// <summary>
4+
/// Warns when a locally edited way has no meaningful tags and is not a member of any relation.
5+
/// A way with only <c>area=yes</c> is treated as untagged because <c>area=yes</c> is a
6+
/// geometry hint, not a descriptive tag.
7+
/// </summary>
8+
public class UntaggedWayValidator : ValidatorBase
9+
{
10+
/// <inheritdoc />
11+
public override string ValidatorName => "untagged_way";
12+
13+
/// <inheritdoc />
14+
public override ValidationSeverities Severity => ValidationSeverities.Warning;
15+
16+
/// <inheritdoc />
17+
public override IEnumerable<ValidationIssue> Check(EditBufferSnapshot snap, PresetService presets)
18+
{
19+
HashSet<long> wayIdsInRelations = [];
20+
21+
foreach (OsmRelation relation in snap.Relations.Values)
22+
{
23+
foreach (OsmMember member in relation.Members)
24+
{
25+
if (member.Type == OsmElementTypes.Way)
26+
{
27+
wayIdsInRelations.Add(member.Ref);
28+
}
29+
}
30+
}
31+
32+
foreach (OsmWay way in snap.Ways.Values)
33+
{
34+
if (!snap.EditStates.TryGetValue(way.Ref, out EditState es) || es == EditState.Fetched)
35+
{
36+
continue;
37+
}
38+
39+
bool isUntagged = way.Tags.Count == 0
40+
|| (way.Tags.Count == 1 && way.Tags.GetValueOrDefault("area") == "yes");
41+
42+
if (isUntagged && !wayIdsInRelations.Contains(way.Id))
43+
{
44+
yield return new(Severity, ValidatorName, $"Way {way.Id} has no tags", way.Ref, false);
45+
}
46+
}
47+
}
48+
}

Alidade/Components/Panels/ValidationPanel.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ protected override void OnInitialized()
1616
validationState.StateChanged += OnStateChanged;
1717
}
1818

19-
private void OnStateChanged(object? sender, EventArgs e) => StateHasChanged();
19+
private void OnStateChanged(object? sender, EventArgs e) => _ = InvokeAsync(StateHasChanged);
2020

2121
private void Close()
2222
=> _ = mediator.Send(new Handlers.Validation.ToggleValidationPanel.Command());

Alidade/Components/Toolbar.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ protected override void OnInitialized()
3434
validationState.StateChanged += OnStateChanged;
3535
}
3636

37-
private void OnStateChanged(object? sender, EventArgs e) => StateHasChanged();
37+
private void OnStateChanged(object? sender, EventArgs e) => _ = InvokeAsync(StateHasChanged);
3838

3939
private void SetTool(ActiveTools tool)
4040
=> _ = mediator.Send(new Handlers.Tool.SetActiveTool.Command(tool));
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
using Microsoft.AspNetCore.Components;
2-
31
namespace Alidade.Handlers.Validation;
42

53
/// <inheritdoc />
6-
public class ValidationResults(ValidationStateService validationState, Dispatcher dispatcher) : INotificationHandler<ValidationResults.Notification>
4+
public class ValidationResults(ValidationStateService validationState) : INotificationHandler<ValidationResults.Notification>
75
{
86
/// <summary>
97
/// Replaces the issue list with the results of a completed validation pass.
108
/// </summary>
119
public record Notification(IReadOnlyList<ValidationIssue> Issues) : NotificationBase;
1210

1311
/// <inheritdoc />
14-
public async Task Handle(Notification notification, CancellationToken cancellationToken)
15-
=> await dispatcher.InvokeAsync(() => validationState.SetState(validationState.State with
12+
public Task Handle(Notification notification, CancellationToken cancellationToken)
13+
{
14+
validationState.SetState(validationState.State with
1615
{
1716
Issues = [.. notification.Issues],
1817
IsRunning = false
19-
}));
18+
});
19+
return Task.CompletedTask;
20+
}
2021
}
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
using Microsoft.AspNetCore.Components;
2-
31
namespace Alidade.Handlers.Validation;
42

53
/// <inheritdoc />
6-
public class ValidationStarted(ValidationStateService validationState, Dispatcher dispatcher) : INotificationHandler<ValidationStarted.Notification>
4+
public class ValidationStarted(ValidationStateService validationState) : INotificationHandler<ValidationStarted.Notification>
75
{
86
/// <summary>
97
/// Marks validation as running.
108
/// </summary>
119
public record Notification : NotificationBase;
1210

1311
/// <inheritdoc />
14-
public async Task Handle(Notification notification, CancellationToken cancellationToken)
15-
=> await dispatcher.InvokeAsync(() => validationState.SetState(validationState.State with { IsRunning = true }));
12+
public Task Handle(Notification notification, CancellationToken cancellationToken)
13+
{
14+
validationState.SetState(validationState.State with { IsRunning = true });
15+
return Task.CompletedTask;
16+
}
1617
}

Alidade/Services/ValidationService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public sealed class ValidationService : IAsyncDisposable
2929
new GeometryMismatchValidator(),
3030
new TagLengthValidator(),
3131
new DeprecatedTagValidator(),
32+
new UntaggedWayValidator(),
33+
new UntaggedNodeValidator(),
3234
// Info
3335
new SuspiciousNameValidator(),
3436
];

0 commit comments

Comments
 (0)