feat: add p-rec-annot elevation annotation planning
This commit is contained in:
@@ -64,6 +64,10 @@ public sealed record LineNumberApplyResult(int Created, int Updated, int Unchang
|
||||
public sealed record LineNumberListApplyPreflight(string StableId, AnnotationPoint Position, double Rotation, IReadOnlyList<LineNumberApplyPreflight> Rows);
|
||||
public sealed record LineNumberPreviewResult(bool Success, LineNumberCommandPlan? Plan, IReadOnlyList<LineNumberApplyPreflight> Apply, string? FailureCode = null);
|
||||
public sealed record LineNumberCandidateReadResult(bool Success, LineNumberCandidate? Candidate, string? FailureCode = null);
|
||||
public sealed record ElevationCandidate(string StableId, double ModelElevation, double DatumOffset, AnnotationPoint Position, double Rotation, string Layer, string TextStyle);
|
||||
public sealed record ExistingElevationAnnotation(string StableId, string SourceStableId, string Text, AnnotationPoint Position);
|
||||
public sealed record ElevationAnnotationOperation(string StableId, AnnotationOperationKind Kind, string? Text, AnnotationPoint Position, double Rotation, string Layer, string TextStyle, IReadOnlyList<string> Reasons);
|
||||
public sealed record ElevationAnnotationPlan(IReadOnlyList<ElevationAnnotationOperation> Operations);
|
||||
public sealed record LineNumberTextAsset(string Layer, string TextStyle, double Size, string TextValue, Point2Dto Position);
|
||||
public sealed record LineNumberLineAsset(string Layer, Point2Dto Start, Point2Dto End);
|
||||
public sealed record LineNumberMarkerAsset(string Layer, double MaskScaleFactor, IReadOnlyList<LineNumberTextAsset> Texts, IReadOnlyList<LineNumberLineAsset> Lines);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using PRecAnnot.Abstractions;
|
||||
|
||||
namespace PRecAnnot.Core;
|
||||
|
||||
public sealed class ElevationAnnotationPlanner
|
||||
{
|
||||
public ElevationAnnotationPlan Build(IEnumerable<ElevationCandidate> candidates, IEnumerable<ExistingElevationAnnotation> existing)
|
||||
{
|
||||
var known = existing.GroupBy(value => value.SourceStableId, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(group => group.Key, group => group.ToArray(), StringComparer.OrdinalIgnoreCase);
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var operations = new List<ElevationAnnotationOperation>();
|
||||
foreach (var candidate in candidates.OrderBy(value => value.StableId, StringComparer.Ordinal))
|
||||
{
|
||||
if (!seen.Add(candidate.StableId))
|
||||
{
|
||||
operations.Add(new(candidate.StableId, AnnotationOperationKind.Conflict, null, candidate.Position, candidate.Rotation, candidate.Layer, candidate.TextStyle, ["DUPLICATE_CANDIDATE"]));
|
||||
continue;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(candidate.Layer) || string.IsNullOrWhiteSpace(candidate.TextStyle) || double.IsNaN(candidate.ModelElevation) || double.IsInfinity(candidate.ModelElevation))
|
||||
{
|
||||
operations.Add(new(candidate.StableId, AnnotationOperationKind.Skip, null, candidate.Position, candidate.Rotation, candidate.Layer, candidate.TextStyle, ["ELEVATION_CANDIDATE_INVALID"]));
|
||||
continue;
|
||||
}
|
||||
var text = LegacyElevationFormatter.Calculate(candidate.ModelElevation, candidate.DatumOffset);
|
||||
var matches = known.GetValueOrDefault(candidate.StableId, []);
|
||||
if (matches.Length > 1)
|
||||
{
|
||||
operations.Add(new(candidate.StableId, AnnotationOperationKind.Conflict, text, candidate.Position, candidate.Rotation, candidate.Layer, candidate.TextStyle, ["DUPLICATE_EXISTING_ANNOTATION"]));
|
||||
continue;
|
||||
}
|
||||
if (matches.Length == 1 && string.Equals(matches[0].Text, text, StringComparison.Ordinal) && matches[0].Position == candidate.Position)
|
||||
operations.Add(new(candidate.StableId, AnnotationOperationKind.Skip, text, candidate.Position, candidate.Rotation, candidate.Layer, candidate.TextStyle, ["ALREADY_CURRENT"]));
|
||||
else
|
||||
operations.Add(new(candidate.StableId, matches.Length == 1 ? AnnotationOperationKind.Update : AnnotationOperationKind.Create, text, candidate.Position, candidate.Rotation, candidate.Layer, candidate.TextStyle, Array.Empty<string>()));
|
||||
}
|
||||
return new(operations);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using PRecAnnot.Abstractions;
|
||||
using PRecAnnot.Core;
|
||||
|
||||
namespace PRecAnnot.Core.Tests;
|
||||
|
||||
public sealed class ElevationAnnotationPlannerTests
|
||||
{
|
||||
[Fact]
|
||||
public void PlansCreateUpdateAlreadyCurrentAndConflictsDeterministically()
|
||||
{
|
||||
var a = new ElevationCandidate("a", 5, 0, new(0, 0), 0, "PXP", "GOSTC");
|
||||
var b = new ElevationCandidate("b", 10, 0, new(1, 0), 0, "PXP", "GOSTC");
|
||||
var plan = new ElevationAnnotationPlanner().Build([b, a, a], [new("old-a", "a", "+0,005", new(0, 0)), new("old-b1", "b", "+0,010", new()), new("old-b2", "b", "+0,010", new())]);
|
||||
Assert.Collection(plan.Operations,
|
||||
value => Assert.Equal(AnnotationOperationKind.Skip, value.Kind),
|
||||
value => Assert.Equal(AnnotationOperationKind.Conflict, value.Kind),
|
||||
value => Assert.Equal(AnnotationOperationKind.Conflict, value.Kind));
|
||||
Assert.Equal("ALREADY_CURRENT", plan.Operations[0].Reasons.Single());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlansUpdateWhenTheFormattedElevationChanged()
|
||||
{
|
||||
var plan = new ElevationAnnotationPlanner().Build([new ElevationCandidate("a", 13, 0, new(), 0, "PXP", "GOSTC")], [new("old", "a", "+0,010", new())]);
|
||||
Assert.Equal(AnnotationOperationKind.Update, plan.Operations.Single().Kind);
|
||||
Assert.Equal("+0,015", plan.Operations.Single().Text);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user