Skip to content

Commit aebe70c

Browse files
committed
Keep building metrics as fixed known values
1 parent 967ec48 commit aebe70c

10 files changed

Lines changed: 64 additions & 154 deletions

src/PlateauResoniteLink/Application/Importing/BuildingAttributeModels.cs

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,7 @@ internal enum PlateauBuildingStructure
5252

5353
internal sealed record BuildingCodeValue<T>(T Value, string Code);
5454

55-
internal enum BuildingMetricKind
56-
{
57-
MeasuredHeightMeters,
58-
StoreysAboveGround,
59-
StoreysBelowGround,
60-
BuildingFootprintArea,
61-
BuildingRoofEdgeArea,
62-
BuildingHeight,
63-
EaveHeight,
64-
}
65-
66-
internal readonly record struct BuildingMetricValue
55+
internal sealed record BuildingMetricValue
6756
{
6857
public BuildingMetricValue(double value)
6958
{
@@ -80,32 +69,20 @@ public BuildingMetricValue(double value)
8069
public double Value { get; }
8170
}
8271

83-
internal sealed record BuildingMetricMeasurements
84-
{
85-
public static BuildingMetricMeasurements Empty { get; } = new(new Dictionary<BuildingMetricKind, BuildingMetricValue>());
86-
87-
public BuildingMetricMeasurements(IReadOnlyDictionary<BuildingMetricKind, BuildingMetricValue> values)
88-
{
89-
ArgumentNullException.ThrowIfNull(values);
90-
Values = new Dictionary<BuildingMetricKind, BuildingMetricValue>(values);
91-
}
92-
93-
public IReadOnlyDictionary<BuildingMetricKind, BuildingMetricValue> Values { get; }
94-
95-
public bool TryGet(BuildingMetricKind kind, out BuildingMetricValue value)
96-
{
97-
return Values.TryGetValue(kind, out value);
98-
}
99-
}
100-
10172
internal sealed record BuildingAttributeContext(
10273
BuildingCodeValue<CityGmlRoofShape>? RoofShape,
10374
IReadOnlyList<BuildingCodeValue<PlateauBuildingUse>> Uses,
10475
IReadOnlyList<BuildingCodeValue<PlateauBuildingUse>> DetailedUses,
10576
IReadOnlyList<BuildingCodeValue<PlateauBuildingStructure>> Structures,
10677
IReadOnlyList<string> CityGmlClassCodes,
10778
IReadOnlyList<string> CityGmlFunctionCodes,
108-
BuildingMetricMeasurements Metrics)
79+
BuildingMetricValue? MeasuredHeightMeters,
80+
BuildingMetricValue? StoreysAboveGround,
81+
BuildingMetricValue? StoreysBelowGround,
82+
BuildingMetricValue? BuildingFootprintArea,
83+
BuildingMetricValue? BuildingRoofEdgeArea,
84+
BuildingMetricValue? BuildingHeight,
85+
BuildingMetricValue? EaveHeight)
10986
{
11087
public static BuildingAttributeContext Empty { get; } = new(
11188
RoofShape: null,
@@ -114,5 +91,11 @@ internal sealed record BuildingAttributeContext(
11491
Structures: [],
11592
CityGmlClassCodes: [],
11693
CityGmlFunctionCodes: [],
117-
Metrics: BuildingMetricMeasurements.Empty);
94+
MeasuredHeightMeters: null,
95+
StoreysAboveGround: null,
96+
StoreysBelowGround: null,
97+
BuildingFootprintArea: null,
98+
BuildingRoofEdgeArea: null,
99+
BuildingHeight: null,
100+
EaveHeight: null);
118101
}

src/PlateauResoniteLink/Application/Importing/BuildingAttributeParser.cs

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,20 @@ internal static class BuildingAttributeParser
1010
{
1111
internal static BuildingAttributeContext Parse(XElement cityObjectElement)
1212
{
13-
BuildingMetricMeasurements metrics = ParseMetrics(cityObjectElement);
1413
return new BuildingAttributeContext(
1514
RoofShape: ParseOptionalRoofShape(GetFirstDirectElementValue(cityObjectElement, "roofType")),
1615
Uses: ParseBuildingUses(GetDirectElementValues(cityObjectElement, "usage")),
1716
DetailedUses: ParseBuildingUses(GetDescendantElementValues(cityObjectElement, "detailedUsage")),
1817
Structures: ParseBuildingStructures(GetDescendantElementValues(cityObjectElement, "buildingStructureType")),
1918
CityGmlClassCodes: GetDirectElementValues(cityObjectElement, "class"),
2019
CityGmlFunctionCodes: GetDirectElementValues(cityObjectElement, "function"),
21-
Metrics: metrics);
20+
MeasuredHeightMeters: ParseMetricValue(GetFirstDirectElement(cityObjectElement, "measuredHeight"), requireMeters: true),
21+
StoreysAboveGround: ParseIntegerMetricValue(GetFirstDirectElement(cityObjectElement, "storeysAboveGround")),
22+
StoreysBelowGround: ParseIntegerMetricValue(GetFirstDirectElement(cityObjectElement, "storeysBelowGround")),
23+
BuildingFootprintArea: ParseMetricValue(GetFirstDescendantElement(cityObjectElement, "buildingFootprintArea"), requireMeters: false),
24+
BuildingRoofEdgeArea: ParseMetricValue(GetFirstDescendantElement(cityObjectElement, "buildingRoofEdgeArea"), requireMeters: false),
25+
BuildingHeight: ParseMetricValue(GetFirstDescendantElement(cityObjectElement, "buildingHeight"), requireMeters: true),
26+
EaveHeight: ParseMetricValue(GetFirstDescendantElement(cityObjectElement, "eaveHeight"), requireMeters: true));
2227
}
2328

2429
private static XElement? GetFirstDirectElement(XElement element, string localName)
@@ -120,51 +125,6 @@ private static PlateauBuildingStructure MapBuildingStructure(string code)
120125
};
121126
}
122127

123-
private static BuildingMetricMeasurements ParseMetrics(XElement cityObjectElement)
124-
{
125-
Dictionary<BuildingMetricKind, BuildingMetricValue> values = [];
126-
AddMetric(
127-
values,
128-
BuildingMetricKind.MeasuredHeightMeters,
129-
ParseMetricValue(GetFirstDirectElement(cityObjectElement, "measuredHeight"), requireMeters: true));
130-
AddMetric(
131-
values,
132-
BuildingMetricKind.StoreysAboveGround,
133-
ParseIntegerMetricValue(GetFirstDirectElement(cityObjectElement, "storeysAboveGround")));
134-
AddMetric(
135-
values,
136-
BuildingMetricKind.StoreysBelowGround,
137-
ParseIntegerMetricValue(GetFirstDirectElement(cityObjectElement, "storeysBelowGround")));
138-
AddMetric(
139-
values,
140-
BuildingMetricKind.BuildingFootprintArea,
141-
ParseMetricValue(GetFirstDescendantElement(cityObjectElement, "buildingFootprintArea"), requireMeters: false));
142-
AddMetric(
143-
values,
144-
BuildingMetricKind.BuildingRoofEdgeArea,
145-
ParseMetricValue(GetFirstDescendantElement(cityObjectElement, "buildingRoofEdgeArea"), requireMeters: false));
146-
AddMetric(
147-
values,
148-
BuildingMetricKind.BuildingHeight,
149-
ParseMetricValue(GetFirstDescendantElement(cityObjectElement, "buildingHeight"), requireMeters: true));
150-
AddMetric(
151-
values,
152-
BuildingMetricKind.EaveHeight,
153-
ParseMetricValue(GetFirstDescendantElement(cityObjectElement, "eaveHeight"), requireMeters: true));
154-
return new BuildingMetricMeasurements(values);
155-
}
156-
157-
private static void AddMetric(
158-
Dictionary<BuildingMetricKind, BuildingMetricValue> values,
159-
BuildingMetricKind kind,
160-
BuildingMetricValue? metric)
161-
{
162-
if (metric.HasValue)
163-
{
164-
values.Add(kind, metric.Value);
165-
}
166-
}
167-
168128
private static BuildingMetricValue? ParseIntegerMetricValue(XElement? element)
169129
{
170130
if (element is null)

src/PlateauResoniteLink/Application/Importing/BuildingAttributeQueries.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ namespace PlateauResoniteLink.Application.Importing;
77

88
internal static class BuildingAttributeQueries
99
{
10-
internal static int? TryGetKnownPositiveInteger(BuildingAttributeContext attributes, BuildingMetricKind kind)
10+
internal static int? TryGetKnownPositiveInteger(BuildingMetricValue? metric)
1111
{
12-
if (!attributes.Metrics.TryGet(kind, out BuildingMetricValue metric))
12+
if (metric is null)
1313
{
1414
return null;
1515
}
@@ -21,9 +21,9 @@ internal static class BuildingAttributeQueries
2121
: null;
2222
}
2323

24-
internal static double? TryGetKnownPositiveMetric(BuildingAttributeContext attributes, BuildingMetricKind kind)
24+
internal static double? TryGetKnownPositiveMetric(BuildingMetricValue? metric)
2525
{
26-
return attributes.Metrics.TryGet(kind, out BuildingMetricValue metric) && metric.Value > 0.0
26+
return metric is not null && metric.Value > 0.0
2727
? metric.Value
2828
: null;
2929
}

src/PlateauResoniteLink/Application/Importing/CityGmlParsedCityObjectReader.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,8 @@ internal static class CityGmlParsedCityObjectReader
3939
actualMeshCode,
4040
sharedAcrossMeshCodes);
4141
BuildingAttributeContext buildingAttributes = BuildingAttributeParser.Parse(cityObjectElement);
42-
int? floorsAboveGround = BuildingAttributeQueries.TryGetKnownPositiveInteger(
43-
buildingAttributes,
44-
BuildingMetricKind.StoreysAboveGround);
45-
double? measuredHeightMeters = BuildingAttributeQueries.TryGetKnownPositiveMetric(
46-
buildingAttributes,
47-
BuildingMetricKind.MeasuredHeightMeters);
42+
int? floorsAboveGround = BuildingAttributeQueries.TryGetKnownPositiveInteger(buildingAttributes.StoreysAboveGround);
43+
double? measuredHeightMeters = BuildingAttributeQueries.TryGetKnownPositiveMetric(buildingAttributes.MeasuredHeightMeters);
4844

4945
bool isMarking = displayName.Contains("Marking", StringComparison.OrdinalIgnoreCase)
5046
|| objectId.Contains("Marking", StringComparison.OrdinalIgnoreCase)

src/PlateauResoniteLink/Application/Importing/CityGmlSurfaceMaterialResolver.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,7 @@ private static ResolvedSurfaceMaterial ResolveSurfaceMaterial(
315315
FloorsAboveGround: cityObject.FloorsAboveGround,
316316
MeasuredHeightMeters: cityObject.MeasuredHeightMeters,
317317
GeometryHeightMeters: cityObject.GeometryHeightMeters,
318-
FootprintAreaSquareMeters: BuildingAttributeQueries.TryGetKnownPositiveMetric(
319-
cityObject.BuildingAttributes,
320-
BuildingMetricKind.BuildingFootprintArea),
318+
FootprintAreaSquareMeters: BuildingAttributeQueries.TryGetKnownPositiveMetric(cityObject.BuildingAttributes.BuildingFootprintArea),
321319
SurfaceRole: ToDefaultMaterialSurfaceRole(face.Role)));
322320
MaterialDepthOffset? depthOffset = cityObject.TerrainAligned
323321
? TerrainAlignedDepthOffset

src/PlateauResoniteLink/Application/Importing/Lod1RoofShapePolicy.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,11 @@ internal static GeneratedLod1RoofShape Select(
2727
};
2828
}
2929

30-
double heightMeters = BuildingAttributeQueries.TryGetKnownPositiveMetric(
31-
attributes,
32-
BuildingMetricKind.MeasuredHeightMeters)
33-
?? BuildingAttributeQueries.TryGetKnownPositiveMetric(
34-
attributes,
35-
BuildingMetricKind.BuildingHeight)
30+
double heightMeters = BuildingAttributeQueries.TryGetKnownPositiveMetric(attributes.MeasuredHeightMeters)
31+
?? BuildingAttributeQueries.TryGetKnownPositiveMetric(attributes.BuildingHeight)
3632
?? geometryHeightMeters;
37-
int? floorCount = BuildingAttributeQueries.TryGetKnownPositiveInteger(
38-
attributes,
39-
BuildingMetricKind.StoreysAboveGround);
40-
double footprintArea = BuildingAttributeQueries.TryGetKnownPositiveMetric(
41-
attributes,
42-
BuildingMetricKind.BuildingFootprintArea)
33+
int? floorCount = BuildingAttributeQueries.TryGetKnownPositiveInteger(attributes.StoreysAboveGround);
34+
double footprintArea = BuildingAttributeQueries.TryGetKnownPositiveMetric(attributes.BuildingFootprintArea)
4335
?? lengthMeters * widthMeters;
4436
bool residential = BuildingAttributeQueries.HasUse(attributes, PlateauBuildingUse.DetachedResidential)
4537
|| BuildingAttributeQueries.HasUse(attributes, PlateauBuildingUse.MixedResidential);

tests/PlateauResoniteLink.Tests/Profiles/BuildingAttributeParserTests.cs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ public void ParseDropsPlateauMissingSentinelsBeforeAttributeContext()
5050

5151
BuildingAttributeContext attributes = BuildingAttributeParser.Parse(element);
5252

53-
Assert.False(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.MeasuredHeightMeters));
54-
Assert.False(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.StoreysAboveGround));
55-
Assert.False(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.StoreysBelowGround));
53+
Assert.Null(attributes.MeasuredHeightMeters);
54+
Assert.Null(attributes.StoreysAboveGround);
55+
Assert.Null(attributes.StoreysBelowGround);
5656
}
5757

5858
[Fact]
@@ -71,9 +71,11 @@ public void ParseDropsNonMeterHeightButKeepsAreaWithoutMeterRequirement()
7171

7272
BuildingAttributeContext attributes = BuildingAttributeParser.Parse(element);
7373

74-
Assert.False(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.MeasuredHeightMeters));
75-
BuildingMetricValue footprintArea = AssertMetric(attributes, BuildingMetricKind.BuildingFootprintArea);
76-
BuildingMetricValue buildingHeight = AssertMetric(attributes, BuildingMetricKind.BuildingHeight);
74+
Assert.Null(attributes.MeasuredHeightMeters);
75+
Assert.NotNull(attributes.BuildingFootprintArea);
76+
Assert.NotNull(attributes.BuildingHeight);
77+
BuildingMetricValue footprintArea = attributes.BuildingFootprintArea;
78+
BuildingMetricValue buildingHeight = attributes.BuildingHeight;
7779
Assert.Equal(160.5, footprintArea.Value);
7880
Assert.Equal(9.25, buildingHeight.Value);
7981
}
@@ -90,7 +92,7 @@ public void ParseDropsBlankMetricElementsBeforeAttributeContext()
9092

9193
BuildingAttributeContext attributes = BuildingAttributeParser.Parse(element);
9294

93-
Assert.False(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.MeasuredHeightMeters));
95+
Assert.Null(attributes.MeasuredHeightMeters);
9496
}
9597

9698
[Fact]
@@ -108,8 +110,8 @@ public void ParseDropsNonFiniteMetricTextBeforeAttributeContext()
108110

109111
BuildingAttributeContext attributes = BuildingAttributeParser.Parse(element);
110112

111-
Assert.False(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.MeasuredHeightMeters));
112-
Assert.False(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.BuildingHeight));
113+
Assert.Null(attributes.MeasuredHeightMeters);
114+
Assert.Null(attributes.BuildingHeight);
113115
}
114116

115117
[Fact]
@@ -135,10 +137,4 @@ public void MetricValueFactoriesRejectInvalidPayloadShapes()
135137
Assert.Throws<ArgumentOutOfRangeException>(() => new BuildingMetricValue(double.PositiveInfinity));
136138
Assert.Throws<ArgumentOutOfRangeException>(() => new BuildingMetricValue(-1.0));
137139
}
138-
139-
private static BuildingMetricValue AssertMetric(BuildingAttributeContext attributes, BuildingMetricKind kind)
140-
{
141-
Assert.True(attributes.Metrics.TryGet(kind, out BuildingMetricValue metric));
142-
return metric;
143-
}
144140
}

tests/PlateauResoniteLink.Tests/Profiles/LocalCityGmlObjectProjectionTests.cs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -537,9 +537,8 @@ public void ProjectCityObjectInfersUrbanFlatRoofFromCityGmlFunctionCodeAndFlatRo
537537
{
538538
CityGmlFunctionCodes = ["401"],
539539
Structures = [new BuildingCodeValue<PlateauBuildingStructure>(structure, CreateStructureTypeCode(structure))],
540-
Metrics = CreateMetrics(
541-
(BuildingMetricKind.MeasuredHeightMeters, 8.0),
542-
(BuildingMetricKind.BuildingFootprintArea, 100.0)),
540+
MeasuredHeightMeters = new BuildingMetricValue(8.0),
541+
BuildingFootprintArea = new BuildingMetricValue(100.0),
543542
});
544543

545544
ImportedCityObject projected = LocalCityGmlObjectProjection.ProjectCityObject(
@@ -597,9 +596,8 @@ public void ProjectCityObjectDoesNotForceUrbanFlatRoofFromGeneralNonWoodStructur
597596
{
598597
CityGmlFunctionCodes = ["401"],
599598
Structures = [new BuildingCodeValue<PlateauBuildingStructure>(structure, CreateStructureTypeCode(structure))],
600-
Metrics = CreateMetrics(
601-
(BuildingMetricKind.MeasuredHeightMeters, 8.0),
602-
(BuildingMetricKind.BuildingFootprintArea, 100.0)),
599+
MeasuredHeightMeters = new BuildingMetricValue(8.0),
600+
BuildingFootprintArea = new BuildingMetricValue(100.0),
603601
});
604602

605603
ImportedCityObject projected = LocalCityGmlObjectProjection.ProjectCityObject(
@@ -3635,13 +3633,6 @@ private static BuildingAttributeContext CreateBuildingAttributes(
36353633
};
36363634
}
36373635

3638-
private static BuildingMetricMeasurements CreateMetrics(params (BuildingMetricKind Kind, double Value)[] values)
3639-
{
3640-
return new BuildingMetricMeasurements(values.ToDictionary(
3641-
static value => value.Kind,
3642-
static value => new BuildingMetricValue(value.Value)));
3643-
}
3644-
36453636
private static string CreateRoofTypeCode(CityGmlRoofShape roofShape)
36463637
{
36473638
return roofShape switch

tests/PlateauResoniteLink.Tests/Profiles/LocalCityGmlSourceFileParserStreamingTests.cs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,15 @@ public async Task SourceFilePipeline_StreamParsedCityObjectsAsync_PreservesFloor
171171
Assert.Contains(attributes.Structures, value => value.Value == PlateauBuildingStructure.Wood && value.Code == "601");
172172
Assert.Equal(["3001"], attributes.CityGmlClassCodes);
173173
Assert.Equal(["401"], attributes.CityGmlFunctionCodes);
174-
Assert.True(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.MeasuredHeightMeters));
175-
Assert.True(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.StoreysAboveGround));
176-
Assert.False(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.StoreysBelowGround));
177-
BuildingMetricValue footprintArea = AssertMetric(attributes, BuildingMetricKind.BuildingFootprintArea);
178-
Assert.False(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.BuildingRoofEdgeArea));
179-
Assert.True(attributes.Metrics.Values.ContainsKey(BuildingMetricKind.BuildingHeight));
180-
BuildingMetricValue eaveHeight = AssertMetric(attributes, BuildingMetricKind.EaveHeight);
174+
Assert.NotNull(attributes.MeasuredHeightMeters);
175+
Assert.NotNull(attributes.StoreysAboveGround);
176+
Assert.Null(attributes.StoreysBelowGround);
177+
Assert.NotNull(attributes.BuildingFootprintArea);
178+
Assert.Null(attributes.BuildingRoofEdgeArea);
179+
Assert.NotNull(attributes.BuildingHeight);
180+
Assert.NotNull(attributes.EaveHeight);
181+
BuildingMetricValue footprintArea = attributes.BuildingFootprintArea;
182+
BuildingMetricValue eaveHeight = attributes.EaveHeight;
181183
Assert.InRange(footprintArea.Value, 120.499999, 120.500001);
182184
Assert.InRange(eaveHeight.Value, 9.699999, 9.700001);
183185
}
@@ -435,13 +437,6 @@ private static async Task<ParsedCityObject> ParseSingleBuildingWithRoofTypeAsync
435437

436438
throw new InvalidOperationException("No city object was parsed.");
437439
}
438-
439-
private static BuildingMetricValue AssertMetric(BuildingAttributeContext attributes, BuildingMetricKind kind)
440-
{
441-
Assert.True(attributes.Metrics.TryGet(kind, out BuildingMetricValue metric));
442-
return metric;
443-
}
444-
445440
private sealed class GateableDatasetContentSource(byte[] payload, int gateOffset) : IPlateauDatasetContentSource
446441
{
447442
private readonly TaskCompletionSource releaseSignal = new(TaskCreationOptions.RunContinuationsAsynchronously);

tests/PlateauResoniteLink.Tests/Profiles/Lod1RoofShapePolicyTests.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Collections.Generic;
21
using System.Linq;
32

43
using PlateauResoniteLink.Application.Importing;
@@ -87,13 +86,13 @@ private static BuildingAttributeContext CreateAttributes(
8786
Structures: CreateCodeValues(structures),
8887
CityGmlClassCodes: [],
8988
CityGmlFunctionCodes: cityGmlFunctionCodes ?? [],
90-
Metrics: footprintArea.HasValue
91-
? new BuildingMetricMeasurements(
92-
new Dictionary<BuildingMetricKind, BuildingMetricValue>
93-
{
94-
[BuildingMetricKind.BuildingFootprintArea] = new(footprintArea.Value),
95-
})
96-
: BuildingMetricMeasurements.Empty);
89+
MeasuredHeightMeters: null,
90+
StoreysAboveGround: null,
91+
StoreysBelowGround: null,
92+
BuildingFootprintArea: footprintArea.HasValue ? new BuildingMetricValue(footprintArea.Value) : null,
93+
BuildingRoofEdgeArea: null,
94+
BuildingHeight: null,
95+
EaveHeight: null);
9796
}
9897

9998
private static BuildingCodeValue<T>[] CreateCodeValues<T>(T[]? values)

0 commit comments

Comments
 (0)