Skip to content

Commit bbb92f6

Browse files
Copilotpetesramek
andauthored
feat: add SensorData sample project files (encoder, decoder, model)
Agent-Logs-Url: https://github.com/petesramek/polyline-algorithm-csharp/sessions/32504d4d-88c9-40a1-aace-d0c432a0ef9b Co-authored-by: petesramek <2333452+petesramek@users.noreply.github.com>
1 parent 7c8b565 commit bbb92f6

5 files changed

Lines changed: 265 additions & 0 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
</PropertyGroup>
6+
7+
<PropertyGroup>
8+
<IsPackable>false</IsPackable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\src\PolylineAlgorithm\PolylineAlgorithm.csproj" />
13+
</ItemGroup>
14+
15+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//
2+
// Copyright © Pete Sramek. All rights reserved.
3+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System.Diagnostics.CodeAnalysis;
7+
8+
[assembly: ExcludeFromCodeCoverage]
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//
2+
// Copyright © Pete Sramek. All rights reserved.
3+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4+
//
5+
6+
namespace PolylineAlgorithm.SensorData.Sample;
7+
8+
using PolylineAlgorithm.Abstraction;
9+
using System.Collections.Generic;
10+
using System.Threading;
11+
12+
/// <summary>
13+
/// Decodes a compact polyline string produced by <see cref="SensorDataEncoder"/> back into a sequence
14+
/// of <see cref="SensorReading"/> values.
15+
/// </summary>
16+
/// <remarks>
17+
/// <para>
18+
/// This class demonstrates implementing <see cref="IPolylineDecoder{TPolyline,TValue}"/> for a custom
19+
/// scalar type, following the same structural pattern as <see cref="AbstractPolylineDecoder{TPolyline,TCoordinate}"/>.
20+
/// </para>
21+
/// <para>
22+
/// Because sensor data is one-dimensional (a single temperature per reading), the base class designed
23+
/// for two-dimensional coordinate pairs is not used. Instead, <see cref="PolylineEncoding"/> static
24+
/// helpers are called directly to read delta-encoded characters and denormalise the recovered values.
25+
/// </para>
26+
/// <para>
27+
/// Timestamps cannot be recovered from the encoded string.
28+
/// The decoded <see cref="SensorReading"/> instances will have <see cref="SensorReading.Timestamp"/>
29+
/// set to <see langword="default"/>.
30+
/// </para>
31+
/// </remarks>
32+
public sealed class SensorDataDecoder : IPolylineDecoder<string, SensorReading> {
33+
/// <summary>
34+
/// Initializes a new instance of the <see cref="SensorDataDecoder"/> class with default encoding options.
35+
/// </summary>
36+
public SensorDataDecoder()
37+
: this(new PolylineEncodingOptions()) { }
38+
39+
/// <summary>
40+
/// Initializes a new instance of the <see cref="SensorDataDecoder"/> class with the specified encoding options.
41+
/// </summary>
42+
/// <param name="options">
43+
/// The <see cref="PolylineEncodingOptions"/> to use for decoding operations.
44+
/// The <see cref="PolylineEncodingOptions.Precision"/> value must match the precision used during encoding.
45+
/// </param>
46+
/// <exception cref="ArgumentNullException">
47+
/// Thrown when <paramref name="options"/> is <see langword="null"/>.
48+
/// </exception>
49+
public SensorDataDecoder(PolylineEncodingOptions options) {
50+
if (options is null) {
51+
throw new ArgumentNullException(nameof(options));
52+
}
53+
54+
Options = options;
55+
}
56+
57+
/// <summary>
58+
/// Gets the encoding options used by this decoder.
59+
/// </summary>
60+
public PolylineEncodingOptions Options { get; }
61+
62+
/// <summary>
63+
/// Decodes a polyline string back into a sequence of <see cref="SensorReading"/> values.
64+
/// </summary>
65+
/// <param name="polyline">
66+
/// The polyline-encoded string produced by <see cref="SensorDataEncoder.Encode"/>.
67+
/// </param>
68+
/// <param name="cancellationToken">
69+
/// A <see cref="CancellationToken"/> that can be used to cancel the decoding operation.
70+
/// </param>
71+
/// <returns>
72+
/// An <see cref="IEnumerable{T}"/> of <see cref="SensorReading"/> whose
73+
/// <see cref="SensorReading.Temperature"/> values are recovered from the encoded string.
74+
/// <see cref="SensorReading.Timestamp"/> will be <see langword="default"/> for every element.
75+
/// </returns>
76+
/// <exception cref="ArgumentNullException">
77+
/// Thrown when <paramref name="polyline"/> is <see langword="null"/>.
78+
/// </exception>
79+
/// <exception cref="ArgumentException">
80+
/// Thrown when <paramref name="polyline"/> is empty.
81+
/// </exception>
82+
/// <exception cref="OperationCanceledException">
83+
/// Thrown when <paramref name="cancellationToken"/> requests cancellation.
84+
/// </exception>
85+
public IEnumerable<SensorReading> Decode(string polyline, CancellationToken cancellationToken = default) {
86+
if (polyline is null) {
87+
throw new ArgumentNullException(nameof(polyline));
88+
}
89+
90+
if (polyline.Length < 1) {
91+
throw new ArgumentException("Encoded polyline must not be empty.", nameof(polyline));
92+
}
93+
94+
ReadOnlyMemory<char> memory = polyline.AsMemory();
95+
int position = 0;
96+
int accumulated = 0;
97+
98+
while (position < memory.Length) {
99+
cancellationToken.ThrowIfCancellationRequested();
100+
101+
if (!PolylineEncoding.TryReadValue(ref accumulated, memory, ref position)) {
102+
yield break;
103+
}
104+
105+
double temperature = PolylineEncoding.Denormalize(accumulated, Options.Precision);
106+
107+
yield return new SensorReading(default, temperature);
108+
}
109+
}
110+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//
2+
// Copyright © Pete Sramek. All rights reserved.
3+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4+
//
5+
6+
namespace PolylineAlgorithm.SensorData.Sample;
7+
8+
using PolylineAlgorithm.Abstraction;
9+
using System.Buffers;
10+
using System.Threading;
11+
12+
/// <summary>
13+
/// Encodes a sequence of <see cref="SensorReading"/> values into a compact polyline string
14+
/// using the polyline delta-encoding algorithm applied to the <see cref="SensorReading.Temperature"/> field.
15+
/// </summary>
16+
/// <remarks>
17+
/// <para>
18+
/// This class demonstrates implementing <see cref="IPolylineEncoder{TValue,TPolyline}"/> for a custom
19+
/// scalar type, following the same structural pattern as <see cref="AbstractPolylineEncoder{TCoordinate,TPolyline}"/>.
20+
/// </para>
21+
/// <para>
22+
/// Because sensor readings carry only a single numeric dimension (temperature), the base class designed
23+
/// for two-dimensional coordinate pairs is not used. Instead, <see cref="PolylineEncoding"/> static
24+
/// helpers are called directly to perform normalisation, delta computation, and character-level encoding.
25+
/// </para>
26+
/// <para>
27+
/// Only <see cref="SensorReading.Temperature"/> values are encoded. Timestamps are not encoded and
28+
/// will not be recovered on decoding.
29+
/// </para>
30+
/// </remarks>
31+
public sealed class SensorDataEncoder : IPolylineEncoder<SensorReading, string> {
32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="SensorDataEncoder"/> class with default encoding options.
34+
/// </summary>
35+
public SensorDataEncoder()
36+
: this(new PolylineEncodingOptions()) { }
37+
38+
/// <summary>
39+
/// Initializes a new instance of the <see cref="SensorDataEncoder"/> class with the specified encoding options.
40+
/// </summary>
41+
/// <param name="options">
42+
/// The <see cref="PolylineEncodingOptions"/> to use for encoding operations.
43+
/// </param>
44+
/// <exception cref="ArgumentNullException">
45+
/// Thrown when <paramref name="options"/> is <see langword="null"/>.
46+
/// </exception>
47+
public SensorDataEncoder(PolylineEncodingOptions options) {
48+
if (options is null) {
49+
throw new ArgumentNullException(nameof(options));
50+
}
51+
52+
Options = options;
53+
}
54+
55+
/// <summary>
56+
/// Gets the encoding options used by this encoder.
57+
/// </summary>
58+
public PolylineEncodingOptions Options { get; }
59+
60+
/// <summary>
61+
/// Encodes a sequence of <see cref="SensorReading"/> values into a polyline string.
62+
/// </summary>
63+
/// <param name="coordinates">
64+
/// The sensor readings whose <see cref="SensorReading.Temperature"/> values are to be encoded.
65+
/// Must contain at least one element.
66+
/// </param>
67+
/// <param name="cancellationToken">
68+
/// A <see cref="CancellationToken"/> that can be used to cancel the encoding operation.
69+
/// </param>
70+
/// <returns>
71+
/// A polyline-encoded string representing the delta-compressed temperature series.
72+
/// </returns>
73+
/// <exception cref="ArgumentException">
74+
/// Thrown when <paramref name="coordinates"/> is empty.
75+
/// </exception>
76+
/// <exception cref="OperationCanceledException">
77+
/// Thrown when <paramref name="cancellationToken"/> requests cancellation.
78+
/// </exception>
79+
public string Encode(ReadOnlySpan<SensorReading> coordinates, CancellationToken cancellationToken = default) {
80+
if (coordinates.Length < 1) {
81+
throw new ArgumentException("Sequence must contain at least one element.", nameof(coordinates));
82+
}
83+
84+
// Maximum number of ASCII characters required to encode a single 32-bit delta value
85+
// using the polyline algorithm (ceil(32 bits / 5 bits per chunk) + sign bit = 7).
86+
const int MaxEncodedCharsPerValue = 7;
87+
88+
int previousNormalized = 0;
89+
int position = 0;
90+
int length = coordinates.Length * MaxEncodedCharsPerValue;
91+
92+
char[]? temp = length <= Options.StackAllocLimit
93+
? null
94+
: ArrayPool<char>.Shared.Rent(length);
95+
96+
Span<char> buffer = temp is null ? stackalloc char[length] : temp.AsSpan(0, length);
97+
98+
try {
99+
for (int i = 0; i < coordinates.Length; i++) {
100+
cancellationToken.ThrowIfCancellationRequested();
101+
102+
int normalized = PolylineEncoding.Normalize(coordinates[i].Temperature, Options.Precision);
103+
int delta = normalized - previousNormalized;
104+
105+
if (!PolylineEncoding.TryWriteValue(delta, buffer, ref position)) {
106+
throw new InvalidOperationException("Encoding buffer is too small to hold the encoded value.");
107+
}
108+
109+
previousNormalized = normalized;
110+
}
111+
112+
return buffer[..position].ToString();
113+
} finally {
114+
if (temp is not null) {
115+
ArrayPool<char>.Shared.Return(temp);
116+
}
117+
}
118+
}
119+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// Copyright © Pete Sramek. All rights reserved.
3+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4+
//
5+
6+
namespace PolylineAlgorithm.SensorData.Sample;
7+
8+
/// <summary>
9+
/// Represents a single temperature reading captured by a sensor.
10+
/// </summary>
11+
/// <param name="Timestamp">The UTC time at which the reading was captured.</param>
12+
/// <param name="Temperature">The temperature value of the reading, in degrees Celsius.</param>
13+
public readonly record struct SensorReading(DateTimeOffset Timestamp, double Temperature);

0 commit comments

Comments
 (0)